From edf3bde5735a12d675b8e5153be8c45da0ebc306 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Mon, 28 Nov 2022 17:46:08 +1100 Subject: [PATCH 001/135] Started working on the integration # Conflicts: # Session.xcodeproj/project.pbxproj # SessionMessagingKit/Configuration.swift # SessionMessagingKit/Database/Migrations/_012_AddClosedGroupInfo.swift # SessionMessagingKit/Database/Migrations/_013_AutoDownloadAttachments.swift # SessionMessagingKit/Open Groups/Models/SOGSBatchRequest.swift # SessionSnodeKit/OnionRequestAPI.swift # SessionSnodeKit/SSKDependencies.swift # SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift # SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift # SessionTests/Settings/NotificationContentViewModelSpec.swift # SessionUtilitiesKit/Networking/BatchResponse.swift --- Session.xcodeproj/project.pbxproj | 143 ++++- Session/Meta/AppDelegate.swift | 1 + SessionMessagingKit/Configuration.swift | 3 +- .../Migrations/_011_SharedUtilChanges.swift | 28 + .../Database/Models/SharedConfigDump.swift | 27 + .../LibSessionUtil/SessionUtil.swift | 78 +++ .../libsession-util.xcframework/Info.plist | 40 ++ .../ios-arm64/libsession-util.a | Bin 0 -> 833504 bytes .../libsession-util.a | Bin 0 -> 1879632 bytes .../module.modulemap | 9 + .../session/bt_merge.hpp | 58 ++ .../session/config.hpp | 369 +++++++++++++ .../session/config/base.h | 85 +++ .../session/config/base.hpp | 503 ++++++++++++++++++ .../session/config/error.h | 23 + .../session/config/namespaces.hpp | 12 + .../session/config/user_profile.h | 51 ++ .../session/config/user_profile.hpp | 43 ++ .../session/fields.hpp | 43 ++ .../session/xed25519.h | 34 ++ .../session/xed25519.hpp | 38 ++ .../Open Groups/Models/SOGSBatchRequest.swift | 121 +++++ .../ConfigUserProfileSpec.swift | 327 ++++++++++++ .../Models/BatchRequestInfoSpec.swift | 264 +-------- .../Open Groups/Models/SOGSMessageSpec.swift | 14 +- .../Open Groups/OpenGroupAPISpec.swift | 183 +++---- .../Open Groups/OpenGroupManagerSpec.swift | 28 +- .../_TestUtilities/TestOnionRequestAPI.swift | 44 +- .../Models/OnionRequestAPIDestination.swift | 2 +- SessionSnodeKit/OnionRequestAPI.swift | 6 +- SessionSnodeKit/SSKDependencies.swift | 34 ++ ...eadDisappearingMessagesViewModelSpec.swift | 2 +- .../ThreadSettingsViewModelSpec.swift | 33 +- .../NotificationContentViewModelSpec.swift | 4 +- .../Networking/BatchResponse.swift | 106 ++++ .../Networking/BatchResponseSpec.swift | 243 +++++++++ .../Networking}/HeaderSpec.swift | 5 +- .../Networking}/RequestSpec.swift | 69 ++- 38 files changed, 2623 insertions(+), 450 deletions(-) create mode 100644 SessionMessagingKit/Database/Migrations/_011_SharedUtilChanges.swift create mode 100644 SessionMessagingKit/Database/Models/SharedConfigDump.swift create mode 100644 SessionMessagingKit/LibSessionUtil/SessionUtil.swift create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/Info.plist create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64/libsession-util.a create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64_x86_64-simulator/libsession-util.a create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/module.modulemap create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/bt_merge.hpp create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config.hpp create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.h create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.hpp create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/error.h create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/namespaces.hpp create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.h create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.hpp create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/fields.hpp create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/xed25519.h create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/xed25519.hpp create mode 100644 SessionMessagingKit/Open Groups/Models/SOGSBatchRequest.swift create mode 100644 SessionMessagingKitTests/LibSessionUtil/ConfigUserProfileSpec.swift create mode 100644 SessionSnodeKit/SSKDependencies.swift create mode 100644 SessionUtilitiesKit/Networking/BatchResponse.swift create mode 100644 SessionUtilitiesKitTests/Networking/BatchResponseSpec.swift rename {SessionMessagingKitTests/Common Networking => SessionUtilitiesKitTests/Networking}/HeaderSpec.swift (68%) rename {SessionMessagingKitTests/Common Networking => SessionUtilitiesKitTests/Networking}/RequestSpec.swift (74%) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 16253cb68..6f25d8457 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -642,8 +642,6 @@ FD3AABE928306BBD00E5099A /* ThreadPickerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3AABE828306BBD00E5099A /* ThreadPickerViewModel.swift */; }; FD3C905C27E3FBEF00CD579F /* BatchRequestInfoSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C905B27E3FBEF00CD579F /* BatchRequestInfoSpec.swift */; }; FD3C906027E410F700CD579F /* FileUploadResponseSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C905F27E410F700CD579F /* FileUploadResponseSpec.swift */; }; - FD3C906227E411AF00CD579F /* HeaderSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C906127E411AF00CD579F /* HeaderSpec.swift */; }; - FD3C906427E4122F00CD579F /* RequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C906327E4122F00CD579F /* RequestSpec.swift */; }; FD3C906727E416AF00CD579F /* BlindedIdLookupSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C906627E416AF00CD579F /* BlindedIdLookupSpec.swift */; }; FD3C906A27E417CE00CD579F /* SodiumUtilitiesSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C906927E417CE00CD579F /* SodiumUtilitiesSpec.swift */; }; FD3C906D27E43C4B00CD579F /* MessageSenderEncryptionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C906C27E43C4B00CD579F /* MessageSenderEncryptionSpec.swift */; }; @@ -753,6 +751,40 @@ FD87DCFE28B7582C00AF0F98 /* BlockedContactsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD87DCFD28B7582C00AF0F98 /* BlockedContactsViewModel.swift */; }; FD87DD0028B820F200AF0F98 /* BlockedContactCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD87DCFF28B820F200AF0F98 /* BlockedContactCell.swift */; }; FD87DD0428B8727D00AF0F98 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD87DD0328B8727D00AF0F98 /* Configuration.swift */; }; + FD8ECF40292AF07900C0D1BB /* Poller.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF3F292AF07900C0D1BB /* Poller.swift */; }; + FD8ECF42292B340D00C0D1BB /* SOGSBatchRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF41292B340D00C0D1BB /* SOGSBatchRequest.swift */; }; + FD8ECF44292B397E00C0D1BB /* SendMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF43292B397E00C0D1BB /* SendMessageRequest.swift */; }; + FD8ECF46292B4BD500C0D1BB /* SendMessageResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF45292B4BD500C0D1BB /* SendMessageResponse.swift */; }; + FD8ECF48292C287500C0D1BB /* UpdateExpiryRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF47292C287500C0D1BB /* UpdateExpiryRequest.swift */; }; + FD8ECF4A292C2A7300C0D1BB /* DeleteMessagesRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF49292C2A7300C0D1BB /* DeleteMessagesRequest.swift */; }; + FD8ECF4C292C2AC200C0D1BB /* RevokeSubkeyRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF4B292C2AC200C0D1BB /* RevokeSubkeyRequest.swift */; }; + FD8ECF4E292C2AF800C0D1BB /* DeleteAllMessagesRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF4D292C2AF800C0D1BB /* DeleteAllMessagesRequest.swift */; }; + FD8ECF50292C2B2B00C0D1BB /* DeleteAllBeforeRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF4F292C2B2B00C0D1BB /* DeleteAllBeforeRequest.swift */; }; + FD8ECF52292C2CAE00C0D1BB /* UpdateExpiryAllRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF51292C2CAE00C0D1BB /* UpdateExpiryAllRequest.swift */; }; + FD8ECF54292C2DB000C0D1BB /* SnodeAuthenticatedRequestBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF53292C2DB000C0D1BB /* SnodeAuthenticatedRequestBody.swift */; }; + FD8ECF56292C327700C0D1BB /* LegacyGetMessagesRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF55292C327700C0D1BB /* LegacyGetMessagesRequest.swift */; }; + FD8ECF58292C350500C0D1BB /* LegacySendMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF57292C350500C0D1BB /* LegacySendMessageRequest.swift */; }; + FD8ECF5A292C431B00C0D1BB /* GetSwarmRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF59292C431B00C0D1BB /* GetSwarmRequest.swift */; }; + FD8ECF5C292C469100C0D1BB /* UpdateExpiryResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF5B292C469100C0D1BB /* UpdateExpiryResponse.swift */; }; + FD8ECF5E292C478900C0D1BB /* SnodeRecursiveResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF5D292C478900C0D1BB /* SnodeRecursiveResponse.swift */; }; + FD8ECF60292C4B2400C0D1BB /* SnodeSwarmItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF5F292C4B2400C0D1BB /* SnodeSwarmItem.swift */; }; + FD8ECF62292C4C8200C0D1BB /* DeleteMessagesResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF61292C4C8200C0D1BB /* DeleteMessagesResponse.swift */; }; + FD8ECF64292C4D6600C0D1BB /* DeleteAllMessagesResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF63292C4D6600C0D1BB /* DeleteAllMessagesResponse.swift */; }; + FD8ECF66292C6F8200C0D1BB /* DeleteAllBeforeResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF65292C6F8200C0D1BB /* DeleteAllBeforeResponse.swift */; }; + FD8ECF68292C72BA00C0D1BB /* UpdateExpiryAllResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF67292C72B900C0D1BB /* UpdateExpiryAllResponse.swift */; }; + FD8ECF6A292C74A000C0D1BB /* RevokeSubkeyResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF69292C74A000C0D1BB /* RevokeSubkeyResponse.swift */; }; + FD8ECF6C292C9B6400C0D1BB /* GetNetworkTimestampResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF6B292C9B6400C0D1BB /* GetNetworkTimestampResponse.swift */; }; + FD8ECF6E292C9EA100C0D1BB /* GetServiceNodesRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF6D292C9EA100C0D1BB /* GetServiceNodesRequest.swift */; }; + FD8ECF72292DCD1A00C0D1BB /* _013_AutoDownloadAttachments.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF71292DCD1A00C0D1BB /* _013_AutoDownloadAttachments.swift */; }; + FD8ECF74292DDB4A00C0D1BB /* Format.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF73292DDB4A00C0D1BB /* Format.swift */; }; + FD8ECF7929340F7200C0D1BB /* libsession-util.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FD8ECF7829340F7100C0D1BB /* libsession-util.xcframework */; }; + FD8ECF7B29340FFD00C0D1BB /* SessionUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7A29340FFD00C0D1BB /* SessionUtil.swift */; }; + FD8ECF7D2934293A00C0D1BB /* _011_SharedUtilChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7C2934293A00C0D1BB /* _011_SharedUtilChanges.swift */; }; + FD8ECF7F2934298100C0D1BB /* SharedConfigDump.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7E2934298100C0D1BB /* SharedConfigDump.swift */; }; + FD8ECF822934387A00C0D1BB /* ConfigUserProfileSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF812934387A00C0D1BB /* ConfigUserProfileSpec.swift */; }; + FD8ECF852934508B00C0D1BB /* BatchResponseSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF842934508B00C0D1BB /* BatchResponseSpec.swift */; }; + FD8ECF8629346DA100C0D1BB /* HeaderSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C906127E411AF00CD579F /* HeaderSpec.swift */; }; + FD8ECF8729346DB500C0D1BB /* RequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C906327E4122F00CD579F /* RequestSpec.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 */; }; @@ -835,6 +867,8 @@ FDF0B75A2807F3A3004C14C5 /* MessageSenderError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B7592807F3A3004C14C5 /* MessageSenderError.swift */; }; FDF0B75C2807F41D004C14C5 /* MessageSender+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B75B2807F41D004C14C5 /* MessageSender+Convenience.swift */; }; FDF0B75E280AAF35004C14C5 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B75D280AAF35004C14C5 /* Preferences.swift */; }; + FDF1AD5F28FF5F930080A701 /* EditGroupViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF1AD5E28FF5F930080A701 /* EditGroupViewModel.swift */; }; + FDF1AD6128FF61110080A701 /* _012_AddClosedGroupInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF1AD6028FF61110080A701 /* _012_AddClosedGroupInfo.swift */; }; FDF222072818CECF000A4995 /* ConversationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF222062818CECF000A4995 /* ConversationViewModel.swift */; }; FDF222092818D2B0000A4995 /* NSAttributedString+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF222082818D2B0000A4995 /* NSAttributedString+Utilities.swift */; }; FDF2220B2818F38D000A4995 /* SessionApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF2220A2818F38D000A4995 /* SessionApp.swift */; }; @@ -1721,6 +1755,13 @@ FD37EA1628AC5605003AE748 /* NotificationContentViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationContentViewModel.swift; sourceTree = ""; }; FD37EA1828AC5CCA003AE748 /* NotificationSoundViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSoundViewModel.swift; sourceTree = ""; }; FD37EA1A28ACB51F003AE748 /* _007_HomeQueryOptimisationIndexes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _007_HomeQueryOptimisationIndexes.swift; sourceTree = ""; }; + FD37F5572908F5C3005A5E92 /* RemoveUsersModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveUsersModal.swift; sourceTree = ""; }; + FD37F559290F9E9A005A5E92 /* GroupMembersViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMembersViewModel.swift; sourceTree = ""; }; + FD37F55F291867A8005A5E92 /* ImagePickerHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePickerHandler.swift; sourceTree = ""; }; + FD37F5612918C471005A5E92 /* GroupMemberLeftMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMemberLeftMessage.swift; sourceTree = ""; }; + FD37F5632918C58D005A5E92 /* GroupInviteMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupInviteMessage.swift; sourceTree = ""; }; + FD37F5652918C697005A5E92 /* GroupPromoteMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupPromoteMessage.swift; sourceTree = ""; }; + FD37F5672918D458005A5E92 /* MessageReceiver+ClosedGroups.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+ClosedGroups.swift"; sourceTree = ""; }; FD39352B28F382920084DADA /* VersionFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionFooterView.swift; sourceTree = ""; }; FD39353528F7C3390084DADA /* _004_FlagMessageHashAsDeletedOrInvalid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _004_FlagMessageHashAsDeletedOrInvalid.swift; sourceTree = ""; }; FD3AABE828306BBD00E5099A /* ThreadPickerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadPickerViewModel.swift; sourceTree = ""; }; @@ -1832,6 +1873,38 @@ FD87DCFD28B7582C00AF0F98 /* BlockedContactsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedContactsViewModel.swift; sourceTree = ""; }; FD87DCFF28B820F200AF0F98 /* BlockedContactCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedContactCell.swift; sourceTree = ""; }; FD87DD0328B8727D00AF0F98 /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; + FD8ECF3F292AF07900C0D1BB /* Poller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Poller.swift; sourceTree = ""; }; + FD8ECF41292B340D00C0D1BB /* SOGSBatchRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOGSBatchRequest.swift; sourceTree = ""; }; + FD8ECF43292B397E00C0D1BB /* SendMessageRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMessageRequest.swift; sourceTree = ""; }; + FD8ECF45292B4BD500C0D1BB /* SendMessageResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMessageResponse.swift; sourceTree = ""; }; + FD8ECF47292C287500C0D1BB /* UpdateExpiryRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateExpiryRequest.swift; sourceTree = ""; }; + FD8ECF49292C2A7300C0D1BB /* DeleteMessagesRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteMessagesRequest.swift; sourceTree = ""; }; + FD8ECF4B292C2AC200C0D1BB /* RevokeSubkeyRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RevokeSubkeyRequest.swift; sourceTree = ""; }; + FD8ECF4D292C2AF800C0D1BB /* DeleteAllMessagesRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteAllMessagesRequest.swift; sourceTree = ""; }; + FD8ECF4F292C2B2B00C0D1BB /* DeleteAllBeforeRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteAllBeforeRequest.swift; sourceTree = ""; }; + FD8ECF51292C2CAE00C0D1BB /* UpdateExpiryAllRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateExpiryAllRequest.swift; sourceTree = ""; }; + FD8ECF53292C2DB000C0D1BB /* SnodeAuthenticatedRequestBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnodeAuthenticatedRequestBody.swift; sourceTree = ""; }; + FD8ECF55292C327700C0D1BB /* LegacyGetMessagesRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyGetMessagesRequest.swift; sourceTree = ""; }; + FD8ECF57292C350500C0D1BB /* LegacySendMessageRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacySendMessageRequest.swift; sourceTree = ""; }; + FD8ECF59292C431B00C0D1BB /* GetSwarmRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetSwarmRequest.swift; sourceTree = ""; }; + FD8ECF5B292C469100C0D1BB /* UpdateExpiryResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateExpiryResponse.swift; sourceTree = ""; }; + FD8ECF5D292C478900C0D1BB /* SnodeRecursiveResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnodeRecursiveResponse.swift; sourceTree = ""; }; + FD8ECF5F292C4B2400C0D1BB /* SnodeSwarmItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnodeSwarmItem.swift; sourceTree = ""; }; + FD8ECF61292C4C8200C0D1BB /* DeleteMessagesResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteMessagesResponse.swift; sourceTree = ""; }; + FD8ECF63292C4D6600C0D1BB /* DeleteAllMessagesResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteAllMessagesResponse.swift; sourceTree = ""; }; + FD8ECF65292C6F8200C0D1BB /* DeleteAllBeforeResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteAllBeforeResponse.swift; sourceTree = ""; }; + FD8ECF67292C72B900C0D1BB /* UpdateExpiryAllResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateExpiryAllResponse.swift; sourceTree = ""; }; + FD8ECF69292C74A000C0D1BB /* RevokeSubkeyResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RevokeSubkeyResponse.swift; sourceTree = ""; }; + FD8ECF6B292C9B6400C0D1BB /* GetNetworkTimestampResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetNetworkTimestampResponse.swift; sourceTree = ""; }; + FD8ECF6D292C9EA100C0D1BB /* GetServiceNodesRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetServiceNodesRequest.swift; sourceTree = ""; }; + FD8ECF71292DCD1A00C0D1BB /* _013_AutoDownloadAttachments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _013_AutoDownloadAttachments.swift; sourceTree = ""; }; + FD8ECF73292DDB4A00C0D1BB /* Format.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Format.swift; sourceTree = ""; }; + FD8ECF7829340F7100C0D1BB /* libsession-util.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = "libsession-util.xcframework"; sourceTree = ""; }; + FD8ECF7A29340FFD00C0D1BB /* SessionUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionUtil.swift; sourceTree = ""; }; + FD8ECF7C2934293A00C0D1BB /* _011_SharedUtilChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _011_SharedUtilChanges.swift; sourceTree = ""; }; + FD8ECF7E2934298100C0D1BB /* SharedConfigDump.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedConfigDump.swift; sourceTree = ""; }; + FD8ECF812934387A00C0D1BB /* ConfigUserProfileSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigUserProfileSpec.swift; sourceTree = ""; }; + FD8ECF842934508B00C0D1BB /* BatchResponseSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchResponseSpec.swift; sourceTree = ""; }; FD90040E2818AB6D00ABAAF6 /* GetSnodePoolJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetSnodePoolJob.swift; sourceTree = ""; }; FD9004132818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_SetupStandardJobs.swift; sourceTree = ""; }; FDA8EAFD280E8B78002B68E5 /* FailedMessageSendsJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailedMessageSendsJob.swift; sourceTree = ""; }; @@ -1915,6 +1988,8 @@ FDF0B7592807F3A3004C14C5 /* MessageSenderError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSenderError.swift; sourceTree = ""; }; FDF0B75B2807F41D004C14C5 /* MessageSender+Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MessageSender+Convenience.swift"; sourceTree = ""; }; FDF0B75D280AAF35004C14C5 /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = ""; }; + FDF1AD5E28FF5F930080A701 /* EditGroupViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditGroupViewModel.swift; sourceTree = ""; }; + FDF1AD6028FF61110080A701 /* _012_AddClosedGroupInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _012_AddClosedGroupInfo.swift; sourceTree = ""; }; FDF222062818CECF000A4995 /* ConversationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationViewModel.swift; sourceTree = ""; }; FDF222082818D2B0000A4995 /* NSAttributedString+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Utilities.swift"; sourceTree = ""; }; FDF2220A2818F38D000A4995 /* SessionApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionApp.swift; sourceTree = ""; }; @@ -1998,6 +2073,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + FD8ECF7929340F7200C0D1BB /* libsession-util.xcframework in Frameworks */, FDC4386C27B4E90300C60D73 /* SessionUtilitiesKit.framework in Frameworks */, C3C2A70B25539E1E00C340D1 /* SessionSnodeKit.framework in Frameworks */, CEE449BA3596483519120D91 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework in Frameworks */, @@ -2241,14 +2317,6 @@ path = "Views & Modals"; sourceTree = ""; }; - 7B81682428B30BEC0069F315 /* Recovered References */ = { - isa = PBXGroup; - children = ( - FD37EA1A28ACB51F003AE748 /* _007_HomeQueryOptimisationIndexes.swift */, - ); - name = "Recovered References"; - sourceTree = ""; - }; 7B8C44C328B49DA900FBE25F /* New Conversation */ = { isa = PBXGroup; children = ( @@ -3232,6 +3300,8 @@ C3A721332558BDDF0043A11F /* Open Groups */, FD3E0C82283B581F002A425C /* Shared Models */, C3BBE0B32554F0D30050F1E3 /* Utilities */, + FD8ECF7529340F4800C0D1BB /* LibSessionUtil */, + FDC438C027BB4E6800C60D73 /* SMKDependencies.swift */, FD245C612850664300B966DD /* Configuration.swift */, ); path = SessionMessagingKit; @@ -3391,7 +3461,6 @@ D221A08C169C9E5E00537ABF /* Frameworks */, D221A08A169C9E5E00537ABF /* Products */, 2BADBA206E0B8D297E313FBA /* Pods */, - 7B81682428B30BEC0069F315 /* Recovered References */, ); sourceTree = ""; }; @@ -3524,6 +3593,7 @@ FDE77F6A280FEB28002CFC5D /* ControlMessageProcessRecord.swift */, FD5C7308285007920029977D /* BlindedIdLookup.swift */, FD09B7E6288670FD00ED0B66 /* Reaction.swift */, + FD8ECF7E2934298100C0D1BB /* SharedConfigDump.swift */, ); path = Models; sourceTree = ""; @@ -3541,6 +3611,9 @@ FD09B7E4288670BB00ED0B66 /* _008_EmojiReacts.swift */, 7BAA7B6528D2DE4700AE1489 /* _009_OpenGroupPermission.swift */, FD7115F128C6CB3900B47552 /* _010_AddThreadIdToFTS.swift */, + FD8ECF7C2934293A00C0D1BB /* _011_SharedUtilChanges.swift */, + FDF1AD6028FF61110080A701 /* _012_AddClosedGroupInfo.swift */, + FD8ECF71292DCD1A00C0D1BB /* _013_AutoDownloadAttachments.swift */, ); path = Migrations; sourceTree = ""; @@ -3728,8 +3801,6 @@ isa = PBXGroup; children = ( FD3C905E27E410EE00CD579F /* Models */, - FD3C906127E411AF00CD579F /* HeaderSpec.swift */, - FD3C906327E4122F00CD579F /* RequestSpec.swift */, ); path = "Common Networking"; sourceTree = ""; @@ -3899,6 +3970,7 @@ children = ( FD37EA1228AB3F60003AE748 /* Database */, FD83B9B927CF20A5005E1583 /* General */, + FD8ECF832934507500C0D1BB /* Networking */, ); path = SessionUtilitiesKitTests; sourceTree = ""; @@ -3949,6 +4021,33 @@ path = Types; sourceTree = ""; }; + FD8ECF7529340F4800C0D1BB /* LibSessionUtil */ = { + isa = PBXGroup; + children = ( + FD8ECF7829340F7100C0D1BB /* libsession-util.xcframework */, + FD8ECF7A29340FFD00C0D1BB /* SessionUtil.swift */, + ); + path = LibSessionUtil; + sourceTree = ""; + }; + FD8ECF802934385900C0D1BB /* LibSessionUtil */ = { + isa = PBXGroup; + children = ( + FD8ECF812934387A00C0D1BB /* ConfigUserProfileSpec.swift */, + ); + path = LibSessionUtil; + sourceTree = ""; + }; + FD8ECF832934507500C0D1BB /* Networking */ = { + isa = PBXGroup; + children = ( + FD3C906127E411AF00CD579F /* HeaderSpec.swift */, + FD3C906327E4122F00CD579F /* RequestSpec.swift */, + FD8ECF842934508B00C0D1BB /* BatchResponseSpec.swift */, + ); + path = Networking; + sourceTree = ""; + }; FD9004102818ABB000ABAAF6 /* JobRunner */ = { isa = PBXGroup; children = ( @@ -4050,6 +4149,7 @@ FDC4389827BA001800C60D73 /* Open Groups */, FD3C906B27E43C2400CD579F /* Sending & Receiving */, FD3C906827E417B100CD579F /* Utilities */, + FD8ECF802934385900C0D1BB /* LibSessionUtil */, ); path = SessionMessagingKitTests; sourceTree = ""; @@ -5390,6 +5490,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + FD8ECF7B29340FFD00C0D1BB /* SessionUtil.swift in Sources */, 7B81682828B310D50069F315 /* _007_HomeQueryOptimisationIndexes.swift in Sources */, FD245C52285065D500B966DD /* SignalAttachment.swift in Sources */, B8856D08256F10F1001CE70E /* DeviceSleepManager.swift in Sources */, @@ -5474,6 +5575,7 @@ FDF0B74F28079E5E004C14C5 /* SendReadReceiptsJob.swift in Sources */, FDF0B7422804EA4F004C14C5 /* _002_SetupStandardJobs.swift in Sources */, B8EB20EE2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift in Sources */, + FD8ECF7D2934293A00C0D1BB /* _011_SharedUtilChanges.swift in Sources */, FD17D7A227F40F0500122BE0 /* _001_InitialSetupMigration.swift in Sources */, FD245C5D2850660F00B966DD /* OWSAudioPlayer.m in Sources */, FDF0B7582807F368004C14C5 /* MessageReceiverError.swift in Sources */, @@ -5520,15 +5622,18 @@ FD83B9CE27D17A04005E1583 /* Request.swift in Sources */, C32C598A256D0664003C73A2 /* SNProtoEnvelope+Conversion.swift in Sources */, FDC438CB27BB7DB100C60D73 /* UpdateMessageRequest.swift in Sources */, + FD8ECF7F2934298100C0D1BB /* SharedConfigDump.swift in Sources */, C352A2FF25574B6300338F3E /* MessageSendJob.swift in Sources */, FDC438C327BB512200C60D73 /* SodiumProtocols.swift in Sources */, B8856D11256F112A001CE70E /* OWSAudioSession.swift in Sources */, + FD8ECF72292DCD1A00C0D1BB /* _013_AutoDownloadAttachments.swift in Sources */, C3DB66C3260ACCE6001EFC55 /* OpenGroupPoller.swift in Sources */, FD716E722850647600C96BF4 /* Data+Utilities.swift in Sources */, FD245C5C2850660A00B966DD /* ConfigurationMessage.swift in Sources */, FD5C7301284F0F7A0029977D /* MessageReceiver+UnsendRequests.swift in Sources */, C3C2A75F2553A3C500C340D1 /* VisibleMessage+LinkPreview.swift in Sources */, FD245C642850664F00B966DD /* Threading.swift in Sources */, + FDF1AD6128FF61110080A701 /* _012_AddClosedGroupInfo.swift in Sources */, FD848B8D283E0B26000E298B /* MessageInputTypes.swift in Sources */, C32C5C3D256DCBAF003C73A2 /* AppReadiness.m in Sources */, FD09799B27FFC82D00936362 /* Quote.swift in Sources */, @@ -5783,12 +5888,15 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + FD8ECF8729346DB500C0D1BB /* RequestSpec.swift in Sources */, FD2AAAF228ED57B500A49611 /* SynchronousStorage.swift in Sources */, FD078E4927E02576000769AF /* CommonMockedExtensions.swift in Sources */, FD83B9BF27CF2294005E1583 /* TestConstants.swift in Sources */, + FD8ECF8629346DA100C0D1BB /* HeaderSpec.swift in Sources */, FD83B9BB27CF20AF005E1583 /* SessionIdSpec.swift in Sources */, FDC290A927D9B46D005DAE71 /* NimbleExtensions.swift in Sources */, FD23EA6328ED0B260058676E /* CombineExtensions.swift in Sources */, + FD8ECF852934508B00C0D1BB /* BatchResponseSpec.swift in Sources */, FD2AAAEE28ED3E1100A49611 /* MockGeneralCache.swift in Sources */, FD37EA1528AB42CB003AE748 /* IdentitySpec.swift in Sources */, FD1A94FE2900D2EA000D73D3 /* PersistableRecordUtilitiesSpec.swift in Sources */, @@ -5814,7 +5922,6 @@ FD3C906027E410F700CD579F /* FileUploadResponseSpec.swift in Sources */, FD83B9C727CF3F10005E1583 /* CapabilitiesSpec.swift in Sources */, FDC2909A27D71376005DAE71 /* NonceGeneratorSpec.swift in Sources */, - FD3C906427E4122F00CD579F /* RequestSpec.swift in Sources */, FD2AAAF128ED57B500A49611 /* SynchronousStorage.swift in Sources */, FD078E4827E02561000769AF /* CommonMockedExtensions.swift in Sources */, FD859EF827C2F58900510D0C /* MockAeadXChaCha20Poly1305Ietf.swift in Sources */, @@ -5830,8 +5937,8 @@ FD23EA6228ED0B260058676E /* CombineExtensions.swift in Sources */, FD83B9C527CF3E2A005E1583 /* OpenGroupSpec.swift in Sources */, FDC2908B27D707F3005DAE71 /* SendMessageRequestSpec.swift in Sources */, + FD8ECF822934387A00C0D1BB /* ConfigUserProfileSpec.swift in Sources */, FDC290A827D9B46D005DAE71 /* NimbleExtensions.swift in Sources */, - FD3C906227E411AF00CD579F /* HeaderSpec.swift in Sources */, FDC2908F27D70938005DAE71 /* SendDirectMessageRequestSpec.swift in Sources */, FDC438BD27BB2AB400C60D73 /* Mockable.swift in Sources */, FD859EF627C2F52C00510D0C /* MockSign.swift in Sources */, @@ -7037,7 +7144,7 @@ ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; - FRAMEWORK_SEARCH_PATHS = ""; + FRAMEWORK_SEARCH_PATHS = "\"$(SRCROOT)/SessionMessagingKit/LibSessionUtil\""; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -7078,6 +7185,7 @@ ); "OTHER_SWIFT_FLAGS[arch=*]" = "-D DEBUG"; SDKROOT = iphoneos; + SWIFT_INCLUDE_PATHS = "\"$(SRCROOT)/SessionMessagingKit/LibSessionUtil\""; SWIFT_VERSION = 4.0; VALIDATE_PRODUCT = YES; }; @@ -7113,7 +7221,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; - FRAMEWORK_SEARCH_PATHS = ""; + FRAMEWORK_SEARCH_PATHS = "\"$(SRCROOT)/SessionMessagingKit/LibSessionUtil\""; GCC_NO_COMMON_BLOCKS = YES; GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES; GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES; @@ -7151,6 +7259,7 @@ ); SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_INCLUDE_PATHS = "\"$(SRCROOT)/SessionMessagingKit/LibSessionUtil\""; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 4.0; VALIDATE_PRODUCT = YES; diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index d91851597..e55a5cb2f 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -27,6 +27,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD // MARK: - Lifecycle func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + SessionUtil.loadState() // TODO: Remove this (move to 'Configuration'? Or call directly as part of 'AppSetup'?) // These should be the first things we do (the startup process can fail without them) SetCurrentAppContext(MainAppContext()) verifyDBKeysAvailableBeforeBackgroundLaunch() diff --git a/SessionMessagingKit/Configuration.swift b/SessionMessagingKit/Configuration.swift index 868961582..a5e2431b1 100644 --- a/SessionMessagingKit/Configuration.swift +++ b/SessionMessagingKit/Configuration.swift @@ -24,7 +24,8 @@ public enum SNMessagingKit { // Just to make the external API nice [ _008_EmojiReacts.self, _009_OpenGroupPermission.self, - _010_AddThreadIdToFTS.self + _010_AddThreadIdToFTS.self, + _011_SharedUtilChanges.self ] ] ) diff --git a/SessionMessagingKit/Database/Migrations/_011_SharedUtilChanges.swift b/SessionMessagingKit/Database/Migrations/_011_SharedUtilChanges.swift new file mode 100644 index 000000000..8206068f5 --- /dev/null +++ b/SessionMessagingKit/Database/Migrations/_011_SharedUtilChanges.swift @@ -0,0 +1,28 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import GRDB +import SessionUtilitiesKit + +/// This migration recreates the interaction FTS table and adds the threadId so we can do a performant in-conversation +/// searh (currently it's much slower than the global search) +enum _011_SharedUtilChanges: Migration { + static let target: TargetMigrations.Identifier = .messagingKit + static let identifier: String = "SharedUtilChanges" + static let needsConfigSync: Bool = false + static let minExpectedRunDuration: TimeInterval = 0.1 + + static func migrate(_ db: Database) throws { + try db.create(table: ConfigDump.self) { t in + t.column(.variant, .text) + .notNull() + .primaryKey() + t.column(.data, .blob) + .notNull() + } + + // TODO: Create dumps for current data + + Storage.update(progress: 1, for: self, in: target) // In case this is the last migration + } +} diff --git a/SessionMessagingKit/Database/Models/SharedConfigDump.swift b/SessionMessagingKit/Database/Models/SharedConfigDump.swift new file mode 100644 index 000000000..c9e8651df --- /dev/null +++ b/SessionMessagingKit/Database/Models/SharedConfigDump.swift @@ -0,0 +1,27 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import GRDB +import SessionUtilitiesKit + +internal struct ConfigDump: Codable, Equatable, Hashable, Identifiable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { + public static var databaseTableName: String { "configDump" } + + public typealias Columns = CodingKeys + public enum CodingKeys: String, CodingKey, ColumnExpression { + case variant + case data + } + + enum Variant: String, Codable, DatabaseValueConvertible { + case userProfile + } + + var id: Variant { variant } + + /// The type of config this dump is for + public let variant: Variant + + /// The data for this dump + public let data: Data +} diff --git a/SessionMessagingKit/LibSessionUtil/SessionUtil.swift b/SessionMessagingKit/LibSessionUtil/SessionUtil.swift new file mode 100644 index 000000000..94f798ab2 --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/SessionUtil.swift @@ -0,0 +1,78 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import GRDB +import SessionUtil +import SessionUtilitiesKit + +/*internal*/public enum SessionUtil { // TODO: Rename this to be cleaner? + /*internal*/public static func loadState() { + let storedDump: Data? = Storage.shared + .read { db in try ConfigDump.fetchOne(db, id: .userProfile) }? + .data + var dump: UnsafePointer? = nil // TODO: Load from DB/Disk + let dumpLen: size_t = 0 + var conf: UnsafeMutablePointer? = nil +// var confSetup: UnsafeMutablePointer?>? = nil + var error: UnsafeMutablePointer? = nil + // TODO: Will need to manually release any unsafe pointers + let result = user_profile_init(&conf, dump, dumpLen, error) + + guard result == 0 else { return } // TODO: Throw error + +// var conf: UnsafeMutablePointer? = confSetup?.pointee + + user_profile_set_name(conf, "TestName") // TODO: Confirm success + + let profileUrl: [CChar] = "http://example.org/omg-pic-123.bmp".bytes.map { CChar(bitPattern: $0) } + let profileKey: [CChar] = "secretNOTSECRET".bytes.map { CChar(bitPattern: $0) } + let profilePic: user_profile_pic = profileUrl.withUnsafeBufferPointer { profileUrlPtr in + profileKey.withUnsafeBufferPointer { profileKeyPtr in + user_profile_pic( + url: profileUrlPtr.baseAddress, + key: profileKeyPtr.baseAddress, + keylen: profileKey.count + ) + } + } + + user_profile_set_pic(conf, profilePic) // TODO: Confirm success + + if config_needs_push(conf) { + print("Needs Push!!!") + } + + if config_needs_dump(conf) { + print("Needs Dump!!!") + } + + var toPush: UnsafeMutablePointer? = nil + var pushLen: Int = 0 + let seqNo = config_push(conf, &toPush, &pushLen) + + //var remoteAddr: [CChar] = remote.bytes.map { CChar(bitPattern: $0) } + //config_dump(conf, &dump1, &dump1len); + + free(toPush) // TODO: Confirm + + var dumpResult: UnsafeMutablePointer? = nil + var dumpResultLen: Int = 0 + + config_dump(conf, &dumpResult, &dumpResultLen) + + print("RAWR") + let str = String(cString: dumpResult!) + let stryBytes = str.bytes + let hexStr = stryBytes.toHexString() + let data = Data(bytes: dumpResult!, count: dumpResultLen) +// dumpResult. +// Storage.shared.write { db in +// try ConfigDump(variant: .userProfile, data: <#T##Data#>) +// .save(db) +// } +// + print("RAWR2") + + //String(cString: dumpResult!) + } +} diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/Info.plist b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/Info.plist new file mode 100644 index 000000000..c9a2d9b3e --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/Info.plist @@ -0,0 +1,40 @@ + + + + + AvailableLibraries + + + LibraryIdentifier + ios-arm64 + LibraryPath + libsession-util.a + SupportedArchitectures + + arm64 + + SupportedPlatform + ios + + + LibraryIdentifier + ios-arm64_x86_64-simulator + LibraryPath + libsession-util.a + SupportedArchitectures + + arm64 + x86_64 + + SupportedPlatform + ios + SupportedPlatformVariant + simulator + + + CFBundlePackageType + XFWK + XCFrameworkFormatVersion + 1.0 + + diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64/libsession-util.a b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64/libsession-util.a new file mode 100644 index 0000000000000000000000000000000000000000..6d20b8a98cf56097e401c223b89b42aad9bccb62 GIT binary patch literal 833504 zcmeEv34GPXwg23E17rb2iGa8yfJ(qEJBdOGOOyabO5$4WFE`1BT**cjmSRf;Yq3?I zvDWgetx;(op;EQQ7GLc%h;8ZiiaxcrudM;?`vO3rz?*_qLwoWjE5g5vCge0UaQ&p<$7K|x+YW=?i? zVSavQ=8W7K`9+evGSx87Ex+Q^^Gh!j(Vvn@LLBx9(R2#_!w=yy#;uI6GhWDa-(o!7 zujofH)&_*=EcA(4a1(xan8NKN6i(;x=Z155lES|*{pT`-$ZGJ3xy1@^9;5J~pu*eP z|NF-)_fzLHUZC)qnF{aXbb4ng_wP7SUR+%K{FjCsoaCgYq^ z<^D7TNPJ#(k;3&06wbI>;Ui@VPhO<(g{u@!T&ys}?%#Y?xyOH6;p>c-U98-HTB`6& zR0!pFZMniRmnfWeslszFQ`pD2=W^xVeuctEuT7D)$cdAIs^EoTB{yUa9a3rhgpzLFHS?;d?mz33h*r z-T%q-Q%+a(m$Lsf#&-?n|1{%SaEN>lh&N z-;A#_o-$qe&tt4-+`{-jjBhbcI!n^rOzqMK3U;? z?Ec3nZaqc$U�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%0DI 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�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 z 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 RU=3Bc&7-7rjv?}C_doR4rAYt) literal 0 HcmV?d00001 diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64_x86_64-simulator/libsession-util.a b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64_x86_64-simulator/libsession-util.a new file mode 100644 index 0000000000000000000000000000000000000000..284fc504fcd04b7ff3a0f8186a09374b1d6f2040 GIT binary patch literal 1879632 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∾xArM0{moDe<_ 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

9l%{tJmRd+DNp`n`LUC~e7=jjwqq~qxc_87 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Ȋ)BW58-JYnUCX~+?J-YPkQeXDx?%snbctS)o%&hh3E z7)p-t@Es*buofQ9=k9PYKDq|kp(%jx4ZydF{NY`|_XcwSd1@H1bMS88rg-;y@$OK2 zXKCX&zfVyAWyYBUPJMyQ>HNT~(+YUNwIM%0_kUZt0UUGmbW*m!`qWjMp@w_SLCFub zt?+R6qvzpAkj>A~cG}^O<>P6d9pQI7`%O5U24BR-)5!h%khA=p+mVHa=Kw#95jTNd zH!FUUhqoPP?EB(n$25LFcFC=?Omc{8c!tMr{qVZGTrv-1Pgq{y#@=6ZN4C6rQnnv> zl-HV}MQ=6xpIZ>bD@=uF(vSN4wa~aseP~?B7u#1{!MLt{ZQ~+-qUgA88*l&YLv~+M zeE(?eJ?ymh+K)cI$*+G}ytV5{eec4-WpAdR%fW@*Z20R9z!^EN7I<3u@)k2xE8KXU za71IOt%2Vzarnc%G?l%qS?>b;R?J0x^=<$UdH)jfPcL|N4xGybIH-AL_}#~E_#Xba zSv}nWE@lE;%uuHf**yoGgrmKRog-#$KE9XmKI)s+ob_e$3-8Eg7Oo)XC0XA9om>yE z5nO*tA3tDxKP-!Nes4ppQ#OtaHfvu_@}XqQ!Y*?V`eSur*@L^|KU|q34&B~W4zBO* zh#k{d1mo`bQ!5X~?;p(Xax!K+dE7HoOx+Oe^e%>vm74=y^yBGCrb_KOf5H$nzaeR! z+51NBv0%S6ppz_sR$rtK5=7Vk2wL5?rGABPTVr`UZPhlJ&YLZr=6(#uA^jo^ogNy- zvzx#n-KF8^c(@JpcpG!rCtp*<4x;(U&LW)2HwEL5n**Pq&r$rB@yY%oxmoiRyfoJY zeB{?7WxIiuFB@%N_>m9lf*bqu;6X1q5smwj&e;z>EQTlaBi}2|OLD)yyJy8?y-uc7rMcTuGqAJ!_aPxTQb3d?f9P)2ZpZg#_A~_J&NFWJthg`7w^6M%3h>HI zU-RjW#J*R>Dz>DUyT+Pjto@9&)Y{~@tH|;e*2?NEL9B~!vpP@Cerx{^bfM5ZZ$-B9 zd-0P)D_MiQ&xr-lpeL`iKr+w%5YYv(f z-ml%yj?h-^5vz9ud&J9b>buZ18QtT0_Q(zFmFeu68`(QE&^;=-_hxdZ;w&X=*!$%Y z&=ct(`1NyB%d*UQD1n_c|3@Z!j+hnAt2N-!DQKJI#gusT`&q{z79ex_2P;xjbQh6} zr|U*<$yi$H?5YMi&A+R@-7GtczV&T%m#QZ7%&ObWx`VV^1uvA|@|}%k$G&}f_KI`Z zEXk35pk>R76?tr~(p|v615J!4(Zt<#mLEEMg?MQSe)#R0<=O6zS%Yd{_1)b_bK#55 zrWat?iTVrRcfzn4Z)3tk4}#Uya{m zZJZAj(PJJ%k7<+L_??f9(PI?vB0VMxETVc$KQdPr@vO&Jj=;1XJ;uY^%N==OnTg}i z57y+{_&^&UfHl_6Fn-){wyadco`4Z!ib`iTV3Fx#=VARv~Y%=j>Lv&5YXN zY*=5?tn2Bxm(We_w7?dl_-&PI;m(Uz{L9UargQb8O7PH{vH+*h?=3H5__)!e&s4mn5n@cjtg^~;~Gx)Gnn z)<$$J@1vUeY~{tL>h z|3361^)Vk`+1D- z4WA{n>*2Egd|{o=MqJPH>-fBlcY=W-4(IQ9{~sBL-n5H!KsN$+-c(&wiKc2en?YmL=2rNi%~uYG*JLf?HIjaktE;kf8Y z{)dJAnT-!;W$%dExemqW5+77{tSYcie-|5}U(Z48HG0>EefseC3+rakX966nLT1c? z>#?SYtxGgDuuqqwbA$WJvpL3HlE!tdswdC;jDBde@MQ_Mk!jF5&0`JE^{kd>jNueE zkaaI>z35lA>oU{+CHCN*lGmf{te?joEYilCzz?<2I@i=q zw5DmR_a3cJ|5;(#0>%_wU)ex1tf_1ueqD151G_S7>+H(q*}~n}MX)PN*Yoy}SlkSq z29HvXFIjs?N-`nqS&YBsyN=d3KZgvu*2$oEu%=<#@_22h|D-T{r|3JZy?7n`_ip&` zUGU>yg)e^q+4cS0`!;!$?5fyLFXIVji9jdG6qQ{auk96_0(%qu)eBYP5 z9@2-e2A=C8^@riHu?RePrg06!106mbH=+HiJN!<*eYHI=-w$kb-=aM;4}8OQGte;E zHo`F%d!Soa3QNh_q62DseyO?qFI-#c2-4}apRK$c>}T0R5*35Xzj2MJ+r&73m5<70 z9{ACUUSdPKp&goo%ITc2WE*Qg(Rp6%toGd@cW(6?aZ|S_sIOQRmCJMXRB9<|x3MT{GbmHR^V@=V?+^ZmW$iV=Grum9&()sB-W0rRjFgcN(8Hh^ zf1Rgk%Y*0g%jo?rk$V1jG1xVqV_pFqzRbMl_&x+zo!t%INv{*^G)DH4?MJ$z4}bJAtRtiWa9HiohJ}%x~_BQ2`Y7#09%mG5I9|~%$^OH#aRNKRq3Z} z1ODPX|I0Z?(2uWI=L>X}K(_kA7bXuXo@x2@?u?k?qW#&hQRfF53~|Y2!{-NDg7X8L zg7X8Z@c9Akw(EwvV|5iH2dykR(Cj_#^#;8&#X%w<@_4@>$0~# z`Qo{ydtdqfZ~nzAAF9c{a;YQUIqwbacb(mk%{@l+8aM+_7Te- z*t&anw5&*URIQL+aE@oX@4SzFe70jvcJOpX_B6OrW$-g&2Os*2+q1>+!F1E6U_rS( z_@5tQoSrslo%HTJbU^sk_|w8V+30-Rr*Cn-j8biX@72PA|A}6d+vj-VJMlxRJ?;am zJk9!hTa02l{de(*@VhI;1@8W}xZDlB^f)7&gN}{D8`+DrxNZiZGKj&;s4|McReg=R?nQGSHxxT!x6F8%@dkzbx9 z-YqdOh%d2E`v;%QMQmIN_W6Y=iKm;df8UC;*bTn%g?k6vxNH02qfhM~L{=g{M4j%A zJ+m-r&n@Xr*vB{aYAf-H#M5)eU0g?NB=nZYGMDf4rAq3ujkTd zoz}pg#pq_NN2wz?oT<4zoA^xf%Zq@+N#=R}CU-V=&7OFtd?h*d_gcoYC6?ILhVT7O z`dz&}UO!0vI`+EOEyKRa5bv5{zK1xEeiofhxZnFHKA zTlrn~Ul((4r4QBH4zJZ+vR&-EJ?N6k1K`^*>_f>G2ZBD5;>$al>eF^^HxJLvK4Y(I zEzb&Pey}F1_PT*(Iv#srxk>EGaPFeNEU`=HW2)K9rEsU5b(#SVohzHM%f&T0Jnp66 z{Em42d95$+s%Xd3i4P}-1dmB>oWt_2og45t4_)kI{(era*gu4SsrQuCD|eQ?lbbz| z0C#lW^t{S0P>vAvebHIuiQ~`?FLT)U&=dk#ItzsxvS&KX&>&+Fo^@j@Lr;0}?A}=C zH;g$TIqCH0l0*7^H)lmIU^iEu0MRCoC-h-`^{gfDXV@6u^T2Qv?r>MU-ZlTneR&Zjw7m?CFOhHuIOhoNa&fdy(--#?zTF?Q46!@>+kt z_Duur;F-5E-^BCH%-82^`6lIs$HJM||17LK4ZH=z(}E#9s+Ik*;dgH%2EoC618er{ zi=mAjP1zI3a$1L!)By<6qzO|M8kKebhn>E)Ms>pexXWAQ{&dG+M*i2+_$yGMrippuP zW`Z$`R{1e&3|6ijhTY789ClAX=9xV2361$jls}DrYW*GTd&P76oCi^TdDSytw)qZ} zH!T*;dC)alpX>u)JrA|?l6{tAUsoAm0Ua2TF=kj9BL|((n#pf-8X6;bWQZ$px&~(^ z!HouN$FeQw7-!+xsh4#=D{2Emf5I1P`wDz-&gLQi)7981s?n=8X6YUaXiNX?nyOu% z`;VUM%!~d@k4#H940Q16Jl7c)z0-I7_i~r?#zf?qGWpQr8vmCbkeUuOMhVAs><#F+mcJ@_R?#k69N6u`p2V<BGpMyrp zZv8y@SeW$wW#wil&3DCE2@k)?`*Yyp5%Ri>Y6m($+|K7$4Hj+Z#v<)pciZT8p!LJ; zTzgybcHUT|oiE%zx*h1ka66yBy?8r|N4KL~5SrWJ>(&nT?zm}CIUx>D`4yWJqPOgg z4}WiiIY!V=cX+1-`Y%W&`sDn zi`8jI#^{GPHIS2SKHujebKx7jOXqj-dEK?MjB~%pw41Jb&$r9*0iGlV=zjXBnn`Ra zx!8WctZVm4`j@O-jjmCJUvoZj%=sWzZ9Cr+w6&PFo_lrb^3COD*{#&^wN?_>2JdW8JlqWB01 zr!PGXjq>9ae)uzN?UHR7v)<`>Z@`<+g?RJlEN`9_k|$;jRR7ZmZ=MBjem=yTw^6?T zCStzeoy_r+`i`MD_Cp_Q|K{Ou!1L`{ob~UL%pe%LTt2nHS-Ry*>MxKvRJSjQJ_5WZ zf;~3`V}mi+U|f3z+LG^o2--POUF(lz-BQ=!9}BHn`9|=IHS0zen+LqXsk$?)`F6Wr z^DNA?Ug==HM2j=Tnl2q@y%MxpTW*&A5IqduXwMQP9@#W#;}mr^655ilk^QFq#`2Bk zVBa5+LvHG%Z21y&-I`eEbCd9U7HPkVvK}YYM`DUudZ=h0?9Wgi{ltl>kNgzkc%ixZ z1$LfA`?yG1--pM$cJ}rEt$u`?kul`i`+vy0`~viZB6YiwDgP7i*Tu!h9X?z5ywlVn z!yl~uWm9K(ew_7}t>}4t%EE~cv92>hb^KTwIr}G`SsuhGG!k#By*7i`g_+nsB-8Ez z=ViA)Kso(RV#hkTiWoxd#pT2qh@KpwjE_agF|Wdh%|XQtiH_kzU6-mhb+s)v_vrF@ z_}yEG!yqN~y5^?hF`k!@J-<_PD{(Dz2PMuYx{$uFMcTUe+ z@g4Y*&IxPI$AuH@&9*JKuITRg(aN07WkKvJ_#m2mk+EIuh}A16?QuTCbflxPK0jDk zn*VSRl01vT zZobt8Tnwv+oyPZRXvC){n*%#TaKy&_y1;P*F)gnb95v2)*P!zTaLdW&2(M-B-ywWm z{Vqons;4)9dM|i#e~sz9AHD8YbZo_&A@AE*&qnlJ`9DYH6AJdZ)=6-${@K*!L#(gr zlS5><&pvER7cA{^@BRw?vLOzx!l!Pf-9>&3jK`1DOke%!CU{m=tlakBPv2Sat=O7d zoTC53lg#o((7P=77S3&`c(;CVZzQMUT&w>qjzQPS(HPCCSkCwxVsi%?fk#67a<9vK znTMWu8*eRR77TNg89OFN_xzsA3wM7K!rh;g#@$~7PG{*~bm0S+ajr{vul>Y1=X&yC z)qDL(ewmonvo1g8bPaHNJ=$48JK=FKKIcCNPyE@8I`+EuxVJ%h-hut3ME-LZpEc(TiT(-ErTb^kDe`^-Q|5o~gc` zOIOzTq#J9WP2;nekIJjfjB*+TZ5|*7^X26p;b$}W z?dPEXwlDS=dv%yUh_;R0myS+Zc!ugV!`D6C$wl^aZl7@0)$?OipA*Hi9=~3>* z_95jX^RY|&_L8S8*x!2R?L~VQCx^bZA=Zga;)@%RfeyY+3!#-DTM`&zkx(8_AuBo@MjR z^Ih;E&(T5o0?sTf%Vw~RhTpL+mH9uqo%oN$Ksz!`mi)2hF;lNI=m~rh^0Ub2y&pM7 z`m58;;MuI{VRW;j=w^LEKfRQr{rzDXU-o+GydyS4@(71)hEW;6U*#EZTDl$bQY$zh z*{JWW@FT|5OZ!#VmaUL2MmD@fjFIsUg<&k(6NPiC#?gcwsceQVi^A2HO1i{u9G8|@~}lnuGmkE zLH7yhIel4Nx+QsVKktM`;pYj>W8o@`M>=QE*{^yVmn|HE*2fr=&l4Zp$6lblvLHW9 ztOni_YCpR6jkKq>hSwh5I=uD?Rc{NSoEug zYd+M*<-+x^UqhWge+_kxP{)4`@m-()5`4t>fRl}Hklg5P>5_3I8?wG*-hD9i%*O6j zAWsE*=Y70WJHNw!FMFvi^`o}*ZWsUmQ}B^5Rs1yE&%61pc#H6OmPE?PR^ZEsPs$b} zJ}G~i_+&T#{a(4;^-z4P{|vVcosZJNKJkxn=x{N zVVuqs-RI74bsKZ>ZCvjB73X-Rbrw^{e`@)zkJe4R*sY%%kEN@{XehjYJQ|w%=wVBn zTd^6ajVOFBcW&Mf=f)J|h6s3&+U0|u89z4Ik|6$k^!;Dw<5R%J$;i!<$e$G_erjw^ z?o7wc_>k}!f4*_B8y?(ZjMe9J$f)qRO7W<~dZRcsmn)m!l3&Hclw04*j^bbNYTrf` zv1&QwKAn-bZJZzvMz{@ryEC~#8~nC*N7-kCaTM^?*fwtR!M7{M*&_IMg*fZiQh29$)brqEUp$I~mEhkj@Xyhde`);l>s}hSD#5KgS+}*U zA90o~RhfiC!zzpvXwf#y~bWh*)KGX4rLEVk> zppC_Fx?tvWpRs<~OvgtD<(Ey+w#v&l>+5*mtlHu{#ZWizHQqjGCiKjWow26T%N4Wa z=Odc^>Na;D2QYB3fVN)x#8l3iCD@x|X{_!pP!18@R{{*Ui@@ldfz98NXf^eZe=a%r zq0c4&=@Ja*egQy--d z7e3a^nYTNJ>4f;Eh3Efd9Xd?sJYY9Bu)hhmvP1d4)$if5<_oR(HuNkxjkCGb_v10> zz(2rN^=&@czfIaWfFAPG!)v$RM-0I8JX=j{zzaNU;F)q!HpFilSa8eJyBFYdJ&6pa z=gDWD++B{%K=tNx*Nxy`jSu`Hb4?}&bqAfb58e_t%bpARc|PdpZu;p7`uS?m&m;8H z#(CQfoVVRDm7JHf?fQ9YcRzi)ex9`bv?AyG_S-p2pH+MUK95_-U()O13f?h??}}em zJj8R=W~uy*jc-OT;D7Abh_|~xdGI+ts<();=nLW71n)=b;-CnSUskhm(PDioePe+zLE?BB0!RGftT zn6t;kNzBQCJ0A#qli14?mvMwRj=t+cu@IkY%I4);UkIME=dcg%UoLzHPh}7D`z_zp zSf3##@k8KTu`vk;{Fq+eC?CqiW9nusiV0B+wazmS`&H>nc$=DL>i!-5oo5`9Ltkd@ zrP}rLJPiMo{wLXvz52y+tr<4L{=M<6)`7Un10SQ!zT-UKmKwBYs#(iu+5BCci=})s zd5V;)#Fho#)}Ld`x8JBT{emB`k8y^moO{Fifn)h(Vo_#>VhBQT%*cLCJ;5^tJ@99` zl{eGd{?u<8Kd$uPXupHU$p`rs)`xze$NNwIQB#(CW{2)1X6@tjtN**uF$S=&ZsPY6 zzMmvs>|Va>ehB4KO^3!HIH%=npuJ}KM_(d-h&>lQyRCXU*U<-Dqhn1GUmV8mAKP=? zE=J@$vWtzi#rJ-idF4ZA#`;zTYXDrepH5p_B{_hwNy-n}LhQMrKX0py#;6{pAH~rg z=6^J{wHsPjs@^{W*VM1Ly`?>_HTVp14_Xt>OfNmpUONGBZ*d8=-CK?9 zF}JrIKkMHIEcZ5Kg%kbktxa`i|7`KdslXw?i3D**{q$RoEqX8Gf+lUqf>n-u|B(8l$IQ?d&z}xg*#b+@5oB1ZuaL{rwTfkbvKZ@ic$3P`w{j$YioVxv_16quWdiwN594P z)0OO{`QWyv|22$F=P)cC#i!N~uN%EX&qXj&OnNbz=HXo%?0M=|5@SU? z!U$}326U(o{-pjh^qIki5#H0X<&D{6_a@ z_E`6SDy&<`Z{1xhy1DwLLfs|!tG6RX?a|oni=LZU2u@yUoq4aJj$$*ew4dtWOosNO zKK!xg?7zf+6v#>fgNA());1H^n|ASBzs0i>K7B3r-oivX1)Nkd3{kc-V zi+%hYdePY=_obDa`ZC6;*tqe=nZz$rYMkBZDPG3YnBKzp7W4UKK1+kK`f(-5t83+3 z&hlN(xZ2UP#~W8W^sdyn?42rs?5S~S3}N~9I>zS5)eD~tueodx(enOT9z8Iz-q|5? ze2?Z-@aqyitKx0PmiNjxP>wB_@nH+?5&xAP<2W*P_*?>QaR-ROt}}8+aH1W14f!_C z;IBAMUG=vex!&6>YvKQ=YfLtEBfc}mX2hKxUFCe+v}x@mskbNwh;y32*~i7CXe&WX zLy9}>qUHU5RKJcsxIHqKRyf=Im6oR+3wJ*kZ;zL4>%zy%u;s+Dw@$+DI%4NKk1eOc zlnvzIqi3+0E0@G+{5A4t%U5+C`d45t9tOt;(Zln9d$@isamRV?E|Fe(0hz3;Y#w)< z?B0D8drk#)a+_CWL(lg6vw5xHiS9gkD`i@c<5rO)?e}9VmY+c9Ja@umhp-prIk%A! z+-n4PcmG~q{FmIe)2TQ3?^k8Vl;>=-JFlv^o(d8`-&gZUc@@*v3|ouRpMt6~VD)aLnV^zfGJPyRZJu&pBSkJ;amQ zKiG)3P0hZ_`iq|Au}6tMi55ZU?3o)cv8N9S?ElNi@)(Z&g0Q40|aZ8(hAJ zeO1Pu+Q&XP^SOBTI6N>DoL>?j_4`0N%ASf<*=RX(1%dyLmSh6F59=r|Tf84_KR7(j z8P^}G{EV^w{rU3MV}pd=yBIs{s?tjq1u=HvJG;5Fd{k~S@q_Vcp0f>HDb0H`F!3Ml zH&63KhxA+X$U}F+v4Y#-dzwcIdgW=QjTwy0$G^MMF?l&kbM|fcap^AiVmQRP9B?RZ z{iDJck4MOr7LTO!dt7=jz@;4o~0WPIW;L>(*DOxU!OX&cY z&V=l<4wu}SP)9>62g0~iiH+(}c%YA)`voq!_*0JuuPZJU;XA2Gqh*`)M_s(~qr>sa zkGcG9f$#L+B5zVItycqjn6S1rWU@wV{zfv{5qlpg@vNinw-c>MO}S&Rd?xo2*C6Nc znGfx*M#jvyIT)V&e_YJ>D$a&(Bz{tB8jGImZ{uv1m5n5ej=tB<$1kW}^qr+u)V1-J z*y}_CJkEsgOIQ8{&hYfiAg4{S{$1N6_m&r)EhA21Dso=}zEuw2s=&Wl*6Cv;bOv0y ztIgx!VFgWBoJ6ihc71mbvX4tOQyf3jd0fHPa5rNvzGG!(GWl(i<`D;Pc+uqK*UTjI zwZV()}YUph&wd{@8p zR_>WHvG#XcyXm1|J>UzfS1g{P4_ncz+tvWv)xfwJ*$^M)vjOj;j+e>RX9m2;(`Fu? z@A*M1b5PrQPTr7Zz|ZMbD)+O(GRffLBlbPNbHP}A{a>taC3?!g;q{Gc|4st8M$4Vi z{VKmn!ylMan~(*3jOMa#bL{*^zyJI`b}`f1SAI_^e$DTzuaL{&b7hluh4+ZxUz{=;n;gVJ@1U#+C0t4 zV4L&pc=^fOj7H03E>6ntHBV>#o(k_-(PO_y{d>u9zgUyeHj01gH3{>4@px!zrGZE4 zKD{vi6s~!ENldzKP;rZ*zhS)X0kc91)b~8So>W+m2Cc*Yo1QT-nNU}{l})&a&|3t z-M|TSoHh8Feq&~G#eb&0>byi7JMlM54^=x2i_F#qw5fA$eh%Z^KR9-8jmClKK0bpU z>Gx7}kA-34*DDP3L@n_d#HEyZOCIZ0&n6q@AAJj_QW+-Ca`&*!p}YJ zh>p?CDSW2H&8evF7Lm85^VhOgAlv@1j^uoTGkT;8?S#KKkk>DVoZx+UQ5`A4IxF{I zBV{m74<){m%%W2tD<|GWYoj<`;aMt(<564d@$ETUORRu=w>8LI$f~UCjVbaSq|gT~ z?IQn<>W1Mz#xBYGMT|kY0H0$nnv;Ap%F~{JmMh2D>dE-(vB4>q|DD)g(>zyRPQ`NV zj2ZMjGtkaCtTg5B#J<}?p2j1r*&Dz$wL6V^&D2BB9SYa41|N6gGxl|=Y42@`rusf*^Ddf*v!^R(~}o+_zx<0ww6BHu;b>*&C+~dYEaMaXJ zcJaI$cu&J7S;4bM$#bVU4Pc9(#_uIl&4F9_T~7NgeAY7mkMsOa&57~hgSYejc<}%8 zygLNk{*n3Yy6>Q$buu`|f7i(C^LEU&|P#k+WkO&$jUU3R_#-7GkvUOUbJQZYm8lt<0p(Gb>CYDD;O^^JtJe_z2>Jr zH>>Tb%%Aq2=iUdc)$idS)4NT4e~#EQ!FjdTfX`~`mDAp8-fO+f>34PTe+lmd>jC;* zeBTn~6CA4G-2k{IJC5|xTH3IC85)(t#^G_Jkz92#L}%f#d9Kvj#zr*X~{fuuo%6Fwz=Ns zQgreC^Pwe!#Nw^Tek7TRGrmuzZXM}wJvORhw9C_|%jwVZ@hg~vkeI}YenYBsqbq4gJ`)tktZxvdO& zob{HB-4>uPJENx3E(B9YyaxLpO^kFadz&^$? zEPs#>#M+Em=SSk?!X~~n@siWa8Rymt&g$NZPJRzDt=7}m zjQ8T`@)s$mec*Gy{y_LgUuouSaSr+=Uhm4x?$J7h%a8aku=&ELg@bypeN+q6(3<%{{*E`36KHt@9tb4&lj*tuyOUT*X2_5^ctzCkxmufrAF^Vr zeC&d=XPsfKU&3ZDdG-nI?NHq3_Ep3=D|P`~_b~z9PI&GfU=VjO&^*Vd@8~$M18t1j zNM{)K>b&H^UheV{?T~FyW6?A1zg%?xZ5-hxx%lCC=ciGh{nJ|3{_Zx*S0eUN=&9QD zbJl!m-^Oc1`Hg(km5e9t`v#tj!NzX2mT{*K=eVMLBuX2?ag=Gsqk8R(trporzoYdv z*UK6QdP^U$NP!=6PNo1MhS8D&bHDx1dp@ zYg%efoC|0S=47y|HFKuL&neuFaP&&&lZO|fV-4wij&jY$I78FV+$1+6ueuy~?6Jgr zd=csGQ>K2AvMXIMY^6%WI=$Zvn+ zT=bwfF#+2W z_@2r->5xZma@B=qLCXHo?PXptryU83>U5%j{`0Z#4#;{LgAjSr{QMq`O3tf1j zd+O4x*9`49@bf7?l~ak81kY=cD-yArc1af$%wOHs46N<|X72%Zw*$l5INR04*)C@n z77tGaYnO}i6vp9UAeadTOXy!~qI!v7%wy_xjEvjD8kn3029j-kUFFXA@&OOOh!ft% z9e$5_`TP&(z>_-mr{Hg4LoE6c)+Zg}+p>G9&FUbYQsdI^2dpeIS`N{3M^h{>txR$^ znRu7>aI2#V-L)DZaqr$Q|Iwd%%&~nagd=sRAu#+P;aqVn*H_xSq_kFcJiw+UqP6m_+S zkMO&dd1>5X{YUL)C=<@7q`dgid`hj{De7hYXg;Ms8(!zpwARb{S|eva7`momj0E?b&gfyTZRo5Y=GjYSHx2yo zolox`m}(BLz_xKIeofEL9j3Dfn5fKs&pf`{>$vw@9d|CW&OCUD%RvoJF8zse8{_K* z={N&9YhWMvbW`wM?~X#-F96GCXylh^w<<>7G|Jqq_+Iv*)ydE=q?663-g#*4?fj44 zORToKrCVdez~MY}hPC?-l+zq`fQxD7)r)=~wkxO|pWA&AYu-TLl3D$FRbabb!*j*) zw9>|Gz6(y$`voWI_JZ9>;Hk5*1HqZqhdHzQ67lj!-}%(;GUjm-n8_B_K>1eQW$iq5 zKKAk5L%>wH^%M9>H}=)T)-OH}I9>xBXTm2&7|L-wdd&rERM-#?;#)cPT0=T)YDX1m%Sx%)*hDtsYsWsf|v zaql}FPm^wXbi-I1oz0gGjaIod=f`R#gVOG++iqhVO^jz1v>H9N$?2)0MT$KWpR{m? zhAz^%Q~4|6qS4SI+7mxkdx~M}#oyY^S{d-d(`&WU22Oig?0NBpkNq+IjfI2sbItoH z?rwRQd%7!uNApKUY+GyIl+4y++xp-$J9g`=cnjx~k6S)CrzcIFG`OC2{!_`E zG1dTDVs!@h{qlTY4dm^Sf25Z4{q5J7b&Fa11Y=o@EnGSt^k6`_?aTSSIcDlxIoG%- zf&7B)Ji%Ek$$-2n7CpJW_rYc=iIwkR9*CrsED@X%Z>O^Em==LdEw?SrEB_|U#we|+p- z_xm!%zAWWSENUO=5N#@h7Q~?m(4}phHGm!n_wvXf!s|28Lh-j*pHB`h!_S?Do{BFo zhL5yBPj@X|mDPTFh&?L#ycd3xXHV*^STt|Ky_C~kYGt=PX>HHX(U#T(d<<;QF4lv! zAGSRg;7_tW_d;i!?Ri#D7x6GpAoo=Q&rLiRPh5oTbR+$%-=gpFhiY&4F5tedBK0?c zTe2lr(w5{XYj0xyVOdIi;ni)o1FPGBSrhaO8MnjH&rU+rC=zI^#6}|}H!z1+R zeGd)s(Y}Xo-cZE%KrZ!(d=IVoO1Q()=yY|;cWOZ?%k?pd}D0*=tUFR@SE8m z#pawHDjd++6>H5%a`N>P?5$#U zed#`n_*B7XpzW+h`(2`sI;W-ICitDG##hXTa$7cxE@yuamwOZC_)J)?mU4V1EcaH* z@tLsPFH?@sgyoh}j?aYUR#1-5gymLJj?aYUl9b~!VY%BV$7jNF&6MLaVYxdg$7jNF zAD|qc3Cpdc9G?lx-Ag$>6P82HT*PO>a;=o(Ghw-3ryQRN%cUsCXTowHryQRN%l#je z<1=Bo2PwyA!g3E&j?aYUev5K^CM?%ZIX)AX`z+=7Ojs^WIX)AX`#s9>nXud!DaU8R za*t7t&xGZkq8y(I%l!f6_^4cNVb?s-zD9U&SRe5Ek>tXmP@D$-+9z8V#5&h6cKVmI z!;Qq+sjcX_Tpt5r$TMzbJLq&b_j?-d_Ox2TI6j$OGfaXq(WlB!k6}!OF*x)%t%{I3W3~6**1w7$u`wJK_F<45xFH;PpxlnmR)x)k^Fw2>RlV1Gzo zhmVwx@{=*G; zDSH>#X>t)1(4BltFE;AzV|STs7rLi%XUiURa$#dp-}WwOq4#Y+3Qb#w{-35^)J__< zp{vbi+RPI_lBIq$UPWVh-FzMU9FBQkM;K1wwN7A1$)H<{h9(?6PuEo*8c88ZST%1K1jKo_G0s~xH`?n zt#nWQ4EtlmZ{~evR|5wV+`%DVisoYMdB5!|{k-(s;IA~t4zD!-y?tfXgN8eL$?Fk2 zT(5E3@pT+tsj-WnDW`U!jXgkn_D(0};o<4qOo7|@uZt_7vnyB|81K+ zW4BB;74`dg%q&+PoDXttKsEzghcdD;`!cepmRd*r(Zqm__UkJ)9((qw=d8V$_P5nLrsxidkG|IP)R*D#5JH2JKMQ?hAtiX>uY z*P@ttA^jH7yAfZx_m7ua&qn4_>Yd`k!|yJ4T@%wb#&n*V@SWHG28Xq&`$Q&rXJx{Bo1m^AHi5! zsj>Ne>*rnCFEt5YW*i@NnT?m$_)f$fZK!1bJcunvu}!*f%*Mz}8_^|l&^?oAe|LWc z@e0^j3-G%{PO?BeoA|9s+2?rv9Q^Ht#mTJBZ2Sf``Om`_^`E;zhklQIpO5nUPQLFV z-)D;NJKw@xue6h9J>_@G10UIM#Y^`CpOk~M^`Q%1d-$z&E2SUIryao{>WlGyu&Dg$ zef?V3R$?iG{_3lqiEZse_GuNZ*LngcyPmYIvN_hXmH5QgDUI1S*7Bca4WBK3UR-n! zdUdLtoLtghkj)%DW6X^_lTBJQZ4>mYnZ2GupG%jBuaK;xc;_TITsp3T_rRiLT!rul z9YC^%#s}$Wj|t+kqA@E8 ze3J>{R=8K#=0!G)DPd#Zbmyt+yEv0P?-eH_I$sBlJr3Svk?VGXpMuLy<}5#GH?mFs ze*B;KM7&IH$8Yu20H%_Gh@J2}LNG0P#=OMchGXfV!y!*MwSRpX;g*eA3bp0ok>gyk z=(6McpB>@*!TEl3>(B?fpu|t!IhJ|Aek=Tg1i7t7Y%PN z8QSXQY(s8I z@S8T%uNIaggVgD~nC+iumL~-Lgn5G6P#HhB8-hCC*X?Po%CT;)J#cOqrxa@x!l`ii zLWlPB6#Pq5@iP&hxQ#pz#M(LdS)O9^J$Tt>Q?-*}M5k)m=LuxFa-Bg%7A-?g(LNGy zclhsMET3N2D)Y<&>YfE|T^}{4_JZr@D0k*1Q@;*7jbyR(D$cF^<8OV*`+Vfv&zCiR z{mj0yYz|$-+4SWDckRBkdFhI?HLJ4u8uX*ns|NEAOd33cU*5-@i-x3tvCgZfkn38( zLAxF`$)ShXH~9x{8%(`o1|R&WsaOBy;0&^ri$P#){;X5JNO{Gm7tQ&=edn)kTMaCm zf$1Ii?B0XV&c!Z@hF2p4R>7l{Q>{w+>umNseH992*_p)ip0)D?ezt!%Pd^{*J~n6a zdi-r?piSF}NAPQzpSsHOZ+Jg)G&B~P(u?nJDe{@xJOmFDAN?%zRDb!JGSNG0FDcen zj@VS2XB=87dsj1e52x-bey*X7zPc2bi(h{xFf4Vhp_*K=CC@d)M`*UkbI~Eqz2Ro_ zn>lE!@VQ$5iJ{l;QtR~~FldI3D5m>Kc!28VloM95@4#K}0XS);L+efde!Yj|$$?|W z>fEb}>fBqvr%llFQt{*ukJE>EhsNjEZ8*-GJ~si+e)NLc$l573Kx^BK59}mou$Cax zM01!I8`CD%uQrz0C7ifhJdycl0{D12{Jqh!TKsdiHNh(tly9P(e!s$h&o4xq!u-O2 zkILEFd%nML-C=&hJ{H}v{G_7@KRE?I`6u|vcSHI{-1(7nGmwkm2YG009^OzWz)xt; zkHy*r>3^7i2=~!DN8KUJoc(_C{6nxR&Oe-NP#w}Yt^VL?KK!KD>kp2%EX#SG(Q>4| z7GCo~&dvS`Ybrdqe1mdUf6#mB56~UKY~~cRY$@gRT_2U#oxJ(U@TH*4TSM<_gEoaL zwfLX&?_ZVO1g?&;?*N0d%(;!&{M;CM%gyh!jbr6K`|VT6QmG)W))1$*9(rx#SlK&X z-tyxM=L7ZoIXr$3XaBD7>?GoouO*iEI%wwg(9Rp6q0`|JIp@+zDOn!2K*dV~?emj;2ZmbFwD5Et?Us4}y`^!;!HJwD!C4 zvSY{~ot4n##Cm8Zc8NmUz?oN~wGWsZihMuT@?3urRWY#{gh zP!r!<9M8Kzxxp?|{}en=u|^4g-vrMaZilmng3|@jzIfx;2lvIYI(861%37*6X=Yja=!`&ek?hjkD}G|*T?)c4y|1;v@*PQeXop`k2BnJ?dl_MgFEu~ zAAuLWy6rA->{r3H4}f#;2lw7b4C#A0PdMo^@)L&jUWZe{0gYRG+S51fRZoNTPC4hp z?}lk#Q<(OpFHe?cP1I(t2HGcCdTk&}7ZSu-I$1jNz}yviWNC2ZVab4d;$_DcfU~MM zObaD*pi_>bg?3HdygV%w{uHN$u3U9+roqaytgWSo!CGb>_|XdFYHLr?_dK+*8=TZQ zBx`3Rd$$}_{;Op<_KTilUs>J+EY+Uqqw1w(Ur~9<*GmFCp`M;UgsitbC~w=6EiY(G zzC>?d3Cq$;v{v+Gp9wel?rD>MKHax@8F-ywA8B9YnZIOCkIR04%g*E5Ers9RHD$*< zet*)efN!|C{P4Jl11lO!cP?y8VGU2zIGn6BBes-9j3H`EF~tA+v%gmq`@2!~T&%8- z{na_V*T-g3Joe44-)NYG*VMx)iLNMngXE}c?9eTjOt!_im`J_TGx>^|fek#<#!802 z&!1$n4b)W|y~u%z4K5~gEup@b0~gay<7D<8^;-CZ+t*!}6~s06!>bl^rcZILid+3^ z5NFy?#|9h4(|r&LR0T zjr=KHp_DC5`{J_gVdC-dsDDX&STx?j=3SEypxm5lLY@-sxAcVb3u)kj+)^){Z$0CF z{C;P1_43a{_nWNzS3Yjm#}as6A^Z;W5&u0ZW2;Y(|Gw=}{%w20@?HA;~N+W0ZF|uxzS}Vict=_Kx=uG=%NRrC?Relv?&chf z`K_wEJx>!%RX9;N(b{b(^r4G3_E`wxyt4MqkJ#ln^{MjQwkjJ+ZaA_KsQOp5L7buKRXm2h1=YYcr=$#R~5W{wH{+yiWabKG?!Wtp_@S z^~B1dFAUm%bCRt_PJ*ApXq$% ztPcG6?{afX(r*|pdy&%;w3V(DEavpcKdEsh80UG$W$!{{Jo3qV{AlMM3_nNZN=wkT z<|sJV5VNZKsp--Bvz)JqIhl)vx(>$qN#>(Fras4aTZcYN!Nc{>nH0g~)JXq=ac=-) z^$$&0*Na@8My?L$3)6YF zhYR`OtnKCCLM>%q3oaaHUd3=>EG~`41)Z~1t{I)DJBwXbb{##_7!;!(J-2t^W1M~N zaA)_L4LXz=u|t%yQH1He;$K&>_u&gR7UT%yX3m|fCvi9UoBr3wzLt_rO3^#vo~L)x z#e5Dn_V&^|`1w6PqI1H#$Kkcg4QF#m1a!_o4=ny*BWNeT>P+^E;60Ol{u{tv_f7ft zt>hdPuBL@w&=UCzL@VD#Ih|AdAo2WuAAgi*S3)awZZJHS!_Wmw*Eq8%Tt*+K4@bY; zJl$D4@mA5S46!J}k@L_j=g)Sw({|qLxoBX5@o8?dW%~WRG#KZV>}UE6^65C5x+0AM>HSN47W#$pY?l9b`|Nt z?1_IdyUHABC4OnqQ2CiC)Lu6Dt*|_$XQO_P8(TN_{C(K2VAu=JUNW{^Df@?) zkzGA!8%`isX^$itk!PVxULKVW9=#Jzb_ySN*vj5bvu|ZX@$twhMKW@L%v!JZL5qS1cM)%--YIM(gVHo)>#o zcy{#tYV+C8UMHLnevQem`#R(X_jXD8cD^yjIN0)Cd}pKcV9HMUDQK8#_OF0CTpXSjZZ;NsW6v}1|9DnB%HaCFs_xN^P zUfPk|0J@h@{-c-5W)G}_rfU4cZ69N){F{R<|J9!T`UOl}>~I(+mp<_we5!0 z3y$s%$05gPQ*=f6-wKacJBr(qeIiAj`Pddp$)%S*@h72kiw^I--CVkDG+&3}dCRNO z6X178*jqa1SquM@ekFau=a`lrQBB+x^9y1(;4eXLX_w2!=`CSd*~`eHBZWA5`Q=X~ zF85K|ZlS$a_zOJB#&#wi**uurC%JG$_ms|}@|zX}dQ7X@AoK zbC4WD^_z&t()hcX)39z!&VSW8P9Hu#O!XAIwHlbJuIl7;S7_KiVf%4@Gi1-%&6G`1 zw%A;4eQ;=0ecK;vfnFyVWazJ0U5hWQbuoUTcY>qFTeOc32Ro}f`dDY`MEOfJpRRPK zydka<9IQ2x7e?n!A2pAfKyGIqDxWSR?#9k#bRCT~>iZkc9~VGj9amDWND`XoD|;+>$Xj-o9qe5 z@#{v{mu~BDEeCB=e|g58hHj}}(Xv5&{AXD|Ps@}$N#*p+(z3vYCt9Ys-Iqc(Ja0!x z((hQ@7}2X$E_16ns94o+L$l6;E5aGcrW#9%-+lakt#mF-TcWYk6UCn?N2~5b)_#I! zyvUw>5gF!-YZ#k8J@I*iC(-XeNUT_)FeO{~!Q|}8npo#LeqSI@%PIP)9-f5cOfesCMNf(4%2xb zpVip0wAKc{V4CmR=hk+J9=(J2y6arDR(AtxkEMxm$pFhstgGNEz4-4~-#mE&%dr(n z25O1jJdg|B0edsg9|ad%u(hwpo~?Y2!*;1>u?03oY=JfoF1T+QoRp6I^klPaXE4@k z#@ZZ=wG}*9TV;%QmBtIqq{sX5HV5NXJ|I8dgLb_0LgSq`kR)E&+qUL0-Zi1|e(kcx z8^qQH<1Gd!$@Nh9;OPqm$7Y+4pFxgp?RWCUdZ9}+uF|^k9j#y$M zM__c*Kr)m&^d{DNb7-yq6xe8<7Z!rA+#}x3+|C1!dzrW9EcoepH#qnq<{aSR)@s%f z9$WYQ(40lfW)Z_Phj}ML^U`xSFK631q&AnP1tG~%d{AI;P`)% zJ<4T2LyCz`1B?9A=HSxpoWHocB3t+{@#c(K{;PqnHWocA_mjIv&dLqkZH6AC&3@_$ z$MSz{4hB59a}{li&J4%UP(FZ zof`|=oZR=Zb*h6H(iC;A?D`tkz8hXLO3tu&;?@HCW#cbD5d5Fcf5{Wl0o9+kS&7yQ zR{FT-Gkavebl=^+*Kg+G#tBZP!1MfHHWt16L;K_`IMqe0{Sn3|d`O_{=I?GQULJfn z3qGj)Vb*5!Sx5)pGeKvX?_vW|#C34D`ocdM=3Ucay zin)Yy>Ypbz^D*p{%BTM+<~OIqoT`RDd3npqC1!L^eXn~RCa3AhJrN^1*Xxo~-@?kB zb@utWGemAhF6_*hbL9&5-gspI&0J$camxyys*g?Te@lsMd_sTcmmmO?DG|RA+A_ zQ)S5sS57Wy^^=f|l0J&H>wLy~3pfckQ)eV+_x;nf+60 zo^|B-rEmdWl|^BL{}Y1^KCVfe6C+pkwke!NRK9BL-2r~chVvlnHLDu@0FTey zW3p-RB0UV7${yjxO8SufxL7_e;e*ddap-r=sSe;Y2OnsH=Q>oxFEohdFi@yWgQ5 z+~p2lp5KU$-(l*{AX}a5+EiIfv-%GR4!tLbY z3+M6SjcNxQV7On&@$CV=9s*zUjL+EtUA}m0J7oRaE#A)Dr9SMwo@@8^v0i=`oA+zM z;ZkcW+vt_9qg_M!FgOdSHC@U+kFH}zu!sjXZ01<^uWYTb0le;OrM&{I$GI!O;j3S( zt1T}-uH-zeuhYL=`<|Aa(0-hQPlmPXBc@XODKROTElZ&DVoTERa4eNCFI#E(^sc|c zk9De*eT*ITl;}Orx31@ZxD6k(p|%v~(Kya~jUim#!{MKmhecun679+Q-ngkd!&uZW z<9=*=MKbGQCfmyC8f0tB<2{Z$J`v^RDQJrL_F?8+jBiKFmzuA`nX!E7O3Rnr=W*pS z8GE z-d{`Jt*{()7TF+LrkPl^aG5;$U7T%V@e5t*&nICI!tg-4~<>vfgM;Jrop zF8h3V-%dBD4r||DYfklH=T+R*dhFcceS0Fpa}{sI94dWolS*iQdI#}K2~!tKYkxS} z0==>M9MP%32}l2HSbxp`LF8B)4^Q8+vmEGn!(jJ5b4>4a*P-4uBGZ#!)5-y&7p?Mh zKvxL^Vc z+rFbI@)!C~F zs07!G(W(5f%=ncs)X@8~P zvMRg%IB?z<&-O1ww?5(Y<}~yv1MN8z^0A?x;ESpZ`PeE446yKi$V&Nt!Ex{ZJ+AUa zXxkq6)A+QlbpF|&7dj<7rvE_8M#-B$uQW<{)eOx9cPkD2%*W0xeyij*cn)!M8;GCN zdy9kcp*`>;TQ-r*2DT;Y2Jhil+k^M>Y0tGw%u5kEV0GaLPtLLb#FMuxhZsD04>4dK z-!t6zwcgT8$9F_8&mf0Nf9)l1-}74f#GWuVPFQ>!yVkmx=824(s4#V&SA#>AS9?DF z5V$V8qJe+xflu!b@#zD>JI|*xmkOhJP9r*@a5;amaMgTTI=STMYIwhwpVN}tpo8g% zZtUrqr$Nzu@p|(1*Rw753i@DJwjV85TrMwO1P=O-{PnUS|ErQ0#-aOt><3Hh1KiCc zbC$&2fX?e)C*iQ@d^Vu-nGl`NKB=g&mwd?ujt;qxz!&d&_d`I)2WeDv(Rld#oNP zeO~*hxfYvGIl2P;ED6t%&ai!+S=X@ux=Q@7ea>0WUgCNZA0a3AY>pHl9}zEX`-$=w z@i~>tPai3lLO1N4ZtDEF{C>^VNGF`p<9W^F?6)t&V??X`o~`CCOzqVyKE?*dco?4A zKLb4zeNXkp#v%&p_Uw!AQEPkM^>*3Wqh->#{itT0D#1tDo5*IadAjvsaFSAAW z@{cL6xiw!idTzDXytcWC$9FTgy~lwAb9n+Acd>DvEi%qrNVbfQQ}@OzUOA6#MeBK3 z>k91J`G2MObO6|@tu*)@)+djUr!?V!C&f)uL`9$~=L!J`O zq<>mi>c<=9;gTbLx!NOMBf7>Xa4@6Df~cJbrHsE26$ zEbIr*BP)3Ofq0kq8TdSBT9@^-?e~S>!%_QNgS8>G-&i}x9BdBsPOXoZ2}Z$GJeK%N z#lRq=9Q1wCrai-`K2yAd9=xOYdLSoJU-+QT`zghIg4;Gn%iM|YN>=U>e^;_{sm$)s9%J8wQ;ChqgV}&S_%n2|c&roI z@g3FZj(f}Oy%5BP`15a^XM=7CM<2mf@d&vE66C-4=WZ*=5w3HS_dN5Yy(_yGzJy$5 z&nHIDQGNt@vx~EgJ2*$FJQLw_lK{1 zEQiaF=qHudpDP^nG`AQmxc766o?*!ESPB*=Eo~o>iw9ep*ng|Yt4#hAd~ebv{oK<3 zFm`U*2cpYv%;VqZT=cx)0(kMTUp6=s$g`p}exuXjgkO2#FgT<9)ayCllIPrl@T8g8 zxYPpESqsl(?{p?O&z09*G2nO%{1L7+m@<1$XY*&1?8g}VGBHYjW=@rAL+!UASJ*wv zJfk|c=3Xow>n!@NVr^4y9$V!PYcMkh+Oex<;NiYc#o{M$(lvl8`i#aXofOj8iGW4vDeDtw~I> zxSggXrVX1g6|%ub36`jY9MX`6*tEql-Qp~5NX>Rh!fr{H?vgIevisqEOS-fr3A-<8 z=q}r3wJQCJIbWBux8;4F zzpp)SBzJF~e6IOl{hsTW+<5Ttx%{W=-hY#IWQ1p2wXh~^vW`5O?@n+T$ zt~sc0bL+^1yw^GOry1{hMphz!4SChIz2_$x2Y;Wr@g=?wsPDSnXny@{q4&G5!!I`+ zQ+c}7hlw|P|5`f#HRnU$W^T|LG{czEcjWKJPTAGSXXUb4`t;q!r&$BErmXNB9QJ$B zGq7rybMB;Ve~sj|FqvuxHdT{Q$SL@QWVaIwv zU)*dx*mXU9>FV$Pd)gbV7n|GGiy!QqQ}gHl|39x6H7h&Yrt!=5i=tP&W-yj-v={sy z^R{&Gdveh|9-0$$KmG6JeUEv@oh#^kV;|$^ZuZ`#AI!~L*S>?Te9`w8m^+X2yl?Ff zzEAA3*N=W{Oy^lMjO7{9bsqI4#%gXH+)!PIeeShy{j8oLa)mu~uFqB&+fkpn=gEB8 zJx@l@rMaE&XcxTU6Vd+pOX$+M@a@s(uUyMjA5mvx-v0S2&%b$;^BwiE?Voq~9dq`_ zNAQX2)jId<^eZ<*Ztbttultd!UvDHo@Akt**waYd<(`w4zaL)07rFDf@4fpW&X?Z0 z^}M?Ldna?hPncUj?&kTU37$V!nt<$)?&dRyeS8M7k61eYR?ah7_jGT;?Faph#7A%6 zV2*z&TBDJ*u*UJZFZajh2L)Mxf9+= z`PWeXHLg78OV{d;Utbw5YRD?j@5 zA5}hm=S=0JclK1Cd-|6v8=w9qp4+1PDSl$(OZfg{j}|fZQ~T~x z?w-`>zH4Gp@tFU7sjJTvvd>AfUq4L$AK||Dqulpi#OF=N_)WON=)QN2N$Y#;#NIEw zec+FO=6`(l#izRe;KlF#=6`!}gy$es{^;}X>hy*#UHNqEt1SfuUuEC?l73S{-&brY zIPsNzKYZvP-u>a@FFbh6`_8wDxgMK6`JFR&LY_PDe$5L%tp3_-&wcWbKlfXoe{uh> z|K}Iqck1ge-u>Y(zi{BTu6Nz{&I2zbe)zVpB{)ue_`nMvEl51~P5w0Yzk&W`H_kMN zBtBgA@w?A`@?D?(tw5msCqL~!|JVF2abBt4 z+L3=e-+R*!(D}(v$9Qh*sc*+v_a9`Of0TO=-c21kPdxRh*jM$P@iY#>Re6=w z_q)?qua@g`oZOhRdQV02uamB4d@FD6v!0%(AG-hHXCAu$*{5Uo*E|!uKeg98oU8k- ze7>`ZeRq6DHWo+a?*B~e`L3&b?|buSVqbkrtl+EffjL=VDS#J)r4-nl-OFNV`_b{=_uf|;EBZ=; zm`V^+=L=4LWte4;ZOZq9Nr-sj{q~m94!vOuc#2z!(&qMsK7vrb@)3GgE)Yn(p|9|?=uev^UzKr~0_gq-r_CCMvXYsM> z?!m|U-d~z}wNBEfMB@diGU-9Gn4m?-o-~J!E=JQyVal6m6f7Y#*V0E1!wouV;V#M#0JF-u=+t=c~TC zAAgxWHUIX<@2`36mir%kXz%Ae#&Z?(YVLP;7|UO6{GWU8>!IAqukU|e&sn+AclGaJ z|M|lY{WNoP9dq;}%+>E=&VDD?SZZ0b3%8!ncQsbXbEyv@Pm=F2`S~ri4;|&+fjDz{ z0sC9q^XAXzBhTE*4>S4CpWx5Nnq6mN(f(YA+RdIY_w3gD3%oZ~>7K0}W5Dh&FyGqy zoKHXTbnN*K{`CFEYWjfR!Mx6Y+9&14_l?SLAWs~62jhrXj`96L;waxvZ%f}Ri$~d` z&-8Y-VfPqTT&&#k4r2JB`!$xnRlvO!d45^a^{2=ursHO9l0T~O$4UX$ZevxEz65O> zdFH)4ue(Qm{|J5gF8cJH^ldGD|5JQl!1`T&y-~SF&Knf3iiypOH;xJY4!53jq&=R_ zeYVGi%4`3uxqcyv1-m|X&F2;?<_n8=>q9q3;S=eY!EfKr_As-gudK(ThjM0cJzhmHC?U_r%U5k{rXw-sBbUd;vK$` zKhyNX3F7ARo*T=3t+~;;(Di+_dGT6bUEY7=vQ>F~wcGgJ;JNazw7-1TmX{(Q+gjSS zHD|kiiPjGDz4^r6=f>;`{@53-(Xsmz%#XRg&#hNW{Ensh`0uFyZ#bv?E&G2fmh#8q z-=P1G+_;a}aG3ev2=fB-`-j-G zGap=ku4eo7k!MbAlzLvYU-~S2_lMcLpJ(s>E%xr(BXfWHcep;sH7mZK?XGcX{r}PP zEnDmV$TK^iAM-`_zCZj>9rZtg9q+=PcQP;4vL+GltZ_Q$xWf8;?$g?L9sOKxt#4s{ zzTGeSHotKhtkzaICSug03Gh?|i7>)N>zrXz%lld24kGYjq22 z^;y>HDV~qgNIPu6s;)-nonQMj`@^U{w+2RSaqCLd zrd&)~J6fW-M|(ugJ-1iroL~NWI?`i0^j%z~J*quWUV3yM>Czv=r`>rnTN8HnSr@;P zDO-1y^H~E6<>bIzZCtw8q_=zLN41;4}|(>~uX`W&Ar zeZuBO`#mshD2hH8dT6)%IBc$4j_&)r{uv~?ue_aeY+d`;H8QPR9^W-lyHxj*zW*He ze|v@N42d-7$7!t%HkYh$%|q8qdiHxSjTdhqtDB9?$*e46Y0#q zW^xCys{LvHXD%CO`JC$^`3DlteeM&r^QZn##yr0r_mJHa8eNlddw1P;{wc0IG~VewfBpG?*B?K7_39T|_)EW3 z^>Lp+>6&6c>*34m^YWX_p4~ICWjA?udw+cl&>yaixqThiYoqTD?ef0!MfUsavBJ-d@cRh2pYy(RC->g}6>FxR zWs`f3qn@)%-8`q~YexhbVBzGq+6rslG3&ykGw&_j&pjW6?4D!QA(Z z?3i;ZS8nBM)sF9bzJ+s$-{bqbxzGRdpOu{Rm0~C7C;2D;e!J~5%a?!ez0DOWhpZ?X z2h2S>&c)+Sf8)QmTpxeC|Hgwo-&FTR_Ha++N$#oDZ`u^#<2QQzjtKly`kemx+>ggj ze&wux?Atv1rta?8vFBdvpZdyyg07FhAAW>;RQ@gJzax7#?)x70mBzY0zH;^de(`K9 z@$C_G)co~FzNYw1-1F>j?8COZ`JLpyMDOic?>p}%Ew?7<+Q5I}GpR>u>jxR9vgOmP zVX|e2ecE|GAAc?J{T@Aw<%b<#%h~h{bNaJ9vqg2km$Dyu-yId_e%8Bx`*{Ar6Mqnm z#}?*;+s~7S>z2Fad5SzW=o0hW^I4t`e1djgXS1IF#oF^N&bj}RIf-@bJD(wLXE^`6 z9Upvyahr&pd`{o}&CmBtbPdY?4(+WwK+Nh~_DBEX=RZF4G}lX?@t)UnX1v0W{C2^? z-ycmEKKR>r9(;QA$9?}hdUof7u>;ZPyAK}t@QX|M=Ja(4nEwKa7z26VMGW3g8n^V8&ZNC2w@AIFE$_CL~!gwqCGWqU4QAODU&*?X>4jl9J zn^)gazO(P;`}EkBd&a7M|6!%zEx)7B)4xZ1r)Zz%)Z8ARl(;zmvs3q<=R0gI_`d-C zx;Ff0?<=j?&Cj{{G(V2>-`kvUeLNHY*XG@5&iUeq(zf3#_g&@*eQ#c$yX$iyjhimo zGsL=}_p(paHQK3Ls$TlD0`J?q$*<7%_WjrHJ*0*4X;` zx3MLT92dm$F6_xaeYiSczh1R_5E*a&AxioKdo=u)dhWVuaQT=dA)vejt~F* z%W^bru|@Tk2mMD5nl2Wt9!h`g2e0|Z?Yg5*yAl1 zly4Mz12Kdnv0#O{L~o~KiRC?BPdqWb#~Xn=ptUl`2n@rDYQ(ano` z(q66S)ds5tUPIA*f!BjYSmBqi`re8^V74SZF7WafOc!{g1*fBOC%rPyD@#oIUPJYa z?+y8hN#7gstE5(Xj|}-mXu@^8jiSQEg7|t- z;c`K8`k1#-km@-e>x%_b$6`~l65cPzN>`4>#%R#7*kpXvC#h#oX&(oB#|nCn$2Rws ztsaXFA5h+z10~(ZW4%FQvnVzg1gk}{xuBYq{)3F#k%PfnQEcj9iiCxObjs*0!Q8Rf z^ew7!@fMu4dP^{SEY^KU34@177(EnBlH*Wn;#h1^39E;KadIS67&Dkuj$|-Kj--?> zDq%Gl43gt;YJePvmE&;GPmaS;ElOBD9Q2aoNL0%aRJAM}CCA#)V5KP5Q>26;z5wKn6$Q&hv6-S2Eng~{$H(Kx6T>NQ@_3c! zZ5~hbro5pO@UI0{gpiLdg4GYF*kiM=sD~y9!w1$_SO&5!0uau z^`zH#3!kKq&~Hg^{1%EV+!9Ps^DWBOdnhr8;mJgQ((g-RxfeAH+s6w0e_G%T^5NrP zEHQ{vV!`MMZ!LP?8?Tz?=FdW7VhQoOagH$l$zy;L=jsOrak`%`Nt zyy*i;eA^q;j!`IR^Ss%E!R85n<6y9H!k;`8^rXC%WWd9jHj+gXDR202Fq-lQj+9KD z@FtI_LyiP=NdM8)c*+}9e@-3^x>MeKl(2R*F_Q8&l-yGka7WNkk@8Kbn`erG8H^~R zOS+B)V<)_UW2w~>-qCYWVP;B7<0XQBM58CGQX&7EYdVeICiU;-18N*LAnnoI`chsM|B z;iBP_-rV8jM#|efTt1uf#*ZWhPvUumvqyr-l(&2&MZ%^MdLz#dA5CpyrhGnov}oys z$0!>);SCohHjd-;@`>a4J3)u`$gu;*iiS^kqsNlt$MJW0-*KEg$H-Vbl~_6DThwXH zjQaiyeosNX`&8k4LA>i!;Z!U=o``M6N`@1$p?G>K5u1u@Mjqa?>5uG-4QW>OmiEQh zPI}Ax5-TUYzGR~3ls9`gSUBZP9SxRGdELi~mri*@$5V@^yulNx5u_)I`c8RECp4Hh zQ^CwBujgcHnmi{lb?js?e#)CUnHnX_Nv4wZlkv@yg&QZC@_A|K7yrPG`xox;k9$*$ zlBrl?Delk2+B|O}o>+`;HHkK@`92npb5b(CH<&-}4Acxs*3|Bt?v`9ARL09Ss;<14pBfR$ojV zEn*NZ94#LwjurodMMaFqv7#g)y;xMfblmGxhYTGH=q);g-ai)5H#2&>a4Z-);VtX! z`mtb;dwv!EeaC~vt?1odNo%>QVtqGh_BU566c5)WqS~wBIpxIMx+QtR0SZ$BPFJ$427a{^a4<(w>rK4)&JJ9*K4B zOZ6X)4enEc@qNX8hhrQ2n8IR1`%|mQ*xdeTs@va)@dtw0!>nSdB@`UM!nFgz_+b__ zB@727j0eHk;limPHF~&kK?y5Cu#k+c2Pw?x)kHaXFqluqMh>PhVoC{f2bpbSOr_M7 zn@T6B{1z3N(7HEsOE8{{E#IOdn@Z?C6pSSc2M(o1lZ9g%hEs=vk!0cQq114)a9IiK zhk~JGVOKIWNWNsZy81{kbEI(ONNV~>;nWdrAQp}WeaF0Yma${G-eX-W94Sh59V?tt zcg__Bo2>suDRu$tMWrY?7N0m?ICQMu^ET+ncHoxaUVl(ac1c`Y}3W~>zz0re-h1PZ~9bf@iwL|x?}ZJ zFjVYqLIGqZk@ny4GWl0>x7S$GUIY1rA1wN8fM1dPg}3>Y zUc2YDmyN|xuO6K$2!>+bYyqm)3W7npwIIcAvtQF$cRU!2`+f1$Xxtx(=dX9t)9HDg zWfL)PG}1Fu5RAmUdFk0G2!^#^O%28TLFws@2NQ9BAf6hJ`(ycf)E;d;M*V<6F`@P> z`axF#durF7gI=}gRTuU7UVWX#*j4{kUKeG${Gw@9UNnyRl#h9Xvrai$zIvtk z%DhI;YfP>eaJo^vQDEEi6+c)i@YZG0KtV89z#vH>n<-!_M)%*Q%vRshVY$9tP~YNN z={)GQcwS4;>wE3&Sd{j{VSf)pZo$uAQx5v^(?M-6U(6qi1+y`KGRC~v&$cV>55?o7Omp%0Oxz!jvv%g%aJT;>J$`~S zErt=ptcS=|m5Q%^Cc;>*HBtX;J14utKj~F^UZsX=dw|89g|7PkoL{k}|EeE&)Rqb= zy%M@K@>wQv&1cA`{dn&lM)@k8LJZszsf;tHRuLtxt(7j|6SS~L5y^Pr?SxM(GLO^Z z-hwo#(c5voosaymRIp`ApMJy+=->dgcilgV7bYPeFzNk3VZU@@uC7?f!RIS;#$o z=vSThPx-+@oLJy37YgR%jM~IR+?yy!_QrJyU^cdO34qP#cx;m5=-GMhrasUV888ud z>1H^AQVX%_$R`hnGFtN;U{_aW4 zCcNc6$-#uzx3_$j*=X;En!hht-RJl24@UR<1N%`zTJ+Mqbu79VL=frZdeis&ePV9Q zR_4ZTF81Um4*BgyeW!V~&GXsC- zen7tssths5?ta$FFjw#MIy|o<$)?|0K|RN#^^2*~tHl?M%$HuPs!$y_vMXiRnJ}(_ z3cCC_Yjn3NApfaIeS#@(+)pqZ7W{Ht(_7FK>C-%Z{oHbx89}o`oQ0W-Kh7rhfve z{3X$BwPODZ1HRwuFZju^BT-x(@_J%=zZpyR9r6bDRC1AHaZh3->GdYkT&5UL=mN#m z{$&3lo)llPe#oB>5(7zp=AbTF%pGiG{LX&XDA%VrI^fdCKtb_V z$*V80eh`Dh><;`SQPGX-3jDsPd><|y8+;;ziC?AoH88@r%QZN7$L0eidA7*Ygh`R<$(Eb zN%HPkFcb6oG)Ilc0(MYSu{2rDKEhN(jmf)T_A$NA_*A}C7gOTP?oHW3dFurMo5C*5 zd|a5stif1R{)Mdnh-*w@J;s8|SQ#q_IE9!jAn$BJuoUwaWa4H)u*e!96Gvmg0&y8j z&By$?7$s)n!Ft@EkEhn+EScB3!t8%}`;s{Pn$wvIuD-IB%!o!D`%}ji*-P{&KtvMazXj z-yVOhFx9(fH9agG=1PdPat`!FT32(Eof#Q3DF|7%yQ4;pxgx zecQg8Bm=L$O+$k?IqBb0nP~7jSVVj-Xl%^@(YQ%^x_GmyEf*af)p4^uH~PFP?Wx%d zXEf^gXRoVVSK{&w$9(p|cw&xNE8wyi3&&O_w<}zt+ zN=NHwa#No#1bl)r5aS$;D^(0Se?E4t6<3+FgY|-VG}Ph@HD?X}vvZQfobR;}C%j0z zmD3`|ISWStAIH#HeMA{oj0?)69M}Fk| z7ou0%(6zn3*IW9QzU}hu`ri9qujnKr+Cx!Ht!wxU2eX7`TWHTZ+h(Fg~!i7anBnbiujYHo_nHsXNG&re)3BD zxkp=@Z<6nx3R8Ht?cAly&1ai0UAlN_r?z|Ub-8wv-}mHs%)9W!*~{mjxNxrh%B35a zbk7^Ngj@BvcHej7cAstkU>l0Af9-dNO^z#E-P9|2mvQKqETF31$|vx=x_*7Y1YiFv z9!13K%fIpFh|34+7)gxqr)s!o6b1ieenlL*^p4a%SoQPC@4g3i{o(L0{E901;>8z# z=~eJ!*^ak8MkP(Z^)J7$#I0W|&&FT%M<08~BkgZEd-k4t%FdpB{K|#L+SOV1_Tl$z z@4g%N&O`US?H!Mtz30A1&s{$M*xAeNm*mi=&NqMH;m4GI&tom;E}d<^bnbln<%b`8 z8!u8Ta&d~0*l1L?;vUb)beZohc(OzXKzPc)|) z_g9+JUi!z<-YYA5q4^0kK9wtZ#!EkXr9G_ylslXA@qJ>NA4(7 z%el*GH-)K!(#vMR=p}9$VaMH_%Gz%I*O?Q+3`Zhk=jLvJ#`v76`I30Dlg(luqYp$tH=bykHY`d#D zugy0rezW$eVQ~B5SMBuPIUuaLCd+afyCVl42R!Y;wX3NajYCm`W zLb|=BIen@5=dLtgruU-3=tUaNU&i#jg7`Z!tQCdc)`VPH_hSz{;EtM0FKa`{9c&_GWrd>np?dEQVda z@+2eh1LxaY(&sLuo1biJ|DcP#XsvmLMK4^ua3&h#igU01V%wS4=BJulHOw!k&(XOT znSBVji|j-h7ods!m6fA-6@zM4( zxu%(#>w`#Bv8lYvV_dqKgP6ydT(I5sz+LI4D_Zk&Q^B^i!h0mz5$w8zTyH?SwfVvm z^f5NJUc8{Cr@aM36_H!}%ciIq-i784=KXf_`_=$)En`4v;j&CGD@4Qf@^tI@%k6nS ze5KQ#if#F2_wrkgYt0+~yUvg|n~b7LubYW>SGC)Gofie>iCpLAHR4(z<SH??N-biGoC+JJKY+P(aiL!16C92!-6okMeRwk=AO@#SJL zic2pSpEiTFUAK8^VK!A@UpG;(lZjRt3ASu$!kJrjnH}1sT)FW63l~4Y=+J~jO;IrX zt!ml|qo}58?U%Nu#Arsoc*! z`O2eFD=u#>_|5|9$T{-?CN#pIb>;HwZLPeVZfm~uS>`o0zqg-C5=R^aWi&(Fq0%Y=T@eza%S(Ac*5d)S?O zMLlB0(0;D14Ts=k?P~J$&TFXKWy7cd2Tco6fjX)ibQ)QBwX@gvZXe(c*UP=V+wD@p20@y5}wvvUS{6 zgsWLxQvF}@+OBu|@!wUYyXpPAN`Iwx?pEI`E&sps{cjKazv+SP@w)vuXUq2V?Z>%% ze^Zul@jb@vcDZutciVM)rR&8#cGt;koqWWu;(L7hq?gNW2l{{1$-7XOuYbaoKh6Eo zdetN6?NyH=4YX2i@n(mw(pm+j#%W+qdcD+)Z@d`0H%6^1T;czI>mP zYo5p-zZ0(fc$<~qE?;u;g-a%P!+b}4FFARDIsDr(epWAAd2R-}uKkH~W`A3^$!h@q z-BEtR$>XNK+H!k*USs7af6-E8E>ppd^2NVw@^wI#t@2f_e6r`p@bBEq_<@=pH__Z6$XPkWK*G*nz*kRwelkXN^lTKbv zc}&<*e$B}%c9EBU;^phFbn>)i+o8X~$(>+F{5LxJn$=%zYPgljwZGPVoW5K8o1A=T zTlwN9TFWc`$q3#eAZ1H`nUgO$Irl>8wZp#Qe-x#AOiz04kdNI&KI!Cj6wa@|+SQ-@ z%}B4OX(V4>=j2^ZUcXJ=2AzDn|At-pbtmsdO}_ufoxE$<^lO^Sm(MzR z;-8qjYny!0$$NH@ujb1CQ!78et^B5wFFN_cHhHocYj} zrR!fqziY?aWo*RZhN2wtV|GUHQ|0WaZO_9py7lK0@Jq{}n$$ z01x>0;o{yUS8ZYw|R%C9*2JeBABXTr%R zziQ=s_FOM7Z87_+HF5DOBfj#-=a7@P{}+>&n0?iDkAB1C+x3q)dHpv{z6tW{pLFuNWs~nVKi2<+$#Wt}*|0x;t)#u%bn-aDeE%=H_Icm7d}Wkme%w*M z?Eg0TZt+#=lpOHh(tUMBe7)J=^SS$l0GOPyO`I4*P=dyZU#L zr#DPKi9COPX?6M+^~642x~)6Eecetz_tz%x0A#7QBd!PK+OL7Z%f;80d@iTo$#;v7 zm7B=dom|(~=>IMK%TE9F|Frt`LmBz)+jR1jHYU8rK)!r{9|hEFW}mSfK2pB4f*|3+-m8Qhm70z z7iCVqkTm%W$Zub*lMfv+`O-Fdo0E4HnY<)w<<9wg*vXfWlXJ`dL07)}n3dQ3k}sch z@}A=+p9T5$EjoFf7G_?0(m;OuH=TU(l*za2Z+Ozumu@wxfAhAu@)C^8w{O|iAH2%y*Zh|+Z@6ITEw47Nqn~$M z{vUJlhS!+aJl}s~uKlNf%F4Tf)N=i+>?ZPZCoi#LJL<1@@}$Xk=x@1+ z{??nw+pgEYqy8=@Z>0Vk`De+=cN>4J*UNY4-?)i9UTgl{ExwXYUcQTc)lQy9&TB{g zO-?T4_wRI@)jR%9W63V%7o2=}m-y?fdwKhNoIJgY{t+j4hVSVAQ73on|Bm)eIe9z! znId+`%YWMJOYKsB#>wlElXFM;;hU77yh-_@pLzNEPdmB#m)DN^8=btwA6q8uE3++hGUfBg8Y6@b7b645hd15#M_o?0lDTn(_Bg zf!8`^%ud2PkdMJra0EUK2jDwkKl};U3r|z7$C&MgKgMyFG203Gi1$nfZ1BBIyD@Bq zZ>M~tlQ%#W%4mLv;S78a`3E3fk?A*vosh0Lqjg68(*R|!_&z>1)cU1kk>jw&9gC7zyJJ!E zDyaU|x+#67Q2m&8$1AMIe?ooZkW0&F`l0f7!2dyd6;!$PpEN(N8MCwQc*Y%1LdC80+t#2qKxaoioR{J7P#Y|PF; z*)s(rdmTsMDCq-G?dgQd*Y4ykjtx-xGEn(WL**-R@|0r$m2ZSgzImv6XB;P? z@{K~}8-VJEUMT(~WVt_7-G#<2t{e=$`4q+=W^ z|KeLM|CD1pq-kdw9V?;AmqV2=bu5M|A3)VN`xeVL4V7=gaTF?FKUBUB#~LWTRZ#iM z9ZR9|7eVD;sB-o}Tyv%mV)~hN&fnB;YsTy}{1M`H0-l89jvJgOYMvL5vQDkLW0B)< z9BSS!g+HTmoOiCM9wk$pQC~Y$eXS5v&on@l%RuRweuMA*1;-;$=^c*4_gXvapzNtNhAa12 zzqLb}mT5DF)vz3W{bg2PFVudk3u-^o0ZWLVc4M{;zM11zW3~l8g}qJiQR-_nhV@YU zlUk^Gq6#VwD~;I_SWo)O>n(lRm|cYLqdmj$jmSGi?Cmgy0hFGEF}wCU^T#TT`Uk3i zXN+MVR2=mh!+NNEwNU-Ia>n|p56bV|Q1VU~#W9q>o7{0FlwT{1;p%DMLse$Q7*4}C zkbeY5`&=k}?NEMgf%0o3lwTW+*?K6y))}+4P=3uo`L)IvRzdl7?MHp@{n)<%<*#li zJ3FE3X>-RdFscvUt@@1FdRVFYjM-ZFYRYBc5!Gi5tDx$ufNDpvOnK5Rkq3W43W+$Qa zOhD-wH-@87_7203(vLmxBd`@pZxehKdK!({2KWHS^~P)+Z<4ybmt8^czp`WElxvl>Dp4a0M#=43uB$q2jL=J`Ag&{8I@vjw+1Va;R}s zX3U<38b_s2AiI|QYF(3l;7(%%oIzt0%anl49H;u-y9!gIwl%5(Wd#a7uDkys@joAt)d&;5gDKmzpPQ%e|u1VSijSbSE1@(hN^$bm|cXbf5DiYhpK-Ls{UDHI1N?* zB$U609NVDkse#g81*N~zn5}?Oe;Tu8FzQbj^`|jRL+MXJ=}AJxVPMQApyDuY%z999 zxOs=g;f66>gVM7CrDqAM-xiJ81*m?TH)iLc`fV0!JkJ=zDJVVTQ1UT&1^K8k9EOq) zLeQ2pBCjvJxcSr64t ze)wF>)(*;8W-tDy9Z+-Bpo1!`WdgBq{3#xMglFDIe=l7J7x&0?#69cmn`8MCWU<6y;@ zU4|M5OHlLiqA{F@s(%)0zc>lC&zmr2hoJV0jV`^xrPo2t+i9r%qUYp1v0de}i%@pX zL)keE<)=v~J130UaVR^-jM-5rJ4c}G95#l7PFI^)w;p4*8>-*BjM+}8e(Qi5 zkL|{=6-rMDRQxBP;(zm$`F8;-uIFH6w>zGMs&^c!pT~^ZQK)_%F=mIM`gsVdp9hU$ zKUBTFP=0B1tb(d14W&N?rN79SO+x7pjM)T~{y3C=&lql;H2rH(dRCzPvuw;RLHTFV zm|cMK&pgz8HfIcHpzNH2(lY^N&$ux=24&BvF*^cf&oI<{HDnA2VANhG?{V^OC+~Ff zc6eF+05xB&rY!y!pyn%nvO)~!pyGcFJ`HQ3{8$6!zX~XO%b?=xv@u%>6<;OBY#J)Q zilO2wWek&0_Qsvub8>$4M)|^ZDETVXI9Y@mCkw{x6x2BBa4dK7GAA#E8ZU82eo`bF zH)BwGMvd8SsCIQiwW}4XT}@E!YBXjWpxRY$%+^7*s}`zV8Dm%tWp^c%o^q(TC^KeH zL&Zg@F2p!CchvvD&9)gQx-T~Ky+K-t{_RbL}i{4^M|^)Q+jjoDfl z&5Ka+Q)3LPpz14k@-ins?c^m+UJQQ@I|BGoxLTw~fHVH!M_?apg`H6LwL{t00OhZG zD1X%%!wgit)ll|VLfKzo%$7shUuMjnhO)mDYCM)0!(u2sMNoPIsQybBvvH{Y^NiWe zqh`+rls)Ula1~0=7<@Mzfh>ucVPn_>6<0~e^&=)z! zx5Joihw9%psQzs=hD}iID~B5Qaj5o895Q>ypzIxns%H?&-T`B_AIjc7W40H{-X17> zyNzKdR6QA}ddlDh>Y2R7>KTVoJus>VM)er8{V=M>nC*p8Jus@r7yiV3t(#4-n&WzSFYTx^hP6=fR}8hjCZY0e9I$-r#;_Z{jr3~B zR~Rx?#&Bc5m0O3>vkFyy7Dm^djA0M_6Z}&FF)dSW43kiL*Z29}cacv+>6wJm!_)Gk za@A1Rk;APR%fATa zp9RM`sQ#UB$77BIPr@R?TvS2Dd8IL10Tt)v#%vi>oS%k@^HO7& zhKh?KC_Mp`o`f+QhtlI2vzvvcX9G&lx-ndZ(lY_I-i;fxqfmYva%_U~TRl|!>Wm@J z4v*58q0;&JE-~cCk)mUM9w|C*bH}ajI0^q0{hI~Wud7h~%FmQUEOF^Y?s&Z~;osAXJ5~s;N<;| zy-@acx#Ld93{<%qV;H#OggajNfwgPi7!JdqA^(suY=;_;ZN{(^G6iHxjNtd4R50y}UTIGgE%{L>CtKYZdVaJ8<*>Rua`d?Z5m!R}6IF3WvH)af5U3wGL zeHZoaxYiw4L)ljeH6BkpdBSmH-SjU(jgtlV3EDSr4CmkvIi59!Q&6A#h==*yXVM*u z9ETI|PW1G_dtod5GWNC@!$!!|lW8!9bx{7Pff{GU@R#x5I`_d}fz$A<#OIVTI|*My zz6r<_lo>aMBT(%ef@)_URC{{hk5azdnC*fg$DPJ(2V|(8X@jc21*%*l)O^uk%+^EA z7j?#LEqpckGBDcz8N(_lJ?k%;J*!amEWyaXF!HZ4oP&{njo}RZHMJYYXy24EoPg3h z0DlR2JCuJmzvFrOJa5r)*s;nn?YOaK`Fb5&9V;By_}+v3w*qDF0+hXTP=1&-W@n)M zFm23ELHS`4G6iKOjNurRy~9pE1etm=gT}Dm$!q?H%^%gqu-F}^+;Q7~H+icu4BTOPooBz$&0RJ3)CGNQC&nO%HDQk*al^9D`d!JT8v>MR6SKt{wao6;QEr) zvkFzu5>!14Q1#3k!#SvWW{u$tR6WyBewi|c6HxW^!QVjM1XWKBjMn=;<*VSI!1e#)dH<1eYsPREYWQ32JDr=j}0)Um|Li=oN~E`5O@lzp>McF!2YUa0atj$KgsTHSGrV}naS z?U;1j_z&jaHK_Sy)flco`FGhEEw2Os5ohW z46RJPF|37zW+r0{tD*c@0o8A1P;pQS6~`q|@lXt9Z^{@JLD`!$h5=MOBp^dA6E}vN ze`0!PVdO_+I0fbJv`a5`>60u{$~R#QJKb@IJFbJ8_lu$Yo^9tVxoOUdB+?ccU6~_rEzmGx9)1$_41Zw^mHiiQ*Nt}q9KlY9;C}MoBvnDa2=}sD^PK<2-Uv}kRg|uH-@uLJ`H8>BvhPC7{hU> zI2kjBqfmNAAVV!PYzzmX>RJBxR_`Lzc%Om{oy?>$oPhUoJZ=nIp#0h3((7IN^1riw zS%mWc9F%=CQ2v}YhEq`foHT|LQ0v|}B&0H9#&876zAmWvs)Jhh0;qMXZPx6rgIb@` zP;rrhsy~3LKMqyDXAC#LV)btr!*!_o*I*g-uNuQ;sQSm?KSJIKRevK?J@ruaR6_Zq z)TI}@iqj@{EGkYLq2jaxYCbE2gkIA6a2_gd zXQ1qxgtC7E5*nFtV>s&MLry*j>B`K2G3;~la`>B+U*n5_^79ImpXZ_cJmxs$*aIVf zz{nrQumeW^ForEK@&}Ck;f|Z^IBbNGKcL!O1aUzoX$%vPf0?*3-25YJ_Zn2YSD@@z zg6iKzW4Hj-zw^d$4$42Xkfvs4jNufNo&l)&uM)~$QTA52V^RHA4)y(sQYb&Aq5PDD zvLgXyN8A{CPkh&ryKsV+5>OrxD09@-z2m8a}8>KSv7_$Q0=LMAEsO}RK6mp>m&(xtjct}K5h9| zq1wL)W%oS1oAqbTn4N_>|D7>rr{Sx~HwAy6`X`OyIF#KZPCg93hm@R=?--}#&(xuN)xysENvlB2{=NyNee9*~dY_zUo6wLNQ>Fu=R z)()3m;f~AQaT?06DJXxgPPus$-ogH(8vZj_0yUmJ81>KZ*}6XbZ>;1*LFPaI5lDV8=>@9!2d$Nntx^KlV7m&>>BuIq?bUoI|)^9!g2X`Ek2fv;pV?I zJJzB4dj&@04wm4@MPqgW>T`&BV|ETcg&ngH6Eic$a0;rw+n}x+lsWeOj@8oxWlsl` zeQoZz2HuOFYGb(Zc`y3>VBHwbL7hj9L;14|N`K#&etekq1L}C-Ul>n2cK>s$w-ahV zwfWoTuURNR4MVMW)iClGlznk1dpu*fIcj=lpz=*Z< z;-R1a{uA{zLA9qIN?#pRJY}HtR>2xr;pAoT$2pEW`TFOq-Zf*m3jZGRMJRm}PCoAB zLoU6`rFXjYrO%qZZBX{M8pDiB51{lV99Ks487Dy0mT%S=c0;Aty7Y`o zkGu4lVaq>l3_GFncR*c-KkekDPM(75r?t;mz9}fXCXL|$RJ(eeyc)_c6(-M=8^fhf zTly?iKTpG7r`_XF<%gl{D2D%)W6!0}eagk{kj3pNRQ)5yunH>fXaBMF!;CQ;glf;~ zZ(6>P!waPgC-e;P{9q~iem z8S3jdhF$QF)jlXcYz)}EvkEouEJLj`OUCRX)H<_Z%+AAClWz_lLGP?FoQ9fr`r%>x z+6ZMwJv;+5Q1w+q&2wc?>-A~)EP6_fVH!$L3d)`&)Vd!S!vxg2A2)^`l%CB`@T?5< zY#75eC_Q@4WK=)Y{v#u@|EO`t5!ruKbFBSG1(e-oP~}S8aSDE(dXiB7jzj4aQ|R;D zvB+__N^mLvB2@l4sQjYx&$?q#`NyE@9f2x02<5MSm)_})+oAl|+{OJ7Nrnp!_1rFN5w_lwSs*{L&BQmp)_I1J#}`sCqh} z>S;HIZBX^J8p9SSzcfMlrO_DHL+LpU<(E=pwiwDU%m0w`C(6x2`DFkqU%xTi4&|3t zD8E!Xmb>&KD1WSe+{&#Ovr90FJNQnOhw^_Nd>1T*uZIEb!rp{2+<4yeo>spa!&Rtt zcNxmQ87TXvjoE%E`+A}3YjCV{>E%%N1yJP@#;gaGf2Pmu>xJ?|JCuDjQ1+dMvai$_ zrrg8XdWqFkLZTMHlHIAhG# zz%==);T7~$8N&)FJ!`+>MfWky!)Ni|xMLsGy3_+@cc(jUhpM*~-c3C%#%vSRdeCUh zHbAWh_3#Mw))~VLRK3+uekpa_=(T#5p!6?5>7O@d=b-e@8nZJ{`lq4vPZ`4rDE(tl zdPbo9Gi=NbLHTFUm>q!fPd|JNJ$=To2S)yRj=0eHf~~Lz{;J{(X35`X3|pc6R}JO2 zN+`dTx#LnOd(u$tDmG?QQ0*!*W|L6u3ZUARFoqtKJsTf0zb!hBLe2HG4(*WhSdSkW@%5SyCYzE42HBf%5Hinf@dWKoN<+m;< zzg5CMcpA!YrN&T;yy9SkMOy7xfvSHAYJ4mjvkNdf&oO4_pvK26)cBY&hEq`WPeADz zgKEd9F*^d)j$vbV2&x@}P~&6381_Nwse$j8-BA4-K-n9IvUmNLt)5jVdsmFvWhi@> zjM+sfdl#VWoi~QFQ1whh**ghk?}RZs4rT9{F*^!n?+BE=!^UtBN>3e>y%q3daP*g~ zA3C7=p$ckWI>BP4c8o!_V+gAL0jPHL8?${-?dUaTd!X9U4b_e=W7q*ze;bsZ7N~YK z8MBR0?PxG&>!I3F2i1;RV^{;FM~kW2vCLwqb}YbXeuBdsw?d7x7RM&2_B6ogd!G;awxk~Q2nv?QPaQbxZ=13<=+K(H~ySAX6K;B`K&QJ1Ecv7K1e-N#&814 zzoSrkhN1Kf8MA{>dIpTyekeVCPC}1CC0EAN>3|`z5JVj>hGbCSRC|2`L`Ipg>orl zxXxxk$7{xL9xjn@4%U%AYYd0se;~cn$vd39-N_r^WyVDks-Be(o1SGTJxj*WgObm6 zTD`Nza0DtIhK*qtR6E*TdYemc{E(GvFoso7_EtjGQ(+9(f8Oj}gwnfU45y*;PeJ9M zG=_sv_SZn!mxjt$Z1POX7-l|bcAbXuZ&!!;w;ih7<_EYR3cVY~aLygiy5lh@y`xa| zj~K%)DF4{y^J9%muXgF>E`6lks0vAak$au_Tx}`mWE=^!343?C6H?hiy>yv^usx*;5Inr^1*mgDO8lV`NV!Ok$5Hdpg{)D0}8xSyY!^hvuMoD!~cVF z)9@I2$Dr&LIseX#x?_>!uoV6_`O9#Q>^*JF>UU+7z7AFYn&S$5nBzrvJP$R`%|gv{ zGsf&R)I2w3%uYhha})3gej7K2qfqnQFqB^gq5NGBbzQO!s=m`u^^`!>lQw3Hq3TH) zvqez#B%$gFjA0zAo((!lde)%ytQxZ`Pq^Xj~X&&2cgcR24FLK`;B2Ql-_Qr>#G~*ot-eU)0ka>k)6iu5{&GGk)6hH z4oY7?l>aKB{F-*hamP7=Q080JTMMNp1Er_N7*;{)DTmT?8mfOv zjoA{Y{z)6NMNs{dG-d;+{z#ny7UaL-=I<(!ot;0P(i>|Oj*a{ue%iV6;t=(Gbw%T=2WMF_G1quWx5Tv*O zK>`E_QY1jY00jdE2v9I;l^|7uM6LH$jMu8a&*wScbZMG)WlUtUzP9I`@AsVNe9!rw zKhJs2^N;BcDXx!$OgE;uJ_<730g&lN6pawbbORvM^?{scdKK6Afk#00D6a1XInQ*1 zO-QFl(QtuGrwe2{?I6=>Q(WHyGMy&H^$j4?SqC;Eoq9#17GydrL8h|;WIC0K>nlK} zQ>M6nA;@$VfE$s{d`06>km(eIOlLO8`qUNITR_(5*d~+fGa&186l8slC>kk{=?sBP zXAq>{;Hlb1|uL3DA02`40d_{wm#`BOg zobw;hrxc9@NV@}~kBGhsbU;4}CotUv$aFm*({X`^ARVWo(GD`52GOq*eJyx4;;#fh zjQCZGMkRO|^b5fC%%`HEgY<6*ndkASAN(-b2QEWAkD{?t!ka|jAo`V}uM(~hR)94~ zr&L%BGTqrCr%_neXISVF?gW{SE@3CgcGL#4owq2iZvxrQ8x+^C1KG~&!7n1;YZVPU z$aYi(GJJ*7+bbo!Ov2|&c&UURD&g}ae71z^63)u{BIik{1g4(`nSKg{-af2o3`uxg z!ebI1mGFp!_k%2F0K5_FzkWr-2mTeZSJCJNIqx9<74r_a$RuQ=2mBP$X#zh7t^@xI z`Kebl)`FGLuL4hoex;(Z0%SSLK;~ls$b8IKG)h6{<4{GT7-T+3=3|b?B=a#FWIi%z zY_yjK{}c97ibfKoy+M%nVj%5B6^#Lq_9BW#2&6rd_WDI8X)g#KhI9g8J?!`u4KGML zJs|D4K-zIC8aqMS=~6V>LE0f{r%hy%c3ME%X#!7zod!jt9;BUBUGvuceh6{s=Yt$S zN`(U_sPnCz;Qt`JLge)ED&PFhB=4Ky_b7RO5CS<)1tr`Iz5=})}Co zT8rg3UvF>>%6eN|5ceO3_#WvYl2c8f9QH+9}C) zx=>`2?Q{Xib~+zK7VV{qMhVDvItQd39pt!bQCvT^Mvbc(#r2~g$F&iV^M|yeF$^-D z0Lb$jzv6l?$nv^C`lSV=U)G7<4xWJUQqTvzZ?&p-H~4sFAvz!xA_ zfNaNUJLVnGM};ns;Z-2RGsmj%L7@x$6~fnnFnRbwkog)r#$vu#&?BrAhHKRR0b%_r zRX-IV&mZQ4+`s2&)EClofymn7D?sWiK$gE$NN`rDzO;8vK-0GzP(9G%51mp^uA9LN;O`k4HX`$HhJnO~LL_G;&1)+rbLda}&sZvkqi_>J^Q(AoEkJXsiU8ACmd05}9OvR)EaUJn-{i z0+R;jD-M1X_G5}h6lA`_AeyK>q-X>|=F0~%KYd^?^5ao7dciTsZbhRTWPVH{KQ56? zB0o;ZoX_tB(e&+Iibgv~J58WjZjg5B6^*qZ?bIq7t3cW@2|FuAHVHdbkZETHxDa+K z6^$~GcIJcha|uX$#UTAWN70xK($Bi0k@L07yF#MI!{VJS6S(i%il^ z5Tu;|h#|`ES2VmJ?W_RJ@fPH9Ahbe_uO5*11?~htf_R-EhA4ZxqERXOG7v+JeW9W; z`~@{{ihvxaz2G8*_bD2kAnjFvzd-tlBUHQrkoLUbSzs^tb+8lM0nP`xUhG@0=3&E? zinZWr2rmN}Z)_Q^n}G?Cav0=(WgzWxvUd`gSgP#0K*nnk)`J{ptH2k))Dkrg4}%=% zhd`zi29cCKq-gYk9Iss<$MZIkTZC&t&cCW8e1UKt$n?_aw44XVLDqW|$^av*9kJcc15EF zWP0mBrn460_*JWD*ugROH$|fgWP3D;_P9c1lW32XkU4%;faogrGDTwnNIQpuv@;K+ zonl2}4oEw*6%7`Sc1*&~*kUD{gq;jz+8G7WwCy8`Mhc{zB#0yqPk^)+2Wc;+XhcEU z8&EVN;N_^FFvxK?q-X>|rso5hP9Mnr>QOX$LH1X-qR|a9-z591OJtJ$)d?PkbasO1 zQuZ!IqaCE3CXo5A2WhVsq&>T$u?nQUm5Rm+koHL0s}z}}y$X=_%0P5U`$9!yK1h2d zAng=`v@=K1m<`g7u4s&*lhY1KI~kEl+8G6DX9Pr7wx<=1VUTu0Ap1cd$o}sJ+5dNf z>=#WS`{Nvt{cg6RktxTw_Mnf07#i#`MPojQA;Dg%Xf%|m{njZO6(X03Tny5F>ho%R zh=LryoZ$ILzY6RF6Q9F~{+ljRoKsD7DW~G<1;dYV1?W&L~JbBZ@{E zq@9$aF$}VQCqcN_KBQ<2f=nj{GT#x<90ydmJ)~#^LB{ie<~RV-o=4H>1!>Q%Xmo=t zmr0b%C9+A51CUv+ogliHy-U$(2We*|Xtpzu{y!9CyGS0U^aCK}CXn^K66Eo90m%BF zE&AXmRd^rBa2Lq*R)Rah5|Hi50w#?I7Fnp%R|{xN65yknsZ`kLMQQz{ga1d?3r&0y3T*WW0qU7Yl=> z>bx!hvYq=twrdZ_dUJzUqn>&cjc)KSkX?#~6TFi71~FvVyA+LfkoB?x>_hl$koib| zRP~1xNP9`pJkEpk^8oliU`XUzkn0(Ckoj5x($6Kr*}{?kQ29-PVdQUE(MW|&r`-sYS zROkUY9`p)#g7kAI$o|o;XtaUsA1#VT1IT=vM84OFY!dmdhs^%57G!^|RWw$CW<7wk zQvu$ObjuWtg`f@TEl@N{LE0fd2>qcVlaP%P@Gzt^55!PyFIF^WgS0btsIoH(n&XS2 zkp@|wl%kOYX~!h&42f(Kb`p?To)kpgLFSkV{)X@{hpgvccA41%;12XP3s#}thLkaj|#SwA4{1QZQFNIO17 zybo6D2c#X3$RzFbg0$lXafr3|C>k!1cDg{ben8r3Q#4vY+G$cW)`4dIfV5LDGD$mY zLE5PWamcjW6^)f3?Nowh{eWiuC>jeuvwjqfLqW5CK-wu0nWUY0Ang=`I8@o^C>lCQ zJDCql{eWiuC>klytRF>V2sG;lq@9GwB<&1>v=b*;KZ?cxNIN0WtRIl$d_d9ggB<65 zibfwuJLLOe$0ITc8SlfD{sZFBXzx)pTp;aqfoA=HX8kA{EudLHipDz7tRIl&sTY}K zdDem~Pc4W;s@<+=tORLi4#;)Q*^28&F<8@X8VsR4DMe!#v?BeaqLBb;m;5*A2Sp|! z8*z|!V;~O2_Nbx}0cp1rEz+G0;!t|{*uSguf?@DH=o26g>4&?8^&sczWndqe`JkGoC&6z*j)F`- z2)+QW6WIJ`@?3f_VJO2Eg#IYNst{jX~Nk_73`L6Gy8sK^Z< z+Z9QB>qI7LZxzVnSSd)mhbpcge!s=?e@LeR{4cxw6FCh23UVLF{XL57 zyFkvb+Ck2*M&76DIRd62dqAEiRf3FH1~T4!kmV==S&lg(d*`ZoSBtP7q+b_;KE(6A zSCx}wIlUs2EN2&J<_F|;jTVshDnZ@{R|eh<7K1ELW{#4FK|AauK%MU^F25l&u1YY5s=p(ogmBI1#u6FC&=tP&6Xo<**k9(FE)v zMI#8(zdn%Z^npyrqiFPkOvkNgxIp^HB>dwP*(Ch46Egj?7F-0D2`wO+nte<&X=FfD znSE5zNP`^rlOXdm2r|EM5KYm}?_7h%fP{xY<~InM$8kl&51PkuMZ*I!za)?2y&{u5 zj=MqTw*_Q=R|-o(G;#Z(ipD$;RcJ3(G-iX$@0d;HcLZd9(;%9xJ*8+QCA<&(Ho|ub zanF`n-{_-eea{A2uNb3DMua$)nKH(9lP$tBkmX+hvcBdk8l@oX>rh3b7-acPqWp72 zHi`1jhRpIOE$TX21Y8E$1G0U)L1fL|qiD2)Y~M{FcC|Ms8tXvrX9rn-IA)pcyB!3);h>Hp|=w@E)sWZaw%tuoi^DxmDl_uo8R^$aYi(E&$ge zUMcuR$R%JASPX80em2N>Y;W%a+0Gbm1UwPpDXsH?dx;2~fG_+hXfoCnr|9{^W@?+2^E z_kool{Zj_s3wZ&E`P|%65c9aX#S%UnybpQ{ct4nde{Be7`}-S)pd|QLFaiDHUkCl*uR$;P8t4I2pc{M@>;{=XCwK$wc7Zp7?I7xTZi9r^gAYKi z1;gMf5cA);mEb){rwr@|7l3zzrQmnL67Vjt7z~25!COHK=m#@seBS{_Kp%)>yyXTk z33|Z<_-!x__JL9GT9EzfTOfSNNATwcLA3R`ey|txf>(eZ@KVqXUIKQ57lTgl5ahoL zyo%)oG5*YL0Wt2(tq0M*=hlL=5N{P&1y+IFuM)fh;bkED?c4<*{krp`^$4@g9m|tDmNzEDEdK3Oq$7v&?|Hb8-!KDQlUkd#H8BXU+5J&g$=?gVX4p} zOkyB2_ZNDF9uQ6OAUAj%^6eCTgRn|iDzpfbnDm?J3B5w6ut8WQEEQUWNlg09{e>Zr z`3Qo{hgbAYVS}(rSSqv#&GSL}7w2tEHw2pLfo6K5cM7{Cyj{W@L|-rZTG3aDzEoHu z;l&be5q+ja*&6|AF9|Xq3DNVsk$RpVn)wrbQ1o8WdqnRRy;JmEqHh;{gXpV-l@eYi z;iaN45q+`fEuzoh{F43|0nPk^W`0E<7kxzZA<+j#?-jjA^ls5RMc*K-5|#=}K-w(^ zY1blpp68nR0nPk?W`0B;7kxzZA<^?ZnDM=$_lVvtdZ*~SMBgs@2GQ4xzE<>AqAwMe zNO-Y?TSTA1NsF0Z(9ADr=2!G_(er%TtZ&ityxPpK=slu$i{2^v24R)35~RH{ko%X4 zzC`rJqPK`XgOeFEf1sH^(9ECcbKD`nc#LqW20t67H68r|27mRl-tX2}nD|AnjO0pTv5ASw5jx=oE6jfZ)p+%U) z$)j05VF+Y8L6G_Jik|BiX8NLcioQ$q?V@iGeZA;wMPDWQO3`!u!}O2nEy4_rz@|Sy z(;uMe57Ea(9}#^>^g+>kMeh`LNqD=2H;BGUSSjIU5?(5Li!h1x5z{`%e8oZLDSBbtZ1Dfqe=oLDJ4Zu0nV1ZmGJdXMP2u1396^j)Io`Wf{NqOTWyt>~*nUn(q-@L~zKh(3w+ zF4Nzj>2HwfMnoSHeNgmX(R)Pi7QIvSU7~LneS_$^zQ*)xMPDWQQelaN7fZNB^hvBU znE3{U5KNcr5KOmJ^cGnu;vwWiG`8f5( zqPK`XgY#4BM?l6;f(SY&BJ>J9Amh0~#&e3kOZ4rcZxDTzuo5)$1Dg2}eTnEXZ7}tK zX?K>bp=p+_$LhAuviWR&+bmn3&5Lc1&5doUC|v~2LEeL`Huph&2idUgJ!qCKFe^9< z@n`w5?VaVpHa07c?bs~mY^!Z_*4Qi?wi#$V2lpL3%jQ0~7hC7SE^I>whp~+v9LILx z;3&4;b9&~?vh~jiVe5Kt_j{4f94EHvIU~@-isQwzZOP(cY!k&p#j|X|5A}a&mTmAu z32b8@ieu|N#CM3Uc|O+nG4IE8t#7_}zHk04+tBRD2tUO+hyp>0>jV_KY#$Jol*d`YbV>`Gwu~^r- zD|#y26^L6A$2MItg6(ic3fn}*5Vk!_+)Lo6B|&U`OZ?b+mh@p8TN1}My<`O2;Uy_- z6HA7$?OEzxiug-|*!q_GvGpwN!#1`wj%|AB2)4sZQ`jb!4q@A~%)Jcpmj$u)E%RgR zS=NVbY*`%J^s*6bhnJUVVkHN z!nS9*dpY7S4`S@01N4YkIMDt?9-#v?h%0(3&K+BWp&nO|3~|+f(bV zMOkWt*!pVy*m`RFunivHe>}Dg{SDZwA&ISbgKq<@Z;Wq5t8D5)&qz0qG$Vyg?oF_= zsTo5ry9Z0_5Pylw8q*0s4C+tB7Pwz17|YzH<+ zu^rhwimkh)w*|IZdaxba?8G+M(vNMtWf0qFOAOm^O9b1ImeH2kHdkwR>ulRti?bCm zTl?{Qpf!qZxHSSz=#=m&@ce1s(~!fiU>El48pYOsM&Jyjc18@_$Qc9J_MZ{LHg!fC z+wPq`JJCCLy09HRBLj_pX8^yWJ7d^Jb`D_MzcYkwYG)eT?lXJNMEo;d*pBYZK;u6% zfZyRWBiQzw z*6_X5hpeFx7`@LLyAK?@&+5D%Ox|xDCY=vhT@Qen`>kV?)Aw6PNdE)Y0QJ!atT8h3 zfOUZEf4~|dQx90vq%UmshrzzE)l2q-t!{E4Y>kqMuyu&^KWGg+2znp1`bhVK)?PCD zpfyGgKWI&n&WEh7hrqsvtX?wokTpyWK4eWi1b?L-vZl%8L)KyHlM(B11oS*??RyyX zJ!17g0!ALO4v<5SSd*mlQLF1w>L0TXJO-v7v!+Sk<5vIUVE9RE$Xs2Trh zYxmR8k3D5|J`KI^X{(=d=xJ-13_fk`r#|ttb%=8MY3m3%{IoSiea|yi_cM^a&scq= z=NW6?Gtft#u?|oke#V+2hn}$}$=Ea2IKy3WYj+&7KW+_>-ni9Ay5rW~IKrcGYmD+x z+?pf@K|995F@4(patZ_2) zJL?$f8MXF}g5f_{BYyxBf3Oabp*O7IH^8ActVwbtV;#+azCT(0e*$BFvc}2epRL1x z21m!NnKA1BVvYX=9QliNl=S@7+V@w`caF_}jtw<%jx9_E&$0ENgBe%i9NQ4(^f|T> za`+ruiu#^&ZSHd+d(XA`NYA;pzH_0EoNF7PJb11xLB`It#i`G@Y-29a`%Rngn_%dC zTljo1exYsfLc|-s(3T>HF0>^tgx-0P&2EpX|HF=Dh~`$ThYB%7fR~5@hTeTb%mAZ`l&xf}Hx6 zElnoBWgGq$+=!ro8*R}WZ882#-DFGO zWE;5&KYMSsd2Y7#-He}pzb)Xm1vyVo`fbDHsNa?$(|+3s>AuC*dkYx6#nw;yZ?Of) zzFTbGTbRVHw!p2n;H~%>zRec7%{FiwLPu}2WytP;ttS9F12z}w3)uYRK)@Cy!vR}_ z37QYMrO5SA~ zCKGqrhVFvC_imf#ZgA)UTk-)g9<~jJLGOb$--BTE`?lEkLI1O-`MPcRb-2F&94&N?7CuMQwahu%*g2Z>T>Kn6S97{FmkU4p&eOc- zX}-CDR?L%uH1d>4S>3$(}u z8v4-%+8`OdK#NhIzDOIn2#j2;4O|QkUaTd^*u`3$9JyE15*+Q-GL(mVwG`QRrRJqRc%{})`mfXi zWcq4t+vGrW4ko`w2@9P^nvTO;Ps&Idd*MvUaxtsM^z2ps3mUH zhHiu^dXpBr397N1H0O6T*LR@m@oR2BnDJ|4Wat(xd<)ojtLD8GjNYcjZUbEbtvdjE z0$LyG4rsk(BA^WgkegUQ!%rI-r@rrY&3ikTzFiw3hi}(Xp z)JB3(CGOUS?gsn%HE%yS+^?nj5yf|p=D$Y^+(XqpT8hlvqm7Xx_h_S}_j{V}d*I;r zw8Zz2o9Op6{IroV>NEFhWA`%tz1k?5yjL40NAJ@z_kjcVYtj1|{(v_60O1uCr1XfQPTC8 z*8LbbHlR64@8g>9aj^e!Ekp($*Mel~2U_|E;Mfl|XA~U$ftI1{iE4e615qtV`l6bj zObu%3K``?pZR|&2D4~TDVE0e7o}Yl}pJ*ebXGrTC0tbe)DB1Ut=6wkay`+U-LZ6Mj zq{YdBm$WGLkzZ;9zXS(=sU^tRFSR%s{goE`72*y3N=uT1ztR$<>s77$RdDQAnv;yY zstvq~`2DYHAu^TH(kb@2l!l+^b15yEf`0HdE%6!{drgaz;n%bX8GK#qe;xFVYW`7h zWKu5 zj-hE~{-TZjMRWcYDwk95cIwFL*?Q<~Fx{<>bc3D?^u7zgpH#rI;8KpPVXb# z*Xh02QNK&?-bMW`y^nP7(tCG7pZ4k_UdYbtb=UP^#;cEcq3^q1_figCulJMw>-E6( z&=1_8M{fW#H|k?If>ECy^C5i5rzgolpPukB+^3I`BRA=zH-WyJb^p!a=*@cOX2k3E z>pgzZ>DOI;hWm9t<&a+wlR>}UPyL`@PmqCI_28{w^j1A~E5e6v)sy7lt$Kp`zJTrx zaKC`wPx=FTAOL+VpvNgE1NtzT2;8#@6h}2&_j3NXW~wM=uSO( zCw3gWQ+EbIPf+g*V#h#G50bv1?x%k6Empchz#7V2dR(W ztq)QjzFSX`LwD;*>Ye?%s~>W2zwRM>`gM0d^nLf}-g_Vi@6r27|2=x(9_UBEr)Rzg zx%*zd=U&ixukN}Rdf&aepK|D4Jxm7g)%)*7QO54oogv*7!q1_Qo($>3{297W58tOp z?!%5l_vy*|u*1lG`Y4&YPft@Hzh57`AB=|eSQz@Du%09b!+IhNeI~4rQSNzAcRvWa z9@M)Zgx>$49-th4P>+!P59%T6BM<2V4?!M$NKcTlhxGVE&^sU2T@QoFNA%%Gz_CYk z=c5RBKdSeV-H+-$k3t`KR1cETNA(!>Lyzi7a_~_-L4D>?eT;I?fbJduT?2ae0QCL= zJwQ1;phw950X;;0>@hw5806$*`Y@SzOdom-`taj=^VLD9Q4WO z^kFjboIXT-|MPn2dC1Y{^%xm>ULSZK`qcA!nsg27-Gk732K7GDJ*f8%a=$@6L^(RB z$H>T_K0y7z3wrbg$cY#9Au|4gKKKIk(HHgDi;#z2)RW}ki+bWk>Jxe_L486`l7k67 zk$^t+V?F(2F!U2W{1dR}XS(}mjQ=w|_A@li?w{*DKiAzqhbr@PeeCB@4ZozPUeePq z;b%ChN0RzL5~0bYKAc3tqe(qOrjz;z^&`K~M}GmvhxNf>Fz}ild<_hz^++0Y{!VxO z4($J(9wMD1x@!a+{ezzQ1L%K454-_}GI}@zrZRe(9R0JN`7^RG@;80-Z+hl$P$kYU z8alrSs{|Jo4PRK4x)47j7ZnX$R201kKZhb|O|=PLa4T~*}2swlvp$*YQnuPRFMr}ye2-_=F_t8vvNadpwq)liLIU6i@HXpBGo z*A@k?Eec+XpV4cJV%HYM`E%sjqS0%MGS_0Cfo~T@zs-HWT{J}Y?ke)^0+YLnhRKk( zDC`A;-lBdo?kyVhA_?d9MXu|My06Di?+r!18;bllAT)79(GWR$Ls902BDCWhi+XMZ zoi`S_$jFUF12-bP_m(2hEw~=ZzxW?`!!?d(BQI`{^&(E0Or~ex>9p zk-sbTyH(^1f2qRXhnK4I?+q#MwUYjgvj2}oentFKCGve@-z({rNchVlFJ*f0f2H{Q zWU>Dl$?qR&AK`=lsqEvKCCgmgTaY+k$sf=9Os6{7u?Rk+hdX*Q@mY_Oh}U6ZxEnA>-bP zCp%R5)mv10L+~sAzJdR&Ph5dA<<(MNn@h=?r94lGzaAF7RQvO~Ee?a^f68U{n ze;xP@sAdNlx|S* z-y`L%68UP@H}cbv4VUsbMZQ<^8x{GxY)^<^yiuihm*l5R|ZAPe}(>sy*3Gd5A9+9mu3G;Wqh3TO_ly{*j^C7SK@Dy z@8)XV0n?>wIctN;}PU0k$-WK+CTAamHsa;RPvC>t3Cx8?Pa!%-^o!k zTwf^duSD|SDd}BshT1=VgGz7K*Ofdh@_AiKPKm6YrsTB9XYWw*h{)Aao?c1+Z)nH# zhxemuHt{;mym5}|ILVmzdL@R z_eg%jBEOPEeB?hS@^2VkglF`m{l}$0j!1a#>nc7UUgY2D|6|(I z8>D^pivNox{5a14kbbGijpCmQk=M)dW2eZ6F&y?QW&ZL>?hpAdlAmu&eg-7IF6|*H z@@4F=i0_yES2Mqm6C&40{f|lfSu(y<$@mhL{{Lr*zk%h!{)e5R%KLdK&!L-@{Fgr> zT(ekeW&c0Ctm1!5;{Tcc$Nnote&v@+{)WhhNclV>7s>eboXB4mf2|UGUlf12MgEAC zuTAEU*UEV1lkhJ|`5GjDEASi@+ha)DsqjBD9RB>V>~F9=K>m<~ zw@Lf+N_r(y-wRvS{{8nLK7M*|9OB;y=4F)oMPB$_B}YVV2`V`zvhNNhCqzyLlw6AQ zHvSzU{;`Wa^WjwF?{djszm&H@!Y>wo-yz|vCI6k-{l)*!NcgMLpSvaecB#J)%l!Ho z`rn53FX4X5u)kcwXG!}FOZbL!RQ@|*hkwVPt>kWzOU_dATI7>||8l01TSWfc#~`CU z&AtFm#NS6G|3i}gEf=WxWf=GQx20RjD@1-x+J{}{+tea1sqj@}D*PE4 z-{KPfU6%?UlJM>4Dmf+crECw#->}3#Rq{V3@(aIE;hs}e`7d}`$w867FXfAi`~{~< zzmE^#>-!Uw1Xb&D~4^N1FmxO=Kt^C=2xQg$`Je~gS6?v)n$1m|; z>QUkAKBwY;MEn(!@Vk--*U`S@e9$fRJt*O0pFlYB6O{ZsiGBimx=W7VZ*YIKpOnOZ z@=7z@(p91Cogv4c*|NT7>s8_QB`Q2D{c*m8A91+~Pb^j8zy5mjk{6#uP~_#YSl zwTZl0>c=DU9g9?Y^))KJ-&ZKvQ?2ArEmLyWu}XeF>kt0$O8$gSZ&%Z~>Z6bgC z1SPkN{Ndx3oEG`ZB}#URyp!WO@_$>W%1_n5sPJSq{xD?J_o@q3_?^pDct*nKtWmOF ztHQtVsFI6CK8EcNdER`U3LpEd3hzH&$>nT+2zN{Pze;->5Puo7RQ#QCeti=Cja`dn zJ#riS6XcAvhZN^ikkcaH%k}|Tm-u(F|G-{$eb~kJf$}Z*CL-al?gb^6NqJ8kgbe?* zZ&%?TVR?`zkA(l_6uc+P8nHO%sqihEMgFLg?{83YgqgtKvJFa36f1e~t4j9C{wp^r zIUxJL?oe|0eJcLNjY{_ZtCBz3tmK%)zq?h*YfDu4UpFc_CE?#{QS!WxsPJD(e&&2a z$t&OsmZ$1*GrN{E4_0zQj)zaf7Yr|bOoe}Mu98PSs^n^sn|`R|VLV63_?e`V&lS1$ zf0X={N)>LG@Q)%xVcOIwYkV}P^h&=RR z6&^lTg|E6uh3n`)v=>0T_1TQdnJGMXovheX-LKI z{-CmV48|L#Kl(W(|C`9kLzH}v#4qkv`+q~i4?SPW=N_r{*Dp}=Cw?O7e^%}POOabX zspN~Z;qO)QPd}sLufTa7|L((h!~84|`9CloQTC%x@XsUh7ydxWe4d=)wH_s3_peG` zC;NY9i;}}wr{mxM{-=^hqe{N=L?y3GDEX3;m7ID?$)7dblT!)UqPfAI(-M&U{Pm|Q zeuLQCCGvcm+U4tLr_5jGf0SIaP01@|{JDIdlIKW!eeC^8=68Jf_rIT~jCZv(n4;w; zW_!o^UWbzFr2Y3_rQ`vL-!AQIj>!DJ3jY@USf$7BcJgnfX&>*7fuH$z<>4xv&zm5Sj1cqrcbvLGAzVVxQlU;oo~Df2$?>A(5 zMx;KTkn)B_Ub9ZcpL3A}ncbk&WeNu&wy$6NDpRHQS-FK+)@G(m6{jQS# zy;jL1lAk4pk{5hWg&$v~X6|m&{h_|2U-NPK?X^>$+FT^J7Z>+|x?--lJrG zFNx(@JFMhjjgsqMRq|hsQ?jQ_rT@VdO72~uk9mvw^qr?rOF<^Bg6QCBb0n?M9HILW^~Kp_bWMiunPa;14eE#+IA1Jx~e3kwOA60Tn=4Y=zLD^z) ziu@g<%l+$RKK44s70M;D|NlL%G#|6u+n=Xc*R z{TH#6i2Pv*Z;|v$B)uw0@7zzr9@lPlNdrT2JVdUs=7$&H`H_>e2hdtOvwv0aJr zH8=dLdG?OWlRuRg|Lb}7f0pN;V?Q%7{g?CneL-G%7OtNd|0ayrx%pd|m)?c?#PAlB zDL4Mr=-;{WgL(1)qhwlA#lJ02J~~hSJm#6X z>7AG7-xKrXujKi+HBa7_7ytS^e+2X7mp(Yr{{4TPD1YX|6XmD#>>ZLLuH{nzwFT z+SJ_H*xFWJv7yt^)V#g9qqVWE_3O!#Mt7b>G_QuwZnq#4>I|^$4LA!W+b0_?*MBX-gxjr|a!<^WM4_wNR)v)m5TMU8kE4Zru*YsrFfkcA0hka(L}% z)4%jCYq{=A4rt7M%u4x^=~Kj`N-D*SV%7;aGxRUqto(c8KgPdZD8ODic;Z0b^0waXUI2H?%c>*&IKPL(6R3-2CNojtK>uKJ}BwQD`f(wrY;ru)1!E zqo!qb-BN7XkkRz9U0sL9epG?RQn!rVB7baCJGE}8UOeBiY&8a}n!00=%u%PFaI86+ z(`y*=9L*ga+d7c^vhlHJdT*C!y?shE#;N?c^!o0aS)PrXb1;#8j-8Pwm$z=+*wMVD zd28nsyOl4V;Q7h%>pII9ckI~O*}A1!6}95Dmd5ea5L({0Z8HvcrW^9Y%){ZtBMWLJ zuL4m+6TQzWTZG1AHf2X!^VZFsE#hV6u4AQvO-y-)$E+EpjEtY!yb*^v^N6~8gv@Wg zdETqukY5%w#2J=m5f0nA$6z#qEnBv2t+8*wk!>cKW}KW%&wT$GHKg*2@s{Lh-O}DB z&1pvaRr zTHED#`)(fh$}45y!{ke*ht1i+=G}QDC@=49?AYAg>F7MYJ?n}Ywa_K)7-_S6WJkGo z+hG*bnzn7@UdZWw6DKVd<|trx$)?86Mrm<5(I@uSW!WR?GVCvqKfob-bXkd|9 zg4V5_&6}G$YEI{&!#sghlkwGci>8_ZAz<cr+P9CZGJ`Om@q*Q=84%QfJ2w9e0M9UKwWlhYTI_2n(M9122D!7sd;0^>Fu3)(a_Eo z<<1qS+<(Hneacvs7%w=xZ*T0_E)(A=<1X3Wx*4ZgJ38_sPHy|zLLnq~-aKW}%eJ&) zeFe)%<0i(Vs-bStvWbgPj%-Kdv1dGPo(~J3bxjpJzhh?0AQ@OxI0NIUy*uXs$%m}BZTM`LqilcNg@)%drltbJSC>E(;d zmN;6QJ2yG71kQnXMscP2a(QFzS1{U58v4zUn!+KHm(9&vvBr*!Y;SRFXlp#BdC>+8 zZ>`7xmgccwjMdxEVPRz4M&1!*+(IEe^EBajF&?|HcH$byc;wtc+`@Cxja%B)l>-t> z_wUkK{8DuWRDA-8 z$hmmqXxx4MrqJvbQ?Irpq zjx{*%!KI$M|HQ8{Uhk=^ci=(~f>&S*Tpagz?k}d3%5l-Y5hrzz6xmt&bE zJFqGvHR3397=oj6Oi$sb?2vkF)<-8`rC_&{>~DvYP>i>&C2{C2eAwA@J31!oqq-VL z-KuOmI`$vvA{;vQ>zg|EqPwr1qPw3s-rdpXveN_H9#dD2nSz=ilrMfes%rW!zyH_R zn;LWq*H(7WLG?&Yoltk;gqm9PcGT1qBxn!E?`ZZFHY(Z#vn5nRm7? zmA@S|w8y@*zn&7ksZLb5lCquXVaJTz)`-RLjaV>njNi>PzW!7w z$2;0_v5vc*xb?JFBzZzJ?{Q*MyU)=#?`Z63f@=K6%Dr0y$d+njPEx4E*3NB>n3e8W zT7e(cr`P5N?_Jx(+8J&(?X2e7g=uRB>#JX`DKE6P#uba)B&Jxr-_zczHQ1-PEhTFs zZ)rDgO7{l5d;zMtlM`S@M@)iY^b#Sui7UKqX$*rv9 z*I=lqvtzX2eYw2J(!5U+PQp_L&|}|nETyZ(4#$=qZJn(wB5$&!1JrU64%N*a=EDrw zqn2gM25;Tg)XZdRu`t=je4M(L^QfF7r+HtZc`s3W$F`;&8*v+TxwSsvbIENP*_*Dbd3W|w z)}87Vc+3X(;8s_E6&C|gVdMYSs=AxtqKX49n$|yy5BNvO=+Cb4?#X$v3tyH}41KEt?pM_tm5V560v?6}HC- z#gRO6nt$fJrO@F3Pu1YTMXCDf%fjPhwk&Vrd7>#zn*sTAsoj}TPFsYNZ`=g8?G#*w z-G)0-@qpBhj;)TnZ!${^F zPCb&uS&y2o6nrG9vfv|0=1%fR5|7;#1&)ZP@|th%;qF~E>BA-XW(+xv8=Ga;XI@)3 z=arZ+@&PW>?G9e6H)nz~oddopujZfE7HT5U-soW><)QD@*1bLBzV zne37y3%LZ!J-Q_4F=skEZxw{9{80|N{MMox@`U@hS=qdkySiY>R8ZmCtt?a&DEC;D z$>))KS)57F*5y7+zK5FLbK~cL&&TQcoPfhTy)y@fDO!Koj;*a^h6tCHK4x zQ^$v!unwb=&5pyoVx$&o_=X4E^!H{e;T<_h<$02a@xArr6h~`w1~iQ)kE177zb&5J z=LhqB=)EZp?B-2S(|C{Tj&;XP~4HcgQj+&P2zp$bI(Hp6k35`+Nn5&Kg`0n|@%NqC3m%}yge=aE_>eRX6CAK~KHk+I5#jm{>+GlJdd-3b7*udQ%c{gJ$LLuvB zymf0(t<0e z=6a8;oXV2m-O!Tn9l3Cyv&fjv2UpZ4(3#+8aD^S zeMjexJkkL6I$M(m&OKzsyq=WJ%2Byl$+^Cb-0$s_2YVJ6bTQ$>MO*Vyd3ANg_R|{M zt4}~REMOGl>bW?5JvP4+GzfZ?o9LI5kCSPQXf4PAPI)0B+Qqsv`5Q zW~%!i`106ny)Mm7llKvs7u7a2;^7Zm$8X$(+cf3YqMB(gET{?UlH9cLc%MoO??A;9 zB{SYTZ(Lp`Z$3don(p50^1Ono`xtTCf#fMaRe49sTTE55G-*hjoEg+m-g$px9mkgm zE|UgSV3^3+7fqd`lXx0F>MW8YF< zZQp|Xktg5RI&spObC0(9;=VO6#~4Bc@4(3HES2Ce(+Ykxzv0mrGxX2yz(*AW!Y)T?n$tjXZ2S3x8O}25lvsI z_s7#utML%}1pgi77=HnEj-PNeho4NxDnG5s?u%14(@zRB^3$T~W6;`ES1549_u>sZ zHf_Stq%KA8p&Kur+>CJFe)cu>@Z;i~x~HVtN$p1S#eKE#K88MwFyrSf1$|Fh1?~;muz-yX~sKHI`Mj;txemjx8!)<9Mf}K@Y3DR=S1vna{fCh zw@c9SrQv5M9E;8SFO)-@J9ZD9xsK!gh`BGZ<_rzG$>V25f$M?RfNVbJoO}Q2qzfu<)?^DWvSe({w8-1?G&n{Q&1cIcraRn0fN&&~jdHmiI1r zWa6!^o#rjz<*@x%twK*otZ1X=1DG2{22@HNHBll)Se35 z(~sMP*^N^zgkb%K!KdO3vdn>7V;NMB`(?4di3`_Q3NT%kpYw@d{lPNO8k}3s zASRZ4W*Ac~ElrW=gvWT*>W{>f)vSVbJNZ+0@JLhZ_IAEU2Fpb1?6#U$W3$?tHQ5&v z{zE^2m$Lvg-XyU8W4LwwpJvC?ue3ZakL6V0G6!PD4Soi~Y@@i3j8?eTX+oUnr zV=aVl*BXC7VIv-NUD=K%v&a55gF3+XiVoC4-bV=E?mFPR2M+Xui64S|yXyce1aGbm zCa;|oS~@APax%rj39p<_|6PzTVQ8sAEOk^ezY>X8eb!**BzrL9S3_1~IVJb{q#1=f znSXmT2%TsSLP+N*N7w0iCLz0cjkgw?U+LUOBT)r1_@;Wnfg0GyHL&R2KI3c@Y4O{ez4C! zpP+_T)yuP=1%InwQ!uX=uf$#9`|Jd%49=L`B;TdZ*w-y%PhXaJcQ%oK4zKL%wldAv zH{PAiWWRSv;TDoz1AN;)DZzWW)JG*If1IRximw$Mg`2k2w~SVgzrK6oe6@yW&0XgG zK)h{_Z$02)diCG?$aKJc=1Xo`x3_jS@?&Lk4o78noms6uPt*E#FW&#dzxq1xzlj~ha3Zv4e4FYkQ;d2ijnDS*z<&0NM-!)eC+A5zzl5c+G$07HJ9y!g>I{j35|L>S+s5Ae7 zW#PKap330qzc(?m@@ZhsE6q;=?xTt2G$ZSQdffdD)G7MpJEtC}8CVb0<9<|+OE6`f z`nH@YrmXwt#lXFos}}w;C%?lv-V?{aqndY$USIHr9!^-z*LQGO<@Y=H(Xh%#i>AJ} z=Rh^)pC_xC>N~C)`IOkyHwYc5#{Hxkr4^$&oo%fU8IL%b` zKsD|^)i}*eb&qEp`|H^HrcV6}cgXRH>h90TPh3=;dZKDRfW?U_KV$y)pQs+F#jTw+ zQ}oAoSS?O7Pd!kJ`%NuQGfh2Ei~CJ2PBTkAP>cIbElx8@Jy47LO)X9{M}3!^YD|5% zQ{n09gtgSNDXvZxo}%Khd-F}KoS~MzEi=>ub-3qq{&z$jPBT9}P>1_T9ZoYlJy3`H zNgYlzH$6~?`$-*6Gc!FBV^2Q1+|ZEqKi=K6YY$Irx<3(;bZ+o451Re#hWH z3ZdR!%5Nv&jobW&0`DNiAA`&{lCq%LkDKoPEcx_b<=J5FKjjBik95=>@8HK8@gXsO zhIqmoe*SqJJnq6{;p(hsSK~z{X5-oSKCPXJPj|l<_pROaZZ;kBEw(%XP>sht71(pz ztHe7Gjx}GPyQfw*{dZ&b(8?+ucp75{t?ZrQk+;Pk$IQeZlZQ1m5ET3}1e(EHeScm4 z;J3SF@wRe)776_YpWQeXFRN4Un$HeUQ;d-Kwt*eP*-;qI_<=zC2HqBJrUN~G?zx37 zF0V zC{)Mc#rgPQ{+{-DH2pUNQ4Ra=v1Z?U)jm_N?(rzUC$Ik9$JS|`g9FXJ8CDAx(_X&3 zKufMJ^uEd|zCp<^m6(fl6JLCZc8|xjm$h!)h!4axWA?Va`DjH9`uV_S!rfwgrrid8eCSd%SIcnRlJ1`9-*Y;SU;N#AIhqF%~T1Jk_- zw%{j~c#N3%=G&UQE z%ze-x@@mtp;mKc^m|VYRC(S(wbI(0|Gcx<}!Rd>G=D=61ZS3l5+|YVz`6B$@-s;%c zhPU;aC#?8*)uwH#hfR!Gj&EHRiYDfkZEo(wzW7v_`abhoOp|c%-hK8;UlwSK%eJ)Q zru?m&kqc=OQ+4mDVoq)jQ^ov>X7jTq=Jd$1`=lFR+rhL_&C)Q{GUMcing=_KGi&)? zXMU4xdGprB4Q(j(rkeWg9XwXHZg*^KZ$}NcZf)OzYQz_48vj3e?*m_Tb?14X-@W;B z6QV^45Rf((r8OX00s)eiJ6!%)gQhLjw56>y34dy$5fFl6#UTQ-8-;}Wk>J&AxV4nBqch2wT=H8otPG>tadtbTv z{C?+q&i8!J_k7Ry{Qdo|{{WiC+SC2j41OH0ObZ&eO;|hsI0dn9gTAVQJdC7vjPWrf zmGx(kG~MDdd-tH$QwG9AMNYH3p(3aIz-jY*C;Vk*%XRlzwPxx>GsH^ExHJ>5gt1S} zZ0tdaot+OED`Db8#rpWx%^SM;S#xZ>p~*JH)xPyEz}F?4*^p~LL(W2(A1;&^`uk2s zQ>tx0co_J4&C-{JpG;fj^ZmMKCcSsRx_2NCWlewI$@WmCV>bLTs{NCeF113hP@*fYS`SqsdWk_x z^bbDC97-^jCkkd4H(9 za>a5v-zhbCSN|1#zGwHskZ5FCYN&f(!A0PBH7}(d`;kN`9GyUHN@grpX7{^Nq}G{@Gjbkah9DN$CeMq5nNf zpW4haPi`2&Ynt!3v>6XCchAAH@R6yFXI%Zz@L<6Dhh60NLkBLLX~|6f!!4Y--_7Nh zb~7)W&0uuQFYMaoGCy(yzx%D|aw1ww9k^EJ97k%oQ|4Cx{0lrtMcbw0|BWj8pI?EK zMiKrZQShtG*t9=MO#iv&p!3Vzny00Y65OnEsGK+xOv|)=b7sC77t;N4(@=bA(VvuU z%7`L!3n;6vTT}l@wg8eOcfU_OHGLVSY59Hnq2i%Sz)xzY4$B58Jox$J8Ozr-t*&GL zbnObQsxDvl_hp>n2gOT+pR0awRpS-3awW0TxzDi7e`|cTWU2*yEdB4N;r=1`Z-FIi zU(;5vUD4F=pxXvX?p)Fb<@8vowXbe0b!*5sH&~Z8uDp*!Xa1=8x5qf8sZ^EDc(CpP z{Z9GP)sL=fs^KWvP#UKHPBjL97mQ!bW~q)J;AV5o@38$sq~tZzW%5ZfaNB5C%))JK z{sU$)aQkS+DhyzGO`?7NZg-BH=}fDsDg78r`@Dp##(#ybUE=4rFSTdsZ&l;fR@XJ! z108kj4K1;!Zj$n0ZrsqcvaU*dQma-z0?a78-P|#6Ud{4#OP6C&P1>rcNDoO5Dy^t6 zjWxHi>UkSisVvIVgGvc=2{KS;W=KGoOOSy&BSQkhT!Q|q>FM#x(t}C~b4fE$rzMD7 zr51H+h9JTmw+z(M^aQ1ZxdfT0Qxeqlq~&P>l~$Ao%(zMww>&}YznYmIt#n2rUjNnf zL|9pd6w?ya{;R1O;*}=iag{1w>68RDoj6mbTz$_!=_3R>K;Rc+!9V?Cq*h+V%=G#s z%vp8@YI%alRVugAnF%UaY1A1BDpzUL=?N-VsnoIrk*ic<>9hp3|7vQ6c!_AGi7PIe z9$Ymejb}xAP$^-7V$!MQ2_jc%63$Fexk{tXNKpH)rl-d%OGM);mC3a9pi;t|z!|7h zGbA9)CCEh0kf1b?fU8uIrcOyvxj@a+1MU^m(;}Cr2bB`$#K}aRnV@o&n)i$ZvHxm% zdbG0ipi;tIt{JG)G9(}@P;&WT4JS5V=atetLRPDPfLV25MP?$WUqrDmac4E zb{kb&^G|s!_3=yb&uSYWD8Bq+X^7Cgaz$4 zojNl?ZT1 z+Qz1a;83f7!cObL$tP%seiDqI<>0XEw`xA7Lem$AGp!A$Ee)sbZ6=>Ezrs1KD?aA+ zv9vPu(IKmqh5xIxVt19253&q?x{wFnu6SbYV`*aOhpDV4zB;rf{=rYWeXIfv{dBG^ z&|L!xbhX31e?}(_`rCWWee2fMtXjT$&B_%#g3$D6&BIgt>A{q=o^eX1fd`Q_@ZgYe za8<6WJF6E=o#J1KIwK%**55^&UKU(U?Z2Al!lqBvg?O-A;SvO-QZ7>a?9vJ%xiL-# zYI%anRchLq=|QE0Id++-GZNJPtLcfbvUGma5=5?2^Pid?#6vMb{+Xzy2`X3V?2@Qc zjQ!kH`uV9-qWz!J88a%TS4^2XZDuq&eG31gmRvez%8atIXtZKV`4oUpZN3hy-NwTi zx2<%{fdBmA-+WYHjHeL&%jO#i^DoMm<YzLjGmOf2*Fe!6iz=C%Dh$I`}G_KAoLp zUD;1fP4(@Is)M_JJpO%qEq=;&=U!vhXK|5bnk~it=jU>kvLl(;*h%FV@L4Az`1Cw( z`PsYl|9aR(kE%4XIHyl4zl9rZnq~`T-5*G#{?E1jm;MzGIH~tegr@R4?)a^By6u7A zP#^f&cYe4?sr+tt=`TfWga=y&a&y@v0@LN2Kc{+5f@~`P$X>Su^}5{{U9LM~%(DJy zi7WAYaFsnM@JPdoWi>Tb^Akiaj+0)W)=ydN*>oVykHF+Y9b@Eh-c zGq3&PSM%F1KQ^rW^6R1PzYd4?b%wotu-b7kZ1&9_XI7)a>Wil5&zeG}_wr+R5&zEi z_rJNIo%Gu~!Zp7%p=M>jXZlu-zc+-eCx<4K=ZSUf1%sWpjURO=LBJa~?#M_^bx6k&> zLvvo-v1`eTExS7Bc?0;gg+JZX8s0rC$Zxu7zWceb_eN)UgfA;5wk_hB?e&y#B(y-D zL>XMXsEMACUfS0DxAHcxXJ%JTM@35r-1BNheHbi$wW2Z$e1-6=o*A8=iuX#+uV|54uzs=HA9B#(F@9;)+9^~5~0 zz2QdF9^}!LJLhM`CenT5b*8#AY>tjc2l$`DfG(S<&x!XQiI@Wop?L2k@~HVCdE~*1 zJR;;FewQDsM7}$)$uG4d?*N;WZiBj%-tk_AfxU`ma;)uk=9*sFfblwFo#7{D1@>UF z<3GhI{?xqj>MV2oiCM)5OhrAicwR?E=S|jDJ2!RA>KxV_>!Pl^ZZN%l8@%>D_;qeF z+s|Y<{=?uqf;iMk*Db{93$^!+FukhxGhr{$W`cZM2DL-mw!WBBj^r|?(o7`1j6pss zYod&K9Wmm@%$e6_na+)~ihKTXlvQmmxz3Wy8eI3cQ>!1}7uCF4QKGhax}zcz#@0u; z_OOBWK=^;jGSf$+N5%EqAMuWOJu}|#>l>X&PaEyL$)yvWHuJZR9;B(f+&8leo@%R| zxi;>5o|zfgvD%i}ozIha5%LJ)sol~C6)Fy8@#6$-FG!Ew7G`RPnSAvbe6{t{gSQD^ z4s18bC!ssS()={`EZYwJW}t7`w%Sv|pJUUq?;u{kU4JtF+NHXae?{jJ)ggMT4E5`6 zWZ2AP99NiJH(jazl(dzqzodGPe3aq`;p_WBS8u8Z`Gr9DfsWq)=D)|sc=(!7`(?^5 z9Y%)7UoOrz57A*)m(Y&uuQxB(-)$<^;R+$}zpo#vF*?Gb7o= z^Jf22;mw{oV09bswe=D24ZA+GmH3w*n@j$4@NuuVrS`c&ov1!ic?9L?qEEKviukew z<;qmnzSqNVe0X1edr!?U@+%?g8B@^Q9C0^uc4m?oq1^@VUsS;`$3Ht>5h~> zwJYEBJzaC%lihjdqo>C{@#L-#(RF3t`Zv3Vo0sDM)5`e)btt&I@n z8d*-~n0Nn}_ShL7?c2cF@F-vRA1n>Ee@K~sNM0(dbkQ?9-n;SIc)Q+uxPH5OC456q-xqDarLjxbe-5|5|CZVQK5g*~Hv0Teyf@xj>TUn;gqx;G1K%E1-?T@3_6$bHg+Y5k&H?SEiT2X+rMVSqZ+^R@%_&{I#xt%RtIgL+zu1GJ z%x#;#)02nIPQ+gIolX5K|I3ek1^fOoc3y?O-_`!%eRsBF|8@*PI;EA54*Z?$Kw;{W z)D8k&M&YIKWS!L!7U+|)`=Gk*{HU+GIVdCbAfJ$|&nKv-fRDyUsd0n0d<`_^lh2p3 z+P*}3z_&2JNZfal7xQEPUV6j-&6K_fe<|=61N7L}m(XKCzqIn-qAL%bh0t9X9cJMp zvfD4u^YD${zIqcnif?>(*fpm5J?0ehGo$eZMXmAnJg?|z-}O&+{}jLZW3~J6n4{;f ze{#zt+UQdS=B0=?=4ig(M)SHSCfaCAcZ4?j#V1;JeJ95psHctA4>K>-x;FYg{BKpc z!y}HCkWXJI()}ON|Ie?FZ0VvsoeqV&JJ93Xw83~bW4Y`nX4QD%?&tdYs$1~Mvyt^F z=(FIV`0t0q`_AL1?hoZ{33>TP^Sr#Hv%RA3x5JTrulal;`=aoP!$;vifzMO$c^=&^ zxHSI<=DNJ2XWX3Owb01!4#vvTUCedMnsC;5FL9NBU{JZ4{O&a1Pc z@Q%ze?@GpJ(9@F8=%drap`$ff=G`sGAm4EY-(s-4BiG-sr8qpoFJmof-RH(Z7C1Gzr*i# z=I$A*KBzD3y>aoY^oNgynL8T#21DNvN%V`)A!8*zr^U6?7TReuZCAD&`1-)lw-Byz zbZw|PcAjx`WO}@};YRhh^N?*Wb4|~0lfDdj<{`D+2z`#?6?klFeO@%Mt z>sj<;*HWfWQMTdmxek8U(~o8K%bL=UMTR%Otg*{`eO|>x#_s3eG>>-8GrcND*R$qj zqdb%3XT0S1Q@ua&w!e2eR55+2+1^6Bu6f?0GiVF`++fp|S^nJMK8^cLd*7Spsq$+HnU}io<9`1DUG>rR`FQV{VtmTk;(uOLt5ch2=Y>hF|in|QqGk8w)j zUHxgbjX(w$H&uo+p>!F}V1Jop2zYl=ch)zX?In-a_jHoS8TFO;!H8+TzDxQ#{hIHm zx}LT2oFR>TSm!;iY%V-AFZ$Sk7j@Xx$NV_kjVVF(L3!w`3%$zT{c*=^=AD$^!#>7q zU1F2je%9&z4E8 zj_aobdXzpgSc3_|nm_7Gk;SI(n-_kgrzVej&qprS-5x7oT_tSi6a)OxxSq%$LG~JwI=?u#!8vuMn{<$_1D{Z%&WBV|8DGU2O=!=^hIfZ1(eqX1>BhaE zB+pLTS~S$3r^ZP^JQ-1hew&#T^oaYAP=jzw=tgULeCNU58v-Ij!?SOzi)hg27a=G zF2?zl*zFC{v5T*DSLK-;rykv$zsYQ$2*0<9_YL9a=eNK8C-D1}>21g}z3*?xYVRX& z-yRsRpCP_K7DRt%hT-eDSDt5X<{BQlZrgNk+L(Ke8wXu}tP0z|i#Bj4zK^x!c>~vy zRnLLm#LHgy&y9n5lj<^98@k#wQLQJzQ{$Cn+22EMjqerys`@N9H@n4!bvhe1&t&*z%$@}Yg95#1ri`LfMGp8>`_Gvm*$#lMC| zcQ2Xx%&zD0m#z8Omh-dGkNV2utk<*=r`Q|Otv-{5gG8LOuHT3oGu_~4*hhG%bM&6E z!I<&=Z_dHC4%_t-=$XP(*LC{WH{!h`X$O(V?Yy!Td$48eh_xVN8)NBK#;SY1Yby4H zOhwyw{k6fFYX7NgH7}7iQ~1EX&hzI;k$dfahSu0;GuEzTFQd|!-qi3o;gYA8@m9-s zZJMWU8@LB@`7zt?5zqH`hB1!n)T4iknqtQ9p*JqAWG&*pFnY?OUbCs!9Qq^H`d`Pt zs2|n1Xai$b*0kSZ9sUF6MfI1~zgfxL>mv0|o7vat)^jumiwqyV1^?yyjouvdhWd*? zrTn}3YEJe%ZRgjkpJ@AFnCbmhmU(F^vV9O5b@Vr^(=@P7qdDMTV;yw) zK4aQn$+t59sK_;M{OKRXdv^1c+-E=RtNsOJuOfVHavu4Y`>MMM?_`|q>uZeljeftv zT12h*Q&#c6{Ma4n_e<#ccJw_Tz0YHwrMZpWvj}{s=J<*q_!appU8~LeYYr)VeLqZm z<)5hIPrEwaHaSh+_;@dM=2f{QU8PXCvONTOzNTH-?okuJ*3&ehc4v zC$|6K$awkxO?ss{4UZ>To09E`&+Lijp&Q}Z36H?H_+?4j|HVfA`I786xF)^b2M)&h zgT%i`JL>P(?7l(J?22)MqNsKu)E%`lc<IwSxw(m1fSKFfuzoy=j`_^EsMtWf^xNki6 z$@Ku6Ene@ndlA!K$y%23V2p5JqKQ|`-tIm0D*Bq3#rPqrz1{w~TXJ0|*O|sdgVAif zbbBB?<33NaZRwms8|yBzEv+>jLB@J?NuO9z%s%vU$f&Uds>wxC?9yJG6aP*24#dJjc~?Dfw9lj96_ z{c+1TR9lrz(Js?`qQ)|5;}=(F;umq{!{>di7(cd=xo&faJ$d#vU)d0Amo58yCbVPU zZrQJIW_;J0`#3h(-){$f^~Wt!a(ok<3CN{AQkJwc2b@8`u43km4$d4j&nj**oH4n& zGY9^@>n1lhOVU+6{q#ueZZI2((l<#@+HdvOW>`m*9^C#wVqfKl>}zXY@^#LVXl~-C zq3=DCwD%IMMP;hTO#9d+)RXoK{+zj?#@{6=b#<0-yVq^kPg>-UXjk%U{+W>TtOwJs zf-@mcawbIQN?JH4(hy?Z`*ZR3h-bD;^vuzg&kcQEL~T9Lk;*@ZGA7quGVKc~w5zSx zNFHqs|65#p_4OL$8?5P=;2hO0jK_XU`|g94GsrvDulRM(KD}S}H#vL3esqRHHsITm z&N?L7lJ@l93-zBvlD#S(d-whxe@S2eTIWmt846qHft?L?b|RC#4e;l(GtMoAIisX{ zF2v5Tp^Dk~v^s3;$sGE8cc$p69DCMCzAo_TzCUM-)DYO6&LYtt26oqg-HrDOkJ|C> zmHVy1^!jn@0X|pm%UszvrIsDPerAxex2BZ6gf=&nE}mno_>WZ=gY8wRjhnD3lYjO5 zb^5g@@Fg2quh_tvsozKBHpl$_VIF$`)9hHiGuQSH%oY8$)t1lAY2Wkv^!dJj8MtTl zaqBaa9bfHPO%u_W|AnXbFlW7x%lIT|to^_0Y-Z=Cx}W)d$N%E9nH%`VpM zn?mO0e>`VZU%38vp^ua2m;C)Lj(@R#CU%DQ>BP&AOB|&S_y=M2^8dv5)3Z|+QJMz~^bKEv=k2Tk&1bLAu7M=CQS(oDY#8W! zdh$8r4Oe%*F5N%db7q0A>jVF+{t7+UYV4lum))AN{BkYh_S&2|?HlmN!MrpvCr$E) zLI0~fg6m9UoS@GO>XbcN)=jTr{Vwy_g0o>euBSio*H7Cvjp@-Cm$AjW?S&e9c>9>^ z+Oez3LzwzajUO)3Hs7Nx&#~?p@_dQ@f56}5o&rdkVz*=W#cr0tKV_0*w z=h$iLb^Sq>HCSPAph&%pT|D_19nX!jy>ctk79gp&ks;GpC@a5TAxrnbo26A-7A_bK%e%!Jf7W<~t*>FWUWpbH;w1+HwW5$bLpelZUTwS)U z`Bk~j8moWMwfdk|Y*O*n$F=#-eY|G-D%y*}nbXXiM||;9pQdzvpQQLYgM5p#lbYB3 zz6xK-d#>CWJi@xvFF9UX|Hlvc{g(Izen9(>fy{~XdBK{hE6a5O&#SQOh|boi9Z`P2 z-*n{<+PBIdO*vx=yU!B%Or`JJ6k-n|hdwWtzK>@}9?Ro=M@Z|d ztm6bea-8oYwGPk_we$V{wSW%0F2%Y{h&g>9`%#()<7e?3ZhlBx-80v`cq3`wqkXe4 z=lihtiFfg|si?+>Y2BiQwad}?us}}jS#^cx{OsaSLhYThE!I8d8_qhur|&N-fA6Nb z?f$;`ujlr3t;y=ScsgEDKi2sk<_h)HWj*_U(qSz=PG#=8i9J93dDoixJ@tQQdYafL zRQ>~e1F{9{z?tH2U|l%V9{QOg`GW=MvJ#!%!Cn&2y==03NdEd9^Es{0but&wy4|az zb*{snH5*C)@Sa~z^ylYq^%s<N21JD2kyrkD8<&$)1J zhCb=Y_2xhsYZ>DC3UemT!~J42<@C=)-DJ;#cZM_P)ye#%_&W2@nH+S;o@!+QYo&K* zUa#IjUVB)Njt(1oy*i4o{Lx_R)yNU7R|joM{!(Svb8emVBiLVQRG7zJa%#Wg>kD1r z%bUFpm)(Z&@p;IeUpDs-)p|Y{E{+Bk8P|eynXZBcAlZ? ztzw>Al6}y>5s+)V?KOP;WY$=CLnzo;(}iah|?N z=SPz3ZiMO%dgX<_k0nzB^~ZVtEt2gXWRjh>q>f19|(t;WvPM>@&D8nJeVPZn74*@rZMyNJ&I#$6 zU-iAGul35M!$^sF8Nc-MZ1ioADe_oDkH&zukGE%0v9*`mCYhI?q`cGdgPIq$-)-8T z#P>)qPvU!2U#-+v*SE15(w{-5#MnD!?e;m&bSPc0=6m_EJE@Na)Jr9O>K%-&hklkc zX!redM%$MLen)l{#PMx1$)Aaq@JYoqg(Op-1Pb^e>{D&8&B(;9{ftw z)*1J7`DfA1qrNUSneA!gX@9h7MUtQ2+XD|hV;PL4;mI?XGk?hVOJ~#l{0WQ5 z54wDkpRZKDiW8JA@l1?6{}Z%X#iQOVKWr{xQ}RPeHkBMd&iq7eQvGR^y}KHoYp=n^ zwNEda!gO7UpTT$f;mOzIvrPMJ(u&6s?DS5apUX5JxP|n=cwiEI=Fkq5XAR$E{$2Qr z{6CEMoM)|J5`F$K#uYr5-TTL!M_Q1lu?ubGanr6e6P-MRox9PqFz4VW-JPAs9Qr0} zdxgl-1V4@W;8Cb{ZHDzfpJtjh-mzz^KES8;{ZZI&>s^dVj!bpqXWIH}OHzF6Ql(=a zfxlNgo*Q{tXM6TAzHFh5AE4dWhwQweh5XfjKF7W0OqvhW;;U4L8dK7?x7U+qJncdC zKG<0vYZn9OF3GkM)HQ2%to?Yb{eM)+PhYu^QQrMU~^+ujjT^?jcI{EyDg zJ^5GhiV~h-d}~!dc?vqoW-PN6IUM^>xb}3x2 z@5zm3`##F4INy5lhr1>*UKEdS6Tg%7mNTro1Zysx#*|$Mk3)w&GYiqnhVZEFpWqjD z7FgG!X}$8pT*i8|!5_f4*emQNUG+WmD;g_)ptC~Q&5^#om&TJ$b$C0Ny1pY`t>o9i zITadC??vik1@TmdjqH!LxW2>Zhs@$9K01q^cg>uhiGM)tgZfDJxF zxj(qkY?n`y4y3DFT^UDHmhOIOh`1I~#f1*~mTA?*??a z1bfgqne)yLx^v?%Z=fvkBwLt6-1igZ8nv&PdTG`3ebn<`(En`U8L{VGozy$IHGY)L z&mh0%#Y2@_{^G;?{vEdOVf!KaOP>9DjA#ECBWui}er+dn#&tZSSBULby7R268~L;f z&aHm^#f`gkW~U}Rrdxaa7g;B4Ax#V8$R2DikNLIgIiF`$R9CWZ#r?*#AMg4RzA1zI_fZc)qrSGIvtueDatb9^3uerM_yFQP*16jD+o+OZlEdCn~d@^IKmP z|4m<6E$_?V9YMY7KQtFndfh9H^&|5s`#f|o7hTN3HvWTuHgC2u-CdkPE@5xKxxiFU z!tX>#JE_i8oS|&U+_qh&b@7VGFf+X*FWx(hvTeX7T9NeuY`F0c z=ad(47Md|uq5Sv*)SJQ@FZY#64>dgh*1=eecWv~_*5&6FN9Xp%k9T$W81U`p`Mvya z-O{swI-jJzfH*fxM#rm`e9zDZwdOmCb?ZsQyB~h)H^fify{* z?|8+bJQFGRM08M0IrET9yz`OkPWVV3Jx6&L_J0f4lDU!Z9-hg)0I!B`P3|cnPt8T& zMh@{>03V+h@l?)y=vU2)d{tK2Zt^)k-5c^qJ|iHXAB^AX@iTdhYbUaJjZ~taw%5lUjEFbqkDq(fyrH!n@%PVG@LrnaGfX;r9GqjS zpUbl+{(6|Tef8l-SP%R4O|0eTGKR+w+UI%f7(S4r^EZ7j>1=56!=Lvaj#w^?akf z@uxk$%BO>NWEigujLR7;p7B$hIEFZdS2KQ1j;}FNrm<1*yud4eWO_8#oo-C;&VLc_DGnEQ z8{hW5eOmkOq0K9-R`W~j`0P&@bAqx5`RQ{S8#>u#^DX$42>n|ZXEn!?p0!#R*T(Cj zAJJY;@SNGqaDTu4ABmsZrv`h!YX5f4!M3lU9^^xYdN#xNQ-*VH=y4|Pp^q_UtKXM;`}B;$ z80!5u=nH*p`*zrD*ZUN@nB!~y{1|0j$JqGau?D%8Holhf7xC=HI>yN#;PZZij{8D| z-BH$Sf;npHoP7-Qb@9{OsDo!9&f-TZAJhEEjrs9Cwr|zB=Kk}OnVx%)4%!^APTKMS zTc$39XHkRah9rAnze>A^F+u$x>mXWVeT8vV9ee}b4!#~TqPvsv+@R|rORZ1Ud1u+* zmFh^cb)kF3)iV-3j0wm5AMu_a>-_~A&Gw!6tOmw6_&{4vnPO;gm38HMETX9>-sgWOOU69X9jv0`}ShnnZ`l2 zjH8+vM^PWl8OC;>C7t3&D1+LZ()|)$^wJha!f!2OmA>u0PX z(!aAe8TMYb>x=mIC1(5ikf&#pde8guZQL>NtDrv1=W$j6U#9anotrk#>fH1Pvm%`J z=;HjtYuHEYP2L;+S|H_-UIJeb@D+g9|dws$FyDgIZwzq zmUd9i_ZK{)5bdn4%MJ;K}2NB!@o>ciT35Z~5)e_tJ}@9Vu>ziV04R%MT= zG7VNgDmUfg*;39ZXpJDLUR9Rb8v~o@uWyB44L`=3K0nD6*h(tTR9fC-{8y3IN%Fgw z;2-lR>sh+|eGg=PdUY%J8|v(PBzgbML*rSyDlpAYtGyd9{8QtF_@%xV8a@0tXkbBql)c-M6QG58~p-|Mw+B0LXyCcb*>mb@a@4Qdbg&$#vA{@ ztK`qKW^d%TdhO%iEiTvGAelFNE^p*PV;<%|3prRHEB;aD!ND`xqZiF-FN)4AsIFYw4fllNqnvE!);J;q)UjU!@W1NTMJ)m($1erBI)44qL@%{6&k=?b} zX)r$<$^2{|I*=V}-Bo-$uvyI!&r_!NXg3Ob0vhY4?e={O&A$j_@K{fjO`lzA$M)~n z#M@8v4ud$hdIY|Ci|v?xSgCzaVH4x|ANuQ*{qO&hF6QLg`q#BxW3hdWczE>PzvkT= zdUvw)@k{0k%EQA~wMnN-(J8cKopv$~szn!Cw^5qG_Buq1GN#iUN_OV))qFBFy?86` zQ15!vI5=4M9UYCgf6z8Ja5F&8^@bB7F?S5gM6?C zf3%TvMt7MJTOJs%ckm4B9`?kRT^GVL_dVbhY-tRKwtz=BoMAnTv#H(GF>CFG5pOi_ zOq=%ujp_3*KmKNZq2B8_n)f=+{egJe_d0&`bj>w-uj8es$MIgrOUw~l`W}C?>w5E2 z^RKOqX?*&tYm2u0<+bB@ciOn_Cy+HC{@VK(`3I(_C5N+4+;bl87s>JFU`rcv&AaT) zR7dbXdBgno2fbVJ(#~Cx_kRD)$h(g}T@jA({X4S{@V4VPvvd7lYV;x_w$ff z``44sKmPPx?>_$KFGIt&tl@jhPt5iUlxyn~X8YgJe>P`t`02)LUfW-rJ^QCiuGzT% zr{m4LvU~Z>R%B_QZT&eis-B)ByczrZ;kQjYFURD0d~-lHpm)XT9Es{+23X%W_Xiuo z=H0O)_w1@fuayzr!{GE<`I`;C4#qJ48e1@S%P&9R{ef;>>%q{)$hmzqY%KnFx`;RI2ox~Z&_iwMgcFTXF%%9@> z4*g{Z<$jHFcT(=NlzaG%w%mK@i&gHbKZy4DOxdwN)H(Um)BOIjNneDBci8~SbWCarte_Sbb&yrMFjxiq@dxb|WCg#SvP z^Q-*$%O{^^4JOx=dgue6fIqPFP1-ci;p5{8lT7}Yn0G>WdcMG${Y%X+1KP=_&C8oY zBez6GHQ!wlj^r0VVIEpycI?V7HY4obNFi%(mak#H8pu(L9Fb`J<#zfp&&!*nd^(Za zM_*^@`)9$rm-bb(J`%_vKjh!L^oe^nCrr*${Pq1G*o$-(cLVJ(b>8jN=|g3lWL>YiQS;(9i1d>C!r&%4z2muG1Q(qFL|)BP&zlX}lcH*L3% z^QPENbuaRNz?!|*Of;t->(A-O`ZBU;wXc#n{RfG)b?w;}vDawVDQ7dM*P8ly`XTLY zb-`;qeaFE3iRcxBVXq?lFGC}b>OHi#q?9AE-{qHORCgV+ z@442l5sbj!S(_ic{-j6c&-f$Qv57yrkmyY=3cvG~Bq9PR0ro#)v1AE+-L4Q=ecw_Lc|%A>Ts5BXNs z#48F}uUkMJ?cx0=GvHl9*hs=A62>zc56vR%bZ8uV(7ShqcxO%t{j$Q%i$C5~$+gmX zFYemq+bieuqPd)pA-s%pE5a!CsJ-*foA->>Uj19xS<8*)jn*9Mn!X}FPGe#_-pOaI zq5W^|3(m%79`pCVS>t1``0VAr1KOigcpRB@K0tZ4UhbQ&c};R2uhZu!k7o9Ylt*;T zC(C0F@+ar92l;R8?eotWTqOTT8OO|KZGS2CvzBtUpwoJM?2q}f27R5*Bghtlcp4u_ z?_aNBuL)W^@1l_|$1$FI3ER`2sIP0zZ@18Jc9I4Y+;J=jdkZ4ifKRH-kuJz>}iQGTjbtCV_ z8bt2tZ&R1ayf-1cc*mIod=lPCW$<~czDlSs>GnzLqAxV2d*Z)jeB|ovnb0%4bOu^= zHyghbr7kWKZ#wZao!NMba~!hSEsT9+o8R|smVNrEyLN44oc0uHtIZ7MbY>Pvy=m z;r+pS|LjEGL9FrXQ=FX-_?(5$v5)$mQTo69sBiQm*s^?6;S)7IPf{lTTn}X`rc655 z6Zin;$_FN*3)yWHA0Qnn&Uk*S#BVFEOtSk9Y?{d?wqnbru%+r}LSwr-KEs|c^l{FX z=4m|{8AR*+H2pt(Q*B>1Bzt=vU$i92hUVZ8bY{r4)v^xuaYs46vY&u&^oFbB8&$h= zzDB&0`AV*fj4vyxAIaLx^9%9?>j)o7yN)Ea>oG~~dNw+E9b1wtnaZYieHvSQZR(E) zv};>7yLRo`q5q7l_-V|1wfszmG6nq9zEl^iKXa~RWcL#4LTdyHH=LOss@=VU%+Dk9 z+_d^g$UI%|6cevj&P-knud^DrW|nulFCXng@`uxl4_sp)1oGTu(&F652iM~(sjyi0?_T9FB9%>H<8}#EZnQPO$h0UF#4PR}VrAutO zV2p63biQ2Z?n?3cLZiBuP#-#1^mW?AQ1P{HHq`t(SZC4R!b}tI9mV(0oiF+e=Zmzb zc%HBdeD2%wxnc9rBEo9IMcs3!?cDVm`*!E4H-$%Ed}i05b8Y?8kNkGr!9HFmeaVjq z&&0c!y}=Osl5?rYAZ_%;AMH|`6;BVI@&|G1v6gzo4)0z}-6-7>)-!eyZvkT{`OiD? z1qf><3$Sg4M;SMz@{KZviZh1#GxYf|-ydd>Q|H9?xqj!5UB9EV zf_XfIJf#i(mKyu%$uYW*__r;#!xpOQvOVJIEi{yUw;erD&4L}=NnjSSu&0O zh<-L}LBL5!Q0_(Xfc=wK_&HOyaT-QUM8kbLWwlm4uj_f_( z6=xqAm0h(t9|de^g%lNr@VijZ?1cUJa_WV zbNS}Ge7{A$Z#x_7b~dK5&1`twNc&K}ZjJ`Yn1Iy(%G`@+SQOK+p`18wnTNwcqGKxNY&#PU0r@08&bX-$ajg$_iAitzMn7k zpKL?a<;Srha&8RvJSI0F&9|MN%mn|gO(2hT%}W-TEP zIrL70gV^zV*zghh@DKRD&m2~B&1UN4B>HIMn>v1dlyVQl?(WBj6i|j5+Uc$rw+v`g z!>~1l*S@%OSLzs4ZMl};!qZx}+Ej?O`!449+K+jO@m0x9%)xjEiq5jMur83t*-X|A zdS}lwFX~K}&cCP1q&A%@)3eAF_?H`L3q#2iztlHfeq|$To*I+=mBxCZF|;sfUbXw;yh-VNEf;Z@*HXc_Z(k*hf0s=F|Jk z=%?tvKGiexadf?QuG!u{b{o0xE$4S0_HCuGyP4o@?Vmq4q+~RC!a+2-5!W`sz=9=wn40zV%^V?k9C!b}|`h4hq=Yk3N5% zC7;8*)2J-huy^-G_U^ve|14e|_A8zG`+BpX&F-fK^26IdOJwu3zXhi-(iQ66IjQe} zP2|n_%)ION{N?QH%)3!!n0*buC&XM;}RT_GXU2=X1#pYH&fMnfCzKjT@b=k@21 zUb<>gS#E%KgXT$viM`#m=ul@a*(<1i4%@oOI3w5>c*)V`u;1dBx0v+B1JeiZ?9uz% zgZK8>a@+T+1>q`ta8_Sy$ezFFVfP=@_YFp~@pN`V?^U(y`qDq)b`OGj)qP+GsrBmD zDep)aPx;PL$DVjFZ&sSr`ueys_7tQqFS;wATragW2 z=c6Yx-r$UgefGD=pMw^0PUJoIAVzW4n0F)C%cH!v;xx}@pW*r& z=w~$hx+6JD@^j7_oW{SshW|bNZS!syHt{fV^&GeMMeZVQ$voaipWvbW%^LQTpJ41W16j^9?vSsX#QUxDN1F24 z-bjD1^YhS-V56yWKAa?{|7<$>pAI1#{=7akYRh@PlI^TyBkV8!`}dzdkGztbf&2DG zzS?`OWe;)*dyvh@wo!YpPB!i5K7sw{Os?eGz;6f)R&VFE2a-YdVsG#?@`tj=R@Q}* z_hW*0h!6CGI_FP6&a+FzukIvGgnRw&i^fg&h975f!yCRoY)*NR7JgAPRK_s$K0xGWQmq2$>cnLJF_!f@iwComQ42Gs~D+ zkq*zyUu4W~uYfh|Hjjx5>2F__bu!niAATg)Yz<{~<(h4wMq@683J!A_j&$Uj-Qmcg zT*MmJmSZ}@a5r0Xlx9cHaH8+cX(mLnJR9?-*A{TJVZV3K+m>Svh8Jzk@m|O(xR`AY zXBD2xHt%E=ypwG%WgSASgE^HiK(zSPoh}l~ZsyV!Hf*=G;TBJbhkCRTkBI%*%QqOdX2jN%9Dz zm*EG(W^Y*3wlD)?_#1^#xl!(=^4`v+`;tVY8pXR^|1AS8&2Jhdf0f z?X{-Kn5F^*YQ}DD*k{dpW7Ze!2xB6o*zZNQg|U!GPl$RiI~+0{p?XDC`uUW=@GUr% z1#7bJAge4C!6n9iBFpRyMGj_}gQ4MDvt-7{!U;1z&SPX@RWN*RFo@5$pv$8M$rV0W z?y*458%!(oR6S8yTE+nQgnKi}J#4->-tPv5g^y$7ij3@}viPJy>0Jp2S_N3({X z9A;XwBNqz1ZQ13RWOl&|!@QQ9g5$%y3poYHhIu<91^b72dn4%nNTlFmfjJRDahvlZ zCx)4hyaRJL%HO6J3JYkldsRaE@&$Us7+5k z*-tm#8{<300^D^|L7OK}eZljNd#EXqcG$u=?rxiRKp}n4PV_$0LU}^wpyHaAP{H1i zw-5T=tdxcz{ z^0Hb&UZ+QsI2_749>UjB87D$nogwd}E}cYv9@o)=@Rv0BkU8h=wj}bt0liVxp@L%} zeusuy-y1485;8BS!AiCQgMCyMG@5D&>pnd1?tc`APc!Gm{7>9M^QSGEsJo|W_@AhS zGQ~L!&rSQ$kIcFa|MDx-SFT&WV(HYWOPXqyEnl;Ibwgca!}pih zG^}`-i)FW)ugqMte9fAMl`Cc}UAf|W4fmB!ndjr&x;4w8y5uu6C$DLGbd_S=(=@H7 zrnGcQP0jb#u2|Y6ZuWA)w};|C{lVp{?^}NR-FJQEo|+jCFJIcUa`gf(OJ^>rThp+# zW=+%Th86eKJlwGSkp)W?Ub?itZgox5>bi!eH4B#B&WF&7y2i$pOY4;Oo{Aa;-ac(Y z&6Ka)GpA<3JzpZ=OAjl9X*H!&Z3%0ZuU@@!wc-sTIJqxh;HOH}O*)6GEuXKT{xVJ- ztdI~gm2&EUV$SR@W}*lcR%a`%()ho;?!nAj8>lm1jR`K(lQq9&?f0&p!-5q}%kNvh zI@)h!b+Uu#_v#uNmoNK5^m{AUu2>drTDkJp#yToJiutWw9yQV5i<+xTx?=f#aC|sO zd8>&oS=$tq0Y)3vMAxoZ({SI4<;#3#t5?)DM*Uj;0)*x3R$*GpmqnK~tZsVLPqkv@ zid!FC+f=utae365`}FvSV>`T5K{2I0m(sP<}wL)raD#(WtG_@2y-Nt&7em zyLCxJQ`FXm_*}7iaZlJr69rio9azRC%i-ByyhrNRNZSo->Rmk#Wp9BlsHgjo2ib03 zo>b<9)0kELdtdm1yIWqeM!vBrx?*DV<`p+bm#IJ8lCi70s9bQv(xw`$aLrmgXY`SV zrut~zisYk-$<0RF3hfirKJc#mzK>mAK(Y zP|cD@k<~AHGD|DnWWV)Mpruq{P}yougGIGoBgsOSCmhOs_<|L-)nJCw6slO#bgRs1 znX1TdCj%8Mk6~HfY_R*}7Mv*l&0nA`uJzlPYicPj&D>*KU`lX8uz>|+El+hn&^#jP0|6Ls)-Uf*I17+tX=WIij|KL8!wka4$2&DY*^D|NF|hq z!Zub7@Vxbyzp@)peNor8MDSC}_Li+&F=#fbZe`t{R<}xnc`T z>Qq)flIT)qMUxEXYJI3LM_>elrw^Lz&C8OU$)N7`N}esDl7w3uoYi2?Kgr~+8Z#84 zztto=TQ>%DdZR^r5s&BkN-RO0&3AB14a*uA53Z0wJ0;($0)B(14>G*D@wcJSgefN! zI=5~a1!UN}0tc~lDcyYZ)>{)H%T_M8z5|PHs%x%`Us&Ib2Du2~6Etx1S#fdi?wLO5yr!hL}t@kJ<1x4v^`?s)If* zw!P8+{f@4sl;rgFF8yRS-SoM@H<`ZG(TgHJ*-Rh<_B|5vwK@7M7AN#!anZ1E?ZmKQ z`agx2DCpzLpC(`2(OFj2B1c;K1CH*NEYi|qb~SM9!!pL9cXly}(VM<9wkLYv3nqjJ;QML`b7IJ9;DZOy#@a=!KR|TKUXaOE_-z zn~?9YlkfNqmOfbebB^9hv`q4E9yfIQosQl%Ncy;2AQ~+H>MPLe9o;D|t^AEwps%_D zy~)v?8K?1Ie+BwxM|b5*tKXfD9<^#rqaSqi!OC~Y(cd9GRXpFaVS~lh?|vRa(Z@Bj zbpDRc(%yjd!yosP`HfOnuKlE?pG>3GXRz{BIr<{vB+D0d{Pzw_pQhh~jy`DluR!m( z0{yU~w<2G%|C-|DTNZ`D$LTLkzEx45Z1w`l<%_%Yh2t&#LJIxfK)*Ly`u-I9dPm>y z=usOjP5;Lnoptg7@^5qLn&t8io1X*x^QsLt{GBg2I*=@Xm!q%yqNSfexMcs{06=mNt5s5mn_}sCoR3X!_plgEq&G%=n+Sk86ZL$|5=Xi{8t+Pg;(HTeFgd= zM_)+hxk>if(rnAs<|?js5cwhtY`nqjr_j+ODg0ZTZMt!H4xPT_3iPro(BoI2S6zX= z$kFGg$bZ<$zneov`s{^{{mr*`>}@sMtN52Ko=c&dCv5nHuUd>kPac03Ir{vCmcBWK zUhe2ezGmso08!HHr&4co;nU1F6a9#zZ-q`1N=sjT4+K6t_-3MaIl9Em#J{{+sf}^{ zYa0J;jy{3JeA4Xuz}Nk79f8m%jegA0oto0<<2WFuPosr2dW)kUBL8IlS3hAB=-7)s zs=ws+(dg(U-?sEB>wo6kJC4dYdedSXvW)b}{0}+$$?sZva|+#TwBd_tExPuVroUy5 zKA(fsnfzZ{gH6ATl4qi`UFv@hlYb`q@dqt^;UMXwt1NxTAn6Z0Wa(_wX5_!V$;eWwm>>Mcm zCmsESqaSzlBaYqy?jipj;C}^IfzN`~?mh~B7CuGp{xEj-e?e~r6@M!@0s3ZlUj_c3 zgwFy$O?U~&nB>-R;7#B=*tx<_fC@hXD!dikO}cXMe}F~ce+PTk81vWQLGWwD8|Scj zwGF=)RQN^O-!{tsj>T9LsPInOt*{ytpA#$X{UV2*E9`x}!w9H+x8e_!uF&Cb{DHt_#yan_;X52o zaoG7i8-B0DEQgzy+wcf@68`Tjv-koi{^J~WEVcKG97clsB{uwGoyBH{6F`->0F?Z- zwamfbR}IQut3b70XD4sa*P+1guM zz$xG&cfSyv#{CrV7x2q+^e+0HYYFcJKTkV3WpVLI@Kc2E1wR9>0+oL~=(m5c5IzeX zR)J4(9|iAGd>pYK4^%ly+YA0==+SLwF z_P-gF{jURM|MS8B4|>NU`e1M;sC?IfNR=RbVf}gi4Ex%Ut*bQ1Xs*_m}Rm@p>#SJ_m|^94rHmfIMq;>ml$x_#L#k z_yDN<_k)VR6;ysL78f^xA9BCS;^Kv%_{{>vZwe@WlPxYT0mUy0ik|_+?-JuVaB&wX ze&;MMJ`5_}0Z@GRgW}r?itlcVi+6(JyA4$QRiOAbT3mcDD87pg{7Y~Gcmwf^K&2ZFDnA1%|DFZ*{x~Q(k6Da0 zgUWZk#n>WHd}e`?s{niyy5^l=?43$uzD{@t_z2;LEXMYN%6B)Yd|SYYS&|&MbF7f36KbImhA@P~|EC#cv!aeHMamf~VrPUbca_z^bhlWA&i) zxCoRyvq05XdAF}4k)%dXmL zF}4|0dDnp&_cen1kiQ#tg7F?O$`SA&wb3fvF9(qe2rc%J*X#n>!Re9FP4gqK;2 zO$L=u(=3~BBlsfm7J{meDvPm7P2aII*k(}mE_|QxW_K^-KDHiIy;p;yz$&nYc$F4olR?=* z1pF@HSr%iL%8j{~`yPw2cR<;}IZ*X;3Y0%M0e*%1HFAB8-B!Mtlr%(bN40geuBF<;LD^tGQ;T=6yJju zV@==z=#xS5kAQy)?w@YVR~hfMSzNph^yRS_TLh{eqoDkr0i~BCWybuB^qWDYZ??F2 z8R**|DF3hsRQg$<_?BCYO#l_I(9thUvvht>UGZYaK+%sl>;T{7{(!sR56W(Lfa1T( zVWq<<4htO`Q1#X`)z;fPp!~}Pi?J>czfyJ1V(cU+I}?6D_z8C}EiS$nRJ|;+7^?)O&q5bI+=UzP41AAIv3}{8 z#n?9RMZ%YXvXA+o%9jQHjPSjmxAs;GD&Ko8E-nLQACp1VV-!?-Dgyrmc?vDYhJ&h? z0*kQ-_*3|1fmQmm80)#smhU+Dr^*+64r~FHPcx`|R)H#CmBU#MCxFU-9QY^jEwUIZ z1eO1Ai?J+lICdeFT^M&SlwDl9)!M}gQ1>1!*f`l$um zk-Hp}{6(PT9}Y_XOOvhqryTBfc(21L4lmu};(;pH32;C89=8}f2Fh-ZSd1M4RW9NC zgdcSGLhfS+K-v3NQ1;#k&H(3w^0TwR5b4S-#>Rm~gbxQj!V4_MBB0XsOtR%Y21>re zpycbY7&`GGEnkO2ERtS5{t0{Q1TgY zA>o%M`WWj0m97g^eH;PhSKC0wOP^ej;I-Bn`q=>(O}aZvdj0hP~Ti?I$+`5dwsI|wSD17H<@FIbGVfy$>9 z6rY`-^t{7jY#Vr<`>hsZE#N-*YzFTlyxC%G9q7vo{%hzZU^{r~W-IRrQ1TuImCqqi z@*cDpI{-@F7c9p1gOaxmTtN6exO@Ft6~ zMo@evgOa!Nb5_oipyWIR$`7=;@ZIjd(cwaevp~sF21<@87Gsk^$x&i4HUX3zQSeKI zkFyvn6p}syijM)MmrI}ZG1dc0FBdJwxMh1QK)C`uYHUeca^q0s8u|7~}oHRv)0R4|jjg-p6=n zfz=17a_<1;Z?{>DwSY11H(QLY2UYG>pvqkjN{(7k^?R?y*dkE%TWvA65R`tZK&BQ| zl@?=hP<)C&^>>GEwDJli??HDjRQ(;$J@I+piuEU;^t1z%99u!@qs3y3_m%kiXto$z z5BmNDWGG+NWHHtVN{(e<8*(fH|Bd7Y<-bb6cJR`8o9{(X`JMxn?uJ+#twnX_W&q9`$5UyW-+!GRDHHujO_*`|4y)+@EsOoTS4(z1WL}~ zU^e#=P;&M}E&q4GLdFpnEH3T>W89y!xVRJ4I{qne2K-N2j2#EX|FENXfDC1;4q1#H zaP&s-Ecw@h8h;jnvflzw@Ch6vbT4$UEicc9h5-bAe zAa|j~#lu0_MFBWj`B;oy`i!eDPFA=eSmuOILl z(zSuoYbz*zTS3(;zp2Gj6|@tJvGt(RHG#^n5tMz@TZ}COWgoQ`WA}pMvk1h+RaIMz zRe|E8b6+ZV8z}!KR6oAg-3!%^x9XmFJ3#4cE2wl6+`R!c&g>dTe+?c150UROQ1Pol z(Px38AG^Vp_b@2>Ua*k(yB+QTM{?ij=mien8Eg3*18*l?mBX$vHoOBA|0$sO9;WiY z2JUva9()eIP2j%YDKyXdmG_#CKq(P?qLwP z_`U$D9=3tc6R#f3<9?P4ALqhVx#Dwi1p5Q1W#=b0Ij2}Fnz zG+HXd#Wf&cCik>Vok#`=q!39^RB$pR1Bv8D5-p@zcYFAOm5(%sf@8FUZq1eko1uPB)MFSNo;;%nldj0BC9UR1|mx>NdZzg z3z&8RNq_xQm0af)Zt4RP-&2hJao7g}`UDVBJV=NvQqaRRA!tt*kn+g^;A;q{2}pbz zfE1qvY`@2;+w@WlSj7kDr>J%VNag#q!c90`MV9ZA3ODrv zseGRRB1sE+6neUWRK9W2f+zqU0Oo*Co5D@4z--Vh3O7{(r@~zckm8jLB)z+UBwqrM z;)jE3B=#_NG2&DgDKG7FJd7asDfHlI4cYGOQRt}!l3oelfxAkk2|;_xfz)620;#{8 z4MY|yNLT1d0@64z9*C?|;8f^w05f2Y6J=z)dw|a)UFiD)YL5;ADLw~)6rT`~;#0=v z8<<|qh?7F3Jq|aK@xtLHGF~SXdU}AbBmCnEJ>9@V2#@yRBWo7m01-mZ5ui*5Al26g zfTW)`g`QR*@o7=$*#{*35R!gEOcRoR_5evg#X#ymr!dma6=d;(3wDVf97H6LWCi^S zJ?DUwu3;d_L;FcdUK~{74|m^mYO%z0Vj5yBQBKHZqnorT{7a$v{f41qwYbAf*@9un9d0 zK#D&hr5Ei}CYq4a3vG&wKk8n!t^uU^lr|vc?-n3LD`-~esRdI0E(5}AL5V`oMj-hs z08;v)EtL5?1U-G)5YrohZ=p!k0==L~-+vFf1b7?h4Zwc@T?j;+rmqD40<;&n2y`}Z zF)#)AN5Ev@7hzB7bOM+JTnKYYpId>HKD{s(z?XnHEJ(Zwq;&Zd>?vLPL7xJWJUA0X z+zF(8V#|TuKyudsbOGCebAfbH4Vj04@4<70BQf)uYuXX ze+H%ie+f)x^910dpq;=FPyqJ=2cYE71J46L2kZmx0fvDO15W{Q*68$J;5pDez;6M& zfnJz*0e=hn2$0-$02@KK19t-3fDOPFU_G!2SO*LNw*wo2Nb~8nz#3pVuo_qbL>`%5 z2&@FI1a1Q&Oz{vf8(0BM0hR-kfm?wtU^*}fmX=jI6jQ+j3LGn#tlG<2ldZLE-#x?KTXDu&0TDs#O4WX zF4%lvD#s5f;|G-SWAk1%?`HFM#t>r(qnFXem;@v_5`e^4uz5I+s>*^KLe$exlSLo42!h z8=JSVd5Fy$*_`@`Qhqkyz~+T)?q&0AHcw%57n>)sc>npC*=i_T)jXFr<=`j ztfG>a&D+_$jm=xwocfW(r-YIEk;Jc%?Y(U7Vib&F^w(tiF;ag~>IX=2w6l4Lv4n90 zQ1SU@EF?}LyRSiUPc$AU<@Nr>7TKkF~nHH=w+mK7=JO; z4wIZNHYY>k6UIyOV{B)n_LBUy0Lfp7&8eLv^IA49Ve<`aUdZNNHg_=!#xU9g{EX>l z>;e+MBS5KdHg99|7B&yDc_W+GvUv%cdl{*HB7P}A8DBO}V)Fzx2S_<$Z6#%~wq~42 z7i&9c>#)Vz4%pi9Znm}H-8bdj6qv-c#~8Mjm;*63yjx>pZHHrz#KL`S2i~o*ZFrxE z?Zx{-Y{+TY`eQG|+VCELNn6~3xL8|DTr1w8xP5qc#T~=@L|iZ4$K!hNZi;V?kF_0* z@4|cEHBHxmPkadPb2uo_X=|HtV20DyGNW}ytgUCp3H&}iBaHXS8K-7A?8g$i6OJdq zX6A{R4*U68{j&yUIqdsxZo0YoX2fD{%iLJoskx`|J~*#qp2L1_-uZd`^I)@}WdVFG zIDz-UTRLvR`__(I9ro5++ipE@DKL@)m6 zk}kXtW1TkEc5=xn{O((F4)5@iGcY;$>5fmseA%gG@V)FD-r;3u@ZRTX@<4u181Ivw zQ+T(2rtLG(z-OBAKKGgP^xNC)jkO)i?aqbdxuOR6{qlS zxvTXq!?y3Pmb+|tH{Auz-xXR34tEXU_nEu;@NQdmU=>nlRTtiUtIn-5Y^PWCt+L@A zhRNa8M^?kv>QDiEtscPdGpqaXK2XpOlOqL3@$M)%OtxzV);U?ViMu@&#o#(ngDaNmQF>cKX=n;&d>&|yFQVEDl^piTsO18}>! zbu&_6^XbhFdt2#&()LnNrKj;eS9%`rGo^iax0bb`r1g}YC^Kxw%6iIdcz46(T=0Al zZi0PypA4SDyKn2ct&p$$Sh>RQ2-luJs4%A%A@< z-l6(^c=tC9G=Rg-qdOtT!{;7G92hxud87~Tw%rGI z8@A@%ZM$uFx9mo^d%E|){hlto5ANx}yZKS0UE?(2frCc-K{7vRoF_bW&^S$a;Cn{<_kb6^ zXN3L)c;Z#5h89)Ax<$?*q^OiP2AV*Y}NML=Sx5xIoy`VVuALqqft3WrY6< zavtq9x_W`74Y9c{Sp3(BO326LeIF#Q4z>d+AA^Dpiv|0eoZXh5c;O)Y#( z`G215P4xdI|Da3QK7R*j@c-%`EBE;v-aj&ZKklj>J})Da_<`2v zW6WZCKkCHw>>+iNUlm0;K@4V={9HRAi zVacp-eICbq6dvT$=U;rD<;iYR@t@1|9;OZUug_EXSHvCs#bMTG)?7u?A`v}BBro{u z^Ap}>e*K*OcXIgp{DUr^@~_W9$jno;KKJ10or*rj{8Muk-OcooI~1+YF<8j?MW0`= z^fSu7%q7JW=UBi0$npCe>;Icf_maGjU!PyFnDc+kmz4j-oc}YK{w%xK=NY`p{F|Oo z?tjMd)#n*J%H^&0F=c-<=l}bdUm@rJeN6ui@q;}2JcFOJ{}B5>&+-qj`&!O#-(mVq z*8jgT-N5uaO#9h?3d=i%^Rqr5VIIf#r))ou_3?e}p7XmtFJT4Czv#;JLHug_0-h~<5n?f--2{RPvRoZs|$3V*`-X<_~RHK&h0PvKKc>+=)- z2fJ@)_s`r2eaB!VtqEV z`*&GirFzuKPiweCu-^`Zp+ApW~2nucGz24VCK^t9#Zl9 zL4vZ^=Ny#HR5X3hK+o=}iq_{CJRVTAKDXcjv{fXpKBwSir=s<_1S{N%*5?ph$Kf}H zBzv*&dS$QAA^0cKC&sMH_9=UsZz2Br9D@5uU&tS>7G>|j`ajVrO#hbhJM`Jk{6AT! z{OfZF?pdQ~eeS?l3ly!-8R%K1Xnn4Lk*{cdj=-m&7m`Px8_>e>InLn?asD{T^fOG; z03w?N3vD(FXn7S5$b{Fux?W&u05MPb&Kg)=xIuR}w!1 z?cXEH{$?&8xt$Fg-7Snk$&{KW3=v1v_9u6mF3sx z(v@(2(C38Fyd=5T=Ni0>g+ihu=Ww9kNVGl|qn>Gfjz%}{WJ#nj|^*Iu+Cn{Q>TawEB^f@PAl=|ux`dp2tla+gY&cwGl zKk0LQ{*!5aPRkcjPAL4c5+xsfe?WA4uA*0@C|aKz^v8=8tG|KcDO#T+61qjv`rMA43ly!->8O~mXniin{hP7> zA*NgOwn%nzL3mC~C{jOu+7I`j#aEU6PaY&1Yi&;|I<{2V53v0Ml>gwqi|fnBDirNv z`j1LLJ3`|0W6FO5@dy10u5X_q{>UR2*}i+TvVV{56Nx|E+rOavXAytUf4xu9=gL)l zj&OWL3G8FK#VMBe-=+NBB8lbQ!|^F$`@aW2(nmJS`z-S-VSZod{H4#e`PH@HkM!5) z;LyHJqP;+R>VY#*{`EP6@#v2btlH2A7=Nzoja25-87QUn=+E7eLrUZ4_N5Bl9BeIX(Q=R zTEe>)^`n{Jn=?kz>n!eL5=Pp8744(h|4Xis^ruk%&G!Fh(brQJ`g@k}Zb5xy_FrwW zUyJdD+5T0F`yW~2Q-b_tcHfEoWTsDB;`3Qd_?zwPZ6j%9*Ae#r)1vP!{*m@yKv^^M z!)2u-=vE7zV2NL@h5sX#_+5+o+RXo3mi$2b{>=6T7I|*4kF;Noa$$CV*rFfW?_sw8 z9~OPQZ?XRui#~o}p?9HuHuL*|g+77uYPNsXLVv|VuSb1jc7L0N{&S1_`Ih`a`!LP^ zXIS);X35|Gjrzvy{x#GeX8IiqJoupyM*6=KVOKGSr)``;g#<6k@w={@7n1rt@N$a z7vhY5kqxe*iN51yonp^7>7)(c<}&6zF22fopV?DRLk(Y<1LI1+UtSc^uoN;gQ{2hJ zC!ymiASr$-Oz32Dc}PC2>E;`^n+^Se$_CuuQ=U0bx?pI5uKIK{sdml{Wnxah9nm($|E?Zu6J%1@z zvZYTp)Ks)PBLXR{sj8}}&M(-E8hSEflN*zoktHYVf4sbxQcYJt`RN8Q&M)J*PB%GQ zJ&rp!bEO)05?oErOy!O)j~Om^Zn`~4xBn{pncR*nqbc>*FIhr^Yr>%)!OHnWh6_tW3uSb{ahT@#6D!fH2E06CP1;R3;4~>RR}!9NKHYP z0*w{agdK&J3jGLdW-lB(M7*|!*>(`N-6qY^T@iZBmJvS&!^fw=2&b$w2N@b9c^ckyeMhDl4jjs-qXN87C$YH>zBx zj(3-C#H}hOnRK&lWiVpO9LBiJH{y)z+AFNV(Bc>GOz~HzrXz=O?^*QEgDXGXh%9fH)izt|f@lhdO`#FA zoTQ2BhpVJQIA=%oPopQn(SNY?PBUp{q`J&kxVqHL42=UOD>3-Rb?^aP?OlhSF|O>y zWXc$=1I@_$tsT5#Kc;kjKJ;-5R~MyY(<3FHrRvKrt*PDRui4QcFR54UQB+LaqeaDS zjf=0au(Z$|3#&Y%UvZB~e)5E@7AW0@7;p2_o!n;44i~R+yP%nk^%yf%`wB}h%Qs3E z<_j;^cVj)$3t_Jw8Th2?$c%^UDjI^?ubi2#-BB0BBvf6)jzHxHS?WMOlItI|)Zq^d zuXcP)z3-1_wZrGe`o`8^H3pBULopjIhZh*vrTE>WjXH|f`K=vx^x=1|D=PA@#Rv@R zBSrV&7s`%*RnZ22(Y-V}$SbWXy5F=+QdSYzT2p;Bb>!G+ytSw?QfD1zgH`&-8Yq%% zHFf!uNFOYw=mfgokJRD>!qVRGLRKV&sM_O7dbldNv<@a}&EU40En9qzsAnT{G}06+ zJ-&xb)m6kcqqH&@7-?r34P__`q*OPlo1`BSwl)D}I}UTPv#3&uS=zy)r`c zs}0c0p(zS!QyHk<3iHZJe`IA4=Ig5;u7LY>L0m!)cN}O{5L22FsvaxJfvrJ5`gFni z+CZt~H{7~y-4UoOi#V1XB1@C#Xvv<4S=&*q@o0^)wJpx93)WXW67*G-esES5)tHuR z@KvF0lguVD%3q%E9<2vUO`Ms<7~`dhe5Wu*T>Dq}QFr2XM(VuTv7dO)S)4@C<*RpA(+0vfrfmKRm1)Q0~LCRdE6 zk5p6=7JXDyV+TZSWw0Umhmuh?kH{#hBNn|SD@U$L(mEyT_TTj?D%uz<)Q;A7z@8nn zBO@|B$~_i>gVZFsY3pX7atB5?n5XHC&(rL8+d#odpopt=BUMn!0aBq@R;rTD9B${-;O zclvylKHp}{Nnq2axgE_^yMv^%Mr)LZg&@9`K9>xNCLHzlS8R)e*(B6S?jrlIvRE}x zhgw#ZeEo70n2cL~q1>porf8KvAM=v5X)7uoS3)*$BeP=krY4(I;#_VH@ViquBh@z0 z&WF*{9OkJg6p_53w)Emrfr z7_HXPP+hnYZM(T6bjf>_v9L@Ndb*vV-PznQ(<&jgAE+pDg4Hw^(i9h zqjiMG5NaJNbkigWrGRYvHwQ2`jlO4K3oQ(DVvbYoV=kxIk3Oi0*O(t4nY(-FwA9f+^~gTa@Mvb0jmSE~WQ0d|KvCRjaAx!Akm|p`a?mx5_us1fHyDviQTa ze0)@vK1$ykX4Y&;4o!K9zEVNSfWy)95Mr%1I;LanBEKxNO#9JiWh*E_B@Z=1!FOdV zP%_Dj^}{ll4S!jt{E^?tv>?QYsuxRxaRm^I(3syQ=!wVdi_w#ehTT;4ZU8~ zt*qWJvtAv4?Vno2$VbMyKccpKjnW)?u zsf}9RM}s77n!omA{tTNY^2Yxx;7+ucSmDA0)v4w(&kqw&+*;B{18cBeW}e%VPRNsd zthoK8oOXk^-q$Q{Z=;0{HOxYAvLX9=B>b(_K`>Va8GK(s68nRsNHfI7yT)C{SZ4;G}s=iI=h;T z*1j-Qg&p_5cgaX&`>}gwSCjDyNk*DV7`uCSH5spvWTaW4vHOTulko~k#uOyu*d5KQ z$#{h%ts;nm*hm7j)JyQaT4X~>(F<-SC$82^5L{E>!LYyI&@Igb6)Ux487V>+h~ z=L%-|>D+L=d&<0NJ^!*l^Z>(gc5W9H`tdyo?YL5hAISA#%jdCUe%;IGFpT_q7VFyR z=w{&qtGXbaJV?jSRM+5~0b*Tz&l*}eE^TZKY_8bprq8hJD{vHMpuS#yt0NEA8Onfs<@m%SHq&Q+ZJL-ct z`KL69lZYtyrB;;5AX#M@rSdbdQlG{Ail{UqN2!nbJ(n!9SLn%FvQ*PKxQN~G`U|$M z_*9-*u5(<#$_gGEXUi%IT7RNugBf$@O(N1xN-ueTsrd!^*km^^g21BAE+6TZAI~i z$0|})esHNG_0|Vx717mj`M9+6#w=~>2V)b7*au=`tr>IZN*UF77rN89F|XPDP_VQ? zCvVgj?r8C>o0{opB2#TBt1sCbv)tt(zk?c+F>1GH#wnvS9;X^Iwf?eDf4Eh=jMg}z zyE7_l@HLj{ync=^pFo5?ri^S;a@OnsH)A35-TIx zC_PhqAO4jVF-wj7az}f0r$lV=0|9142N-PYUGzY+JieKVv8RLI6W$u zXp@ZeNGQ>kX%WleU#Sr@ceDq0O2jsOqJleNt2#<4_4$^T)>M`)MJR}0T2%a^O){d5 z($gYQj{HiC{7Q}daz}f0r$lViKFatZ6vQtzDt;NM5z(V9(;}9`zfz-}xg*Z-%UUJ5 zQzEvhA7um)3L@x^4qsF*FU&`OD#|7&+9*5PD8n65P~=xyFF+4b~aw}DVZvJwo6Nw)+ko;{fqbBd>>bw%d21Mg=?op5B+fA z9}ljj71`2Ol2NY^oAj|8P2+lMLI1cmS3Wps0G&W40Dgb5Z$t4gt$*Pfcli)MT!Aaf z@xnFeLO4W-KtGuF$EW)kfv|y z7*~P;#jOp}vlRn)uq z&M1*zjrFsNGM8LM6YHzDRo6UR?UI>lSR&RtMQg+}8x*2eKyd|nkmo4w1(1u-gX^d% zegyzR4`WQMEopYF%^4qSLk_@`f&b~OaH4TdwU`bDcN2k!!Y6vUq?C?{X1T;4vAw2o zZLydRflAmtv7Te%A30SS@kEB>^2;3nF3h^Q)LmMxs3k`#ui0Dz2_oe+WAyTxmYS88 z%Hax*rYV2ve=?tJ zxjnpuTnj<`DA=CQ2=PBjk5T1zYbmZWm6u=8(92qGHJ=kK`^gl=uWR*iT|(jL$L~k$ zh|}#V(buQP$}a@}^tj|(hz?W;G9xjKk~!j;s9tGjeg!OFkFH>ti&f=Rui!wpNElZ6 z&QV2yXR88{sG`ACG(_{u%Pr0wcDI4$NhaOn34tFaaWC{YPnwK;O;qoN(v^D`S3DhA zieDHdb(69h5#@>My`Hr~tb}ajiRyv`6x=po%l$&6+%mG>qe_7OZ^wTn{&ktf&q+T+ zcKb{3+#A=~zjkWp#og07FaFfF|1G=iP}pu90+ZH$yEt^mY*7al7Wa#Rzi6=t$hu94 z#CH3}0VQ{LPWk&ondnm_5}l>i|8%4a}V6 z<`e7&vh@7-P*#Q^GNy8PBwvRZmJjiia%g?%G#pxPLmr7in#UrKI6E)eZTqJ>yn`#_ z#BW}Ah+<0fIN=?<&G6>z{hqOX?i5jc+Z3^X)fDm4wnd_I(G+iv|2eTeV2I*{abkbP zJkhDc=!?z$@f_hiJMUUi9JY(Un+qMF{z=sKlxNCgyn{VXakRqb9bAMk{9i{Hao~k8 zoCt&XUEIAA^4*0zxuO&Dj>wawTb-7qchokCI=_;gh*5br94iJX4+x{%6ShCPOy>tN z#rp$Pia!u9y|_&DKDunqQIS&)Sq!5)Cp=%})$rrp%fiz_o<5{?-zUW2&|af+2>im2 zi~X-SnEy2Joe4KclfK*FX2{k#G*b*xy1!yKB4tL0+caiARAmcI)QEVP<7l-L&geMm za44>kIL38*;MOBv`N=X7er(yCf!~j~QkgTSnK`cd_i#B?`jK^!|HYhSDw{8N=Q!=i z>od7L>_vHi`9C{E2KqROqqL*?6*_lP z8bWU?ZNqw-Wfxf(eYbR*i;o-o-?rNi4WQi|M7#M8)hVX-aLyjlDYy=h_01VUL(CuK z7~*IW{&m@W7ye$c-+1U%dy;OC63uo(`ZIn&ewE%s>vS3JLtXJr{5O1Fbp8|Y;_elY z&j&g4An)?d_qN{I3HfEcNO^34)4{qfX%+bjBfX z8+ZJYWF;Bqc*T!?5-W-+&T}@3-+TvoZvc7doxLLG9z#5L4fyulDfZv^=ANg*h#UDC zfZJqy?vLM@Z*)?f8%BIzfgG=LSaY^zW>Z@CiGeKVC%sSEP|v?2^Xe?=|61_uo8}#y zV>o{E8p5MG_?0=KbvD$a#9!tUr1?AF^>&iquzeQzt9a=6kh`YOcjuI&EQip(5FJ9D z{tR>%5**iSR9toX>-^wCI2|bey6&gCiQ-S;TJw?23*b%Vo5CRaQp2VENd9#AIt~|i zztBlM>3_&hZS)kRIr8Ifw9`oIo@>ybu!~*J+1|m|(VrL^66mjq!M-81O=x@GC>8sQ za>f4KN5%fv=ZL|Dh}Y|P7&(bkyn~^6s%$qQ?J(XuTY|RRS>kgBc1EpKy*%v9@&_~2LKCut! z^MWn$Y#3&<0^+5(-@Ke%F*KAZ9(_>ulLkoF@ZW!fkm*EvxVZZ+#B~Mow-0er?LCz( zYq-=G6?2h4xbLRZl5}^`j{XDkv@pDb2>Yn2*Sv$eF0@DbtD?1p^2A1&U$QRlez8OL zdEr<8rnw> z0Ln)<$T~pPH?q9ga(~<(sROP@8J&eXK(*a3Ka`#w)u*ETJ3|b<@;H@g)uvF}fP9BE z8O{#_{=7(@(DnY9_K=ZuFZBUlGS6~bp~Dz@+(7(oh`$|q&VllUdVjgGzn|l*^N=IDImHL-uauGenodhG=x zXC~^k60Xb1AgFI-P$^j50ZW^oyv#bxyRLoeHA$ufHVSY;Ic z-@aAVYfU$M2Nxo4ug?^_UWab=7{VMc%w?$h+7z!@h*vwut78)JN~HSQ6t6_8uRp?g z{Ta$dEB?zLME{uE^d=#m!<0!PjqN&6w>FU-`g+vfm&1tr-p)ghe_~+KdGEl_e`_1K1M@X^V7_K5^6DEXhYum&o;8)jbKf=ge;sY^ z8$&TDkFlL+?-GNHP(Qq}*OqhW@dpMvZ_DZY*>CS0_}Op8Ku7&e*)M?i%itS^zh@C1 zmHm$Tf5~o&qdxyna(aMof7&}ZYpQsz1hOu~+(Z6j*cQ+E|23yooRIljZj=Ret-ySN2QpxXgYD{P*9X$}hV`u@Tu0q7RviWZZ*v@*9`xpXX%Xdzn+BfvE#(Je;2DfLo|%d?ru=}qOXdet z{YCkqbcdgkO=yqgxB+v4G;T95OXfCr>S}^{P<6oh3z2^^)vdWA1x=*Xe!cyN@q*wI`T>8C`9 z^bqnE&9}U4yW#AesMkn-Rj(~a-0y@Aa?vIom;K7CI*HcF&`$;CY|QPPaVQz(2L%s# z0c8W}*MspzJapU#-6md>JMbmc-FFyj-X#w9efBtM{sheZM%>xJ&=ATQrTfgYxkwXC z`VBe{dOH`Q48=KVuG5LSNsXh+H_P_a9frGr4fAOz8?^T%c~|*>95Z8{6=m(Z^3CG; z@?WWXyyQAnubaz+ZnqGhNZ(^2WOxI0_0JF&iWAEJ(Kk$S%Ey@Tb=0S~q24RNoNklM zt5eU;r95c}#6_G9T_=j0;I1HmH8;aJ`y%RvPeK>OcN+YygumoCF=+PhLLKp?DKKL{ zF7R7|wcgLd-BRE`Ae@)M>({Z*+;r%^r2|XTy`4XY-I@&1`B}({g~Y)nCElFR21L#r zVVw0Ep{Ey57wOm{bo5WJ5$QBXN$D{M_B0ngRfZ$BJFfM<7rsUmPlefPgs~U>v%VXi zxoPEnyiNS_L`C+y-miqlz#NS_A!wpj7pr$EPnzaRgr5&ue< z7f_ra-xD0)O2)6i?geA&**6iNFWG&77KgB4 zwheS8=o3znuH!>}rV!dlir1mRp$xMu;lhmKNOHNQQSL3S1R{j(UQ=X^>7{O7|T#)5|Gd~3cIt&Yqvm;H2z8k59&`*yAeit(Rm~HzmQi@7xtlT zrF12olRK>&k_F{Prg=mb$Q*|-CM64G*0Ll)7RvvWXXC(^_~|e!@H-Ciz&bV3Ner$+ zJV|$yZx%7?JVo)$M?BG=g&xEp$vZAz@LUAmlpd4fi|{pH+Ji9{Wf%25`c%WeN3FM# zhU*e*JtFHHU6o*%8gu=1R17f0VhnAD%4el6zUzZpP(mf9E0w|ukwL#VqZBS!%B z*0^$fYB=6Bp0LK9)~a+}Ia+v>el#Dm5^X8{b|J2$@6p>!r4Op#1s?h zymr+wPmQTU7tqFFOttdDP{u5bseZXx>?ij$rg{`Q(0v5$Ui`OmPir^NzK7pfx1n)d zE`G;t%$5C^sSBvh65H=YAEO*&{Hf^MY(sjZ9oSxuK9V1MG+rE_w$0eT7bJi~UC zvh5SwAN|qSo_^iP&YCwkbTnCbbLJSgZJYbc44boQJ-PrGNb??q4r_S@o9}$@M?6 z{Q=~&z4n{V?o507>2IK)yYTVh{xbD})tc?_ns7JzyJ3`3io4(biL;NR4WGfke}do1 zcJ#$iR}5f{c@TN%4AO(vw#{i-!hL4wE|J2175>P*6Y@8q&L@Argug+oN6!Mke})b<=xg)c$qdzFHu1Y$hHJ3-JoQKrdo7U&sAA5XR`(s~NMr)elEBltA3=Y14`LX9w z{;^*H{V+8TOW`3s1`Y5;en})bOHkg3FVz<)8~c%dm{SO?BVAb6g(%G~)cmdN+fzE| zGOz18%+JdHPjZRK3B`+Intyd7oJDBsUO_ympYb~SM{%GZGDHr|D=tL;EE#R1bDGFV zroIKr_+IpB+o7jzXb&pClA9fmyiaYZZllSp^5RfBwQ38zZ; zacAEEKjXed0}CC{8@WMzsEuC;9Z)|e4su!BVrsirqhEg;+>^|zALW$&s0#F>k`d-i zg#QNQAYS?4BYDBCwZAYSFX)5fO6?5FJ=Wx%hwk`_7@+a?bJ&(j0{ReJ%14>Lkifdt*%d84!Qn9-7DOA9t9@d_p{DD&O+SYM*Tp2fMoopH8vOOOFi$BkNFtzlXcdkXh%XQUsRrSd7w6* z!W)+_^2vwK*LG;-M$oHet!-vRM&%HWNj~oww9qIlY@(I0M|BZRat02KNDR96yd5@#kYiuaO``*l?bU5eIC}6EWhLExs*AoUx@3 zOc8B%Sa#Us`_PWl&*OIInHbUJa2}5l9gg@jQ$(*Ly=#itHwBihQ{sDKMEjI8;Ef02 zxaeb?!uWGGu`%(aO*BCwNMWR(Gen=!MzVan!nj8)6JnY3fxz+tz|dDJ0#Y~Tl(4mcpx0wGS=9mgG_*X~7i=pYAO4oA0R$}tBevM@e} zBCnp)?+@WIrWQN)*~ErZQb3}YIl(-SB$4tah!*^8G2#)014bFimusYKjNfM%<+Vbb zGMr~@&=o@7XNx~!6D_tBDH=@sY>s}L{hST65Qd0^Tc*Ojc7x-z10O3n4m*$tr<66q zzq!YVKOQR%+MMm!$ZU6>j}@n;#J9zYGgF{5lIh#|MozX1G3zkmTW$8E22>~7?aqS{ z4a7G)M4#Q!vBPo1G3Br$kNj&DoW3aI{|P}+Z8PFe8{!~E1K~p%Ck^_}Xz1J5 z8Zie9=PARG`XK*9-@e9hERHYKCPYsua6%u-iL@_pRu0?W0$vATdBBJtuo)dji4aY; z`13a7pe?@N7I8OZTq}?l@bBobLo>~hASqa}M85HDgTfF#i;<64K`6gk9_HdI3*uv+ z3VdJnNT8vjrrNb7fRBF4KIPg{v!lAq)lgHjq!MQV2Gz$`F5&u&3*Tc+#%pzOD>&}d zK9>mB<{b?#I{A#g?b2tyh`cev$Gz;Xnk_E%tsP7AFs|H#Zw@}#Sc~&9f@Nc~UWVO5R!p0na8T(Jf zL<&7lc!vI>{LjY66ZF&~e6#;1_TP*N7{+nP%fUR z_#K!l&9(ed_8k-0A7y*$CqcxBew->Jr0<9vFeAh|mZBzMPvd6-`lJ%>P5{Y$3-DgxO5mfw zY~YuHeW;hd4m`-11l$5Tkuibs*eycfub@kzryYo(3l1pslmj=w-v%J6w1Q%Vp4n`E zW`Xh_R_HkmBzLD2dX58Y;J+0}@|;3_yB^rVxDiP4SP5JSOl11PJSA@*knB$aNv>Xn zo@TbkoWHcMWh?+fw1SliJq{qn`_x?J|D;0C5#UCc9|S%?{Lv1QyJJ8K?E)T(J51Bq8cy z9tM5`*bSt7+YJ0Buo3tbU^$TF*ua>;NQV=U`MDbuo@P7&Tn+P9;C^5;(2saFDfH|E zegRPlDcn>G{2cKI?g2W1_XE#dFT{Poql|4p%E$YFlz(Y~k1zp9dT|2L{x9jDrQ`_% z$@~=XX5eu)Kf>l6z)!-wjm`J5`5qv$)RIaz-vIm?=oDZpST0t$X#v~ggC41eQ;7j zcmkcX^{{UOlDkI6WsLn8r;vRckn(9OkoY$Pp9F@0QjpsTa5xc!cQ|##-Q)VZIFbP2d6` z$>#u)e@vFjdXg{*y>v582-?#Htb@CQK;na`RIwe{#B?R&M#f|y#oGn^Dlh>k<&05? z`D{sdF}46(h#&9{guhRrCj_K=e~&^>Ef7WEAR*P~l}r;-eO?Z11RpQ(5nwhDMWP^G zp(hFWd6>rok);cq3Ox?sV=(WZqS}uhpbs?7ckBWl1X6qs04Y8pAjPMQ%{MT;n6clX z>`wtHUcEqy*9nE59w5aFv+RVPZlDM5jscOS3%V3~jsTlsj%jJh2NU#?Pn$wdE0FkL zg+lV#2PFLvl72!=6Ow-R07*Z^zz{HnQ2>$k3oh6tda&3*UTut^}K#KPfU@q*jU_$7@oV0il^g)H5HXzAMSO)V}rU^lNT7V>P zIgsSdV{`#g6bfc5^dth2garu-Jx(CWd%>pUIS(XxY4#skzMxN`C(QN-fO)Xr!{`E5 zf=&WbdOLxX-n0Zm*v&{Y@Avmf5vu3s!zzggwf0BVib&F2m5DiXACixFm3=MX=4h3 zPaz&&HqT~rD)(gWVspVr|?QBlvmCUJpl6i>D8`->;%}dza%jjYh zj00doe5ia9pD<8{&)CiwVk}|Y0K{KRAztLq%jPadvLrrXya=%vC*Ah~$$dAQw==e} zeGA)%*t~>s1KSs}y_e0i**t~KU2LAj<_T;r*n9x_38KZE2g>*YVHMNO*aal}BS5lm zXY)2TZ(;Kgn>Vs~Et{9HxtB2;Nc>WO#Lva%No=0L<^U;2tgWOh7K?^0My&0it-}^; zJ78N>Mbk8_G1EE~scD>Wqe0|IHv9?n;oxaIw>%HmZP2e-Hcb?OBd|uByhyBd_ zzWL|oJM4!S9a(gA5%}KTe7n;Yx_#g6kT~r~TCDAKdN@7Sc0Roy@4obNcsFOXWZ?bj zmQTmpPA)&SJl58ecOoy=*6Tazi?xOG&*US#{L^@!Ty<&{bg{a9HR7=P*lMS(z35<( z)7DzthK}anEP%3+u=@EVDwOFcW9^g>c#7vM`u{R*BN_>%*J1vS{F~@o=7C0;39lS4{!1OBe5BFYTt#2U?jIl;?)AFQSK*fQaTJ3ddLDHtd%fvtXiP# z_5A5sk`Mg#y3Qe{^*YWq6hGMOb({Un-+LPZ$CFR;BRoByn#J+c>o_#ePxkGLmHSgJ zMeF&`zh{1WzV!>tPtUJD#_`wdI_2aa^5}J%uaiA=@)g$iKaf4>mze$q$49TL?1dQ9 z(T1E#!w)=v3gy2dxI0}cJ^^^5DSRkU8OcyflK_4>qrz%B7R&-^ZOczT`U7}I)P z;yuzoBZ;pz2=n-UeR*B^FbUST@gEF6_CpAME+uOqByx`gvj73l-~^!)Bk zO20zS7vHAzEA}l>{Ji8J?hi8kZz_KXJ^%bQw%7B)>xn<~tIOYRvWI;7yvt7!4Sj52 zdFK(0Hb0au%y$^wq8)>CdY*?J(R#ftmubB|TF$gy@4|i{>Asu&{~dHW#|TMY z^oqkj(R%%qKKvp6dcEy!bl8Zd{u(`hF-_5WJ?Dpr3)$=S!~J$e>-C&}#XOz#AFuqs zb+e-NdW#P|9qdsR$B}R7$(7*^ucx+heDwOtY^L>kOo-#B*LN1NJbJyv58YGvp+$=S zYUv*NRrav3Ua9@F{eMdT*hfQ(!Q+M8|;Wa{P7?fP@>y7zr4hBIp>#8kozg2Nd8JA zd(hXg`yVik>jmWVw)EdEa7}@HoJ=2K`b$jHBsx9#zDvq`jP=tDoPqSy>*F^dzY?w2 zqo<<%AzH7$?_l=}pci@!cCXi$gRqD5$dilwWTu;tK4$t8NMAF(1NqBLKZE>arm-Dv z1pNc#ceDL{7XHgE{9m`wg(weZ|FbRO|IiYi!{Ywu(2v=F7xZDKKWDLDVzK{oi@qMR z@b@9Vn)xlV=(C*CAfj#5I~MzYM}2Cx$A-2M^hS$(Z5H=GvCuOu`rB#ozt}=Aw8U?j zgc=^_% zQ7$bS?Xsq*yPBdlR;83x;KX^k?F;u6>D!Bz%iD`^u_77O1}f_ER~Ie!s|)aOdlAWj zi>k;rizAr7su3=v-Cekku~0LRzkEIg1uAaE6dj-2=gaZaCa{LOnq9t~zV(&siv2$S zs&z&85TCr#wMF;x-M>q5`(;%ChwV3hENX5e9m$DvaO36Y z;6__4kq=y$9Pl@k*VQ~sT#FiVu%EH2qB_tJ+$n<@ zmtP*T-inG~Wf?a1HSWsUimT7cD(V_`MXpv$HQx`KU%I9!eY8YKkILmcQBaHhQi8I| z!U-kGkfHjNJKR_6c30MHt-u-ab#*m$=?|9&8noSb(Q)%_ZIywo_1Znx15xo9EgIHX z&=u5Dv;N9p_11=Rz7{SbWttzE6jg^=M|6cmRZVq%!Df`M@fnO4m6YmWu&mx+hFchP zOeW!+vIRRI$94MToVn9S=8g=UuwNe>OB(a`&}_;_)q$!>Id*5&qn-_H4N4#NwYaKz zGGVwgC=k9F!CzZZI+iEXCAjVs(;ajw6_M34_0Cu9t=>VmlINvPtV3Llr5H@&Ht#(5 z82rpSi#TG2(HDNAg{UaAIvZ~nZ(hn|!qtu@2v<4tm+eBjqF|N=aGwEP29!UV28G{z zZ3VU#S2Up6SW0(i$Sy)vt**ENm6bK6sH^xk2<&>-He2d~BTZ#kIqKF3=??5rE=h{& zhpPy5qkmb2JrnoLP^&uQ>N2Rh zU_NfLF1nMhiRX(&v(Rsp%{zMG8R%)t3q{lMeMlKOm5iuYK=P}LU;OfR8X8T&EpD@i zv}|2nQwIJpM1QFgU8dEn1=VZ0!j<68;8z@>$`@@I)i(Ju^*CMplzP}PPG%I0p>bISdSGaef_3?;3aU~ODK|R7+BARd@5R`d z25xIG)}^{YkBxo)qLnnB!voXN#>R5Ex27l`C3c-=Ab(+OOa_x2HuB_vO!fkdkKMji zMQi;<>;24&#?3|dVMJM+G9oKnZW>@1aWxGr^AjI;8l+rJgQz*8k2?)6tsHzD6G$U9 zu!3rW#-co~gir~r{O~G)OUr=2pONQw@5<@YrK0~5tg5ZVjD&wjb;Wk{K(_^V`7upU zPaVBg>sF(sNp;s&1WSVtSJZQ7uWkosa~r4;%4pb8TN%uUJJ~IS(P#bnt5B8TiL_k! zamoFp&8nxESG#s(8&lxU8ATW$bx4ZuSwjtZX=7txbHz?KO-$8S_)9C%m6zRW^l!G* zXqVRRmuU(`ImwUiN-wXe-cqskPMRU&@Fp=cqMQ~@?vrGSMgHWS-V-IGxfiKJwnjDD zO>|tN-Q1(a8e`T*hc^BYRk?~paXen!<-OvHRF+OGrcXIkT+0&`g%V_obj zgY_uC*|G~r!#Dcn8&9xoIXoL(uq$J5%2T&c-DM6;W#p}8#Z)a==LiBtsNv+r+wLs$ zSRG4m!G~$Uo)$e~_sRS}ZmG@I(_0g8A7$V-26s~&Ftswi%wuy^Z-ttZ+b?I}sbXu8ZUx zJ#UVh&X$P-)uNA{V@;vEGb(EWWtiN?`mw(ZcWLV^#*4@ZDk|cm1aApkYLuItY%Hwn zwc}JhLzN>dw=5bU!;Dvtb7&Te(gLXwb#;A|Nor1%kvoSc@s~#1Wk`MmtN3HcCy` z(!0R~Q`OciX6^)hBvVpLOKU32mO_k%D(p|PCRo~NF^hKQPK!h%@(W&nckzJ~h)?Qh z(&GkoxkR)Swh(aj_!^H|{ZslsKj!Q7G1ZfTwa)uNj$zZ*6S!*9PnAYpSuh+OW&NGezdll&CaG z87&FnSSDe#^R#FqH(Yb7^7dOX-+g;(ifj0BXJ+PPk*6x&)%=C1Z z%bl468+uBnZlPTp^-D`@YnRsOEo{Gd@6GqIWm9g^q8GOF3Hp{wxbTmMw!t=;H*iVQ z;TL^xMNc-Prm>BTKE)B(z9t_`YNJJC0${z*UfXo@58_8Gy(^j#sg)=5v~j=npjEyVP_ zBYpFNZI1Lkn0&Csl0I{iiB7tuG{4kmv~$4waV{&}0XOG!xGx3gxYC)`bM+nHxZB}b zoN0=1T5;Foy6f@(O`Ho2SzD5DzBIn|O7EF2u7eH|zhU#{^nBh?^4^Cf@ z)NY7M=Rrd>6u|7#AH!yMOo@r5Z++!6bz1y2)3JectY;$ovHX2Z<|*#fwDgS3tn8e? z=F+m@7IU!OAdpw`v>DO#9K%1%mmCFRtL>6nAk-nhUql)dtOO18WV88VAoA%F%A?5O zF_d?aH|6efxWhL}D`i^D1R=j$fIU6Gz(0LAqs!sDM1w&$ke(aKy@~z}%*^i35{)vU z%N->d?W5OE9e$Wd`gU;y{g{O&w`TVjEi|>GkzH(&eo0OdCK9j_B|k5%JlM!vZN?hS zj5e6Dj%Bj2ks?^P6FVsopk8uAaqI%>Ut#E`Ne3lZ>F?r89FZ&UTrRA&Y|MlPY1 zgy!&hiu-nIt|AY&Yng5|X`{7k89DAu<+x;OWyR+DnzD)=RsPDrl0XoHC+so6UusHi zP310kT1v+F?OhKgq>H3b?Hzq9IM%ZPnVcS-k2Fp7DE;eZVY2OA4|&2v{E&U+_Y+h} zMz?nuTVwx}eyr`2=0oL)9y-Thyl0~J=@iQqBBAhfgCE7M7bP`Onn-cgFT5vepK4Jp zQu{;fsO)!x{v)94I`BlfG`CM65q%X7xs{az@KT;3d+bXWYOcpQ{Wsu_ge2VgbR+J5 zx(WAQeFFDh#q58_9&_kj`xM;I?7&(5bY{N|cX}8&BVQc)%bMIy)KThuahx&Ufjb?; z%f)`f8Cv(M{iZ{&**_uM5W3Iq6_`V=R&B3-(>;d6XY{{=yAPz>`C`8f_lrq4I5+vc-2i9!E+^#JqjaZmlRT@L!c}(*g7GpASE zuJJyF&oElvwTb;w??}r_O*7ncAo-a6kCqR9pVGGP-r6504?OeRy|~llLr>%7R32|Y zK1)J=yAk>BCgeYq#oe=U-+V0Y(u_sfi#hZj^3?$H72TupF7g)LhyEP$R#bU>T$jgK z2U%AeAR_JKt0uiI!zn^$+{UaHfZW!TQ+`SRH zdjLATAG*8`I^BS>dvE8^^6|>9aj4ZRezP3qpUP^5aC~_|u6U`>E(TwPu3zK2YJ__f zs?nZ2^p}t~shcq3A#-}l@sFQT_J`TNVGRE+?!eJFW;FkFjs`uoWBAvD=`y$(%|D$( zL{Iw|{vF)!8p}U@uS^f>4|=$FrU-i1PGH|SfqiHK`=$x(TPCol^Qh<^ml~ zKQe)R*97+66WAYz{d|mH={sw}Y#{osOQ`dYzUmVCUKVp^l|2eKbpu^6KLAACB+t!S z0Bi(34@l=>5nTerr{7D8f%wE$o{L4em}xo>i)cCri|_*S5wcJLuBagNoCA{iX&~|I z1>#fQCC7m%C-QtOlItL7;HGpSSS`8WP;%2bS7d)&;ihB2xv-~sB$9g{Z~?FZNOG4m zy%9)q)A=n|y{u%`>l817!Ufb*M{|l%!$V+a0 zFpA~(UFYnR%uFT;VEy0Q|DXBfWS#w5XYaN5S$nOu*WTv^pgezvf{!XF=R`^T3ZV3Z zoI@kwNkE~W$AS~bs`w#5!J7e;dL{vdejk%ro`2MW^J{=o&y7IFa;^s`^_26VB%j-Y z^FLv%3%*vM)bm}SJb$l(F8~J-enN#eDR?(f%B=uOJxYK=uSr0`@3G+g66(jJVpTh0 zvs749u!TfPuUBxZg5?U9DCkizOF>P+7M{axY`ub86)ab&SqcKoaxSN5(lnQI zlRB8Dp3jxIoJXA>J3n!9@JoywRO52K8TVG)J8>?j94phv!7`^3Pba#ZI}&RWp9bzt zZcKhtZGYGNF7!bvWdC+bn6}#b?~aJ@&m_zkdWZK@esjnx`|E8!(L$VrZNA-?{1UeL z{39yeo>zVYE;B#z@*Aq~*?jYP;w0VX<9}B9ZN9%)rQ7pNg-W;QrEJPdd7ICB6@Htq zl4PdaeE3b3f3<4gJt};+3eQvF$5j~nsaf9UbK?}g7gYLJD*sj$mT@livH9+wC{M8M zx0?EghBro}%Q9GVt!xJOQ{|5SprVE`y!_y~fh1 z7cV&*gps*ctz2^FvfG!G-o-{%+m>c+&Fi|!<|A=@G)dG=BhE6LiINQ!i_Qk;+2*s5 zuD)yGSs^{kd?wP;v%zR*Qr1gOLio{=@2}DpEVMwEqsk`nZL*!Q%bXC{%oMtf&)QO` zibd^Z_9m6G>+t)fD;KZ2-E7Q)g?HWB_9%S2mz3V#W@t5EjJCw62=7{rGs_Zff!)|; zYuNCK)w<*6!Y*55wp$H+^Lz$2p0FXY%QEMf-F$?470&h%oo0_ls0DYgVs{X`=v1vE z^F$>7fG<4V`a^aBDMtv)$S(g`VqUPibTJ@0rq1o^mt#GTW>+6`h$DQw1`M)|Fk|K98wYF?%~K(~w-SY4dlscJ&*X2jnOCVELNNpLr7D(#i$c zzwXw3Z9KMJ-Gk8F)vr6dy2#D)^Qd-S13qb628P-MGsUc~b;tki?CMJ?H=2CXM&wWJ zal*)%=J$27K^)DlF3%C0x-C<2d)B`*yT#^{iOs1mwx@pBp!#Erx)?j&0OPBQIMc2k zjAB=p?*-Q-cC@RX>BF~oE5v@**Uwk_{+)_!nTCC;wL(9|x4o+mU8hyQN4nVFC9TEj zAg^xO_<-T@Vl%hCPi)(98F%J)j;(T*{qH?+egIDEJGhn&ehBvWcKl+Kv+Qtfc3kth zR(aEoE8)TI_CwQ-8xdzc)3W)=y&lCzFKyS=8hK|nKaIM5bNjMsXAE@K|EI9yevTbi#=vLTaitEQnrj4_e_IDT zZogCQ{U6uX|8Begi=y;@5p*Zs>?>ef|4Y2NW@6duTb+GO8z1k4>0i)drdu{z>%JF# z+|#rVwmN$%TfN}0@_cTtxrF!eU!ufWxa|70_pyxKgK8{_4O_yxvI)P1O&CKNHevau zwp9oGAxxWa``EO~+ZlvyS8U4B#!A=Y3h_KvY0&4%4e$*ZWBv5PnM|B-K6Jt zlkVvzeOfo^VsDXOXZ4eF!x{UztVfu3o_ZiWKUefx=&?fVEWr62f$+#&IS;rua2}Ak zF4v>tM*~?4%N?S^`08jE5f=X^77232uS($i3W{Au!u1O70z%)JJ1sbIhl<|Qr9F#*lCQ*q^KS$SUXOxffl}XW zpwuf}L8+94k3vI%VwaKj*Q@X@AX#SawBW=YDt-<T(VH`PTx?dIQaR1I>C{aDEm@ z_=}Z)0$Yd|Sg+t#1)quD&2nH{;JaL_v`nhOBwqo<0Ulwj}hsq zv_CZcj}ht5f+#dSIRf7m5$XRHk^VtM_;dvR`y;}65&7FBdDH&TLEi4h01>H3SjrN9 zm$rV;71pTMgidO3o`!->uthfkbdtFf-M+Kj(Uta2a-V}j-U)_pMA`lu>eHQ6yK4=( zlS1dF^Wy#+srR0PV!McM>n>PotDmM`J4QKxWy#O>7wb%~#S1Lo%$OCS*V={yOkC|= zV)z?I@ryneeb#0owmxfz&sU!nze)Mo`mC&nu!Iv$pM?o5TX~zL@r(2mv+;=jC%<&? zg#IFel<(S)*`o^AE0_%eSga;|R;kKiC@&{X^i*qpq81T;%p!M)Oe2LP-!#Iar)D6} zv;d>&v+^9#Y2~@+{Ilg$8=^0O!`7(~4YYLK($CVD-d?(B&C=3wd6!>4PUeBoUuSZd zitkiTL)J2XNfrpBl|-@jWB!!5*C9QtoAeb zEESeC`CZ8+={B8Z+)LPQ_fJ&VrZY)qdKOTAJGmsz{vpeSg*Qcn+h#S{p*eo+IX^5z1&nr_>uVG@+UvLYG$B2J}An)M->u2n9oZo zFVx+{?BfR!v^KXz7%hKg+0ag znf(;|24oBIm%aGZ0+)WW!n8*y-#qMT;;(AkTn;+L{${21S)YTwq!yc5 zEjE{3o7fNXdTjZ%Zau!ZwL%-eG*&yA6{pP`I7mCWkWX|B$38X!@(k2!emeFq+aW5r(FEX(^~%_D+l!-y0WnUr7IWp zAGWe2=4#S6JY5B@C)r;zP>(-S@8ntW>;5YXzt_Z*>mFOKdw!i813vIp>B-(7JCeOQ zdSXMl?)mD;`#q<%{(~+nzh66@Gf4YY=^4S1c%+ni!e`TmQ+AUh!F!mpIeLP3xg()r zgzg!i)XS4!rY9AT=%wWs$7m;|ocJ;djU3AM`TRD$jYj1kIy%tkp~MR`YJp~LHbG~o zZFPPJd)YzyL^Exx(;3&(u&qut80YrfzvZiYeur*jb&t2QJce z-UA(f)yQ{Xz^Q|St+HF_H@O#kpTl~euw%E|{#r;=@7b1`itlx(=gV#NoJ2i~@blE$ z>UhQ(gFQZ#x~GwL5q0W`f4i%TceDSg&!jF~2eHkz)yE9Czn_x+uo;g*HOo7sq0*9*Vo`{9@E~A3Tekhr1d0EI_>BMkBRBJ z9ouOKS6DmDa7a4{ch!zh22-BP-eVTp7AxN3Q9pO|I?^-s=Qmq)ohTRpiy9lz%~`?oFD(2%((pNAqGneIhfbh)~6D(-@ zt%*IaKVfK)n+g;=oC}DakoyUEvA^ZE044mE3hx1uCAS7B_;y%u{#u|sN9<1$KU2j| z0t#NSGYKB?XA``lhe$X{g~gvu@U{S@{JR#MzY8dM#oj9MVs8bS_Ey0w_Ey2O0w{QI zRAJF~B>yA}&KLhTNf&)e@QL0h>0)mceD4AUpV(am?g2`E@h1}d=vJVFH$i6!-wiaM z3zYa&SqcKoa>`<9dwf|oN<6+| z_r^BHx}1&fH{EZ!UCwvoTH@Zv6YQmg`h-0R_<+UN#6Knqtlk$>Z;`P59{qz#x8J+Dq)EE{o^Wf1?Q#7X zVTrSUUIL-vOC!P)$rze0{>7o;e~k!xBEm03l>dE1II1FmTpm?HX&2kWoqd*;asn_K zu#__o=x#GBiUJc-D!PUfl7R!I@3b7KI<1&;pt0RW_)3Tz0p1=A_mSyXfv$aCBGi)w z%H2h&U7-!_#t|kGk?rOZ1(TiBh09BLGumjQ3}s_D@4`GS!t(u)v32hH+7+GncZ56| z3&-C|U)vg?ui4@A)z`{lw(_&}wR~RK^U>FAJXz``%TW57>~mE56Wpc!*PXuRMnChA zT?z@$n5OhK`L6XhqOYM7h%BlhhnR3|gudoNvN+rWF?dK+cC)!WEp>TRi(Ps#Iz&Nfg$^f%I_?(&QSQQFZ#e+y}g)n}dPZ*Be6x&C%$x4YB!QXliD&UC;{?w<3(gmPL(;Q=f=~QqBwh4wsi*9Z5PU5Pz6F&0yXX|r z*J^+g-b6qA576H}i8&Q>8efwq;!s_MO;w(cj*6x47R&e>)oYaoi`wHzvH9 z@D}>pu6X%uGTi;v8~shPoVWg_DscAtn-qwyzlEf9 zwvi#S>9&#*k7 z&qt4YlX9ae4;!}TPV`s!vo@NB${tX7c-uK@l1DCij>N0En!9uO+4`FeBlI{h$+BNM zGGhmsmFI=5xwWr1v{ckjI9T!WgpydT_FU!B&u;iPWYKmX@~&mki%*@^I>L5Ke%o5{ z#|ckX{AmI<0P5AbJa5UF);Tes-LSiB8M7!{#{5_0zi_(rKXq1X1eAl$Tk2yywJmX4 z?O)J64uuvkL8Gm;6|1(8@2lAseRAm^4{f4LzuciDtq-R_4o%&xts?whT@;)jw zDiR=mo$_ns;{EE7{)zRj<%*qGz&P|7y~rTi`{eZ@`-&aVPW`K2m;v5LP2DCIpWeu9c0 z0u+2%K*84^__wvEFJ8LB<(%M|9_LP zK)XZj=l@5-q}lxQ*Mvo$vir?X8424zDJx;S-v_JoER~@|h21LrCl$8!n2jn-pP9e! zDSSdF`N^XMzdbIbeW4>CDyybQ0o;>%&Q zlN>g`?kqi3q3JB$=I@=QXLVCPyPNdU-K6JtlkVvzeVR%~7LZ>D`pmQ9Z2_f2`iyGdW#P5O$nNSFOgf=Bi-$@65NRSB>+Q0m_YDD9-LbA=wf>mAPb70SVy zVq5qpuwKEf3YIHaqM%2?ECn?MTfon4Y`ub86)ab&SqcKoa`ySb|C{=R$~#9$ zH~j@9BftNUbgtD_P}?tI7>&Gi@}mx>JYv)LcSMMMQX+}`>~m!7{(XcvNry(}uY~^+ zw#V;(sjxj>-d5p!p!|>_&HNq-@H4oie3k_GZQx$Qc7GmFVS9XRq5hI?kLSshk+9n= zt!cwmI8}w^92QBp$9KI-SIOEBRXAHk_`y&1wqI66cyUDdIx>gm-y9J>8WEPWm_zfo z&Dl((q4%91zCy+Y91rsSC3kiZpd^WXKr5Clikd{SI1?yoseJ-p5IuI`l3Uq~veG&f zBdl&KSMl-Jj%d~_S+cleESm|e$OxBjsVvo$FR+9m7c5Z|I-dfd$GMk@_Uu)BnLMmA zT|BcxUh`wPD>#?hqmw;P42u~UbHOhRqoE;-rk4#xOzG5uq}7;+MB<# zl{=>?K~?Zc*%sunO9ewmxwFgfC)jvwrqP1%Ca6=XnBsYM)dYLD=V)9*IS=`yknr$F z35y)K6=Y&l2$d5hk6iK`+&s=tPE>6Eji-Z*2fKgjb!#j}8(XF9p|HkQM|$6<`ciyntTT~&cRFql zpW);Xt8V~p21I!+!+x*@^Y;?}d8O^3iq*7wb*JRC4(T7ObSV#i>5%?3yg_~{p~LfK zT~mIYJzvhHlwY<5I+Pc=PJX+%qCI~Sk?^2gSr-+1qpUZJy;0Vc;aMw`jywMy!tjRN zr&WB71?N8sl>AG9CLWcZuhK^YO*)X* zu~zyD=~sC!z)Wue@`%`a1-B|#u3(9R9tE=$1eoPq&e3D@Ib%V8RNsQBb)!R0wsbj9 zIZivuF}c3$Y;nF1e8KgStDdtMYGR&_*~xmjoOAgxQ=N7j4-eH#N+k?!?fL8?!XlU3 z18o&$By9iWR>JnU6#YTM^(wxGJE4<=?Qx3j+zi{}YOV^~<8U`&vbX)-j|e{# z5&k?PJdDDjDxcnz!{eeRnnk*{02X(B_p5+Wz;56PZZp@_{-pto2Msx&0ur zU1S?iEok@A)YwC7`ih4Pn=C#0`m4mt?|rUla+6C2BtK{)KNYe9b_`=R_Z{%WGGD)C z!TF6q#&537Pcolx1Idb$^{M_`9^-q}p zUvktt_Avjy<$Q;^pF9oS={K{_k#{&bJ$)I8qk)Bn(LQ&C z_w`S9wObYQ@yft zMDt}UXtQ%n<@r;vE3xkb*+}N!_rYU*`^mO*SZ9Dn+U7n|HV(@Df1({4W5<*~I)i7w zHct0O|7{|g{*u=)>?iT^8_N|fUF-|;>#Y1F3dv6)4&xybKdqbenJRrRdErML>X)uC zc9w4Yak=GTQRJ=nRr$`+#U3QT&eA{WCjE3b=`O{0JHwyUO?rAa>HWJ&7yA}3y}1wm zVqiUCS*P9zWL zef%Y$(aiB&+sdnZuxi~URR%6O6b$+!}H9-urY%SvA%@~-3) zhA4Q&F2>(X`N!YHkLgRn`wmd>zGcDrF92mde+RHXa3fIgF1FHFlvr^7jX=pa6DaK( z3&fym`Y8%NaGTF>v7Q%Oui#b%%M~n9(4%0Mf|`OY;Ndp5Ucs#jmMd7Iphv+h1p#I` zm$PK?QkOHMe-=Wv{*L~x9_O&U9iMP)b?iX&{@C$};}nOWZgD;?hBxO+&iXb3T%6-w zR|{si@|cY=;t{TCg|2YzdKa;UGT{7k6cne`~5DZeFWQnzl;dW`e0~!c0{-?BLBvS@H-LV zA5o7`d}|`o$3>*KiS%8|)m^(0bS{r~BP~YixSa&Ai5~f<{lC1Hqb@>ujT5{g{IlXBW==MCPZ%=cNU1Yc08cpBs%)58ZqS6(m-@pB?6?cYcxC<7RuIci#FHuXK1$Eo9 zbp7TlM+|#Zg=~on)9>wc>)cu8S_#+d%zi$0Omw|JFE4-06{ZC}!hXJFvFWwB;QLE% z%^TgO=10}@Ul3lLWwBi5Y%N>yW-BKA|E3)lw-k9uesJ>gE zZ)%T&kEx;U$Fvjn6T3lIc8E6>zLtBfVl@%<3qwYrJ3GYAf41l*_6PZG`l(gF^8cl4 zIaX9yFD_WHWa)xizh8R$5-TyXy>C8Y;j_QfR>Ot}Y42Vf6UFe3Ibx|*()!<>UE{`A zbZsv+kV=TGm-VWyO$9#sx~|ngJn*&IH7?~|F62b5h%1t|M0NYi*7%0=>z>DQN<61> z{!nyac)jOT@xdtvMvN{vRn}B+U}TafSQVUkz&%7W5=QDqQmPgdT`|(85V<~K+CrSx z+C=LxkKt~L_kT31z?iw@<^vCno92?fiW_bZ>cv;3mkI}tdsWCHod;tuP2z{95cLcb3jit!^LK}Ve`{^k{K>D z!;PC);_F>AsE6;^8+$ib5ohS}Ucq(j4HK7F@Em)?#N$2oM#E;o0}ij0KlVnWSw_mc zXKB@fqr37_R>}z;DL0F6ZyeKF{>#nTl~P{t_BeCqu^hrVdJp1W-(1Xnv3b95a~bz# z+-v%6Kg+r}WA@9*?%AAiIlGYXLc#^yw^Gkdy4%a(sCCC^`=vc@yAO9ev0o%%2T8^b z!+A6nau!XbP3z)Q!$*3I>>&llTi6FytV{PLb6p0#=LFXuSk0A3_%3LdwXT=%6|SOn z`+UXg5`25sHToLYCHbCO7w_|H34y29z3$ttIqQn`q`(TzRrlVy2H*SZ5`C|(Yw|U% zOZL_3u7ISw*1qo3*2WXo0wv^gu6x7hT9@KmLjE%Hd$q*CA}ywF$i2`!E3Yz)!4nL> z^WsyD&@{mHEOZ^smByu+v~8x|J$&y$-xJXHIQUa&qqNCMo11lKV2+kjw+)&**SdX$ znlrGSKG4?g^_6HEc@oUHB5y-BxH%xj^B%G+(M?U>)2ia0%sV>6f|d z_R|k(b$Q@S)6xRv(4G^P|+QlDbtvw<_wh zh&~5j-9pMOCT=NXf$Q6+Kxj+jW%tkeH(di zALU(5bvVs*=h_DPCy}rg@Q|;N{wkvXhN!W&Op6Z`3ZJ;Q5x$WGUD5(`$+wVvi^#VJ zdcO+2;TdMWA!{3bgVrYbN;TaKSCFrYe7Dn=W%TuI@(Z7HL5~lhXDf7bJKTX3cup#D zEh;WsXz$SKQi#hVPUxFWo2F~LSJDrJm(wQ!y@$ygg=b9H;~DE|bvAE5=&YY8w4Mh4 zaHsY1#e>V;Jk6T`JUqf>w9-GVBk(`1xZmsxS!Zq#DUe3Qvn zNIu>tGdu}=#pLV3JNFcAN^Wl2oWi^3h9Fw~IO~h{{?rC_9@YN<=ud~Ltt8OOzN@!XsWl1k%F1(%l#O4`ZnO8>u zb7HhRN2rM&X`^$wCCE3d{Pbn@NhlJ zwG5eL8+>9n*J8!*UxmK=p>OQkCSTmzWZw~Fi9OI&WQc0s+xMaC2hjDkb@jgbbsBS5 zjF~=}{DtI~xp4#eCPA;s(CGoy4^Pt%(70|WGR7d}4bCYK+(*70LRa#|GT+8A|30O~ znfY!d-y-rM@0j6N$tN<&vy78k#>qACmMQR-+n~vC`eHvfyw}Lp4Kgy6v7#DTRiMx+DFZrI^F2tujpU<0&F}-{t1@Z1-}fPHN@;v! zv%Fggr`H@9g}k#ITDA~h0FC55EpB>av&@;ZHNCEYczJK%AZ;HseHfZPi)^)z_(Q}U zM|P6;BgNsYlR5a99$WXYNn1x(i3lZ>OI zCL7b>Ew96`m(vf&80YPM5f6`u_budF1pnK?W%HR|z-P9ae5L_Dlh`Sr`6Ybj$uK_C z0IyAi?^t~1e)z%zQTdF;<83~(gnYM}e5MgT!<^8@XPV$M$cu{4{DOSKvsW{>*nH*= z_)MbWGi6$$$!AL88x_nKW$>8>_^X=c`+7x#~H__YC*AaZNtuJe*v-X;Fa$k!D0=R#$~F-|_OomVP1q2US%a`_2ph?F zKl%QQ9rHuv;&vMXc1!ZvHiW0hS4+N!(aWpQ%SS1@=M-eZgywyl#YR8ekr22BTlL(K zII+!)AnqFCO#1<{D7taLi`*(QYZ?0QL)ar5v9*iMy$bsQ-&G5Eu^)KU+LLWZ78|9k zab#z@O&evlvQfroYlhe>-EM5g<@ZgHHN;Qm{LaY9_83CrDbROBeu1$BIEwz*1Dxyf z81DhE8OypMahKftI`oeBZC>{p@_Q^gfYbM&BQ7v@?QY)&hodh2UYGAfWc2iV_pr{9 zUbkyqj2Ygw?p5DB!iTUsJmBbAH;nbAF|03*#U8W9kscU}ZH97`cbeh!dv}}RUF%$C zc-Oi;W_SyD?g!@*M@(Q0whJlqR^uz1pW>Nf_jnC?|IgSbhGDZd>xKOq{NRn3ywvM; zGrVh^(+uxgx7!SF0slsx^*;Kt8H*YlYCYpF}b;Q<VE?JNFwz|PpNwb9Zc4m{EUCbN}ZSIi2m{r&%sVzx36)xX*+G8{fvvcMAj5aSW_s~ zZu?mQ>j^oGpW?lq$Fg(WJ_A_E6}g^Zt}in-vifJumhth)9P68IU)=U0XB_CfyAIyr z_HVm|?u*MRe`UfTx2HC1W6SJ8<+~>oY&UzRV+Py1wf4!Hr@dAj`s}i%Vs92> z*h4+zsrMkYhMJRIXyi1_FxPfR@(#$F$BnGd{?=9gSWs{IuWYBgfprw`ra>9LK}q!! zKW|w7>q9-C#7kPrk zz#lt2!Ij@GGFDHSWYjm!@;*uZ4>ftb8nkyd7kUeULz;`ci-7mjZySNP4w!5Va_SA( z=e$q4GJGwX)`0h^cgc6A7~3>QLm~Iu9nOYDz#lB1YOukoVI%jP9bhc%p&N@Y(t_Bv zy~nj4UU9;(&!8yYv%Mv zAJ)TD8p?pId6_gX*3-;BSaf-uvGg*xv1~Zs$h^neoAB8Ug^aBNQtF-d_CurplOpx`;hq>Nn9(rV5 zZ(k}`9@js>BeT}^@fC2{YnMgv`8n`;7kniK9wYOItdl_lwNBQ^yp`0p_RzjgX05N= z>tqgWpH6I@8qNu1cDN3upHHt1^Nyu^*5Ju_KnxHBY9 z*3t~t$z%;PhjlVHqQ<}S97QJ@nvu?b4@1jh1P!!6A6T zY^_JbOWdF4zFE)k_Q=gVEgOP70W5im2vetfg zK>K5?F`ZxxB#V6=TbQgr*>NI&)X5G)m(=dH!@~ghfOs;8a2&0 zIwN#VwKwZFy;;-l&6;)}yUcxU z^CjMTm z{oSbpDThqO(HQ}x4 z-uJKc1gkaSuj$@zX>RW=$QS9z7eCYzy^m{2-kPS_fy4L-%37ZIUgW?Fk0`&vGWbUs zdfZXMRm?#m_sRSxeud(XSBt&fpVP-z#l83f*1)Ta&HHeFzW_X2%8H+$`1wg$@%59k z#ioDYKFeRQi}F%da732Bp1I{y`~-{jUibv=!=ESFD}I7*U3>(kJ?;L2wvS*x(gmM< zUrqci6Y^#~=l*Kef$SwWKbP~>%?FnKYttJyo3txNKC@_-9znZIlXfQEpkI}qVbV_MCN$`ZcH-YC{)wH@PG}xVzw@Gf z2+iDjzYu!Kz4U!DGNjypJzB}xaBZ>%G_?6vHZq@E?>&PvEE-Dqrh&-ohf)e^j|}z% zhoF0S`tMj-%2mR*7{p(bxvX-T9$%TQyZuX%uj40V%udj5{aFrri;TMwghvvtQhr%w zO*6ehC%4jFl9>~Qrq3Xs97jH3eY;^c<7Zb+58p?~9@p#f4L9mZ4L6~y+^oABwhnge zx9eI{al&27nsb@vwK{qa z=g^6{U)^&9^7(g=)2~NfpNAZ;zUSgp9Y`>K-?dA~H`tW#L>_kqT=*ABz9H!N z5*~zJU%)!!E95&DpVvu@8PUC>`Mip4XhfGjui~2-;q#i-yMxc`@hfAE!-M0DkC3t2 zeO`Ov^V$oa*IxL%3T-W4kVDWn)aO;&0+9f48Eh)+Uu2Yyn6@Xy&sABdl(2fr8x zGOxrHf-BPJm2!as>~ogSs~#9cxz2rF^*Y`3dDR02%7-et&nrH$^f|lZ>Ppdj7NI9~ z?(^z62cK6bGOQlO=e2hiKCj`l6#p&p*RpAO1l>t|UTs>I;a?-Pd>OsLrsXS))6Qra z>hs!>mRhzc8^XdtqWk;V^?4-~#i+;S1{2hH>9cSV5s-L6JYws?6UY*8A!)%{d z{4C$YuYt?vmn{|WFD{9*bOe0VM&Wl^6e?%-LPzL@j?fDop*K20?+ZFYS2{wN-)rwM z9pUhZSW8E{N_4abCz$k?KA{gT&>tUT^x&KbzvJ`sd+ptg->WsAWsic+cotrJ!SA&@ zzt`TK`@KHrG>&X{!8>GJ!&5@W^pP>}lq>Ojg{NHbd;Oo|_c{*y=d=GW`n`@b{a$7Ha9A9Z1D|4;IJy)d@V)~6f4*Ae)=_ETfK$dV65 z7CG@^5q=Akj4;1f#`(GWiHyUB-Wi|yCj4HK& zn1zj7{81MvTdoTq)Kp}&q4&OwKdSB%- zH#)-U2soVmseeD#JNnkK_7#|c50IqG+N`XjBsm5^#}w!&`{jj>mTyO-58pEAnjK^L zp}HHVslD>CMvd&1S2jbj*NWfWA?1fxQ_*qFX%K5pU)|I0Lwst%4Azs1jnk{9hxrqq z>BfIdzMB-yhpf$~FvOQE2S2jm_>jr@JGSrGu{bT5EIwxVmrcjFtP;PnWaUdXl6A$T z2w$=ljGbq={=|Fv?7F`AlJ&)xtnYdGl5Jp|RIn~!?_+QG6N>aDv;2g1sBv{W{zjp` zWSk{pp1C0BiwutHClu*R)`)*ll5ZsA^=y5~SU<)`$nqrk3MAj z35ENT^=3Rq`jYi0?$nwD<4eZQm)Eh5xJV1Ot?#}kc7Mw+=zM+2E;D@_EZ?yp>#{#K z{l+rAt4zPKOyxJ0DgKoBHDr2^UzuQ>C`vR=QjcRVP7U!Ho7V0#HpliE!v%B^gi?P-tWUplfelJCg3$8I?Fzh@z%ZDu7S4_&ke|-!JUS_V1MU6XJX5Yuh_6$I z>FZ>jB`5dxS#omU?)Q|9k5~>qV$<;vE5=7`d5Di#&9HpaN6bHFys_2k*smjF2#@n5 zYU5gmiQjT>Zw@|rA$?~0fr&4Y_~;hv{k*-XDPX zA52{G{n9?x^UQD`!s_{l>}U1nSw%dnkY^R(XST^-am(pLG1||Cf^xqNsk9`qd*_U*L{#%G&p}j9@G-s;CrS3CWNj?0&CbXFZlj~toliXU9>isjL80TB3djCW=D6t__ReTm3UkN|o#=X72={|G6lNBfFaz?0}4=U$@+B{eGVbs7YWUt3Zicb_HBZ$r? z{<^X+FkRNln$JdxJ`xdxo z4~AJ6InNV)mV4oWJ-lW3=Z;jo*MRrd!h2`Id&}_8EyF)|2EMr~cn@#q9XuQ;G>))t zdW<=XaS=GK=+q2tXpFDZl75j9a%JKf_&`dEGL2mRons zC0xApUTu8mXTMJ8{hG!%fUR$}$e8)2_XgVUpuBsb`&%R5L78J;;JrH~@140XFp+(M zeawA%&vfpYe`xMKyK0eM2HFe`s zUG^|$dJA>gvzY0v&}GkJrnh8Dj8V$|!s*UBwLftAG$7T<0?mKg6W8YaroB9?HZRp#8T*;Y|7qN%& zqGGPg%=bnc-x#BfZ;IE(?@QLkyVJDlfx7Et!B=PIx%sxHJY#r_Ry_h)Q0$T%wVuvC zf$AzQ$%pU$yxe6=^KU`L0x3#7|{l`SQ%XX6hn)kWT<>rCtu#$)eL|=Dm&lX|*Z; zRuoem+>I>;cn~Oa`CG`+pBJ$&m+K($@|}5!Z$VD{1J~Q8>@DAA>utrGVUf*MK9R-E za+Yjv;s)0t>SyBNIiYy3+|IL*>klH!+c@)08&C#u?RdvyLy~eW&dfHs|3@3ZCG~gq zwdx}6kZ0N9h-XDUr^Olmj1Kr67G1tE{2S=AZ?ygi-z@$ON#87fN6I&fzhpz2iC@O0 z@S30ZM9<<3fO4l+n*$I0$l2dNowEWyV!fxlLa+Uj@UoPcQ{|lPEWGuj-TkV|u`efn zbA1gQ<9@z3V$L6Mi`gQRs z<<}-KuP`4}J8}$P=zA<<`84l=jNP_(ll@$w?^%npU)VdgG*;Wc-l3iR3v)nne{G)N z`+_zfyd&z-C6X-RL-iE{a;n;+=FUmQ{9T5 z(}JGE8O~nOU1p&B&-2r+`@xlcjN?~l7#~l~G>*M^3w4`soc!DqJlnHbenH*-Ox-TkW4$j^hbh$U zNms0okFaq@OF!>IQ`hO|Ei!eTe%_6`oXyhD`z3of4?6oAhf?|(26m9W$UH|F6A6w4 zFXxJS-yhcBXdQJiXKW8J(vW?k+3sZh8@aOjt9vG+gHJ-{EJF4yL=b!V zyOK+78{23&f@wrd;RvYcTv7EGF|6DG#y@d>#ui6!GvD=`<5Y;RF6UCIa?QS#*sBzNR-fAEPeMI@0Q% z(R*+{Y2MRq3Pa_dG8lpG{v~<+5+QO4yU)=;qv|f zo4}IqO*P(e#CSh!oZ@|rcj5WwsooEPTbi%;K9BzWcJn;%7T_Q8<#-!-Gvn_?#^jfb zNqHYQGpJ!uTwmXx;Q@tO9PdS}cahc$y*t*+IaZ9tSnpF>Z{CwwZ(Q>n?^y8nYMkfo z{l@j)ZPfMp#scpT8?VQHaNE!RvGFGF;DH&le@l1=;g>nHV-V*;y)qzUb|G-k#zQM# zNpcIEUCP-IrNj*(e>rf`MU!evHy&JB1&vrYU=M$@Y3tjBeQG*a2X39jxiSh6X(kje~h+2OgrD8y(?G?@-ps@`@d-rUZcyRrOiCabItKj}Y zxi75UhTc~rdY_lLM>b46w0XnC))6tmQ-cSCV=(?Q!_0o}jehk-MOt+W*FoZKUCe_# z(1$C7dH*1K+97l%d8WqTel7i8o_KKJ%M)$7vYp%*zv7%L=2= zX8`mW5J8_L=!4Ef{CU;YEN9aP-49&t^yvqE`bD8n0(sEmNIS1*n&oWzfCpSAed4~? zHfCDl7PO6-7AJIxgD#2CrC&6pI05tayDI{FSu;Fe7?Ombm<*N zmrvnMr|9bz>2K0(TfD}&`D~k3{W)@sWjnBNYHefJvPoz%R?TwO*v*ph2QG^)H-*sU z=61ToeYf_|d-W4r8wxs%mCpyKLzDD2nwa%t{I|$EK>T^t&n&0vC+{S=-_K{3$JS5OiE|KFaGy4FdD+tZVm&@_#vmn-qh!PV6G}EbFd_e-Uj5Pbq187(sa0ReHNHS6ZPSGB z0^f}*zjpm>eDyBYdbT*xG2mY&y(xc4!uI+K<%K8Nd`9$Fi|<(ahNZKL4anj{E#y5& z857svVOeRb_&c?=2YwmeUeUkW+iT?){mUBLHT3%vnzqZmY;FD%aoYYGbO(oCKfWs( zf9+>h+|=Pb7*jW?xVl^2yxx!TUp=9xR$jyzsM>&E zuN=fBZ6qIU>`}=&o7u+Ev@4HxjiOyn@<_Xe0IhcQs1zLNr)IlG?H?Mb0MLuo@EZOEk!zwG~Ovkh~}pGF&4dxuV`mFNLx8)mt*Yip@H_WrrG z)cvw@NA)G?lWRwpc!DSKw@r1VRbEd0uN&~|+1K?iSve25tAFXrp4Vu#+s$@3E0Y}w z{$%q_S)N9_^a$~%v|>9i>;b8_wLvpm59^633`*z0c6^>hf_6YDY&pe}=`@!kg8<+0%1aGDt_*7K} z@p;>)XI9RJHp>YoIx@|1*n@MLM>zUcuFx_$t0~ccl(c8LXDs*=N9*_*_pN+`@G%)r z&|xR%Pd&@|Q+ZY2G4AL70q%!2-e8Q|;|ZQ1FTTo^qZ}#zRB+xwxzUbPe@eXrdrIHR z_XyvirTMpWzl40~<^HrPr?HXnQ-pVDE|Ye*f_I7Foq3aSJ@?qY@jjP`A1fL@B zhv4CRQvTPtzm0O7!R(L8h%tW8Gyg)E^Nsz_ao@|CS^1o!r$26Htg(ggU4(Zz(*0L} zQ|LZU@V0!{=tsCe_s^5(bIO0}=;goi)Gfx_gfl498(x`x)D!IWPMq<$)5`O?Bg20; z_y0-y-OdbupBA@~L;G3NuZ-s%l76azw@lOA#cukFIdHC>!zyPY}jG(LX^e2uk1Jp9oI+i@w>!{R+~tkC=T^L-@ypIX~3wn`~D> z@G5sfa6(c6ePNYL(Np}{sRhAQ+I0_kS36VvSEUyOrB9@< zFD7lH<~I8zI|JPP!JSn=A6R)~^cX+ii4DF+Sa9F!jP+lYT@aMMkUkRJ4?5%gYj}>d zp{H*3#U{dclD5U+HphSVNN|q^_t=8q^D1vIy_Y{bzaaQFVZr?a`rxVw1!f;e9|`V% zW^R)H5!@NN)d$(Mdl_k*QD*kRG}pju!|3nfIg<*4-=0}hJ(GMjN{jm5v`e7-!SActUL_gda z(GPc@M?b6ucR9E>M)bp`h<>O#kA84#;r;L^&)GshJl;t^JQ2|kThF5(cF>+0p7V4> zKkSU?hh68<4=;fGC2-eA^uwNre%N~+{m=;RH^Kc@L_fR}(GTyQeLsxneaM6-P17z8 z>xZ0{g5V|Z^Q>0-;%GszpUIp0nY=06a@-W7RYQvBa`6$Vor{g7Gu za;5l_UvLQyQ&Ye8xeePwxxr_TD*@eNLP7B9ot*3to z_djXA#rs0@P2L|g-{>7QvM@LZ9N2>WS2ljfdosoI+aUwuuKOvzPhVnt{2q4JFPwcF zepEKV_oD9J_gAN@;U4FJ%Kdu$zC6c(N^Fh$avbsdqz_v4gne#(KxMomXf_a$aKjNy(Rb?Z_k!{kQ`{MCEtHG|@id}b_(~W&| zK;=U&ZGWXR9v+wA|A_U31ZWnM5o^R{#Tmn?XDc>ep4Ct+xE=B4`HqRiGe;r&4yb&Z zIAmad!brEV51Xo;w@l?dLLC08_`eOP1-k4* z=(1Jlvb1N$417*Pb=khm?=@>~$`^gMW=&?kt#{+uHi40PE{bXc*)i4ME4>~wz1V9{BX-vN0KnPKZ20@&r0PG{+8md~Kr z%4{9h*6FNtE5GZ};8wgekrR*ViT ztX+tGZ|S!@!K@fZ?IoG`bu?(fFUi+J9S#C59ot1+siV>vf3#hEE<~?xuhTu)NZNI4 zyWXASh%En2mHoQ3l(Enco5@S`-G;I+ztMhMs`_m`{r08mx0=p$W!Ag9T7Qs!>+HP} z9l01Cd0PLt>%_Oq`fh=xBa41KU2oTeM^4;av<_yX8CBNV?MiqFBldPwl zK!4FU6&MdPzr3e;9y^MhnS%Z`I;{X5BhJ(>B;3qB-yZQlqLHS>iGJuG9aCtCuEsi3 zWsJk&PfaZ{cr`08$rR}H7jmEKh$Ef6vnCn432!7X=b@s9q*b~yCZS`*Rl4BMs|mlx zeX1kYf9c%G#%tu&(#t3IoHyB61C9gm><7rRi+jHR=pTFi6m$pjWRy?reajSdf;i;X z-u~x_`;hxzqHB!1X{sUNx%$9s^9E=a<;7}q^X?sZ-RI7U2G0s4w$UpUOKKQ(p4E2@V(O0q#@D8^e7A_oKOw<^G7=U;G`) z^{MPlc!oaU+A?%Gd?)9YG2TbdzX7~A8b4Ax_LuA@JA!N>bKld{HyOD=Y!M<09p_$b zGRL`3K_-wf{w(zw&ArGuqc6V67&rfB#s+mEFXPsq$MX-0JcF!L!2LGjIlI-rko)(z zUr2v1BLA@Kzl*-ruktVS_ptf5P^bAu1@R)YR5+aUtLV)AD*4`!-_)OpxAo^Zo-eYG z=+0xge~|IKi+6pVo?g6#e%(U9ZlPbt-Sj=<%F+eKc)cL_Q~Fl=;}d6k!}E^x$^f(x z8^a&Ce_HkPr`*@7e(vQ=uY5-J^EmFGrM=@~3W8U}7X+_NE(qqQ6$Hok5}CYjrQqW` zGUhlCe0(>PDUK`f_T=3_Ce;oM7gMZ@Rhzg@Fn9x#++A;hqt(| zQsdzh?(wNG-}`jN*~4l)$e4SC@i2B>L2%rC;JzQ+4}tr~79F-wj&G=#<3Vs&tMMSX z?^EMJaN~Djjt9ZbH?#a>tHJk=;QI;q{snwbT67S6_B$c?HmdO;__i<}US$mYnfpJ$ z|32V87JR?wzF5CF%;q-qse<682KZ~i|10o6Ul68ipb&AAZU$FIOAcC|cg zEH86!@rTUHSHNfT2l9Nt{Wkc+(58amnEl}T1GxTJ5X|}CLWfLvdf&=lz%MGeUrgTn z<(_hTxZlYAcJBYe{UY8SvE_(uD@Tv>{}O+Z1L7yrIMw?p@HTup#82dkrm5b~3BQ95 z^LxVIL67+hyuat1-oAfDzxWLO;uLyA5M89NBVpfF4h+m%GWw0XVLbZH2q!uU^WF@t z$G)2hi_Y@|m*^A4&V+sVWb8}Wsj+t#y+-fBy>1M5rtC{#E-ZGYlAea%<0M_*$GO(~ z5=Od=<&NH7(NV-lvO%-Xp-*)7vUC)}mW~o*BrtAGU8XHhA2ZL2wmh-qi8J$L>+Z^F z=+d%Rt}D6fOhpXq52@%dY3MQ+q0{t4w@F9G>BSzpnCh^#cKq~H_%`8N_&1fVOZT~l z6d0+%qxeEB=emshHTXK^t?TJab{87kf$wv^`w7m0e@cD(I+kxj#qmuje9!{&ZNd}y z1l@;!(MHaVx!?RYq4fTCZ7x6qZoXRBEL=eD*jN-oCCr>wZOyV z8$!OJipY;jlQSJm&OMW-`<$Kw~@xL3@_{L`Dy#W4CbsrNq z7yq4NzLULNPY5({zRPU!)gjLOJ|R9_k8n=8`0|`2PQD*SKk!|sSiT396v)wI+P({A z`TFpEpNo5%XGbUTU8qfzYvel#hqosfM+1q*k=r;|CBMi>@k}!2o?3t4Noc$s8t>uy zfa}lDH}2k^zU$Y%f=`#j*PHJHI{8-MKjSO*IKE;I&aA;kT!(xW*zV|EH}2jh-xZwC z+K2BDy7(sHMyTz>a=q`}&sjiezLV;V9yvSzKkzxb zk@s*V-zi)OogdbE1YY61qTSG7c4PDALgLtq74W9gZG#&k*ATk`7we3zyPM}_uGfgq3}mdY=ezD1#5sIF;anu_$AKS`_otkbG~U^_t}pfa zE_pMY{Q@_WSK?lTo?k$Z5BNqSGD84asqT5=K2vcY5citayY837y-nP!sl9ms`LClfkkWg;CJ}4uM|H&Cw0LuFrZP7EY7|!)nah%?p>!fzP$Ncl-E}D z^z{dBZT5Kk0fUVm&M}BFg73u|UtA6UQ{Mow_j#AlpR!+5c<^uFy`RH-4|D!O{E*)A z-7fE^@L{(@_8<209@WKHw-?_W6MIfC@8Q8t_IrzMhO`lU*JlK0o9xC0!F6bn<*Q}d zB({s5Z}|$?dxg#T!Iq!rLtm~xu#{{0`gGq2t|YGH^*wzLuYcF~$ol@Af$}HL`RVJE zbDdVLd&k#nnq9*vjfgf;WrjZ^a=l2Cf_aNj4$jzfn}6E z%(pQ|tk2~6T42=rKl-38=UVB3V)~+)%k-}X&nWWBw{(_K&PzSyoU7$IJTJ=TZ4)4F`$XNL8?p{t6>*T*a4!A%}{ zuABt`jT&-!W+1n>@7UFo3_pGCmNc$b_Msjd;Rzagd_xuCMl~K}EXWw(>@LA?p3xv< z%KRP@JX+3dkTV>lObxVXKf}R1_reUzGvw@tKyFW;JWI}+K=$Dr1@&zp_F_u;Xx~+` zzYQeiqlFeL(0zLJvheRZ28uY)Iq+TWPTS3no3=)b1~%J`nFQ& zca^;C%dX-~jG64q4vfj?8=-d|kh3uo*q@!l{_Gs~XUDTY+dnM)T#TA1-?FjJ6}uie zU>@?ob;t$RA|K4f#(53w*{7&lnP2#Vx=>E;sA78w0wEm2_ zZxX-w%$)=N&)Gv2*~gzb`>{W%A9m>Wu_XO`ig6U$*BInmVTkJkLq-OpU{0&v@i7CYccm~ zwZC`b4;e`(*}rJNZ>@hVIB=NvIDtJ*j-d&@P`)AisIHWKRA2WQ&OYWos`lsj!=7XA z8Ktdl{Dya3_N2D)Bd4r6g!i@dzuktly0%Mv*jk4bus^AgGe|g3RLHt$C~r3 z^P%i{PUbRejx_O@b9dx?Y0kA@^Bg04E|hNw%6UdI7lwX6Fmx`oza@CKXGFDn8M}|@ zLHU;8N1GYD|IC)l?sQ1VLIPxANdhE5KxhcqS%54o2}#&x7XpC*f{3yO!kP|iP}D*N0+LRH zfas{v8G#@I4M7Zxjt-Lqm23o1lte)OzjM1A1Frx1zW1H^o^Sej?o+qcu6ycK)j6k5 zoulbyGHN@UjIJF`MqP->sBdF3x&@ew27i;$-N$4UJWWQK!DJL&O-8xeWK_tJW@1^e ztD{#&iF#F(poi>Jql!}KA=gHza)d69Q0oYFj?hgCPcjz`?3&=w;&H{J!=uOJhR1-% z9gl!VhDXFB$D`0dXK*NJ#W2zZa$zQ8Zp-YUI8RRkEvX5`+z$xM&>R-WU8J?78u$6z zD2_zYIi^WC+-IeKK$i18r^);BChyCdyst9JvsU}RuCKv&*5W&HS0-z^pb4wRx7Oi1 zp_>~9+p37WAg*l^*CFoqAP$+##;Jno-U|Qr;VaOd8SUkvy>(~@v~n15BrFf@twTGI zr#RwT#C1*LZqoZMlOk)E|1Q)QkuT^$3x-iUpv4fX9HEON)H*_)BXpC($tE#t655!F zHnPwLWa?pAXan>yLX{(QafDh&sB?sF`dE`JE5^S-KLquYQJ?DRWo}_Z&_*)qgZBu7 zUoym95Z5+|>kxN)5Dzj5S%LmL^$}>NGunYHBdjyp06&0GI|XOLfy2mEFj>B~Cx7J>I+(;YgD^q$+~ zy{O6isV46a^te!-1*%hNuH286L(ZlJ9qkHwS_isX5Bk~-ayEmd9D8aAGd&xMxgXZY zkecW|8#)S_BC?AbkMu!EFLI>2B7GC&WFc59Tw3}>hG5-rX=#WI!y1y%QXLuoWWO>q zWGkUqSHh4Vj5TJuzaetO$e6NFtU19*3&r{qj5<+Ri*j14BSWz+MNvA|s3@ej#d`ID zKhm*wwMBaSD3kGPv=NFiYft6kC`3HoWQ=rQytzI8H*_H0TP`>hnoQwrgwYGSk3qlt z+P^0N{SF;(GIpo>lPNSrK9*rJUTB57Gfc+Eklt>#$#{|eXP(K}j@nv4A=<)NwriPj zC;{Kx3FQ*-&7Dv`4&S^E{Y=2Own2I_zE$4J5Sf7Q>`dkGot;r`IKJ}+{wD$7*@M#Y zojs5~3g0;#-=Bc*?1l6+eCNwlAK%%H%Hcb^pAB*oiiT_E&caEiWeCJrCN8>xg{1+UG#&?GK z&pxyUJo^reUr2zSS0%B=b(7-C;_>Yoe0zv2ZquT4-0Mrv+#CX#NEPH(Yas8OG-E(n zmjIEk84*+F-p_~2vj&uz5ljIOgu!41(mzR@J|+Y{r;Zp8TT z0mGxqYE38$UnFyXibXkwavmt>j&cfqBSyz>p#00z`=B)z_$)4i}LZ|?Myn}xuZJ|QZwFRvKQn~E?iK+H=*2* zS11{;kbCfGl{;UiRPYLgj_**4{D{hjM=SKa0<^3!2O;csDt$%13hh<2(4(&k^cCMl z{WJNXuX=uCwag-OL4VZlyi%j!m1-T2(TaSz#)sc<(Q|DlKW^^Gcx|``uSI{7BGCVE z9k1;qa%;E`w?zPkC4!|EKG%g#uDk*^yw&)w_2_SXFE3u-(~l4C&G`CW9=zV@&fn;z z;HABFJjp2X+F*=hfETX{^5azjjK>Fi@HIj1yed$^3xjn0M6f8~!g9#KD_y+!PK_Tw zs%1O|{k?+!DA0TH5;s2{t3zEDw`oe+8nfDxxf%Mrwkj=a+=+TeweWrC;>SxMSBlkn zN#(K}(U0A6WY4-B79E`$shq6 z(6F`|%&&P!D@scNHK!wO zH_FB#O@sH)^jvdWgtb~zpLYRDr`q7Q+?*P27|pC2(7+ewt0lxB~Y zLXJg!arr4df8+Ian-Bc-;kO+E4SXv4b}bmXU?}S|Jl7nLGUo29H*1k^?5^dVikA-^ z#I`^6>|`DP{7D0kz`2xf!OEeHNUNFg+R!jz`%~>8_nYFQ;m3WoyeiTJy=T&wi`&E; zW8Th3$6xm`@DmvOO~X^n{qc?MWJ=q)VWZ7+@okYZ4_iuFx;X~@|9%*35#XC1lgVsz zmaiO|g7$YVaObOh-T87CUw#Q?Mx|{vSE3%|_qJp1?!1k`m+vxYc)Nk__#a=C)$kSW zT3&{-E7J0%|2WP(Y>`-(<_`4bWrH+4a-fzU8mPnhQFHU#fVDdPdGp(7Gk=hQ(rizU zaOW4&eR=mZ4d0!n<$29@d?mhV?l3#d^8oX#)gPpFY@JLjA$qtA`Kylj#ZOK*&Ff8&H1xYz;pw)ADke zj*piaI7{1LzK%MOo4kr3=~!xtE?lV<_b6DW=WtUEH~=(U-4J-(=21nx>0}4@_Tg zo}P^NP%TfwxD`=)sE#M5o6Y->uI_H&ZdhYZAYGll!Q6JJA@xiOYqmSZFR~=XBXu3# z3sPD|u1L|R&cyq!6yM01DekGsc-IZ}k4#SSOudTt8_BIBuco-Adf6@87Fx$W8S^~WiwhZ9#-Yyk0)HWY#; z4>1`Jg4PY`2>Ae|cc#z~*`}MxxRTO)g7!svyC{?K7-;AalgX&0a&Z(Q9&a*wfQD6q zh7B2rchImQ$rR2;7`>pw81M-W9)t9$tJ=2e8ZwW{q1^=(qTNLlW*j0~f%ur=YLl_X!4IhLAMvgxqc_@!H&6&31+ef2 zEaCwRAEb{2EPA#!M8*RazElpd@I|>~z`_^wd^})bpme~(fOMkglc_#n(E{mX0gJy= zeZZm>l>;nVp=Niys@qkAf;F0U-Yjf%=VBtw^02ZET zBN4Djqxudwm;eV2;9diIGahhY)F$A-&?eFF3~G5`x2gBgIu8pLM+7Hbfn30M@O>ySPj zuoy(?fJH9SvjB@zR3ETdPvroM^%Msz)}vfK=#}Mw$qc|_Ir=^W@K}z%&j1|Oqrdgu ztT78PI8AK<2B%SX0$|V)xR(VOz!pm6EWn@w=@p>6vH*j#XeSFWIE!|&0E4rD0r98X zK;!=a9-?}YKR?+{&%F_^Zs*4{iANQ*=4#NIJ;BH99v_D?3Y78T)$ROw;H&{

6cs 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

~oqn<7$WVmGBl)=n02dApLk?I8VyD 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 literal 0 HcmV?d00001 diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/module.modulemap b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/module.modulemap new file mode 100644 index 000000000..a9e908ab1 --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/module.modulemap @@ -0,0 +1,9 @@ +module SessionUtil { + module capi { + header "session/config/error.h" + header "session/config/user_profile.h" + header "session/config/base.h" + header "session/xed25519.h" + export * + } +} diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/bt_merge.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/bt_merge.hpp new file mode 100644 index 000000000..e5042a5d1 --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/bt_merge.hpp @@ -0,0 +1,58 @@ +#pragma once +#include + +#include +#ifndef NDEBUG +#include +#endif + +namespace session::bt { + +using oxenc::bt_dict; +using oxenc::bt_list; + +/// Merges two bt dicts together: the returned dict includes all keys in a or b. Keys in *both* +/// dicts get their value from `a`, otherwise the value is that of the dict that contains the key. +bt_dict merge(const bt_dict& a, const bt_dict& b); + +/// Merges two ordered bt_lists together using a predicate to determine order. The input lists must +/// be sorted to begin with. `cmp` must be callable with a pair of `const bt_value&` arguments and +/// must return true if the first argument should be considered less than the second argument. By +/// default this skips elements from b that compare equal to a value of a, but you can include all +/// the duplicates by specifying the `duplicates` parameter as true. +template +bt_list merge_sorted(const bt_list& a, const bt_list& b, Compare cmp, bool duplicates = false) { + bt_list result; + auto it_a = a.begin(); + auto it_b = b.begin(); + + assert(std::is_sorted(it_a, a.end(), cmp)); + assert(std::is_sorted(it_b, b.end(), cmp)); + + if (duplicates) { + while (it_a != a.end() && it_b != b.end()) { + if (!cmp(*it_a, *it_b)) // *b <= *a + result.push_back(*it_b++); + else // *a < *b + result.push_back(*it_a++); + } + } else { + while (it_a != a.end() && it_b != b.end()) { + if (cmp(*it_b, *it_a)) // *b < *a + result.push_back(*it_b++); + else if (cmp(*it_a, *it_b)) // *a < *b + result.push_back(*it_a++); + else // *a == *b + ++it_b; // skip it + } + } + + if (it_a != a.end()) + result.insert(result.end(), it_a, a.end()); + else if (it_b != b.end()) + result.insert(result.end(), it_b, b.end()); + + return result; +} + +} // namespace session::bt diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config.hpp new file mode 100644 index 000000000..90ab8676a --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config.hpp @@ -0,0 +1,369 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace session::config { + +inline constexpr int MAX_MESSAGE_SIZE = 76800; // 76.8kB = Storage server's limit + +// Application data data types: +using scalar = std::variant; + +using set = std::set; +struct dict_value; +using dict = std::map; +using dict_variant = std::variant; +struct dict_value : dict_variant { + using dict_variant::dict_variant; + using dict_variant::operator=; +}; + +// Helpers for gcc-10 and earlier which don't like visiting a std::variant subtype: +constexpr inline dict_variant& unwrap(dict_value& v) { + return static_cast(v); +} +constexpr inline const dict_variant& unwrap(const dict_value& v) { + return static_cast(v); +} + +using seqno_t = std::int64_t; +using hash_t = std::array; +using seqno_hash_t = std::pair; + +using ustring = std::basic_string; +using ustring_view = std::basic_string_view; + +class MutableConfigMessage; + +/// Base type for all errors that can happen during config parsing +struct config_error : std::runtime_error { + using std::runtime_error::runtime_error; +}; +/// Type thrown for bad signatures (bad or missing signature). +struct signature_error : config_error { + using config_error::config_error; +}; +/// Type thrown for a missing signature when a signature is required. +struct missing_signature : signature_error { + using signature_error::signature_error; +}; +/// Type thrown for an unparseable config (e.g. keys with invalid types, or keys before "#" or after +/// "~"). +struct config_parse_error : config_error { + using config_error::config_error; +}; + +/// Class for a parsed, read-only config message; also serves as the base class of a +/// MutableConfigMessage which allows setting values. +class ConfigMessage { + public: + using lagged_diffs_t = std::map; + +#ifndef SESSION_TESTING_EXPOSE_INTERNALS + protected: +#endif + dict data_; + + // diff data for *this* message, parsed during construction. Subclasses may use this for + // managing their own diff in the `diff()` method. + oxenc::bt_dict diff_; + + // diffs of previous messages that are included in this message. + lagged_diffs_t lagged_diffs_; + + // Unknown top-level config keys which we preserve even though we don't understand what they + // mean. + oxenc::bt_dict unknown_; + + /// Seqno and hash of the message; we calculate this when loading. Subclasses put the hash here + /// (so that they can return a reference to it). + seqno_hash_t seqno_hash_{0, {0}}; + + bool verified_signature_ = false; + + bool merged_ = false; + + public: + constexpr static int DEFAULT_DIFF_LAGS = 5; + + /// Verification function: this is passed the data that should have been signed and the 64-byte + /// signature. Should return true to accept the signature, false to reject it and skip the + /// message. It can also throw to abort message construction (that is: returning false skips + /// the message when loading multiple messages, but can still continue with other messages; + /// throwing aborts the entire construction). + using verify_callable = std::function; + + /// Signing function: this is passed the data to be signed and returns the 64-byte signature. + using sign_callable = std::function; + + ConfigMessage(); + ConfigMessage(const ConfigMessage&) = default; + ConfigMessage& operator=(const ConfigMessage&) = default; + ConfigMessage(ConfigMessage&&) = default; + ConfigMessage& operator=(ConfigMessage&&) = default; + + virtual ~ConfigMessage() = default; + + /// Initializes a config message by parsing a serialized message. Throws on any error. See the + /// vector version below for argument descriptions. + explicit ConfigMessage( + std::string_view serialized, + verify_callable verifier = nullptr, + sign_callable signer = nullptr, + int lag = DEFAULT_DIFF_LAGS, + bool signature_optional = false); + + /// Constructs a new ConfigMessage by loading and potentially merging multiple serialized + /// ConfigMessages together, according to the config conflict resolution rules. The result + /// of this call can either be one of the config messages directly (if one is found that + /// includes all the others), or can be a new config message that merges multiple configs + /// together. You can check `.merged()` to see which happened. + /// + /// This constructor always requires at least one valid config from the given inputs; if all are + /// empty, + /// + /// verifier - a signature verification function. If provided and not nullptr this will be + /// called to verify each signature in the provided messages: any that are missing a signature + /// or for which the verifier returns false will be dropped from consideration for merging. If + /// *all* messages fail verification an exception is raised. + /// + /// signer - a signature generation function. This is not used directly by the ConfigMessage, + /// but providing it will allow it to be passed automatically to any MutableConfigMessage + /// derived from this ConfigMessage. + /// + /// lag - the lag setting controlling the config merging rules. Any config message with lagged + /// diffs that exceeding this lag value will have those early lagged diffs dropping during + /// loading. + /// + /// signature_optional - if true then accept a message with no signature even when a verifier is + /// set, thus allowing unsigned messages (though messages with an invalid signature are still + /// not allowed). This option is ignored when verifier is not set. + /// + /// error_callback - if set then any config message parsing error will be passed to this + /// function for handling: the callback typically warns and, if the overall construction should + /// abort, rethrows the error. If this function is omitted then the default skips (without + /// failing) individual parse errors and only aborts construction if *all* messages fail to + /// parse. A simple handler such as `[](const auto& e) { throw e; }` can be used to make any + /// parse error of any message fatal. + explicit ConfigMessage( + const std::vector& configs, + verify_callable verifier = nullptr, + sign_callable signer = nullptr, + int lag = DEFAULT_DIFF_LAGS, + bool signature_optional = false, + std::function error_handler = nullptr); + + /// Returns a read-only reference to the contained data. (To get a mutable config object use + /// MutableConfigMessage). + const dict& data() const { return data_; } + + /// The verify function; if loading a message with a signature and this is set then it will + /// be called to verify the signature of the message. Takes a pointer to the signing data, + /// the data length, and a pointer to the 64-byte signature. + verify_callable verifier; + + /// The signing function; this is not directly used by the non-mutable base class, but will be + /// propagated to mutable config messages that are derived e.g. by calling `.increment()`. This + /// is called when serializing a config message to add a signature. If it is nullptr then no + /// signature is added to the serialized data. + sign_callable signer; + + /// How many lagged config diffs that should be carried forward to resolve conflicts, + /// including this message. If 0 then config messages won't have any diffs and will not be + /// mergeable. + int lag = DEFAULT_DIFF_LAGS; + + /// The diff structure for changes in *this* config message. Subclasses that need to override + /// should populate into `diff_` and return a reference to it (internal code assumes `diff_` is + /// correct immediately after a call to this). + virtual const oxenc::bt_dict& diff(); + + /// Returns the seqno of this message + const seqno_t& seqno() const { return seqno_hash_.first; } + + /// Calculates the hash of the current message. For a ConfigMessage this is calculated when the + /// message is first loaded; for a MutableConfigMessage this serializes the current value to + /// properly compute the current hash. Subclasses must ensure that seqno_hash_.second is set to + /// the correct value when this is called (and typically return a reference to it). + virtual const hash_t& hash() { return seqno_hash_.second; } + + /// After loading multiple config files this flag indicates whether or not we had to produce a + /// new, merged configuration message (true) or did not need to merge (false). (For config + /// messages that were not loaded from serialized data this is always true). + bool merged() const { return merged_; } + + /// Returns true if this message contained a valid, verified signature when it was parsed. + /// Returns false otherwise (e.g. not loaded from verification at all; loaded without a + /// verification function; or had no signature and a signature wasn't required). + bool verified_signature() const { return verified_signature_; } + + /// Constructs a new MutableConfigMessage from this config message with an incremented seqno. + /// The new config message's diff will reflect changes made after this construction. + virtual MutableConfigMessage increment() const; + + /// Serializes this config's data. Note that if the ConfigMessage was constructed from signed, + /// serialized input, this will only produce an exact copy of the original serialized input if + /// it uses the identical, deterministic signing function used to construct the original. + /// + /// The optional `enable_signing` argument can be specified as false to disable signing (this is + /// typically for a local serialization value that isn't being pushed to the server). Note that + /// signing is always disabled if there is no signing callback set, regardless of the value of + /// this argument. + virtual std::string serialize(bool enable_signing = true); + + protected: + std::string serialize_impl(const oxenc::bt_dict& diff, bool enable_signing = true); +}; + +// Constructor tag +struct increment_seqno_t {}; +inline constexpr increment_seqno_t increment_seqno{}; + +class MutableConfigMessage : public ConfigMessage { + protected: + dict orig_data_{data_}; + + friend class ConfigMessage; + + public: + MutableConfigMessage(const MutableConfigMessage&) = default; + MutableConfigMessage& operator=(const MutableConfigMessage&) = default; + MutableConfigMessage(MutableConfigMessage&&) = default; + MutableConfigMessage& operator=(MutableConfigMessage&&) = default; + + /// Constructs a new, empty config message. Takes various fields to pre-fill the various + /// properties during construction (these are for convenience and equivalent to setting them via + /// properties/methods after construction). + /// + /// seqno -- the message's seqno, default 0 + /// lags -- number of lags to keep (when deriving messages, e.g. via increment()) + /// signer -- if specified and not nullptr then this message will be signed when serialized + /// using the given signing function. If omitted no signing takes place. + explicit MutableConfigMessage( + seqno_t seqno = 0, int lag = DEFAULT_DIFF_LAGS, sign_callable signer = nullptr) { + this->lag = lag; + this->seqno(seqno); + this->signer = signer; + } + + /// Wraps the ConfigMessage constructor with the same arguments but always produces a + /// MutableConfigMessage. In particular this means that if the base constructor performed a + /// merge (and thus incremented seqno) then the config stays as is, but contained in a Mutable + /// message that can be changed. If it did *not* merge (i.e. the highest seqno message it found + /// did not conflict with any other messages) then this construction is equivalent to doing a + /// base load followed by a .increment() call. In other words: this constructor *always* gives + /// you an incremented seqno value from the highest valid input config message. + /// + /// This is almost equivalent to ConfigMessage{args...}.increment(), except that this + /// constructor only increments seqno once while the indirect version would increment twice in + /// the case of a required merge conflict resolution. + explicit MutableConfigMessage( + const std::vector& configs, + verify_callable verifier = nullptr, + sign_callable signer = nullptr, + int lag = DEFAULT_DIFF_LAGS, + bool signature_optional = false, + std::function error_handler = nullptr); + + /// Wrapper around the above that takes a single string view to load a single message, doesn't + /// take an error handler and instead always throws on parse errors (the above also throws for + /// an erroneous single message, but with a less specific "no valid config messages" error). + explicit MutableConfigMessage( + std::string_view config, + verify_callable verifier = nullptr, + sign_callable signer = nullptr, + int lag = DEFAULT_DIFF_LAGS, + bool signature_optional = false); + + /// Does the same as the base incrementing, but also records any diff info from the current + /// MutableConfigMessage. *this* object gets pruned and signed as part of this call. If the + /// sign argument is omitted/nullptr then the current object's `sign` callback gets copied into + /// the new object. After this call you typically do not want to further modify *this (because + /// any modifications will change the hash, making *this no longer a parent of the new object). + MutableConfigMessage increment() const override; + + /// Constructor that does the same thing as the `m.increment()` factory method. The second + /// value should be the literal `increment_seqno` value (to select this constructor). + explicit MutableConfigMessage(const ConfigMessage& m, increment_seqno_t); + + using ConfigMessage::data; + /// Returns a mutable reference to the underlying config data. + dict& data() { return data_; } + + using ConfigMessage::seqno; + + /// Sets the seqno of the message to a specific value. You usually want to use `.increment()` + /// from an existing config message rather than manually adjusting the seqno. + void seqno(seqno_t new_seqno) { seqno_hash_.first = new_seqno; } + + /// Returns the current diff for this data relative to its original data. The data is pruned + /// implicitly by this call. + const oxenc::bt_dict& diff() override; + + /// Prunes empty dicts/sets from data. This is called automatically when serializing or + /// calculating a diff. Returns true if the data was actually changed, false if nothing needed + /// pruning. + bool prune(); + + /// Calculates the hash of the current message. Can optionally be given the already-serialized + /// value, if available; if empty/omitted, `serialize()` will be called to compute it. + const hash_t& hash() override; + + protected: + const hash_t& hash(std::string_view serialized); + void increment_impl(); +}; + +/// Encrypts a config message using XChaCha20-Poly1305, using a blake2b keyed hash of the message +/// for the nonce (rather than pure random) so that different clients will encrypt the same data to +/// the same encrypted value (thus allowing for server-side deduplication of identical messages). +/// +/// `key_base` must be 32 bytes. This value is a fixed key that all clients that might receive this +/// message can calculate independently (for instance a value derived from a secret key, or a shared +/// random key). This key will be hashed with the message size and domain suffix (see below) to +/// determine the actual encryption key. +/// +/// `domain` is a short string (1-24 chars) used for the keyed hash. Typically this is the type of +/// config, e.g. "closed-group" or "contacts". The full key will be +/// "session-config-encrypted-message-[domain]". This value is also used for the encrypted key (see +/// above). +/// +/// The returned result will consist of encrypted data with authentication tag and appended nonce, +/// suitable for being passed to decrypt() to authenticate and decrypt. +/// +/// Throw std::invalid_argument on bad input (i.e. from invalid key_base or domain). +ustring encrypt(ustring_view message, ustring_view key_base, std::string_view domain); + +/// Same as above but works with strings/string_views instead of ustring/ustring_view +std::string encrypt(std::string_view message, std::string_view key_base, std::string_view domain); + +/// Thrown if decrypt() fails. +struct decrypt_error : std::runtime_error { + using std::runtime_error::runtime_error; +}; + +/// Takes a value produced by `encrypt()` and decrypts it. `key_base` and `domain` must be the same +/// given to encrypt or else decryption fails. Upon decryption failure a std:: +ustring decrypt(ustring_view ciphertext, ustring_view key_base, std::string_view domain); + +/// Same as above but using std::string/string_view +std::string decrypt( + std::string_view ciphertext, std::string_view key_base, std::string_view domain); + +} // namespace session::config + +namespace oxenc::detail { + +template <> +struct bt_serialize : bt_serialize {}; + +} // namespace oxenc::detail diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.h new file mode 100644 index 000000000..55649ecc8 --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.h @@ -0,0 +1,85 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#if defined(_WIN32) || defined(WIN32) +#define LIBSESSION_EXPORT __declspec(dllexport) +#else +#define LIBSESSION_EXPORT __attribute__((visibility("default"))) +#endif +#define LIBSESSION_C_API extern "C" LIBSESSION_EXPORT + +typedef int64_t seqno_t; + +// Config object base type: this type holds the internal object and is initialized by the various +// config-dependent settings (e.g. config_user_profile_init) then passed to the various functions. +typedef struct config_object { + // Internal opaque object pointer; calling code should leave this alone. + void* internals; + // When an error occurs in the C API this string will be set to the specific error message. May + // be NULL. + const char* last_error; +} config_object; + +// Common functions callable on any config instance: + +/// Frees a config object created with one of the config-dependent ..._init functions (e.g. +/// user_profile_init). +void config_free(config_object* conf); + +/// Returns the numeric namespace in which config messages of this type should be stored. +int16_t config_storage_namespace(const config_object* conf); + +/// Merges the config object with one or more remotely obtained config strings. After this call the +/// config object may be unchanged, complete replaced, or updated and needing a push, depending on +/// the messages that are merged; the caller should check config_needs_push(). +/// +/// `configs` is an array of pointers to the start of the strings; `lengths` is an array of string +/// lengths; `count` is the length of those two arrays. +void config_merge(config_object* conf, const char** configs, const size_t* lengths, size_t count); + +/// Returns true if this config object contains updated data that has not yet been confirmed stored +/// on the server. +bool config_needs_push(const config_object* conf); + +/// Obtains the configuration data that needs to be pushed to the server. A new buffer of the +/// appropriate size is malloc'd and set to `out` The output is written to a new malloc'ed buffer of +/// the appropriate size; the buffer and the output length are set in the `out` and `outlen` +/// parameters. Note that this is binary data, *not* a null-terminated C string. +/// +/// Generally this call should be guarded by a call to `config_needs_push`, however it can be used +/// to re-obtain the current serialized config even if no push is needed (for example, if the client +/// wants to re-submit it after a network error). +/// +/// NB: The returned buffer belongs to the caller: that is, the caller *MUST* free() it when done +/// with it. +seqno_t config_push(config_object* conf, char** out, size_t* outlen); + +/// Reports that data obtained from `config_push` has been successfully stored on the server. The +/// seqno value is the one returned by the config_push call that yielded the config data. +void config_confirm_pushed(config_object* conf, seqno_t seqno); + +/// Returns a binary dump of the current state of the config object. This dump can be used to +/// resurrect the object at a later point (e.g. after a restart). Allocates a new buffer and sets +/// it in `out` and the length in `outlen`. Note that this is binary data, *not* a null-terminated +/// C string. +/// +/// NB: It is the caller's responsibility to `free()` the buffer when done with it. +/// +/// Immediately after this is called `config_needs_dump` will start returning true (until the +/// configuration is next modified). +void config_dump(config_object* conf, char** out, size_t* outlen); + +/// Returns true if something has changed since the last call to `dump()` that requires calling +/// and saving the `config_dump()` data again. +bool config_needs_dump(const config_object* conf); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.hpp new file mode 100644 index 000000000..dc14a37bc --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.hpp @@ -0,0 +1,503 @@ +#pragma once + +#include +#include +#include +#include + +#include "base.h" +#include "namespaces.hpp" + +namespace session::config { + +template +static constexpr bool is_one_of = (std::is_same_v || ...); + +/// True for a dict_value direct subtype, but not scalar sub-subtypes. +template +static constexpr bool is_dict_subtype = is_one_of; + +/// True for a dict_value or any of the types containable within a dict value +template +static constexpr bool is_dict_value = + is_dict_subtype || is_one_of; + +// Levels for the logging callback +enum class LogLevel { debug, info, warning, error }; + +/// Our current config state +enum class ConfigState : int { + /// Clean means the config is confirmed stored on the server and we haven't changed anything. + Clean = 0, + + /// Dirty means we have local changes, and the changes haven't been serialized yet for sending + /// to the server. + Dirty = 1, + + /// Waiting is halfway in-between clean and dirty: the caller has serialized the data, but + /// hasn't yet reported back that the data has been stored, *and* we haven't made any changes + /// since the data was serialize. + Waiting = 2, +}; + +/// Base config type for client-side configs containing common functionality needed by all config +/// sub-types. +class ConfigBase { + private: + // The object (either base config message or MutableConfigMessage) that stores the current + // config message. Subclasses do not directly access this: instead they call `dirty()` if they + // intend to make changes, or the `set_config_field` wrapper. + std::unique_ptr _config; + + // Tracks our current state + ConfigState _state = ConfigState::Clean; + + protected: + // Constructs an empty base config with no config settings and seqno set to 0. + ConfigBase(); + + // Constructs a base config by loading the data from a dump as produced by `dump()`. + explicit ConfigBase(std::string_view dump); + + // Tracks whether we need to dump again; most mutating methods should set this to true (unless + // calling set_state, which sets to to true implicitly). + bool _needs_dump = false; + + // Sets the current state; this also sets _needs_dump to true. + void set_state(ConfigState s) { + _state = s; + _needs_dump = true; + } + + // If set then we log things by calling this callback + std::function logger; + + // Invokes the above if set, does nothing if there is no logger. + void log(LogLevel lvl, std::string msg) { + if (logger) + logger(lvl, std::move(msg)); + } + + // Returns a reference to the current MutableConfigMessage. If the current message is not + // already dirty (i.e. Clean or Waiting) then calling this increments the seqno counter. + MutableConfigMessage& dirty(); + + // class for proxying subfield access; this class should never be stored but only used + // ephemerally (most of its methods are rvalue-qualified). This lets constructs such as + // foo["abc"]["def"]["ghi"] = 12; + // work, auto-vivifying (or trampling, if not a dict) subdicts to reach the target. It also + // allows non-vivifying value retrieval via .string(), .integer(), etc. methods. + class DictFieldProxy { + private: + ConfigBase& _conf; + std::vector _inter_keys; + std::string _last_key; + + // See if we can find the key without needing to create anything, so that we can attempt to + // access values without mutating anything (which allows, among other things, for assigning + // of the existing value to not dirty anything). Returns nullptr if the value or something + // along its path would need to be created, or has the wrong type; otherwise a const pointer + // to the value. The templated type, if provided, can be one of the types a dict_value can + // hold to also check that the returned value has a particular type; if omitted you get back + // the dict_value pointer itself. + template >> + const T* get_clean() const { + const config::dict* data = &_conf._config->data(); + // All but the last need to be dicts: + for (const auto& key : _inter_keys) { + auto it = data->find(key); + data = it != data->end() ? std::get_if(&it->second) : nullptr; + if (!data) + return nullptr; + } + + const dict_value* val; + // The last can be any value type: + if (auto it = data->find(_last_key); it != data->end()) + val = &it->second; + else + return nullptr; + + if constexpr (std::is_same_v) + return val; + else if constexpr (is_dict_subtype) { + if (auto* v = std::get_if(val)) + return v; + } else { // int64 or std::string, i.e. the config::scalar sub-types. + if (auto* scalar = std::get_if(val)) + return std::get_if(scalar); + } + return nullptr; + } + + // Returns a lvalue reference to the value, stomping its way through the dict as it goes to + // create subdicts as needed to reach the target value. If given a template type then we + // also cast the final dict_value variant into the given type (and replace if with a + // default-constructed value if it has the wrong type) then return a reference to that. + template >> + T& get_dirty() { + config::dict* data = &_conf.dirty().data(); + for (const auto& key : _inter_keys) { + auto& val = (*data)[key]; + data = std::get_if(&val); + if (!data) + data = &val.emplace(); + } + auto& val = (*data)[_last_key]; + + if constexpr (std::is_same_v) + return val; + else if constexpr (is_dict_subtype) { + if (auto* v = std::get_if(&val)) + return *v; + return val.emplace(); + } else { // int64 or std::string, i.e. the config::scalar sub-types. + if (auto* scalar = std::get_if(&val)) { + if (auto* v = std::get_if(scalar)) + return *v; + return scalar->emplace(); + } + return val.emplace().emplace(); + } + } + + template + void assign_if_changed(T value) { + // Try to avoiding dirtying the config if this assignment isn't changing anything + if (!_conf.is_dirty()) + if (auto current = get_clean(); current && *current == value) + return; + + get_dirty() = std::move(value); + } + + void insert_if_missing(config::scalar&& value) { + if (!_conf.is_dirty()) + if (auto current = get_clean(); current && current->count(value)) + return; + + get_dirty().insert(std::move(value)); + } + + void set_erase_impl(const config::scalar& value) { + if (!_conf.is_dirty()) + if (auto current = get_clean(); current && !current->count(value)) + return; + + config::dict* data = &_conf.dirty().data(); + + for (const auto& key : _inter_keys) { + auto it = data->find(key); + data = it != data->end() ? std::get_if(&it->second) : nullptr; + if (!data) + return; + } + + auto it = data->find(_last_key); + if (it == data->end()) + return; + auto& val = it->second; + if (auto* current = std::get_if(&val)) + current->erase(value); + else + val.emplace(); + } + + public: + DictFieldProxy(ConfigBase& b, std::string key) : _conf{b}, _last_key{std::move(key)} {} + + /// Descends into a dict, returning a copied proxy object for the path to the requested + /// field. Nothing is created by doing this unless you actually assign to a value. + DictFieldProxy operator[](std::string subkey) const& { + DictFieldProxy subfield{_conf, std::move(subkey)}; + subfield._inter_keys.reserve(_inter_keys.size() + 1); + subfield._inter_keys.insert( + subfield._inter_keys.end(), _inter_keys.begin(), _inter_keys.end()); + subfield._inter_keys.push_back(_last_key); + return subfield; + } + + // Same as above, but when called on an rvalue reference we just mutate the current proxy to + // the new dict path. + DictFieldProxy&& operator[](std::string subkey) && { + _inter_keys.push_back(std::move(_last_key)); + _last_key = std::move(subkey); + return std::move(*this); + } + + /// Returns a const pointer to the string if one exists at the given location, nullptr + /// otherwise. + const std::string* string() const { return get_clean(); } + + /// returns the value as a string_view or a fallback if the value doesn't exist (or isn't a + /// string). The returned view is directly into the value (or fallback) and so mustn't be + /// used beyond the validity of either. + std::string_view string_view_or(std::string_view fallback) const { + if (auto* s = string()) + return {*s}; + return fallback; + } + + /// Returns a copy of the value as a string, if it exists and is a string; returns + /// `fallback` otherwise. + std::string string_or(std::string fallback) const { + if (auto* s = string()) + return *s; + return std::move(fallback); + } + + /// Returns a const pointer to the integer if one exists at the given location, nullptr + /// otherwise. + const int64_t* integer() const { return get_clean(); } + + /// Returns the value as an integer or a fallback if the value doesn't exist (or isn't an + /// integer). + int64_t integer_or(int64_t fallback) const { + if (auto* i = integer()) + return *i; + return fallback; + } + + /// Returns a const pointer to the set if one exists at the given location, nullptr + /// otherwise. + const config::set* set() const { return get_clean(); } + /// Returns a const pointer to the dict if one exists at the given location, nullptr + /// otherwise. (You typically don't need to use this but can rather just use [] to descend + /// into the dict). + const config::dict* dict() const { return get_clean(); } + + /// Replaces the current value with the given string. This also auto-vivifies any + /// intermediate dicts needed to reach the given key, including replacing non-dict values if + /// they currently exist along the path. + void operator=(std::string value) { assign_if_changed(std::move(value)); } + /// Same as above, but takes a string_view for convenience. + void operator=(std::string_view value) { *this = std::string{value}; } + /// Replace the current value with the given integer. See above. + void operator=(int64_t value) { assign_if_changed(value); } + /// Replace the current value with the given set. See above. + void operator=(config::set value) { assign_if_changed(std::move(value)); } + /// Replace the current value with the given dict. See above. This often isn't needed + /// because of how other assignment operations work. + void operator=(config::dict value) { assign_if_changed(std::move(value)); } + + /// Returns true if there is a value at the current key. If a template type T is given, it + /// only returns true if that value also is a `T`. + template >> + bool exists() const { + return get_clean() != nullptr; + } + + // Alias for `exists()` + template + bool is() const { + return exists(); + } + + /// Removes the value at the current location, regardless of what it currently is. This + /// does nothing if the current location does not have a value. + void erase() { + if (!_conf.is_dirty() && !get_clean()) + return; + + config::dict* data = &_conf.dirty().data(); + for (const auto& key : _inter_keys) { + auto it = data->find(key); + data = it != data->end() ? std::get_if(&it->second) : nullptr; + if (!data) + return; + } + data->erase(_last_key); + } + + /// Adds a value to the set at the current location. If the current value is not a set or + /// does not exist then dicts will be created to reach it and a new set will be created. + void set_insert(std::string_view value) { + insert_if_missing(config::scalar{std::string{value}}); + } + void set_insert(int64_t value) { insert_if_missing(config::scalar{value}); } + + /// Removes a value from the set at the current location. If the current value does not + /// exist then nothing happens. If it does exist, but is not a set, it will be replaced + /// with an empty set. Otherwise the given value will be removed from the set, if present. + void set_erase(std::string_view value) { + set_erase_impl(config::scalar{std::string{value}}); + } + void set_erase(int64_t value) { set_erase_impl(scalar{value}); } + + /// Emplaces a value at the current location. As with assignment, this creates dicts as + /// needed along the keys to reach the target. The existing value (if present) is destroyed + /// to make room for the new one. + template < + typename T, + typename... Args, + typename = std::enable_if_t< + is_one_of>> + T& emplace(Args&&... args) { + if constexpr (is_one_of) + return get_dirty().emplace(std::forward(args)...); + + return get_dirty().emplace(std::forward(args)...); + } + }; + + /// Wrapper for the ConfigBase's root `data` field to provide data access. Only provides a [] + /// that gets you into a DictFieldProxy. + class DictFieldRoot { + ConfigBase& _conf; + DictFieldRoot(DictFieldRoot&&) = delete; + DictFieldRoot(const DictFieldRoot&) = delete; + DictFieldRoot& operator=(DictFieldRoot&&) = delete; + DictFieldRoot& operator=(const DictFieldRoot&) = delete; + + public: + DictFieldRoot(ConfigBase& b) : _conf{b} {} + + /// Access a dict element. This returns a proxy object for accessing the value, but does + /// *not* auto-vivify the path (unless/until you assign to it). + DictFieldProxy operator[](std::string key) const& { + return DictFieldProxy{_conf, std::move(key)}; + } + }; + + // Called when dumping to obtain any extra data that a subclass needs to store to reconstitute + // the object. The base implementation does nothing. The counterpart to this, + // `load_extra_data()`, is called when loading from a dump that has extra data; a subclass + // should either override both (if it needs to serialize extra data) or neither (if it needs no + // extra data). Internally this extra data (if non-empty) is stored in the "+" key of the dump. + virtual oxenc::bt_dict extra_data() const { return {}; } + + // Called when constructing from a dump that has extra data. The base implementation does + // nothing. + virtual void load_extra_data(oxenc::bt_dict extra) {} + + public: + virtual ~ConfigBase() = default; + + // Proxy class providing read and write access to the contained config data. + const DictFieldRoot data{*this}; + + // Accesses the storage namespace where this config type is to be stored/loaded from. See + // namespaces.hpp for the underlying integer values. + virtual Namespace storage_namespace() const = 0; + + // How many config lags should be used for this object; default to 5. Implementing subclasses + // can override to return a different constant if desired. More lags require more "diff" + // storage in the config messages, but also allow for a higher tolerance of simultaneous message + // conflicts. + virtual int config_lags() const { return 5; } + + // This takes all of the messages pulled down from the server and does whatever is necessary to + // merge (or replace) the current values. + // + // After this call the caller should check `needs_push()` to see if the data on hand was updated + // and needs to be pushed to the server again. + // + // Will throw on serious error (i.e. if neither the current nor any of the given configs are + // parseable). + virtual void merge(const std::vector& configs); + + // Returns true if we are currently dirty (i.e. have made changes that haven't been serialized + // yet). + bool is_dirty() const { return _state == ConfigState::Dirty; } + + // Returns true if we are curently clean (i.e. our current config is stored on the server and + // unmodified). + bool is_clean() const { return _state == ConfigState::Clean; } + + // Returns true if this object contains updated data that has not yet been confirmed stored on + // the server. This will be true whenever `is_clean()` is false: that is, if we are currently + // "dirty" (i.e. have changes that haven't been pushed) or are still awaiting confirmation of + // storage of the most recent serialized push data. + virtual bool needs_push() const; + + // Returns the data to push to the server along with the seqno value of the data. If the config + // is currently dirty (i.e. has previously unsent modifications) then this marks it as + // awaiting-confirmation instead of dirty so that any future change immediately increments the + // seqno. + virtual std::pair push(); + + // Should be called after the push is confirmed stored on the storage server swarm to let the + // object know the data is stored. (Once this is called `needs_push` will start returning false + // until something changes). Takes the seqno that was pushed so that the object can ensure that + // the latest version was pushed (i.e. in case there have been other changes since the `push()` + // call that returned this seqno). + // + // It is safe to call this multiple times with the same seqno value, and with out-of-order + // seqnos (e.g. calling with seqno 122 after having called with 123; the duplicates and earlier + // ones will just be ignored). + virtual void confirm_pushed(seqno_t seqno); + + // Returns a dump of the current state for storage in the database; this value would get passed + // into the constructor to reconstitute the object (including the push/not pushed status). This + // method is *not* virtual: if subclasses need to store extra data they should set it in the + // `subclass_data` field. + std::string dump(); + + // Returns true if something has changed since the last call to `dump()` that requires calling + // and saving the `dump()` data again. + virtual bool needs_dump() const { return _needs_dump; } +}; + +// The C++ struct we hold opaquely inside the C internals struct. This is designed so that any +// internals has the same layout so that it doesn't matter whether we unbox to an +// internals or internals. +template < + typename ConfigT = ConfigBase, + std::enable_if_t, int> = 0> +struct internals final { + std::unique_ptr config; + std::string error; + + // Dereferencing falls through to the ConfigBase object + ConfigT* operator->() { + if constexpr (std::is_same_v) + return config.get(); + else { + auto* c = dynamic_cast(config.get()); + assert(c); + return c; + } + } + const ConfigT* operator->() const { + if constexpr (std::is_same_v) + return config.get(); + else { + auto* c = dynamic_cast(config.get()); + assert(c); + return c; + } + } + ConfigT& operator*() { return *operator->(); } + const ConfigT& operator*() const { return *operator->(); } +}; + +template , int> = 0> +inline internals& unbox(config_object* conf) { + return *static_cast*>(conf->internals); +} +template , int> = 0> +inline const internals& unbox(const config_object* conf) { + return *static_cast*>(conf->internals); +} + +// Sets an error message in the internals.error string and updates the last_error pointer in the +// outer (C) config_object struct to point at it. +void set_error(config_object* conf, std::string e); + +// Same as above, but gets the error string out of an exception and passed through a return value. +// Intended to simplify catch-and-return-error such as: +// try { +// whatever(); +// } catch (const std::exception& e) { +// return set_error(conf, LIB_SESSION_ERR_OHNOES, e); +// } +inline int set_error(config_object* conf, int errcode, const std::exception& e) { + set_error(conf, e.what()); + return errcode; +} + +// Copies a value contained in a string into a new malloced char buffer, returning the buffer and +// size via the two pointer arguments. +void copy_out(const std::string& data, char** out, size_t* outlen); + +} // namespace session::config diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/error.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/error.h new file mode 100644 index 000000000..598a1539a --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/error.h @@ -0,0 +1,23 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +enum config_error { + /// Value returned for no error + SESSION_ERR_NONE = 0, + /// Error indicating that initialization failed because the dumped data being loaded is invalid. + SESSION_ERR_INVALID_DUMP = 1, + /// Error indicated a bad value, e.g. if trying to set something invalid in a config field. + SESSION_ERR_BAD_VALUE = 2, +}; + +// Returns a generic string for a given integer error code as returned by some functions. Depending +// on the call, a more details error string may be available in the config_object's `last_error` +// field. +const char* config_errstr(int err); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/namespaces.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/namespaces.hpp new file mode 100644 index 000000000..f5ab57c20 --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/namespaces.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include + +namespace session::config { + +enum class Namespace : std::int16_t { + UserProfile = 2, + ClosedGroupInfo = 11, +}; + +} // namespace session::config diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.h new file mode 100644 index 000000000..6a0123027 --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.h @@ -0,0 +1,51 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "base.h" + +/// Constructs a user profile config object and sets a pointer to it in `conf`. To restore an +/// existing dump produced by a past instantiation's call to `dump()` pass the dump value via `dump` +/// and `dumplen`; to construct a new, empty profile pass NULL and 0. +/// +/// `error` must either be NULL or a pointer to a buffer of at least 256 bytes. +/// +/// Returns 0 on success; returns a non-zero error code and sets error (if not NULL) to the +/// exception message on failure. +/// +/// When done with the object the `config_object` must be destroyed by passing the pointer to +/// config_free() (in `session/config/base.h`). +int user_profile_init(config_object** conf, const char* dump, size_t dumplen, char* error) + __attribute__((warn_unused_result)); + +/// Returns a pointer to the currently-set name (null-terminated), or NULL if there is no name at +/// all. Should be copied right away as the pointer may not remain valid beyond other API calls. +const char* user_profile_get_name(const config_object* conf); + +/// Sets the user profile name to the null-terminated C string. Returns 0 on success, non-zero on +/// error (and sets the config_object's error string). +int user_profile_set_name(config_object* conf, const char* name); + +typedef struct user_profile_pic { + // Null-terminated C string containing the uploaded URL of the pic. Will be NULL if there is no + // profile pic. + const char* url; + // The profile pic decryption key, in bytes. This is a byte buffer of length `keylen`, *not* a + // null-terminated C string. Will be NULL if there is no profile pic. + const char* key; + size_t keylen; +} user_profile_pic; + +// Obtains the current profile pic. The pointers in the returned struct will be NULL if a profile +// pic is not currently set, and otherwise should be copied right away (they will not be valid +// beyond other API calls on this config object). +user_profile_pic user_profile_get_pic(const config_object* conf); + +// Sets a user profile +int user_profile_set_pic(config_object* conf, user_profile_pic pic); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.hpp new file mode 100644 index 000000000..b4cdba80b --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include +#include + +#include "base.hpp" +#include "namespaces.hpp" + +namespace session::config { + +/// keys used in this config, either currently or in the past (so that we don't reuse): +/// +/// n - user profile name +/// p - user profile url +/// q - user profile decryption key (binary) + +class UserProfile final : public ConfigBase { + + public: + /// Constructs a new, blank user profile. + UserProfile() = default; + + /// Constructs a user profile from existing data + explicit UserProfile(std::string_view dumped) : ConfigBase{dumped} {} + + Namespace storage_namespace() const override { return Namespace::UserProfile; } + + /// Returns the user profile name, or nullptr if there is no profile name set. + const std::string* get_name() const; + + /// Sets the user profile name + void set_name(std::string_view new_name); + + /// Gets the user's current profile pic URL and decryption key. Returns nullptr for *both* + /// values if *either* value is unset or empty in the config data. + std::pair get_profile_pic() const; + + /// Sets the user's current profile pic to a new URL and decryption key. Clears both if either + /// one is empty. + void set_profile_pic(std::string url, std::string key); +}; + +} // namespace session::config diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/fields.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/fields.hpp new file mode 100644 index 000000000..6ca71a245 --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/fields.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include +#include +#include +#include + +namespace session { + +using namespace std::literals; + +/// An uploaded file is its URL + decryption key +struct Uploaded { + std::string url; + std::string key; +}; + +/// A conversation disappearing messages setting +struct Disappearing { + /// The possible modes of a disappearing messages setting. + enum class Mode : int { None = 0, AfterSend = 1, AfterRead = 2 }; + + /// The mode itself + Mode mode = Mode::None; + + /// The timer value; this is only used when mode is not None. + std::chrono::seconds timer = 0s; +}; + +/// A Session ID: an x25519 pubkey, with a 05 identifying prefix. On the wire we send just the +/// 32-byte pubkey value (i.e. not hex, without the prefix). +struct SessionID { + /// The fixed session netid, 0x05 + static constexpr unsigned char netid = 0x05; + + /// The raw x25519 pubkey, as bytes + std::array pubkey; + + /// Returns the full pubkey in hex, including the netid prefix. + std::string hex() const; +}; + +} // namespace session diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/xed25519.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/xed25519.h new file mode 100644 index 000000000..5348dafa0 --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/xed25519.h @@ -0,0 +1,34 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/// XEd25519-signed a message given a curve25519 privkey and message. Writes the 64-byte signature +/// to `sig` on success and returns 0. Returns non-zero on failure. +__attribute__((warn_unused_result)) int session_xed25519_sign( + unsigned char* signature /* 64 byte buffer */, + const unsigned char* curve25519_privkey /* 32 bytes */, + const unsigned char* msg, + const unsigned int msg_len); + +/// Verifies an XEd25519-signed message given a 64-byte signature, 32-byte curve25519 pubkey, and +/// message. Returns 0 if the signature verifies successfully, non-zero on failure. +__attribute__((warn_unused_result)) int session_xed25519_verify( + const unsigned char* signature /* 64 bytes */, + const unsigned char* pubkey /* 32-bytes */, + const unsigned char* msg, + const unsigned int msg_len); + +/// Given a curve25519 pubkey, this writes the associated XEd25519-derived Ed25519 pubkey into +/// ed25519_pubkey. Note, however, that there are *two* possible Ed25519 pubkeys that could result +/// in a given curve25519 pubkey: this always returns the positive value. You can get the other +/// possibility (the negative) by flipping the sign bit, i.e. `returned_pubkey[31] |= 0x80`. +/// Returns 0 on success, non-0 on failure. +__attribute__((warn_unused_result)) int session_xed25519_pubkey( + unsigned char* ed25519_pubkey /* 32-byte output buffer */, + const unsigned char* curve25519_pubkey /* 32 bytes */); + +#ifdef __cplusplus +} +#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/xed25519.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/xed25519.hpp new file mode 100644 index 000000000..9889113c2 --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/xed25519.hpp @@ -0,0 +1,38 @@ +#pragma once +#include +#include +#include + +namespace session::xed25519 { + +using ustring_view = std::basic_string_view; + +/// XEd25519-signs a message given the curve25519 privkey and message. +std::array sign( + ustring_view curve25519_privkey /* 32 bytes */, ustring_view msg); + +/// "Softer" version that takes and returns strings of regular chars +std::string sign(std::string_view curve25519_privkey /* 32 bytes */, std::string_view msg); + +/// Verifies a curve25519 message allegedly signed by the given curve25519 pubkey +[[nodiscard]] bool verify( + ustring_view signature /* 64 bytes */, + ustring_view curve25519_pubkey /* 32 bytes */, + ustring_view msg); + +/// "Softer" version that takes strings of regular chars +[[nodiscard]] bool verify( + std::string_view signature /* 64 bytes */, + std::string_view curve25519_pubkey /* 32 bytes */, + std::string_view msg); + +/// Given a curve25519 pubkey, this returns the associated XEd25519-derived Ed25519 pubkey. Note, +/// however, that there are *two* possible Ed25519 pubkeys that could result in a given curve25519 +/// pubkey: this always returns the positive value. You can get the other possibility (the +/// negative) by flipping the sign bit, i.e. `returned_pubkey[31] |= 0x80`. +std::array pubkey(ustring_view curve25519_pubkey); + +/// "Softer" version that takes/returns strings of regular chars +std::string pubkey(std::string_view curve25519_pubkey); + +} // namespace session::xed25519 diff --git a/SessionMessagingKit/Open Groups/Models/SOGSBatchRequest.swift b/SessionMessagingKit/Open Groups/Models/SOGSBatchRequest.swift new file mode 100644 index 000000000..bb38ed258 --- /dev/null +++ b/SessionMessagingKit/Open Groups/Models/SOGSBatchRequest.swift @@ -0,0 +1,121 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import PromiseKit +import SessionUtilitiesKit + +internal extension OpenGroupAPI { + struct BatchRequest: Encodable { + let requests: [Child] + + init(requests: [Info]) { + self.requests = requests.map { $0.child } + } + + func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + + try container.encode(requests) + } + + // MARK: - BatchRequest.Info + + struct Info { + public let endpoint: any EndpointType + public let responseType: Codable.Type + fileprivate let child: Child + + public init(request: Request, responseType: R.Type) { + self.endpoint = request.endpoint + self.responseType = HTTP.BatchSubResponse.self + self.child = Child(request: request) + } + + public init(request: Request) { + self.init( + request: request, + responseType: NoResponse.self + ) + } + } + + // MARK: - BatchRequest.Child + + struct Child: Encodable { + enum CodingKeys: String, CodingKey { + case method + case path + case headers + case json + case b64 + case bytes + } + + let method: HTTPMethod + let path: String + let headers: [String: String]? + + /// The `jsonBodyEncoder` is used to avoid having to make `Child` a generic type (haven't found a good way + /// to keep `Child` encodable using protocols unfortunately so need this work around) + private let jsonBodyEncoder: ((inout KeyedEncodingContainer, CodingKeys) throws -> ())? + private let b64: String? + private let bytes: [UInt8]? + + internal init(request: Request) { + self.method = request.method + self.path = request.urlPathAndParamsString + self.headers = (request.headers.isEmpty ? nil : request.headers.toHTTPHeaders()) + + // Note: Need to differentiate between JSON, b64 string and bytes body values to ensure + // they are encoded correctly so the server knows how to handle them + switch request.body { + case let bodyString as String: + self.jsonBodyEncoder = nil + self.b64 = bodyString + self.bytes = nil + + case let bodyBytes as [UInt8]: + self.jsonBodyEncoder = nil + self.b64 = nil + self.bytes = bodyBytes + + default: + self.jsonBodyEncoder = { [body = request.body] container, key in + try container.encodeIfPresent(body, forKey: key) + } + self.b64 = nil + self.bytes = nil + } + } + + func encode(to encoder: Encoder) throws { + var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(method, forKey: .method) + try container.encode(path, forKey: .path) + try container.encodeIfPresent(headers, forKey: .headers) + try jsonBodyEncoder?(&container, .json) + try container.encodeIfPresent(b64, forKey: .b64) + try container.encodeIfPresent(bytes, forKey: .bytes) + } + } + } +} + +// MARK: - Convenience + +internal extension Promise where T == HTTP.BatchResponse { + func map( + requests: [OpenGroupAPI.BatchRequest.Info], + toHashMapFor endpointType: E.Type + ) -> Promise<[E: (ResponseInfoType, Codable?)]> { + return self.map { result in + result.enumerated() + .reduce(into: [:]) { prev, next in + guard let endpoint: E = requests[next.offset].endpoint as? E else { return } + + prev[endpoint] = next.element + } + } + } +} diff --git a/SessionMessagingKitTests/LibSessionUtil/ConfigUserProfileSpec.swift b/SessionMessagingKitTests/LibSessionUtil/ConfigUserProfileSpec.swift new file mode 100644 index 000000000..85d88995e --- /dev/null +++ b/SessionMessagingKitTests/LibSessionUtil/ConfigUserProfileSpec.swift @@ -0,0 +1,327 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import SessionUtil + +import Quick +import Nimble + +/// This spec is designed to replicate the initial test cases for the libSession-util to ensure the behaviour matches +class ConfigUserProfileSpec: QuickSpec { + // MARK: - Spec + + override func spec() { + it("behaves correctly") { + // Initialize a brand new, empty config because we have no dump data to deal with. + let error: UnsafeMutablePointer? = nil + var conf: UnsafeMutablePointer? = nil + expect(user_profile_init(&conf, nil, 0, error)).to(equal(0)) + + // We don't need to push anything, since this is an empty config + expect(config_needs_push(conf)).to(beFalse()) + // And we haven't changed anything so don't need to dump to db + expect(config_needs_dump(conf)).to(beFalse()) + + // Since it's empty there shouldn't be a name. + let namePtr = user_profile_get_name(conf) + expect(namePtr).to(beNil()) + + var toPush: UnsafeMutablePointer? = nil + var toPushLen: Int = 0 + // We don't need to push since we haven't changed anything, so this call is mainly just for + // testing: + let seqno: Int64 = config_push(conf, &toPush, &toPushLen) + expect(toPush).toNot(beNil()) + expect(seqno).to(equal(0)) + expect(String(cString: toPush!)).to(equal("d1:#i0e1:&de1:? = nil + var toPush2Len: Int = 0 + let seqno2 = config_push(conf, &toPush2, &toPush2Len); + // incremented since we made changes (this only increments once between + // dumps; even though we changed two fields here). + expect(seqno2).to(equal(1)) + + // Note: This hex value differs from the value in the library tests because + // it looks like the library has an "end of cell mark" character added at the + // end (0x07 or '0007') so we need to manually add it to work + let expHash0: [CChar] = Data(hex: "ea173b57beca8af18c3519a7bbf69c3e7a05d1c049fa9558341d8ebb48b0c965") + .bytes + .map { CChar(bitPattern: $0) } +// .withUnsafeBufferPointer { profileKeyPtr in +// String(cString: profileKeyPtr.baseAddress!) +// } + // The data to be actually pushed, expanded like this to make it somewhat human-readable: + let expPush1: [CChar] = [""" + d + 1:#i1e + 1:& d + 1:n 6:Kallie + 1:p 34:http://example.org/omg-pic-123.bmp + 1:q 6:secret + e + 1:< l + l i0e 32: + """.removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) // For readability + .bytes + .map { CChar(bitPattern: $0) }, + expHash0, + """ + de e + e + 1:= d + 1:n 0: + 1:p 0: + 1:q 0: + e + e + """.removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) // For readability + .bytes + .map { CChar(bitPattern: $0) }, +// [CChar()] // Need to null-terminate the string or it'll crash + ] + .flatMap { $0 } + + expect(String(cString: toPush2!)) + // Need to null-terminate the string or it'll crash + .to(equal(String(cString: expPush1.appending(CChar())))) + + // We haven't dumped, so still need to dump: + expect(config_needs_dump(conf)).to(beTrue()) + // We did call push, but we haven't confirmed it as stored yet, so this will still return true: + expect(config_needs_push(conf)).to(beTrue()) + + var dump1: UnsafeMutablePointer? = nil + var dump1Len: Int = 0 + + config_dump(conf, &dump1, &dump1Len) + // (in a real client we'd now store this to disk) + + expect(config_needs_dump(conf)).to(beFalse()) + + let expDump1: [CChar] = [ + """ + d + 1:! i2e + 1:$ \(expPush1.count): + """ + .removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) + .bytes + .map { CChar(bitPattern: $0) }, + expPush1, + """ + e + """.removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) + .bytes + .map { CChar(bitPattern: $0) } + ] + .flatMap { $0 } + expect(String(cString: dump1!)) + .to(equal(String(cString: expDump1.appending(CChar()))))// Need to null-terminate the string or it'll crash + + // So now imagine we got back confirmation from the swarm that the push has been stored: + config_confirm_pushed(conf, seqno2) + + expect(config_needs_push(conf)).to(beFalse()) + expect(config_needs_dump(conf)).to(beTrue()) // The confirmation changes state, so this makes us need a dump + + var dump2: UnsafeMutablePointer? = nil + var dump2Len: Int = 0 + config_dump(conf, &dump2, &dump2Len) + + expect(config_needs_dump(conf)).to(beFalse()) + + // Now we're going to set up a second, competing config object (in the real world this would be + // another Session client somewhere). + + // Start with an empty config, as above: + let error2: UnsafeMutablePointer? = nil + var conf2: UnsafeMutablePointer? = nil + expect(user_profile_init(&conf2, nil, 0, error2)).to(equal(0)) + expect(config_needs_dump(conf2)).to(beFalse()) + + // Now imagine we just pulled down the `exp_push1` string from the swarm; we merge it into + // conf2: + let mergeData: [[CChar]] = [expPush1] + var mergeDataPtr = mergeData.map { value in + let cStringCopy = UnsafeMutableBufferPointer.allocate(capacity: value.count) + _ = cStringCopy.initialize(from: value) + let ptr = UnsafePointer(cStringCopy.baseAddress) + return UnsafePointer(cStringCopy.baseAddress) + } + var mergeSize: [Int] = [expPush1.count] + config_merge(conf2, &mergeDataPtr, &mergeSize, 1) + + // Our state has changed, so we need to dump: + expect(config_needs_dump(conf2)).to(beTrue()) + var dump3: UnsafeMutablePointer? = nil + var dump3Len: Int = 0 + config_dump(conf, &dump3, &dump3Len) + // (store in db) + expect(config_needs_dump(conf2)).to(beFalse())// TODO: This one is broken now!!! + + // We *don't* need to push: even though we updated, all we did is update to the merged data (and + // didn't have any sort of merge conflict needed): + expect(config_needs_push(conf2)).to(beFalse()) + + // Now let's create a conflicting update: + + // Change the name on both clients: + user_profile_set_name(conf, "Nibbler") + user_profile_set_name(conf2, "Raz") + + // And, on conf2, we're also going to change the profile pic: + let profile2Url: [CChar] = "http://new.example.com/pic" + .bytes + .map { CChar(bitPattern: $0) } + let profile2Key: [CChar] = "qwert\0yuio" + .bytes + .map { CChar(bitPattern: $0) } + let p2: user_profile_pic = profile2Url.withUnsafeBufferPointer { profile2UrlPtr in + profile2Key.withUnsafeBufferPointer { profile2KeyPtr in + user_profile_pic( + url: profile2UrlPtr.baseAddress, + key: profile2KeyPtr.baseAddress, + keylen: 10 + ) + } + } + user_profile_set_pic(conf2, p2) + + // Both have changes, so push need a push + expect(config_needs_push(conf)).to(beTrue()) + expect(config_needs_push(conf2)).to(beTrue()) + var toPush3: UnsafeMutablePointer? = nil + var toPush3Len: Int = 0 + let seqno3 = config_push(conf, &toPush3, &toPush3Len) + expect(seqno3).to(equal(2)) // incremented, since we made a field change + + var toPush4: UnsafeMutablePointer? = nil + var toPush4Len: Int = 0 + let seqno4 = config_push(conf2, &toPush4, &toPush4Len) + expect(seqno4).to(equal(2)) // incremented, since we made a field change + + var dump4: UnsafeMutablePointer? = nil + var dump4Len: Int = 0 + config_dump(conf, &dump4, &dump4Len); + var dump5: UnsafeMutablePointer? = nil + var dump5Len: Int = 0 + config_dump(conf2, &dump5, &dump5Len); + // (store in db) + + // Since we set different things, we're going to get back different serialized data to be + // pushed: + expect(String(cString: toPush3!)).toNot(equal(String(cString: toPush4!))) + + // Now imagine that each client pushed its `seqno=2` config to the swarm, but then each client + // also fetches new messages and pulls down the other client's `seqno=2` value. + + // Feed the new config into each other. (This array could hold multiple configs if we pulled + // down more than one). + let mergeData2: [String] = [String(cString: toPush3!)] + var mergeData2Ptr = mergeData2.map { value in + value.bytes.map { CChar(bitPattern: $0) }.withUnsafeBufferPointer { valuePtr in + valuePtr.baseAddress + } + } + var mergeSize2: [Int] = [toPush3Len] + config_merge(conf2, &mergeData2Ptr, &mergeSize2, 1) + let mergeData3: [String] = [String(cString: toPush4!)] + var mergeData3Ptr = mergeData3.map { value in + value.bytes.map { CChar(bitPattern: $0) }.withUnsafeBufferPointer { valuePtr in + valuePtr.baseAddress + } + } + var mergeSize3: [Int] = [toPush4Len] + config_merge(conf, &mergeData3Ptr, &mergeSize3, 1) + + // Now after the merge we *will* want to push from both client, since both will have generated a + // merge conflict update (with seqno = 3). + expect(config_needs_push(conf)).to(beTrue()) + expect(config_needs_push(conf2)).to(beTrue()) + let seqno5 = config_push(conf, &toPush3, &toPush3Len); + let seqno6 = config_push(conf2, &toPush4, &toPush4Len); + + expect(seqno5).to(equal(3)) + expect(seqno6).to(equal(3)) + + // They should have resolved the conflict to the same thing: + expect(String(cString: user_profile_get_name(conf)!)).to(equal("Nibbler")) + expect(String(cString: user_profile_get_name(conf2)!)).to(equal("Nibbler")) + // (Note that they could have also both resolved to "Raz" here, but the hash of the serialized + // message just happens to have a higher hash -- and thus gets priority -- for this particular + // test). + + // Since only one of them set a profile pic there should be no conflict there: + let pic3 = user_profile_get_pic(conf) + expect(pic3.url).toNot(beNil()) + expect(String(cString: pic3.url!)).to(equal("http://new.example.com/pic")) + expect(pic3.key).toNot(beNil()) + expect(String(cString: pic3.key!)).to(equal("qwert\0yuio")) + let pic4 = user_profile_get_pic(conf2) + expect(pic4.url).toNot(beNil()) + expect(String(cString: pic4.url!)).to(equal("http://new.example.com/pic")) + expect(pic4.key).toNot(beNil()) + expect(String(cString: pic4.key!)).to(equal("qwert\0yuio")) + + config_confirm_pushed(conf, seqno5) + config_confirm_pushed(conf2, seqno6) + + var dump6: UnsafeMutablePointer? = nil + var dump6Len: Int = 0 + config_dump(conf, &dump6, &dump6Len); + var dump7: UnsafeMutablePointer? = nil + var dump7Len: Int = 0 + config_dump(conf2, &dump7, &dump7Len); + // (store in db) + + expect(config_needs_dump(conf)).to(beFalse()) + expect(config_needs_dump(conf2)).to(beFalse()) + expect(config_needs_push(conf)).to(beFalse()) + expect(config_needs_push(conf2)).to(beFalse()) + } + } +} diff --git a/SessionMessagingKitTests/Open Groups/Models/BatchRequestInfoSpec.swift b/SessionMessagingKitTests/Open Groups/Models/BatchRequestInfoSpec.swift index 97fbebfdb..aeda2a2f1 100644 --- a/SessionMessagingKitTests/Open Groups/Models/BatchRequestInfoSpec.swift +++ b/SessionMessagingKitTests/Open Groups/Models/BatchRequestInfoSpec.swift @@ -21,12 +21,12 @@ class BatchRequestInfoSpec: QuickSpec { override func spec() { // MARK: - BatchSubRequest - describe("a BatchSubRequest") { - var subRequest: OpenGroupAPI.BatchSubRequest! + describe("a BatchRequest.Child") { + var subRequest: OpenGroupAPI.BatchRequest.Child! context("when initializing") { it("sets the headers to nil if there aren't any") { - subRequest = OpenGroupAPI.BatchSubRequest( + subRequest = OpenGroupAPI.BatchRequest.Child( request: Request( server: "testServer", endpoint: .batch @@ -37,7 +37,7 @@ class BatchRequestInfoSpec: QuickSpec { } it("converts the headers to HTTP headers") { - subRequest = OpenGroupAPI.BatchSubRequest( + subRequest = OpenGroupAPI.BatchRequest.Child( request: Request( method: .get, server: "testServer", @@ -54,7 +54,7 @@ class BatchRequestInfoSpec: QuickSpec { context("when encoding") { it("successfully encodes a string body") { - subRequest = OpenGroupAPI.BatchSubRequest( + subRequest = OpenGroupAPI.BatchRequest.Child( request: Request( method: .get, server: "testServer", @@ -72,7 +72,7 @@ class BatchRequestInfoSpec: QuickSpec { } it("successfully encodes a byte body") { - subRequest = OpenGroupAPI.BatchSubRequest( + subRequest = OpenGroupAPI.BatchRequest.Child( request: Request<[UInt8], OpenGroupAPI.Endpoint>( method: .get, server: "testServer", @@ -90,7 +90,7 @@ class BatchRequestInfoSpec: QuickSpec { } it("successfully encodes a JSON body") { - subRequest = OpenGroupAPI.BatchSubRequest( + subRequest = OpenGroupAPI.BatchRequest.Child( request: Request( method: .get, server: "testServer", @@ -109,112 +109,9 @@ class BatchRequestInfoSpec: QuickSpec { } } - // MARK: - BatchSubResponse - - describe("a BatchSubResponse") { - context("when decoding") { - it("decodes correctly") { - let jsonString: String = """ - { - "code": 200, - "headers": { - "testKey": "testValue" - }, - "body": { - "stringValue": "testValue" - } - } - """ - let subResponse: OpenGroupAPI.BatchSubResponse? = try? JSONDecoder().decode( - OpenGroupAPI.BatchSubResponse.self, - from: jsonString.data(using: .utf8)! - ) - - expect(subResponse).toNot(beNil()) - expect(subResponse?.body).toNot(beNil()) - } - - it("decodes with invalid body data") { - let jsonString: String = """ - { - "code": 200, - "headers": { - "testKey": "testValue" - }, - "body": "Hello!!!" - } - """ - let subResponse: OpenGroupAPI.BatchSubResponse? = try? JSONDecoder().decode( - OpenGroupAPI.BatchSubResponse.self, - from: jsonString.data(using: .utf8)! - ) - - expect(subResponse).toNot(beNil()) - } - - it("flags invalid body data as invalid") { - let jsonString: String = """ - { - "code": 200, - "headers": { - "testKey": "testValue" - }, - "body": "Hello!!!" - } - """ - let subResponse: OpenGroupAPI.BatchSubResponse? = try? JSONDecoder().decode( - OpenGroupAPI.BatchSubResponse.self, - from: jsonString.data(using: .utf8)! - ) - - expect(subResponse).toNot(beNil()) - expect(subResponse?.body).to(beNil()) - expect(subResponse?.failedToParseBody).to(beTrue()) - } - - it("does not flag a missing or invalid optional body as invalid") { - let jsonString: String = """ - { - "code": 200, - "headers": { - "testKey": "testValue" - }, - } - """ - let subResponse: OpenGroupAPI.BatchSubResponse? = try? JSONDecoder().decode( - OpenGroupAPI.BatchSubResponse.self, - from: jsonString.data(using: .utf8)! - ) - - expect(subResponse).toNot(beNil()) - expect(subResponse?.body).to(beNil()) - expect(subResponse?.failedToParseBody).to(beFalse()) - } - - it("does not flag a NoResponse body as invalid") { - let jsonString: String = """ - { - "code": 200, - "headers": { - "testKey": "testValue" - }, - } - """ - let subResponse: OpenGroupAPI.BatchSubResponse? = try? JSONDecoder().decode( - OpenGroupAPI.BatchSubResponse.self, - from: jsonString.data(using: .utf8)! - ) - - expect(subResponse).toNot(beNil()) - expect(subResponse?.body).to(beNil()) - expect(subResponse?.failedToParseBody).to(beFalse()) - } - } - } - // MARK: - BatchRequestInfo - describe("a BatchRequestInfo") { + describe("a BatchRequest.Info") { var request: Request! beforeEach { @@ -229,26 +126,26 @@ class BatchRequestInfoSpec: QuickSpec { } it("initializes correctly when given a request") { - let requestInfo: OpenGroupAPI.BatchRequestInfo = OpenGroupAPI.BatchRequestInfo( + let requestInfo: OpenGroupAPI.BatchRequest.Info = OpenGroupAPI.BatchRequest.Info( request: request ) - expect(requestInfo.request).to(equal(request)) - expect(requestInfo.responseType == OpenGroupAPI.BatchSubResponse.self).to(beTrue()) + expect(requestInfo.endpoint.path).to(equal(request.endpoint.path)) + expect(requestInfo.responseType == HTTP.BatchSubResponse.self).to(beTrue()) } it("initializes correctly when given a request and a response type") { - let requestInfo: OpenGroupAPI.BatchRequestInfo = OpenGroupAPI.BatchRequestInfo( + let requestInfo: OpenGroupAPI.BatchRequest.Info = OpenGroupAPI.BatchRequest.Info( request: request, responseType: TestType.self ) - expect(requestInfo.request).to(equal(request)) - expect(requestInfo.responseType == OpenGroupAPI.BatchSubResponse.self).to(beTrue()) + expect(requestInfo.endpoint.path).to(equal(request.endpoint.path)) + expect(requestInfo.responseType == HTTP.BatchSubResponse.self).to(beTrue()) } it("exposes the endpoint correctly") { - let requestInfo: OpenGroupAPI.BatchRequestInfo = OpenGroupAPI.BatchRequestInfo( + let requestInfo: OpenGroupAPI.BatchRequest.Info = OpenGroupAPI.BatchRequest.Info( request: request ) @@ -256,130 +153,17 @@ class BatchRequestInfoSpec: QuickSpec { } it("generates a sub request correctly") { - let requestInfo: OpenGroupAPI.BatchRequestInfo = OpenGroupAPI.BatchRequestInfo( - request: request + let batchRequest: OpenGroupAPI.BatchRequest = OpenGroupAPI.BatchRequest( + requests: [ + OpenGroupAPI.BatchRequest.Info( + request: request + ) + ] ) - let subRequest: OpenGroupAPI.BatchSubRequest = requestInfo.toSubRequest() - expect(subRequest.method).to(equal(request.method)) - expect(subRequest.path).to(equal(request.urlPathAndParamsString)) - expect(subRequest.headers).to(beNil()) - } - } - - // MARK: - Convenience - // MARK: --Decodable - - describe("a Decodable") { - it("decodes correctly") { - let jsonData: Data = "{\"stringValue\":\"testValue\"}".data(using: .utf8)! - let result: TestType? = try? TestType.decoded(from: jsonData) - - expect(result).to(equal(TestType(stringValue: "testValue"))) - } - } - - // MARK: - --Promise - - describe("an (OnionRequestResponseInfoType, Data?) Promise") { - var responseInfo: OnionRequestResponseInfoType! - var capabilities: OpenGroupAPI.Capabilities! - var pinnedMessage: OpenGroupAPI.PinnedMessage! - var data: Data! - - beforeEach { - responseInfo = OnionRequestAPI.ResponseInfo(code: 200, headers: [:]) - capabilities = OpenGroupAPI.Capabilities(capabilities: [], missing: nil) - pinnedMessage = OpenGroupAPI.PinnedMessage(id: 1, pinnedAt: 123, pinnedBy: "test") - data = """ - [\([ - try! JSONEncoder().encode( - OpenGroupAPI.BatchSubResponse( - code: 200, - headers: [:], - body: capabilities, - failedToParseBody: false - ) - ), - try! JSONEncoder().encode( - OpenGroupAPI.BatchSubResponse( - code: 200, - headers: [:], - body: pinnedMessage, - failedToParseBody: false - ) - ) - ] - .map { String(data: $0, encoding: .utf8)! } - .joined(separator: ","))] - """.data(using: .utf8)! - } - - it("decodes valid data correctly") { - let result = Promise.value((responseInfo, data)) - .decoded(as: [ - OpenGroupAPI.BatchSubResponse.self, - OpenGroupAPI.BatchSubResponse.self - ]) - - expect(result.value).toNot(beNil()) - expect((result.value?[0].1 as? OpenGroupAPI.BatchSubResponse)?.body) - .to(equal(capabilities)) - expect((result.value?[1].1 as? OpenGroupAPI.BatchSubResponse)?.body) - .to(equal(pinnedMessage)) - } - - it("fails if there is no data") { - let result = Promise.value((responseInfo, nil)).decoded(as: []) - - expect(result.error?.localizedDescription).to(equal(HTTP.Error.parsingFailed.localizedDescription)) - } - - it("fails if the data is not JSON") { - let result = Promise.value((responseInfo, Data([1, 2, 3]))).decoded(as: []) - - expect(result.error?.localizedDescription).to(equal(HTTP.Error.parsingFailed.localizedDescription)) - } - - it("fails if the data is not a JSON array") { - let result = Promise.value((responseInfo, "{}".data(using: .utf8))).decoded(as: []) - - expect(result.error?.localizedDescription).to(equal(HTTP.Error.parsingFailed.localizedDescription)) - } - - it("fails if the JSON array does not have the same number of items as the expected types") { - let result = Promise.value((responseInfo, data)) - .decoded(as: [ - OpenGroupAPI.BatchSubResponse.self, - OpenGroupAPI.BatchSubResponse.self, - OpenGroupAPI.BatchSubResponse.self - ]) - - expect(result.error?.localizedDescription).to(equal(HTTP.Error.parsingFailed.localizedDescription)) - } - - it("fails if one of the JSON array values fails to decode") { - data = """ - [\([ - try! JSONEncoder().encode( - OpenGroupAPI.BatchSubResponse( - code: 200, - headers: [:], - body: capabilities, - failedToParseBody: false - ) - ) - ] - .map { String(data: $0, encoding: .utf8)! } - .joined(separator: ",")),{"test": "test"}] - """.data(using: .utf8)! - let result = Promise.value((responseInfo, data)) - .decoded(as: [ - OpenGroupAPI.BatchSubResponse.self, - OpenGroupAPI.BatchSubResponse.self - ]) - - expect(result.error?.localizedDescription).to(equal(HTTP.Error.parsingFailed.localizedDescription)) + expect(batchRequest.requests[0].method).to(equal(request.method)) + expect(batchRequest.requests[0].path).to(equal(request.urlPathAndParamsString)) + expect(batchRequest.requests[0].headers).to(beNil()) } } } diff --git a/SessionMessagingKitTests/Open Groups/Models/SOGSMessageSpec.swift b/SessionMessagingKitTests/Open Groups/Models/SOGSMessageSpec.swift index 859cff307..176201e26 100644 --- a/SessionMessagingKitTests/Open Groups/Models/SOGSMessageSpec.swift +++ b/SessionMessagingKitTests/Open Groups/Models/SOGSMessageSpec.swift @@ -106,7 +106,7 @@ class SOGSMessageSpec: QuickSpec { expect { try decoder.decode(OpenGroupAPI.Message.self, from: messageData) } - .to(throwError(HTTP.Error.parsingFailed)) + .to(throwError(HTTPError.parsingFailed)) } it("errors if the data is not a base64 encoded string") { @@ -128,7 +128,7 @@ class SOGSMessageSpec: QuickSpec { expect { try decoder.decode(OpenGroupAPI.Message.self, from: messageData) } - .to(throwError(HTTP.Error.parsingFailed)) + .to(throwError(HTTPError.parsingFailed)) } it("errors if the signature is not a base64 encoded string") { @@ -150,7 +150,7 @@ class SOGSMessageSpec: QuickSpec { expect { try decoder.decode(OpenGroupAPI.Message.self, from: messageData) } - .to(throwError(HTTP.Error.parsingFailed)) + .to(throwError(HTTPError.parsingFailed)) } it("errors if the dependencies are not provided to the JSONDecoder") { @@ -159,7 +159,7 @@ class SOGSMessageSpec: QuickSpec { expect { try decoder.decode(OpenGroupAPI.Message.self, from: messageData) } - .to(throwError(HTTP.Error.parsingFailed)) + .to(throwError(HTTPError.parsingFailed)) } it("errors if the session_id value is not valid") { @@ -181,7 +181,7 @@ class SOGSMessageSpec: QuickSpec { expect { try decoder.decode(OpenGroupAPI.Message.self, from: messageData) } - .to(throwError(HTTP.Error.parsingFailed)) + .to(throwError(HTTPError.parsingFailed)) } @@ -239,7 +239,7 @@ class SOGSMessageSpec: QuickSpec { expect { try decoder.decode(OpenGroupAPI.Message.self, from: messageData) } - .to(throwError(HTTP.Error.parsingFailed)) + .to(throwError(HTTPError.parsingFailed)) } } @@ -274,7 +274,7 @@ class SOGSMessageSpec: QuickSpec { expect { try decoder.decode(OpenGroupAPI.Message.self, from: messageData) } - .to(throwError(HTTP.Error.parsingFailed)) + .to(throwError(HTTPError.parsingFailed)) } } } diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift b/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift index 47e461442..e0f46abc9 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift +++ b/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift @@ -25,8 +25,8 @@ class OpenGroupAPISpec: QuickSpec { var mockNonce24Generator: MockNonce24Generator! var dependencies: SMKDependencies! - var response: (OnionRequestResponseInfoType, Codable)? = nil - var pollResponse: [OpenGroupAPI.Endpoint: (OnionRequestResponseInfoType, Codable?)]? + var response: (ResponseInfoType, Codable)? = nil + var pollResponse: [OpenGroupAPI.Endpoint: (ResponseInfoType, Codable?)]? var error: Error? describe("an OpenGroupAPI") { @@ -136,7 +136,7 @@ class OpenGroupAPISpec: QuickSpec { override class var mockResponse: Data? { let responses: [Data] = [ try! JSONEncoder().encode( - OpenGroupAPI.BatchSubResponse( + HTTP.BatchSubResponse( code: 200, headers: [:], body: OpenGroupAPI.Capabilities(capabilities: [], missing: nil), @@ -144,7 +144,7 @@ class OpenGroupAPISpec: QuickSpec { ) ), try! JSONEncoder().encode( - OpenGroupAPI.BatchSubResponse( + HTTP.BatchSubResponse( code: 200, headers: [:], body: try! JSONDecoder().decode( @@ -163,7 +163,7 @@ class OpenGroupAPISpec: QuickSpec { ) ), try! JSONEncoder().encode( - OpenGroupAPI.BatchSubResponse( + HTTP.BatchSubResponse( code: 200, headers: [:], body: [OpenGroupAPI.Message](), @@ -212,7 +212,6 @@ class OpenGroupAPISpec: QuickSpec { let requestData: TestOnionRequestAPI.RequestData? = (pollResponse?[.capabilities]?.0 as? TestOnionRequestAPI.ResponseInfo)?.requestData expect(requestData?.urlString).to(equal("testserver/batch")) expect(requestData?.httpMethod).to(equal("POST")) - expect(requestData?.server).to(equal("testserver")) expect(requestData?.publicKey).to(equal("88672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b")) } @@ -369,7 +368,7 @@ class OpenGroupAPISpec: QuickSpec { override class var mockResponse: Data? { let responses: [Data] = [ try! JSONEncoder().encode( - OpenGroupAPI.BatchSubResponse( + HTTP.BatchSubResponse( code: 200, headers: [:], body: OpenGroupAPI.Capabilities(capabilities: [], missing: nil), @@ -377,7 +376,7 @@ class OpenGroupAPISpec: QuickSpec { ) ), try! JSONEncoder().encode( - OpenGroupAPI.BatchSubResponse( + HTTP.BatchSubResponse( code: 200, headers: [:], body: try! JSONDecoder().decode( @@ -396,7 +395,7 @@ class OpenGroupAPISpec: QuickSpec { ) ), try! JSONEncoder().encode( - OpenGroupAPI.BatchSubResponse( + HTTP.BatchSubResponse( code: 200, headers: [:], body: [OpenGroupAPI.Message](), @@ -404,7 +403,7 @@ class OpenGroupAPISpec: QuickSpec { ) ), try! JSONEncoder().encode( - OpenGroupAPI.BatchSubResponse( + HTTP.BatchSubResponse( code: 200, headers: [:], body: [OpenGroupAPI.DirectMessage](), @@ -412,7 +411,7 @@ class OpenGroupAPISpec: QuickSpec { ) ), try! JSONEncoder().encode( - OpenGroupAPI.BatchSubResponse( + HTTP.BatchSubResponse( code: 200, headers: [:], body: [OpenGroupAPI.DirectMessage](), @@ -575,7 +574,7 @@ class OpenGroupAPISpec: QuickSpec { override class var mockResponse: Data? { let responses: [Data] = [ try! JSONEncoder().encode( - OpenGroupAPI.BatchSubResponse( + HTTP.BatchSubResponse( code: 200, headers: [:], body: OpenGroupAPI.Capabilities(capabilities: [], missing: nil), @@ -583,7 +582,7 @@ class OpenGroupAPISpec: QuickSpec { ) ), try! JSONEncoder().encode( - OpenGroupAPI.BatchSubResponse( + HTTP.BatchSubResponse( code: 200, headers: [:], body: OpenGroupAPI.PinnedMessage(id: 1, pinnedAt: 1, pinnedBy: ""), @@ -591,7 +590,7 @@ class OpenGroupAPISpec: QuickSpec { ) ), try! JSONEncoder().encode( - OpenGroupAPI.BatchSubResponse( + HTTP.BatchSubResponse( code: 200, headers: [:], body: OpenGroupAPI.PinnedMessage(id: 1, pinnedAt: 1, pinnedBy: ""), @@ -626,9 +625,9 @@ class OpenGroupAPISpec: QuickSpec { ) expect(error?.localizedDescription).to(beNil()) - let capabilitiesResponse: OpenGroupAPI.BatchSubResponse? = (pollResponse?[.capabilities]?.1 as? OpenGroupAPI.BatchSubResponse) - let pollInfoResponse: OpenGroupAPI.BatchSubResponse? = (pollResponse?[.roomPollInfo("testRoom", 0)]?.1 as? OpenGroupAPI.BatchSubResponse) - let messagesResponse: OpenGroupAPI.BatchSubResponse<[Failable]>? = (pollResponse?[.roomMessagesRecent("testRoom")]?.1 as? OpenGroupAPI.BatchSubResponse<[Failable]>) + let capabilitiesResponse: HTTP.BatchSubResponse? = (pollResponse?[.capabilities]?.1 as? HTTP.BatchSubResponse) + let pollInfoResponse: HTTP.BatchSubResponse? = (pollResponse?[.roomPollInfo("testRoom", 0)]?.1 as? HTTP.BatchSubResponse) + let messagesResponse: HTTP.BatchSubResponse<[Failable]>? = (pollResponse?[.roomMessagesRecent("testRoom")]?.1 as? HTTP.BatchSubResponse<[Failable]>) expect(capabilitiesResponse?.failedToParseBody).to(beFalse()) expect(pollInfoResponse?.failedToParseBody).to(beTrue()) expect(messagesResponse?.failedToParseBody).to(beTrue()) @@ -651,7 +650,7 @@ class OpenGroupAPISpec: QuickSpec { expect(error?.localizedDescription) .toEventually( - equal(HTTP.Error.parsingFailed.localizedDescription), + equal(HTTPError.parsingFailed.localizedDescription), timeout: .milliseconds(100) ) @@ -680,7 +679,7 @@ class OpenGroupAPISpec: QuickSpec { expect(error?.localizedDescription) .toEventually( - equal(HTTP.Error.parsingFailed.localizedDescription), + equal(HTTPError.parsingFailed.localizedDescription), timeout: .milliseconds(100) ) @@ -709,7 +708,7 @@ class OpenGroupAPISpec: QuickSpec { expect(error?.localizedDescription) .toEventually( - equal(HTTP.Error.parsingFailed.localizedDescription), + equal(HTTPError.parsingFailed.localizedDescription), timeout: .milliseconds(100) ) @@ -738,7 +737,7 @@ class OpenGroupAPISpec: QuickSpec { expect(error?.localizedDescription) .toEventually( - equal(HTTP.Error.parsingFailed.localizedDescription), + equal(HTTPError.parsingFailed.localizedDescription), timeout: .milliseconds(100) ) @@ -750,7 +749,7 @@ class OpenGroupAPISpec: QuickSpec { override class var mockResponse: Data? { let responses: [Data] = [ try! JSONEncoder().encode( - OpenGroupAPI.BatchSubResponse( + HTTP.BatchSubResponse( code: 200, headers: [:], body: OpenGroupAPI.Capabilities(capabilities: [], missing: nil), @@ -758,7 +757,7 @@ class OpenGroupAPISpec: QuickSpec { ) ), try! JSONEncoder().encode( - OpenGroupAPI.BatchSubResponse( + HTTP.BatchSubResponse( code: 200, headers: [:], body: try! JSONDecoder().decode( @@ -799,7 +798,7 @@ class OpenGroupAPISpec: QuickSpec { expect(error?.localizedDescription) .toEventually( - equal(HTTP.Error.parsingFailed.localizedDescription), + equal(HTTPError.parsingFailed.localizedDescription), timeout: .milliseconds(100) ) @@ -819,7 +818,7 @@ class OpenGroupAPISpec: QuickSpec { } dependencies = dependencies.with(onionApi: TestApi.self) - var response: (info: OnionRequestResponseInfoType, data: OpenGroupAPI.Capabilities)? + var response: (info: ResponseInfoType, data: OpenGroupAPI.Capabilities)? mockStorage .read { db in @@ -846,7 +845,6 @@ class OpenGroupAPISpec: QuickSpec { // Validate request data let requestData: TestOnionRequestAPI.RequestData? = (response?.info as? TestOnionRequestAPI.ResponseInfo)?.requestData expect(requestData?.httpMethod).to(equal("GET")) - expect(requestData?.server).to(equal("testserver")) expect(requestData?.urlString).to(equal("testserver/capabilities")) } } @@ -890,7 +888,7 @@ class OpenGroupAPISpec: QuickSpec { } dependencies = dependencies.with(onionApi: TestApi.self) - var response: (info: OnionRequestResponseInfoType, data: [OpenGroupAPI.Room])? + var response: (info: ResponseInfoType, data: [OpenGroupAPI.Room])? mockStorage .read { db in @@ -917,7 +915,6 @@ class OpenGroupAPISpec: QuickSpec { // Validate request data let requestData: TestOnionRequestAPI.RequestData? = (response?.info as? TestOnionRequestAPI.ResponseInfo)?.requestData expect(requestData?.httpMethod).to(equal("GET")) - expect(requestData?.server).to(equal("testserver")) expect(requestData?.urlString).to(equal("testserver/rooms")) } } @@ -960,7 +957,7 @@ class OpenGroupAPISpec: QuickSpec { override class var mockResponse: Data? { let responses: [Data] = [ try! JSONEncoder().encode( - OpenGroupAPI.BatchSubResponse( + HTTP.BatchSubResponse( code: 200, headers: [:], body: capabilitiesData, @@ -968,7 +965,7 @@ class OpenGroupAPISpec: QuickSpec { ) ), try! JSONEncoder().encode( - OpenGroupAPI.BatchSubResponse( + HTTP.BatchSubResponse( code: 200, headers: [:], body: roomData, @@ -982,7 +979,7 @@ class OpenGroupAPISpec: QuickSpec { } dependencies = dependencies.with(onionApi: TestApi.self) - var response: (capabilities: (info: OnionRequestResponseInfoType, data: OpenGroupAPI.Capabilities?), room: (info: OnionRequestResponseInfoType, data: OpenGroupAPI.Room?))? + var response: (capabilities: (info: ResponseInfoType, data: OpenGroupAPI.Capabilities?), room: (info: ResponseInfoType, data: OpenGroupAPI.Room?))? mockStorage .read { db in @@ -1011,7 +1008,6 @@ class OpenGroupAPISpec: QuickSpec { // Validate request data let requestData: TestOnionRequestAPI.RequestData? = (response?.capabilities.info as? TestOnionRequestAPI.ResponseInfo)?.requestData expect(requestData?.httpMethod).to(equal("POST")) - expect(requestData?.server).to(equal("testserver")) expect(requestData?.urlString).to(equal("testserver/sequence")) } } @@ -1024,7 +1020,7 @@ class OpenGroupAPISpec: QuickSpec { override class var mockResponse: Data? { let responses: [Data] = [ try! JSONEncoder().encode( - OpenGroupAPI.BatchSubResponse( + HTTP.BatchSubResponse( code: 200, headers: [:], body: capabilitiesData, @@ -1038,7 +1034,7 @@ class OpenGroupAPISpec: QuickSpec { } dependencies = dependencies.with(onionApi: TestApi.self) - var response: (capabilities: (info: OnionRequestResponseInfoType, data: OpenGroupAPI.Capabilities?), room: (info: OnionRequestResponseInfoType, data: OpenGroupAPI.Room?))? + var response: (capabilities: (info: ResponseInfoType, data: OpenGroupAPI.Capabilities?), room: (info: ResponseInfoType, data: OpenGroupAPI.Room?))? mockStorage .read { db in @@ -1056,7 +1052,7 @@ class OpenGroupAPISpec: QuickSpec { expect(error?.localizedDescription) .toEventually( - equal(HTTP.Error.parsingFailed.localizedDescription), + equal(HTTPError.parsingFailed.localizedDescription), timeout: .milliseconds(100) ) @@ -1096,7 +1092,7 @@ class OpenGroupAPISpec: QuickSpec { override class var mockResponse: Data? { let responses: [Data] = [ try! JSONEncoder().encode( - OpenGroupAPI.BatchSubResponse( + HTTP.BatchSubResponse( code: 200, headers: [:], body: roomData, @@ -1110,7 +1106,7 @@ class OpenGroupAPISpec: QuickSpec { } dependencies = dependencies.with(onionApi: TestApi.self) - var response: (capabilities: (info: OnionRequestResponseInfoType, data: OpenGroupAPI.Capabilities?), room: (info: OnionRequestResponseInfoType, data: OpenGroupAPI.Room?))? + var response: (capabilities: (info: ResponseInfoType, data: OpenGroupAPI.Capabilities?), room: (info: ResponseInfoType, data: OpenGroupAPI.Room?))? mockStorage .read { db in @@ -1128,7 +1124,7 @@ class OpenGroupAPISpec: QuickSpec { expect(error?.localizedDescription) .toEventually( - equal(HTTP.Error.parsingFailed.localizedDescription), + equal(HTTPError.parsingFailed.localizedDescription), timeout: .milliseconds(100) ) @@ -1169,7 +1165,7 @@ class OpenGroupAPISpec: QuickSpec { override class var mockResponse: Data? { let responses: [Data] = [ try! JSONEncoder().encode( - OpenGroupAPI.BatchSubResponse( + HTTP.BatchSubResponse( code: 200, headers: [:], body: capabilitiesData, @@ -1177,7 +1173,7 @@ class OpenGroupAPISpec: QuickSpec { ) ), try! JSONEncoder().encode( - OpenGroupAPI.BatchSubResponse( + HTTP.BatchSubResponse( code: 200, headers: [:], body: roomData, @@ -1185,7 +1181,7 @@ class OpenGroupAPISpec: QuickSpec { ) ), try! JSONEncoder().encode( - OpenGroupAPI.BatchSubResponse( + HTTP.BatchSubResponse( code: 200, headers: [:], body: OpenGroupAPI.PinnedMessage(id: 1, pinnedAt: 1, pinnedBy: ""), @@ -1199,7 +1195,7 @@ class OpenGroupAPISpec: QuickSpec { } dependencies = dependencies.with(onionApi: TestApi.self) - var response: (capabilities: (info: OnionRequestResponseInfoType, data: OpenGroupAPI.Capabilities?), room: (info: OnionRequestResponseInfoType, data: OpenGroupAPI.Room?))? + var response: (capabilities: (info: ResponseInfoType, data: OpenGroupAPI.Capabilities?), room: (info: ResponseInfoType, data: OpenGroupAPI.Room?))? mockStorage .read { db in @@ -1216,7 +1212,7 @@ class OpenGroupAPISpec: QuickSpec { expect(error?.localizedDescription) .toEventually( - equal(HTTP.Error.parsingFailed.localizedDescription), + equal(HTTPError.parsingFailed.localizedDescription), timeout: .milliseconds(100) ) @@ -1258,7 +1254,7 @@ class OpenGroupAPISpec: QuickSpec { } it("correctly sends the message") { - var response: (info: OnionRequestResponseInfoType, data: OpenGroupAPI.Message)? + var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage .read { db in @@ -1291,7 +1287,6 @@ class OpenGroupAPISpec: QuickSpec { // Validate signature headers let requestData: TestOnionRequestAPI.RequestData? = (response?.0 as? TestOnionRequestAPI.ResponseInfo)?.requestData expect(requestData?.httpMethod).to(equal("POST")) - expect(requestData?.server).to(equal("testServer")) expect(requestData?.urlString).to(equal("testServer/room/testRoom/message")) } @@ -1304,7 +1299,7 @@ class OpenGroupAPISpec: QuickSpec { } it("signs the message correctly") { - var response: (info: OnionRequestResponseInfoType, data: OpenGroupAPI.Message)? + var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage .read { db in @@ -1344,7 +1339,7 @@ class OpenGroupAPISpec: QuickSpec { _ = try OpenGroup.deleteAll(db) } - var response: (info: OnionRequestResponseInfoType, data: OpenGroupAPI.Message)? + var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage .read { db in @@ -1379,7 +1374,7 @@ class OpenGroupAPISpec: QuickSpec { _ = try Identity.filter(id: .x25519PrivateKey).deleteAll(db) } - var response: (info: OnionRequestResponseInfoType, data: OpenGroupAPI.Message)? + var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage .read { db in @@ -1412,7 +1407,7 @@ class OpenGroupAPISpec: QuickSpec { mockEd25519.reset() // The 'keyPair' value doesn't equate so have to explicitly reset mockEd25519.when { try $0.sign(data: anyArray(), keyPair: any()) }.thenReturn(nil) - var response: (info: OnionRequestResponseInfoType, data: OpenGroupAPI.Message)? + var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage .read { db in @@ -1452,7 +1447,7 @@ class OpenGroupAPISpec: QuickSpec { } it("signs the message correctly") { - var response: (info: OnionRequestResponseInfoType, data: OpenGroupAPI.Message)? + var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage .read { db in @@ -1492,7 +1487,7 @@ class OpenGroupAPISpec: QuickSpec { _ = try OpenGroup.deleteAll(db) } - var response: (info: OnionRequestResponseInfoType, data: OpenGroupAPI.Message)? + var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage .read { db in @@ -1527,7 +1522,7 @@ class OpenGroupAPISpec: QuickSpec { _ = try Identity.filter(id: .ed25519SecretKey).deleteAll(db) } - var response: (info: OnionRequestResponseInfoType, data: OpenGroupAPI.Message)? + var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage .read { db in @@ -1568,7 +1563,7 @@ class OpenGroupAPISpec: QuickSpec { } .thenReturn(nil) - var response: (info: OnionRequestResponseInfoType, data: OpenGroupAPI.Message)? + var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage .read { db in @@ -1621,7 +1616,7 @@ class OpenGroupAPISpec: QuickSpec { } dependencies = dependencies.with(onionApi: TestApi.self) - var response: (info: OnionRequestResponseInfoType, data: OpenGroupAPI.Message)? + var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage .read { db in @@ -1651,7 +1646,6 @@ class OpenGroupAPISpec: QuickSpec { // Validate request data let requestData: TestOnionRequestAPI.RequestData? = (response?.info as? TestOnionRequestAPI.ResponseInfo)?.requestData expect(requestData?.httpMethod).to(equal("GET")) - expect(requestData?.server).to(equal("testserver")) expect(requestData?.urlString).to(equal("testserver/room/testRoom/message/123")) } } @@ -1674,7 +1668,7 @@ class OpenGroupAPISpec: QuickSpec { } it("correctly sends the update") { - var response: (info: OnionRequestResponseInfoType, data: Data?)? + var response: (info: ResponseInfoType, data: Data?)? mockStorage .read { db in @@ -1703,7 +1697,6 @@ class OpenGroupAPISpec: QuickSpec { // Validate signature headers let requestData: TestOnionRequestAPI.RequestData? = (response?.0 as? TestOnionRequestAPI.ResponseInfo)?.requestData expect(requestData?.httpMethod).to(equal("PUT")) - expect(requestData?.server).to(equal("testserver")) expect(requestData?.urlString).to(equal("testserver/room/testRoom/message/123")) } @@ -1716,7 +1709,7 @@ class OpenGroupAPISpec: QuickSpec { } it("signs the message correctly") { - var response: (info: OnionRequestResponseInfoType, data: Data?)? + var response: (info: ResponseInfoType, data: Data?)? mockStorage .read { db in @@ -1755,7 +1748,7 @@ class OpenGroupAPISpec: QuickSpec { _ = try OpenGroup.deleteAll(db) } - var response: (info: OnionRequestResponseInfoType, data: Data?)? + var response: (info: ResponseInfoType, data: Data?)? mockStorage .read { db in @@ -1789,7 +1782,7 @@ class OpenGroupAPISpec: QuickSpec { _ = try Identity.filter(id: .x25519PrivateKey).deleteAll(db) } - var response: (info: OnionRequestResponseInfoType, data: Data?)? + var response: (info: ResponseInfoType, data: Data?)? mockStorage .read { db in @@ -1821,7 +1814,7 @@ class OpenGroupAPISpec: QuickSpec { mockEd25519.reset() // The 'keyPair' value doesn't equate so have to explicitly reset mockEd25519.when { try $0.sign(data: anyArray(), keyPair: any()) }.thenReturn(nil) - var response: (info: OnionRequestResponseInfoType, data: Data?)? + var response: (info: ResponseInfoType, data: Data?)? mockStorage .read { db in @@ -1860,7 +1853,7 @@ class OpenGroupAPISpec: QuickSpec { } it("signs the message correctly") { - var response: (info: OnionRequestResponseInfoType, data: Data?)? + var response: (info: ResponseInfoType, data: Data?)? mockStorage .read { db in @@ -1899,7 +1892,7 @@ class OpenGroupAPISpec: QuickSpec { _ = try OpenGroup.deleteAll(db) } - var response: (info: OnionRequestResponseInfoType, data: Data?)? + var response: (info: ResponseInfoType, data: Data?)? mockStorage .read { db in @@ -1933,7 +1926,7 @@ class OpenGroupAPISpec: QuickSpec { _ = try Identity.filter(id: .ed25519SecretKey).deleteAll(db) } - var response: (info: OnionRequestResponseInfoType, data: Data?)? + var response: (info: ResponseInfoType, data: Data?)? mockStorage .read { db in @@ -1973,7 +1966,7 @@ class OpenGroupAPISpec: QuickSpec { } .thenReturn(nil) - var response: (info: OnionRequestResponseInfoType, data: Data?)? + var response: (info: ResponseInfoType, data: Data?)? mockStorage .read { db in @@ -2010,7 +2003,7 @@ class OpenGroupAPISpec: QuickSpec { } dependencies = dependencies.with(onionApi: TestApi.self) - var response: (info: OnionRequestResponseInfoType, data: Data?)? + var response: (info: ResponseInfoType, data: Data?)? mockStorage .read { db in @@ -2037,13 +2030,12 @@ class OpenGroupAPISpec: QuickSpec { // Validate request data let requestData: TestOnionRequestAPI.RequestData? = (response?.info as? TestOnionRequestAPI.ResponseInfo)?.requestData expect(requestData?.httpMethod).to(equal("DELETE")) - expect(requestData?.server).to(equal("testserver")) expect(requestData?.urlString).to(equal("testserver/room/testRoom/message/123")) } } context("when deleting all messages for a user") { - var response: (info: OnionRequestResponseInfoType, data: Data?)? + var response: (info: ResponseInfoType, data: Data?)? beforeEach { class TestApi: TestOnionRequestAPI { @@ -2082,7 +2074,6 @@ class OpenGroupAPISpec: QuickSpec { // Validate request data let requestData: TestOnionRequestAPI.RequestData? = (response?.info as? TestOnionRequestAPI.ResponseInfo)?.requestData expect(requestData?.httpMethod).to(equal("DELETE")) - expect(requestData?.server).to(equal("testserver")) expect(requestData?.urlString).to(equal("testserver/room/testRoom/all/testUserId")) } } @@ -2096,7 +2087,7 @@ class OpenGroupAPISpec: QuickSpec { } dependencies = dependencies.with(onionApi: TestApi.self) - var response: OnionRequestResponseInfoType? + var response: ResponseInfoType? mockStorage .read { db in @@ -2123,7 +2114,6 @@ class OpenGroupAPISpec: QuickSpec { // Validate request data let requestData: TestOnionRequestAPI.RequestData? = (response as? TestOnionRequestAPI.ResponseInfo)?.requestData expect(requestData?.httpMethod).to(equal("POST")) - expect(requestData?.server).to(equal("testserver")) expect(requestData?.urlString).to(equal("testserver/room/testRoom/pin/123")) } } @@ -2135,7 +2125,7 @@ class OpenGroupAPISpec: QuickSpec { } dependencies = dependencies.with(onionApi: TestApi.self) - var response: OnionRequestResponseInfoType? + var response: ResponseInfoType? mockStorage .read { db in @@ -2162,7 +2152,6 @@ class OpenGroupAPISpec: QuickSpec { // Validate request data let requestData: TestOnionRequestAPI.RequestData? = (response as? TestOnionRequestAPI.ResponseInfo)?.requestData expect(requestData?.httpMethod).to(equal("POST")) - expect(requestData?.server).to(equal("testserver")) expect(requestData?.urlString).to(equal("testserver/room/testRoom/unpin/123")) } } @@ -2174,7 +2163,7 @@ class OpenGroupAPISpec: QuickSpec { } dependencies = dependencies.with(onionApi: TestApi.self) - var response: OnionRequestResponseInfoType? + var response: ResponseInfoType? mockStorage .read { db in @@ -2200,7 +2189,6 @@ class OpenGroupAPISpec: QuickSpec { // Validate request data let requestData: TestOnionRequestAPI.RequestData? = (response as? TestOnionRequestAPI.ResponseInfo)?.requestData expect(requestData?.httpMethod).to(equal("POST")) - expect(requestData?.server).to(equal("testserver")) expect(requestData?.urlString).to(equal("testserver/room/testRoom/unpin/all")) } } @@ -2241,7 +2229,6 @@ class OpenGroupAPISpec: QuickSpec { // Validate signature headers let requestData: TestOnionRequestAPI.RequestData? = (response?.0 as? TestOnionRequestAPI.ResponseInfo)?.requestData expect(requestData?.httpMethod).to(equal("POST")) - expect(requestData?.server).to(equal("testserver")) expect(requestData?.urlString).to(equal("testserver/room/testRoom/file")) } @@ -2277,7 +2264,7 @@ class OpenGroupAPISpec: QuickSpec { // Validate signature headers let requestData: TestOnionRequestAPI.RequestData? = (response?.0 as? TestOnionRequestAPI.ResponseInfo)?.requestData - expect(requestData?.headers[Header.contentDisposition.rawValue]) + expect(requestData?.headers[HTTPHeader.contentDisposition]) .toNot(contain("filename")) } @@ -2314,7 +2301,7 @@ class OpenGroupAPISpec: QuickSpec { // Validate signature headers let requestData: TestOnionRequestAPI.RequestData? = (response?.0 as? TestOnionRequestAPI.ResponseInfo)?.requestData - expect(requestData?.headers[Header.contentDisposition.rawValue]).to(contain("TestFileName")) + expect(requestData?.headers[HTTPHeader.contentDisposition]).to(contain("TestFileName")) } } @@ -2352,7 +2339,6 @@ class OpenGroupAPISpec: QuickSpec { // Validate signature headers let requestData: TestOnionRequestAPI.RequestData? = (response?.0 as? TestOnionRequestAPI.ResponseInfo)?.requestData expect(requestData?.httpMethod).to(equal("GET")) - expect(requestData?.server).to(equal("testserver")) expect(requestData?.urlString).to(equal("testserver/room/testRoom/file/1")) } } @@ -2383,7 +2369,7 @@ class OpenGroupAPISpec: QuickSpec { } it("correctly sends the message request") { - var response: (info: OnionRequestResponseInfoType, data: OpenGroupAPI.SendDirectMessageResponse)? + var response: (info: ResponseInfoType, data: OpenGroupAPI.SendDirectMessageResponse)? mockStorage .read { db in @@ -2413,7 +2399,6 @@ class OpenGroupAPISpec: QuickSpec { // Validate signature headers let requestData: TestOnionRequestAPI.RequestData? = (response?.0 as? TestOnionRequestAPI.ResponseInfo)?.requestData expect(requestData?.httpMethod).to(equal("POST")) - expect(requestData?.server).to(equal("testserver")) expect(requestData?.urlString).to(equal("testserver/inbox/testUserId")) } } @@ -2421,7 +2406,7 @@ class OpenGroupAPISpec: QuickSpec { // MARK: - Users context("when banning a user") { - var response: (info: OnionRequestResponseInfoType, data: Data?)? + var response: (info: ResponseInfoType, data: Data?)? beforeEach { class TestApi: TestOnionRequestAPI { @@ -2461,7 +2446,6 @@ class OpenGroupAPISpec: QuickSpec { // Validate request data let requestData: TestOnionRequestAPI.RequestData? = (response?.info as? TestOnionRequestAPI.ResponseInfo)?.requestData expect(requestData?.httpMethod).to(equal("POST")) - expect(requestData?.server).to(equal("testserver")) expect(requestData?.urlString).to(equal("testserver/user/testUserId/ban")) } @@ -2531,7 +2515,7 @@ class OpenGroupAPISpec: QuickSpec { } context("when unbanning a user") { - var response: (info: OnionRequestResponseInfoType, data: Data?)? + var response: (info: ResponseInfoType, data: Data?)? beforeEach { class TestApi: TestOnionRequestAPI { @@ -2570,7 +2554,6 @@ class OpenGroupAPISpec: QuickSpec { // Validate request data let requestData: TestOnionRequestAPI.RequestData? = (response?.info as? TestOnionRequestAPI.ResponseInfo)?.requestData expect(requestData?.httpMethod).to(equal("POST")) - expect(requestData?.server).to(equal("testserver")) expect(requestData?.urlString).to(equal("testserver/user/testUserId/unban")) } @@ -2638,7 +2621,7 @@ class OpenGroupAPISpec: QuickSpec { } context("when updating a users permissions") { - var response: (info: OnionRequestResponseInfoType, data: Data?)? + var response: (info: ResponseInfoType, data: Data?)? beforeEach { class TestApi: TestOnionRequestAPI { @@ -2680,7 +2663,6 @@ class OpenGroupAPISpec: QuickSpec { // Validate request data let requestData: TestOnionRequestAPI.RequestData? = (response?.info as? TestOnionRequestAPI.ResponseInfo)?.requestData expect(requestData?.httpMethod).to(equal("POST")) - expect(requestData?.server).to(equal("testserver")) expect(requestData?.urlString).to(equal("testserver/user/testUserId/moderator")) } @@ -2773,7 +2755,7 @@ class OpenGroupAPISpec: QuickSpec { expect(error?.localizedDescription) .toEventually( - equal(HTTP.Error.generic.localizedDescription), + equal(HTTPError.generic.localizedDescription), timeout: .milliseconds(100) ) @@ -2782,14 +2764,14 @@ class OpenGroupAPISpec: QuickSpec { } context("when banning and deleting all messages for a user") { - var response: [OnionRequestResponseInfoType]? + var response: [ResponseInfoType]? beforeEach { class TestApi: TestOnionRequestAPI { override class var mockResponse: Data? { let responses: [Data] = [ try! JSONEncoder().encode( - OpenGroupAPI.BatchSubResponse( + HTTP.BatchSubResponse( code: 200, headers: [:], body: nil, @@ -2797,7 +2779,7 @@ class OpenGroupAPISpec: QuickSpec { ) ), try! JSONEncoder().encode( - OpenGroupAPI.BatchSubResponse( + HTTP.BatchSubResponse( code: 200, headers: [:], body: nil, @@ -2842,7 +2824,6 @@ class OpenGroupAPISpec: QuickSpec { // Validate request data let requestData: TestOnionRequestAPI.RequestData? = (response?.first as? TestOnionRequestAPI.ResponseInfo)?.requestData expect(requestData?.httpMethod).to(equal("POST")) - expect(requestData?.server).to(equal("testserver")) expect(requestData?.urlString).to(equal("testserver/sequence")) } @@ -3013,14 +2994,13 @@ class OpenGroupAPISpec: QuickSpec { let requestData: TestOnionRequestAPI.RequestData? = (response?.0 as? TestOnionRequestAPI.ResponseInfo)?.requestData expect(requestData?.urlString).to(equal("testserver/rooms")) expect(requestData?.httpMethod).to(equal("GET")) - expect(requestData?.server).to(equal("testserver")) expect(requestData?.publicKey).to(equal("88672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b")) expect(requestData?.headers).to(haveCount(4)) - expect(requestData?.headers[Header.sogsPubKey.rawValue]) + expect(requestData?.headers[HTTPHeader.sogsPubKey]) .to(equal("00bac6e71efd7dfa4a83c98ed24f254ab2c267f9ccdb172a5280a0444ad24e89cc")) - expect(requestData?.headers[Header.sogsTimestamp.rawValue]).to(equal("1234567890")) - expect(requestData?.headers[Header.sogsNonce.rawValue]).to(equal("pK6YRtQApl4NhECGizF0Cg==")) - expect(requestData?.headers[Header.sogsSignature.rawValue]).to(equal("TestSignature".bytes.toBase64())) + expect(requestData?.headers[HTTPHeader.sogsTimestamp]).to(equal("1234567890")) + expect(requestData?.headers[HTTPHeader.sogsNonce]).to(equal("pK6YRtQApl4NhECGizF0Cg==")) + expect(requestData?.headers[HTTPHeader.sogsSignature]).to(equal("TestSignature".bytes.toBase64())) } it("fails when the signature is not generated") { @@ -3083,13 +3063,12 @@ class OpenGroupAPISpec: QuickSpec { let requestData: TestOnionRequestAPI.RequestData? = (response?.0 as? TestOnionRequestAPI.ResponseInfo)?.requestData expect(requestData?.urlString).to(equal("testserver/rooms")) expect(requestData?.httpMethod).to(equal("GET")) - expect(requestData?.server).to(equal("testserver")) expect(requestData?.publicKey).to(equal("88672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b")) expect(requestData?.headers).to(haveCount(4)) - expect(requestData?.headers[Header.sogsPubKey.rawValue]).to(equal("1588672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b")) - expect(requestData?.headers[Header.sogsTimestamp.rawValue]).to(equal("1234567890")) - expect(requestData?.headers[Header.sogsNonce.rawValue]).to(equal("pK6YRtQApl4NhECGizF0Cg==")) - expect(requestData?.headers[Header.sogsSignature.rawValue]).to(equal("TestSogsSignature".bytes.toBase64())) + expect(requestData?.headers[HTTPHeader.sogsPubKey]).to(equal("1588672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b")) + expect(requestData?.headers[HTTPHeader.sogsTimestamp]).to(equal("1234567890")) + expect(requestData?.headers[HTTPHeader.sogsNonce]).to(equal("pK6YRtQApl4NhECGizF0Cg==")) + expect(requestData?.headers[HTTPHeader.sogsSignature]).to(equal("TestSogsSignature".bytes.toBase64())) } it("fails when the blindedKeyPair is not generated") { diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift index 03618a3b5..ac36d002e 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift +++ b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift @@ -47,7 +47,7 @@ class OpenGroupManagerSpec: QuickSpec { override class var mockResponse: Data? { let responses: [Data] = [ try! JSONEncoder().encode( - OpenGroupAPI.BatchSubResponse( + HTTP.BatchSubResponse( code: 200, headers: [:], body: capabilitiesData, @@ -55,7 +55,7 @@ class OpenGroupManagerSpec: QuickSpec { ) ), try! JSONEncoder().encode( - OpenGroupAPI.BatchSubResponse( + HTTP.BatchSubResponse( code: 200, headers: [:], body: roomData, @@ -917,7 +917,7 @@ class OpenGroupManagerSpec: QuickSpec { expect(error?.localizedDescription) .toEventually( - equal(HTTP.Error.parsingFailed.localizedDescription), + equal(HTTPError.parsingFailed.localizedDescription), timeout: .milliseconds(50) ) } @@ -1953,7 +1953,7 @@ class OpenGroupManagerSpec: QuickSpec { var didComplete: Bool = false // Prevent multi-threading test bugs mockOGMCache.when { $0.groupImagePromises } - .thenReturn([OpenGroup.idFor(roomToken: "testRoom", server: "testServer"): Promise(error: HTTP.Error.generic)]) + .thenReturn([OpenGroup.idFor(roomToken: "testRoom", server: "testServer"): Promise(error: HTTPError.generic)]) testPollInfo = OpenGroupAPI.RoomPollInfo( token: "testRoom", @@ -3194,7 +3194,7 @@ class OpenGroupManagerSpec: QuickSpec { override class var mockResponse: Data? { let responses: [Data] = [ try! JSONEncoder().encode( - OpenGroupAPI.BatchSubResponse( + HTTP.BatchSubResponse( code: 200, headers: [:], body: capabilitiesData, @@ -3202,7 +3202,7 @@ class OpenGroupManagerSpec: QuickSpec { ) ), try! JSONEncoder().encode( - OpenGroupAPI.BatchSubResponse( + HTTP.BatchSubResponse( code: 200, headers: [:], body: roomsData, @@ -3349,7 +3349,7 @@ class OpenGroupManagerSpec: QuickSpec { expect(error?.localizedDescription) .toEventually( - equal(HTTP.Error.invalidResponse.localizedDescription), + equal(HTTPError.invalidResponse.localizedDescription), timeout: .milliseconds(50) ) expect(TestRoomsApi.callCounter).to(equal(9)) // First attempt + 8 retries @@ -3369,7 +3369,7 @@ class OpenGroupManagerSpec: QuickSpec { expect(error?.localizedDescription) .toEventually( - equal(HTTP.Error.invalidResponse.localizedDescription), + equal(HTTPError.invalidResponse.localizedDescription), timeout: .milliseconds(50) ) expect(mockOGMCache) @@ -3414,7 +3414,7 @@ class OpenGroupManagerSpec: QuickSpec { override class var mockResponse: Data? { let responses: [Data] = [ try! JSONEncoder().encode( - OpenGroupAPI.BatchSubResponse( + HTTP.BatchSubResponse( code: 200, headers: [:], body: capabilitiesData, @@ -3422,7 +3422,7 @@ class OpenGroupManagerSpec: QuickSpec { ) ), try! JSONEncoder().encode( - OpenGroupAPI.BatchSubResponse( + HTTP.BatchSubResponse( code: 200, headers: [:], body: roomsData, @@ -3568,12 +3568,12 @@ class OpenGroupManagerSpec: QuickSpec { it("adds the image retrieval promise to the cache") { class TestNeverReturningApi: OnionRequestAPIType { - static func sendOnionRequest(_ request: URLRequest, to server: String, using version: OnionRequestAPIVersion, with x25519PublicKey: String) -> Promise<(OnionRequestResponseInfoType, Data?)> { - return Promise<(OnionRequestResponseInfoType, Data?)>.pending().promise + static func sendOnionRequest(_ request: URLRequest, to server: String, with x25519PublicKey: String) -> Promise<(ResponseInfoType, Data?)> { + return Promise<(ResponseInfoType, Data?)>.pending().promise } - static func sendOnionRequest(to snode: Snode, invoking method: SnodeAPIEndpoint, with parameters: JSON, associatedWith publicKey: String?) -> Promise { - return Promise.value(Data()) + static func sendOnionRequest(_ payload: Data, to snode: Snode) -> Promise<(ResponseInfoType, Data?)> { + return Promise.value((HTTP.ResponseInfo(code: 200, headers: [:]), Data())) } } dependencies = dependencies.with(onionApi: TestNeverReturningApi.self) diff --git a/SessionMessagingKitTests/_TestUtilities/TestOnionRequestAPI.swift b/SessionMessagingKitTests/_TestUtilities/TestOnionRequestAPI.swift index 67b7dde86..ba6e47349 100644 --- a/SessionMessagingKitTests/_TestUtilities/TestOnionRequestAPI.swift +++ b/SessionMessagingKitTests/_TestUtilities/TestOnionRequestAPI.swift @@ -13,14 +13,18 @@ class TestOnionRequestAPI: OnionRequestAPIType { let urlString: String? let httpMethod: String let headers: [String: String] - let snodeMethod: String? let body: Data? + let destination: OnionRequestAPIDestination - let server: String - let version: OnionRequestAPIVersion - let publicKey: String? + var publicKey: String? { + switch destination { + case .snode: return nil + case .server(_, _, let x25519PublicKey, _, _): return x25519PublicKey + } + } } - class ResponseInfo: OnionRequestResponseInfoType { + + class ResponseInfo: ResponseInfoType { let requestData: RequestData let code: Int let headers: [String: String] @@ -34,18 +38,20 @@ class TestOnionRequestAPI: OnionRequestAPIType { class var mockResponse: Data? { return nil } - static func sendOnionRequest(_ request: URLRequest, to server: String, using version: OnionRequestAPIVersion, with x25519PublicKey: String) -> Promise<(OnionRequestResponseInfoType, Data?)> { + static func sendOnionRequest(_ request: URLRequest, to server: String, with x25519PublicKey: String) -> Promise<(ResponseInfoType, Data?)> { let responseInfo: ResponseInfo = ResponseInfo( requestData: RequestData( urlString: request.url?.absoluteString, httpMethod: (request.httpMethod ?? "GET"), headers: (request.allHTTPHeaderFields ?? [:]), - snodeMethod: nil, body: request.httpBody, - - server: server, - version: version, - publicKey: x25519PublicKey + destination: OnionRequestAPIDestination.server( + host: request.url!.host!, + target: OnionRequestAPIVersion.v4.rawValue, + x25519PublicKey: x25519PublicKey, + scheme: request.url!.scheme, + port: request.url!.port.map { UInt16($0) } + ) ), code: 200, headers: [:] @@ -54,7 +60,19 @@ class TestOnionRequestAPI: OnionRequestAPIType { return Promise.value((responseInfo, mockResponse)) } - static func sendOnionRequest(to snode: Snode, invoking method: SnodeAPIEndpoint, with parameters: JSON, associatedWith publicKey: String?) -> Promise { - return Promise.value(mockResponse!) + static func sendOnionRequest(_ payload: Data, to snode: Snode) -> Promise<(ResponseInfoType, Data?)> { + let responseInfo: ResponseInfo = ResponseInfo( + requestData: RequestData( + urlString: "\(snode.address):\(snode.port)/onion_req/v2", + httpMethod: "POST", + headers: [:], + body: payload, + destination: OnionRequestAPIDestination.snode(snode) + ), + code: 200, + headers: [:] + ) + + return Promise.value((responseInfo, mockResponse)) } } diff --git a/SessionSnodeKit/Models/OnionRequestAPIDestination.swift b/SessionSnodeKit/Models/OnionRequestAPIDestination.swift index 8483ce347..235bb817e 100644 --- a/SessionSnodeKit/Models/OnionRequestAPIDestination.swift +++ b/SessionSnodeKit/Models/OnionRequestAPIDestination.swift @@ -2,7 +2,7 @@ import Foundation -public enum OnionRequestAPIDestination: CustomStringConvertible { +public enum OnionRequestAPIDestination: CustomStringConvertible, Codable { case snode(Snode) case server(host: String, target: String, x25519PublicKey: String, scheme: String?, port: UInt16?) diff --git a/SessionSnodeKit/OnionRequestAPI.swift b/SessionSnodeKit/OnionRequestAPI.swift index d2a71b66c..379633e21 100644 --- a/SessionSnodeKit/OnionRequestAPI.swift +++ b/SessionSnodeKit/OnionRequestAPI.swift @@ -393,7 +393,11 @@ public enum OnionRequestAPI: OnionRequestAPIType { } /// Sends an onion request to `server`. Builds new paths as needed. - public static func sendOnionRequest(_ request: URLRequest, to server: String, using version: OnionRequestAPIVersion = .v4, with x25519PublicKey: String) -> Promise<(OnionRequestResponseInfoType, Data?)> { + public static func sendOnionRequest( + _ request: URLRequest, + to server: String, // TODO: Remove this 'server' value (unused) + with x25519PublicKey: String + ) -> Promise<(ResponseInfoType, Data?)> { guard let url = request.url, let host = request.url?.host else { return Promise(error: OnionRequestAPIError.invalidURL) } diff --git a/SessionSnodeKit/SSKDependencies.swift b/SessionSnodeKit/SSKDependencies.swift new file mode 100644 index 000000000..8f271429f --- /dev/null +++ b/SessionSnodeKit/SSKDependencies.swift @@ -0,0 +1,34 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import GRDB +import SessionUtilitiesKit + +open class SSKDependencies: Dependencies { + public var _onionApi: Atomic + public var onionApi: OnionRequestAPIType.Type { + get { Dependencies.getValueSettingIfNull(&_onionApi) { OnionRequestAPI.self } } + set { _onionApi.mutate { $0 = newValue } } + } + + // MARK: - Initialization + + public init( + onionApi: OnionRequestAPIType.Type? = nil, + generalCache: Atomic? = nil, + storage: Storage? = nil, + scheduler: ValueObservationScheduler? = nil, + standardUserDefaults: UserDefaultsType? = nil, + date: Date? = nil + ) { + _onionApi = Atomic(onionApi) + + super.init( + generalCache: generalCache, + storage: storage, + scheduler: scheduler, + standardUserDefaults: standardUserDefaults, + date: date + ) + } +} diff --git a/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift b/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift index 3f23e35f2..abae1c949 100644 --- a/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift +++ b/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift @@ -195,7 +195,7 @@ class ThreadDisappearingMessagesViewModelSpec: QuickSpec { ) ) - viewModel.settingsData.first?.elements.last?.onTap?(nil) + viewModel.tableData.first?.elements.last?.onTap?() } it("shows the save button") { diff --git a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift index 0ecc1d0a6..4069525c8 100644 --- a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift +++ b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift @@ -97,7 +97,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { .first(where: { $0.model == .content })? .elements .first(where: { $0.id == .searchConversation })? - .onTap?(nil) + .onTap?() expect(didTriggerSearchCallbackTriggered).to(beTrue()) } @@ -106,8 +106,8 @@ class ThreadSettingsViewModelSpec: QuickSpec { viewModel.settingsData .first(where: { $0.model == .content })? .elements - .first(where: { $0.id == .notificationMute })? - .onTap?(nil) + .first(where: { $0.id == .notifications })? + .onTap?() expect( mockStorage @@ -208,15 +208,11 @@ class ThreadSettingsViewModelSpec: QuickSpec { context("when entering edit mode") { beforeEach { viewModel.rightNavItems.firstValue()??.first?.action?() - - let leftAccessory: SessionCell.Accessory? = viewModel.settingsData.first? - .elements.first? - .leftAccessory - - switch leftAccessory { - case .threadInfo(_, _, _, _, let titleChanged): titleChanged?("TestNew") - default: break - } + viewModel.textChanged("TestNew", for: .nickname) + // TODO: Enter edit mode by pressing on the first item +// viewModel.tableData.first? +// .elements.first? +// .onTap?() } it("enters the editing state") { @@ -341,15 +337,12 @@ class ThreadSettingsViewModelSpec: QuickSpec { context("when entering edit mode") { beforeEach { viewModel.rightNavItems.firstValue()??.first?.action?() + viewModel.textChanged("TestUserNew", for: .nickname) - let leftAccessory: SessionCell.Accessory? = viewModel.settingsData.first? - .elements.first? - .leftAccessory - - switch leftAccessory { - case .threadInfo(_, _, _, _, let titleChanged): titleChanged?("TestUserNew") - default: break - } + // TODO: Enter edit mode by pressing on the first item +// viewModel.tableData.first? +// .elements.first? +// .onTap?() } it("enters the editing state") { diff --git a/SessionTests/Settings/NotificationContentViewModelSpec.swift b/SessionTests/Settings/NotificationContentViewModelSpec.swift index f683a2a49..6b9b4ef39 100644 --- a/SessionTests/Settings/NotificationContentViewModelSpec.swift +++ b/SessionTests/Settings/NotificationContentViewModelSpec.swift @@ -132,7 +132,7 @@ class NotificationContentViewModelSpec: QuickSpec { context("when tapping an item") { it("updates the saved preference") { - viewModel.settingsData.first?.elements.last?.onTap?(nil) + viewModel.tableData.first?.elements.last?.onTap?() expect(mockStorage[.preferencesNotificationPreviewType]) .to(equal(Preferences.NotificationPreviewType.noNameNoPreview)) @@ -147,7 +147,7 @@ class NotificationContentViewModelSpec: QuickSpec { receiveCompletion: { _ in }, receiveValue: { _ in didDismissScreen = true } ) - viewModel.settingsData.first?.elements.last?.onTap?(nil) + viewModel.tableData.first?.elements.last?.onTap?() expect(didDismissScreen).to(beTrue()) } diff --git a/SessionUtilitiesKit/Networking/BatchResponse.swift b/SessionUtilitiesKit/Networking/BatchResponse.swift new file mode 100644 index 000000000..d0407810f --- /dev/null +++ b/SessionUtilitiesKit/Networking/BatchResponse.swift @@ -0,0 +1,106 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import PromiseKit + +public extension HTTP { + // MARK: - Convenience Aliases + + typealias BatchResponse = [(ResponseInfoType, Codable?)] + typealias BatchResponseTypes = [Codable.Type] + + // MARK: - BatchSubResponse + + struct BatchSubResponse: Codable { + /// The numeric http response code (e.g. 200 for success) + public let code: Int32 + + /// Any headers returned by the request + public let headers: [String: String] + + /// The body of the request; will be plain json if content-type is `application/json`, otherwise it will be base64 encoded data + public let body: T? + + /// A flag to indicate that there was a body but it failed to parse + public let failedToParseBody: Bool + + public init( + code: Int32, + headers: [String: String] = [:], + body: T? = nil, + failedToParseBody: Bool = false + ) { + self.code = code + self.headers = headers + self.body = body + self.failedToParseBody = failedToParseBody + } + } +} + +public extension HTTP.BatchSubResponse { + init(from decoder: Decoder) throws { + let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) + let body: T? = try? container.decode(T.self, forKey: .body) + + self = HTTP.BatchSubResponse( + code: try container.decode(Int32.self, forKey: .code), + headers: ((try? container.decode([String: String].self, forKey: .headers)) ?? [:]), + body: body, + failedToParseBody: ( + body == nil && + T.self != NoResponse.self && + !(T.self is ExpressibleByNilLiteral.Type) + ) + ) + } +} + +// MARK: - Convenience + +public extension Decodable { + static func decoded(from data: Data, using dependencies: Dependencies = Dependencies()) throws -> Self { + return try data.decoded(as: Self.self, using: dependencies) + } +} + +public extension Promise where T == (ResponseInfoType, Data?) { + func decoded(as types: HTTP.BatchResponseTypes, on queue: DispatchQueue? = nil, using dependencies: Dependencies = Dependencies()) -> Promise { + self.map(on: queue) { responseInfo, maybeData -> HTTP.BatchResponse in + // Need to split the data into an array of data so each item can be Decoded correctly + guard let data: Data = maybeData else { throw HTTPError.parsingFailed } + guard let jsonObject: Any = try? JSONSerialization.jsonObject(with: data, options: [.fragmentsAllowed]) else { + throw HTTPError.parsingFailed + } + + let dataArray: [Data] + + switch jsonObject { + case let anyArray as [Any]: + dataArray = anyArray.compactMap { try? JSONSerialization.data(withJSONObject: $0) } + + guard dataArray.count == types.count else { throw HTTPError.parsingFailed } + + case let anyDict as [String: Any]: + guard + let resultsArray: [Data] = (anyDict["results"] as? [Any])? + .compactMap({ try? JSONSerialization.data(withJSONObject: $0) }), + resultsArray.count == types.count + else { throw HTTPError.parsingFailed } + + dataArray = resultsArray + + default: throw HTTPError.parsingFailed + } + + do { + return try zip(dataArray, types) + .map { data, type in try type.decoded(from: data, using: dependencies) } + .map { data in (responseInfo, data) } + } + catch { + throw HTTPError.parsingFailed + } + } + } +} diff --git a/SessionUtilitiesKitTests/Networking/BatchResponseSpec.swift b/SessionUtilitiesKitTests/Networking/BatchResponseSpec.swift new file mode 100644 index 000000000..2919fcf0f --- /dev/null +++ b/SessionUtilitiesKitTests/Networking/BatchResponseSpec.swift @@ -0,0 +1,243 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import PromiseKit + +import Quick +import Nimble + +@testable import SessionUtilitiesKit + +class BatchRequestInfoSpec: QuickSpec { + struct TestType: Codable, Equatable { + let stringValue: String + } + + struct TestType2: Codable, Equatable { + let intValue: Int + let stringValue2: String + } + + // MARK: - Spec + + override func spec() { + // MARK: - HTTP.BatchSubResponse + + describe("an HTTP.BatchSubResponse") { + context("when decoding") { + it("decodes correctly") { + let jsonString: String = """ + { + "code": 200, + "headers": { + "testKey": "testValue" + }, + "body": { + "stringValue": "testValue" + } + } + """ + let subResponse: HTTP.BatchSubResponse? = try? JSONDecoder().decode( + HTTP.BatchSubResponse.self, + from: jsonString.data(using: .utf8)! + ) + + expect(subResponse).toNot(beNil()) + expect(subResponse?.body).toNot(beNil()) + } + + it("decodes with invalid body data") { + let jsonString: String = """ + { + "code": 200, + "headers": { + "testKey": "testValue" + }, + "body": "Hello!!!" + } + """ + let subResponse: HTTP.BatchSubResponse? = try? JSONDecoder().decode( + HTTP.BatchSubResponse.self, + from: jsonString.data(using: .utf8)! + ) + + expect(subResponse).toNot(beNil()) + } + + it("flags invalid body data as invalid") { + let jsonString: String = """ + { + "code": 200, + "headers": { + "testKey": "testValue" + }, + "body": "Hello!!!" + } + """ + let subResponse: HTTP.BatchSubResponse? = try? JSONDecoder().decode( + HTTP.BatchSubResponse.self, + from: jsonString.data(using: .utf8)! + ) + + expect(subResponse).toNot(beNil()) + expect(subResponse?.body).to(beNil()) + expect(subResponse?.failedToParseBody).to(beTrue()) + } + + it("does not flag a missing or invalid optional body as invalid") { + let jsonString: String = """ + { + "code": 200, + "headers": { + "testKey": "testValue" + }, + } + """ + let subResponse: HTTP.BatchSubResponse? = try? JSONDecoder().decode( + HTTP.BatchSubResponse.self, + from: jsonString.data(using: .utf8)! + ) + + expect(subResponse).toNot(beNil()) + expect(subResponse?.body).to(beNil()) + expect(subResponse?.failedToParseBody).to(beFalse()) + } + + it("does not flag a NoResponse body as invalid") { + let jsonString: String = """ + { + "code": 200, + "headers": { + "testKey": "testValue" + }, + } + """ + let subResponse: HTTP.BatchSubResponse? = try? JSONDecoder().decode( + HTTP.BatchSubResponse.self, + from: jsonString.data(using: .utf8)! + ) + + expect(subResponse).toNot(beNil()) + expect(subResponse?.body).to(beNil()) + expect(subResponse?.failedToParseBody).to(beFalse()) + } + } + } + + // MARK: - Convenience + // MARK: --Decodable + + describe("a Decodable") { + it("decodes correctly") { + let jsonData: Data = "{\"stringValue\":\"testValue\"}".data(using: .utf8)! + let result: TestType? = try? TestType.decoded(from: jsonData) + + expect(result).to(equal(TestType(stringValue: "testValue"))) + } + } + + // MARK: - --Promise + + describe("a (ResponseInfoType, Data?) Promise") { + var responseInfo: ResponseInfoType! + var testType: TestType! + var testType2: TestType2! + var data: Data! + + beforeEach { + responseInfo = HTTP.ResponseInfo(code: 200, headers: [:]) + testType = TestType(stringValue: "Test") + testType2 = TestType2(intValue: 1, stringValue2: "Test2") + data = """ + [\([ + try! JSONEncoder().encode( + HTTP.BatchSubResponse( + code: 200, + headers: [:], + body: testType, + failedToParseBody: false + ) + ), + try! JSONEncoder().encode( + HTTP.BatchSubResponse( + code: 200, + headers: [:], + body: testType2, + failedToParseBody: false + ) + ) + ] + .map { String(data: $0, encoding: .utf8)! } + .joined(separator: ","))] + """.data(using: .utf8)! + } + + it("decodes valid data correctly") { + let result = Promise.value((responseInfo, data)) + .decoded(as: [ + HTTP.BatchSubResponse.self, + HTTP.BatchSubResponse.self + ]) + + expect(result.value).toNot(beNil()) + expect((result.value?[0].1 as? HTTP.BatchSubResponse)?.body) + .to(equal(testType)) + expect((result.value?[1].1 as? HTTP.BatchSubResponse)?.body) + .to(equal(testType2)) + } + + it("fails if there is no data") { + let result = Promise.value((responseInfo, nil)).decoded(as: []) + + expect(result.error?.localizedDescription).to(equal(HTTPError.parsingFailed.localizedDescription)) + } + + it("fails if the data is not JSON") { + let result = Promise.value((responseInfo, Data([1, 2, 3]))).decoded(as: []) + + expect(result.error?.localizedDescription).to(equal(HTTPError.parsingFailed.localizedDescription)) + } + + it("fails if the data is not a JSON array") { + let result = Promise.value((responseInfo, "{}".data(using: .utf8))).decoded(as: []) + + expect(result.error?.localizedDescription).to(equal(HTTPError.parsingFailed.localizedDescription)) + } + + it("fails if the JSON array does not have the same number of items as the expected types") { + let result = Promise.value((responseInfo, data)) + .decoded(as: [ + HTTP.BatchSubResponse.self, + HTTP.BatchSubResponse.self, + HTTP.BatchSubResponse.self + ]) + + expect(result.error?.localizedDescription).to(equal(HTTPError.parsingFailed.localizedDescription)) + } + + it("fails if one of the JSON array values fails to decode") { + data = """ + [\([ + try! JSONEncoder().encode( + HTTP.BatchSubResponse( + code: 200, + headers: [:], + body: testType, + failedToParseBody: false + ) + ) + ] + .map { String(data: $0, encoding: .utf8)! } + .joined(separator: ",")),{"test": "test"}] + """.data(using: .utf8)! + let result = Promise.value((responseInfo, data)) + .decoded(as: [ + HTTP.BatchSubResponse.self, + HTTP.BatchSubResponse.self + ]) + + expect(result.error?.localizedDescription).to(equal(HTTPError.parsingFailed.localizedDescription)) + } + } + } +} diff --git a/SessionMessagingKitTests/Common Networking/HeaderSpec.swift b/SessionUtilitiesKitTests/Networking/HeaderSpec.swift similarity index 68% rename from SessionMessagingKitTests/Common Networking/HeaderSpec.swift rename to SessionUtilitiesKitTests/Networking/HeaderSpec.swift index df47313ff..535e30c6d 100644 --- a/SessionMessagingKitTests/Common Networking/HeaderSpec.swift +++ b/SessionUtilitiesKitTests/Networking/HeaderSpec.swift @@ -5,7 +5,7 @@ import Foundation import Quick import Nimble -@testable import SessionMessagingKit +@testable import SessionUtilitiesKit class HeaderSpec: QuickSpec { // MARK: - Spec @@ -13,7 +13,8 @@ class HeaderSpec: QuickSpec { override func spec() { describe("a Dictionary of Header to String values") { it("can be converted into a dictionary of String to String values") { - expect([Header.authorization: "test"].toHTTPHeaders()).to(equal(["Authorization": "test"])) + expect([HTTPHeader.authorization: "test"].toHTTPHeaders()) + .to(equal(["Authorization": "test"])) } } } diff --git a/SessionMessagingKitTests/Common Networking/RequestSpec.swift b/SessionUtilitiesKitTests/Networking/RequestSpec.swift similarity index 74% rename from SessionMessagingKitTests/Common Networking/RequestSpec.swift rename to SessionUtilitiesKitTests/Networking/RequestSpec.swift index 44d87d22d..d8eb0d738 100644 --- a/SessionMessagingKitTests/Common Networking/RequestSpec.swift +++ b/SessionUtilitiesKitTests/Networking/RequestSpec.swift @@ -4,11 +4,22 @@ import Foundation import Quick import Nimble -import SessionUtilitiesKit -@testable import SessionMessagingKit +@testable import SessionUtilitiesKit class RequestSpec: QuickSpec { + enum TestEndpoint: EndpointType { + case test1 + case testParams(String, Int) + + var path: String { + switch self { + case .test1: return "test1" + case .testParams(let str, let int): return "testParams/\(str)/int/\(int)" + } + } + } + struct TestType: Codable, Equatable { let stringValue: String } @@ -18,9 +29,9 @@ class RequestSpec: QuickSpec { override func spec() { describe("a Request") { it("is initialized with the correct default values") { - let request: Request = Request( + let request: Request = Request( server: "testServer", - endpoint: .batch + endpoint: .test1 ) expect(request.method.rawValue).to(equal("GET")) @@ -31,42 +42,42 @@ class RequestSpec: QuickSpec { context("when generating a URL") { it("adds a leading forward slash to the endpoint path") { - let request: Request = Request( + let request: Request = Request( server: "testServer", - endpoint: .batch + endpoint: .test1 ) - expect(request.urlPathAndParamsString).to(equal("/batch")) + expect(request.urlPathAndParamsString).to(equal("/test1")) } it("creates a valid URL with no query parameters") { - let request: Request = Request( + let request: Request = Request( server: "testServer", - endpoint: .batch + endpoint: .test1 ) - expect(request.urlPathAndParamsString).to(equal("/batch")) + expect(request.urlPathAndParamsString).to(equal("/test1")) } it("creates a valid URL when query parameters are provided") { - let request: Request = Request( + let request: Request = Request( server: "testServer", - endpoint: .batch, + endpoint: .test1, queryParameters: [ .limit: "123" ] ) - expect(request.urlPathAndParamsString).to(equal("/batch?limit=123")) + expect(request.urlPathAndParamsString).to(equal("/test1?limit=123")) } } context("when generating a URLRequest") { it("sets all the values correctly") { - let request: Request = Request( + let request: Request = Request( method: .delete, server: "testServer", - endpoint: .batch, + endpoint: .test1, headers: [ .authorization: "test" ] @@ -79,22 +90,22 @@ class RequestSpec: QuickSpec { } it("throws an error if the URL is invalid") { - let request: Request = Request( + let request: Request = Request( server: "testServer", - endpoint: .roomPollInfo("!!%%", 123) + endpoint: .testParams("!!%%", 123) ) expect { try request.generateUrlRequest() } - .to(throwError(HTTP.Error.invalidURL)) + .to(throwError(HTTPError.invalidURL)) } context("with a base64 string body") { it("successfully encodes the body") { - let request: Request = Request( + let request: Request = Request( server: "testServer", - endpoint: .batch, + endpoint: .test1, body: "TestMessage".data(using: .utf8)!.base64EncodedString() ) @@ -106,24 +117,24 @@ class RequestSpec: QuickSpec { } it("throws an error if the body is not base64 encoded") { - let request: Request = Request( + let request: Request = Request( server: "testServer", - endpoint: .batch, + endpoint: .test1, body: "TestMessage" ) expect { try request.generateUrlRequest() } - .to(throwError(HTTP.Error.parsingFailed)) + .to(throwError(HTTPError.parsingFailed)) } } context("with a byte body") { it("successfully encodes the body") { - let request: Request<[UInt8], OpenGroupAPI.Endpoint> = Request( + let request: Request<[UInt8], TestEndpoint> = Request( server: "testServer", - endpoint: .batch, + endpoint: .test1, body: [1, 2, 3] ) @@ -135,9 +146,9 @@ class RequestSpec: QuickSpec { context("with a JSON body") { it("successfully encodes the body") { - let request: Request = Request( + let request: Request = Request( server: "testServer", - endpoint: .batch, + endpoint: .test1, body: TestType(stringValue: "test") ) @@ -151,9 +162,9 @@ class RequestSpec: QuickSpec { } it("successfully encodes no body") { - let request: Request = Request( + let request: Request = Request( server: "testServer", - endpoint: .batch, + endpoint: .test1, body: nil ) From a1e09d830f99fd892aa6f8c836c957a501339ced Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 29 Nov 2022 08:58:19 +1100 Subject: [PATCH 002/135] Got test working --- .../ConfigUserProfileSpec.swift | 32 ++++++------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/SessionMessagingKitTests/LibSessionUtil/ConfigUserProfileSpec.swift b/SessionMessagingKitTests/LibSessionUtil/ConfigUserProfileSpec.swift index 85d88995e..6cd6a6adf 100644 --- a/SessionMessagingKitTests/LibSessionUtil/ConfigUserProfileSpec.swift +++ b/SessionMessagingKitTests/LibSessionUtil/ConfigUserProfileSpec.swift @@ -91,9 +91,6 @@ class ConfigUserProfileSpec: QuickSpec { let expHash0: [CChar] = Data(hex: "ea173b57beca8af18c3519a7bbf69c3e7a05d1c049fa9558341d8ebb48b0c965") .bytes .map { CChar(bitPattern: $0) } -// .withUnsafeBufferPointer { profileKeyPtr in -// String(cString: profileKeyPtr.baseAddress!) -// } // The data to be actually pushed, expanded like this to make it somewhat human-readable: let expPush1: [CChar] = [""" d @@ -120,8 +117,7 @@ class ConfigUserProfileSpec: QuickSpec { e """.removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) // For readability .bytes - .map { CChar(bitPattern: $0) }, -// [CChar()] // Need to null-terminate the string or it'll crash + .map { CChar(bitPattern: $0) } ] .flatMap { $0 } @@ -199,9 +195,9 @@ class ConfigUserProfileSpec: QuickSpec { expect(config_needs_dump(conf2)).to(beTrue()) var dump3: UnsafeMutablePointer? = nil var dump3Len: Int = 0 - config_dump(conf, &dump3, &dump3Len) + config_dump(conf2, &dump3, &dump3Len) // (store in db) - expect(config_needs_dump(conf2)).to(beFalse())// TODO: This one is broken now!!! + expect(config_needs_dump(conf2)).to(beFalse()) // We *don't* need to push: even though we updated, all we did is update to the merged data (and // didn't have any sort of merge conflict needed): @@ -261,22 +257,12 @@ class ConfigUserProfileSpec: QuickSpec { // Feed the new config into each other. (This array could hold multiple configs if we pulled // down more than one). - let mergeData2: [String] = [String(cString: toPush3!)] - var mergeData2Ptr = mergeData2.map { value in - value.bytes.map { CChar(bitPattern: $0) }.withUnsafeBufferPointer { valuePtr in - valuePtr.baseAddress - } - } + var mergeData2Ptr: [UnsafePointer?] = [UnsafePointer(toPush3)] var mergeSize2: [Int] = [toPush3Len] config_merge(conf2, &mergeData2Ptr, &mergeSize2, 1) - let mergeData3: [String] = [String(cString: toPush4!)] - var mergeData3Ptr = mergeData3.map { value in - value.bytes.map { CChar(bitPattern: $0) }.withUnsafeBufferPointer { valuePtr in - valuePtr.baseAddress - } - } + var mergeData3: [UnsafePointer?] = [UnsafePointer(toPush4)] var mergeSize3: [Int] = [toPush4Len] - config_merge(conf, &mergeData3Ptr, &mergeSize3, 1) + config_merge(conf, &mergeData3, &mergeSize3, 1) // Now after the merge we *will* want to push from both client, since both will have generated a // merge conflict update (with seqno = 3). @@ -300,12 +286,14 @@ class ConfigUserProfileSpec: QuickSpec { expect(pic3.url).toNot(beNil()) expect(String(cString: pic3.url!)).to(equal("http://new.example.com/pic")) expect(pic3.key).toNot(beNil()) - expect(String(cString: pic3.key!)).to(equal("qwert\0yuio")) + expect(String(data: Data(bytes: pic3.key, count: pic3.keylen), encoding: .utf8)) + .to(equal("qwert\0yuio")) let pic4 = user_profile_get_pic(conf2) expect(pic4.url).toNot(beNil()) expect(String(cString: pic4.url!)).to(equal("http://new.example.com/pic")) expect(pic4.key).toNot(beNil()) - expect(String(cString: pic4.key!)).to(equal("qwert\0yuio")) + expect(String(data: Data(bytes: pic4.key, count: pic4.keylen), encoding: .utf8)) + .to(equal("qwert\0yuio")) config_confirm_pushed(conf, seqno5) config_confirm_pushed(conf2, seqno6) From d03d2ce8abff897d65be8ca46b631fe15c65f9df Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 29 Nov 2022 09:48:55 +1100 Subject: [PATCH 003/135] Fixed remaining issues, cleaned up memory & logic # Conflicts: # Session.xcodeproj/project.pbxproj --- Session.xcodeproj/project.pbxproj | 7 +- .../ConfigUserProfileSpec.swift | 82 +++++++++++-------- .../General/Collection+Subscripting.swift | 7 -- .../General/Collection+Utilities.swift | 22 +++++ .../General/String+Utilities.swift | 14 ++++ 5 files changed, 86 insertions(+), 46 deletions(-) delete mode 100644 SessionUtilitiesKit/General/Collection+Subscripting.swift create mode 100644 SessionUtilitiesKit/General/Collection+Utilities.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 6f25d8457..6e4f6dc62 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -254,7 +254,7 @@ B8DE1FB626C22FCB0079C9CE /* CallMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8DE1FB526C22FCB0079C9CE /* CallMessage.swift */; }; B8EB20EE2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8EB20ED2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift */; }; B8EB20F02640F7F000773E52 /* OpenGroupInvitationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8EB20EF2640F7F000773E52 /* OpenGroupInvitationView.swift */; }; - B8F5F58325EC94A6003BF8D4 /* Collection+Subscripting.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8F5F58225EC94A6003BF8D4 /* Collection+Subscripting.swift */; }; + B8F5F58325EC94A6003BF8D4 /* Collection+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8F5F58225EC94A6003BF8D4 /* Collection+Utilities.swift */; }; B8F5F60325EDE16F003BF8D4 /* DataExtractionNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8F5F60225EDE16F003BF8D4 /* DataExtractionNotification.swift */; }; B8F5F71A25F1B35C003BF8D4 /* MediaPlaceholderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8F5F71925F1B35C003BF8D4 /* MediaPlaceholderView.swift */; }; B8FF8DAE25C0D00F004D1F22 /* SessionMessagingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A6F025539DE700C340D1 /* SessionMessagingKit.framework */; }; @@ -1371,7 +1371,7 @@ B8EB20E6263F7E4B00773E52 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Localizable.strings; sourceTree = ""; }; B8EB20ED2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VisibleMessage+OpenGroupInvitation.swift"; sourceTree = ""; }; B8EB20EF2640F7F000773E52 /* OpenGroupInvitationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupInvitationView.swift; sourceTree = ""; }; - B8F5F58225EC94A6003BF8D4 /* Collection+Subscripting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection+Subscripting.swift"; sourceTree = ""; }; + B8F5F58225EC94A6003BF8D4 /* Collection+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection+Utilities.swift"; sourceTree = ""; }; B8F5F60225EDE16F003BF8D4 /* DataExtractionNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataExtractionNotification.swift; sourceTree = ""; }; B8F5F71925F1B35C003BF8D4 /* MediaPlaceholderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlaceholderView.swift; sourceTree = ""; }; B8FF8E6125C10DA5004D1F22 /* GeoLite2-Country-Blocks-IPv4 */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; name = "GeoLite2-Country-Blocks-IPv4"; path = "Countries/GeoLite2-Country-Blocks-IPv4"; sourceTree = ""; }; @@ -2577,6 +2577,7 @@ B8F5F58225EC94A6003BF8D4 /* Collection+Subscripting.swift */, FDFD645A27F26D4600808CA1 /* Data+Utilities.swift */, FDC6D75F2862B3F600B04575 /* Dependencies.swift */, + FD26FA5D291CAFF9005801D8 /* Data+Utilities.swift */, C3C2A5D52553860A00C340D1 /* Dictionary+Utilities.swift */, B87EF18026377A1D00124B3C /* Features.swift */, B8BC00BF257D90E30032E807 /* General.swift */, @@ -5445,7 +5446,7 @@ FD52090028AF6153006098F6 /* OWSBackgroundTask.m in Sources */, C32C5DDB256DD9FF003C73A2 /* ContentProxy.swift in Sources */, C3A71F892558BA9F0043A11F /* Mnemonic.swift in Sources */, - B8F5F58325EC94A6003BF8D4 /* Collection+Subscripting.swift in Sources */, + B8F5F58325EC94A6003BF8D4 /* Collection+Utilities.swift in Sources */, C33FDEF8255A656D00E217F9 /* Promise+Delaying.swift in Sources */, 7BD477A827EC39F5004E2822 /* Atomic.swift in Sources */, B8BC00C0257D90E30032E807 /* General.swift in Sources */, diff --git a/SessionMessagingKitTests/LibSessionUtil/ConfigUserProfileSpec.swift b/SessionMessagingKitTests/LibSessionUtil/ConfigUserProfileSpec.swift index 6cd6a6adf..e08840408 100644 --- a/SessionMessagingKitTests/LibSessionUtil/ConfigUserProfileSpec.swift +++ b/SessionMessagingKitTests/LibSessionUtil/ConfigUserProfileSpec.swift @@ -2,6 +2,7 @@ import Foundation import SessionUtil +import SessionUtilitiesKit import Quick import Nimble @@ -11,11 +12,12 @@ class ConfigUserProfileSpec: QuickSpec { // MARK: - Spec override func spec() { - it("behaves correctly") { + it("generates configs correctly") { // Initialize a brand new, empty config because we have no dump data to deal with. let error: UnsafeMutablePointer? = nil var conf: UnsafeMutablePointer? = nil expect(user_profile_init(&conf, nil, 0, error)).to(equal(0)) + error?.deallocate() // We don't need to push anything, since this is an empty config expect(config_needs_push(conf)).to(beFalse()) @@ -23,7 +25,7 @@ class ConfigUserProfileSpec: QuickSpec { expect(config_needs_dump(conf)).to(beFalse()) // Since it's empty there shouldn't be a name. - let namePtr = user_profile_get_name(conf) + let namePtr: UnsafePointer? = user_profile_get_name(conf) expect(namePtr).to(beNil()) var toPush: UnsafeMutablePointer? = nil @@ -33,14 +35,14 @@ class ConfigUserProfileSpec: QuickSpec { let seqno: Int64 = config_push(conf, &toPush, &toPushLen) expect(toPush).toNot(beNil()) expect(seqno).to(equal(0)) - expect(String(cString: toPush!)).to(equal("d1:#i0e1:&de1:? = user_profile_get_name(conf) expect(namePtr2).toNot(beNil()) expect(String(cString: namePtr2!)).to(equal("Kallie")) - let pic2 = user_profile_get_pic(conf); + let pic2: user_profile_pic = user_profile_get_pic(conf); expect(pic2.url).toNot(beNil()) expect(pic2.key).toNot(beNil()) expect(pic2.keylen).to(equal(6)) expect(String(cString: pic2.url!)).to(equal("http://example.org/omg-pic-123.bmp")) - expect(String(cString: pic2.key!)).to(equal("secret")) + expect(String(pointer: pic2.key, length: pic2.keylen)).to(equal("secret")) // Since we've made changes, we should need to push new config to the swarm, *and* should need // to dump the updated state: @@ -80,7 +82,7 @@ class ConfigUserProfileSpec: QuickSpec { var toPush2: UnsafeMutablePointer? = nil var toPush2Len: Int = 0 - let seqno2 = config_push(conf, &toPush2, &toPush2Len); + let seqno2: Int64 = config_push(conf, &toPush2, &toPush2Len); // incremented since we made changes (this only increments once between // dumps; even though we changed two fields here). expect(seqno2).to(equal(1)) @@ -121,9 +123,9 @@ class ConfigUserProfileSpec: QuickSpec { ] .flatMap { $0 } - expect(String(cString: toPush2!)) - // Need to null-terminate the string or it'll crash - .to(equal(String(cString: expPush1.appending(CChar())))) + expect(String(pointer: toPush2, length: toPush2Len, encoding: .ascii)) + .to(equal(String(pointer: expPush1, length: expPush1.count, encoding: .ascii))) + toPush2?.deallocate() // We haven't dumped, so still need to dump: expect(config_needs_dump(conf)).to(beTrue()) @@ -155,8 +157,9 @@ class ConfigUserProfileSpec: QuickSpec { .map { CChar(bitPattern: $0) } ] .flatMap { $0 } - expect(String(cString: dump1!)) - .to(equal(String(cString: expDump1.appending(CChar()))))// Need to null-terminate the string or it'll crash + expect(String(pointer: dump1, length: dump1Len, encoding: .ascii)) + .to(equal(String(pointer: expDump1, length: expDump1.count, encoding: .ascii))) + dump1?.deallocate() // So now imagine we got back confirmation from the swarm that the push has been stored: config_confirm_pushed(conf, seqno2) @@ -167,7 +170,7 @@ class ConfigUserProfileSpec: QuickSpec { var dump2: UnsafeMutablePointer? = nil var dump2Len: Int = 0 config_dump(conf, &dump2, &dump2Len) - + dump2?.deallocate() expect(config_needs_dump(conf)).to(beFalse()) // Now we're going to set up a second, competing config object (in the real world this would be @@ -178,18 +181,14 @@ class ConfigUserProfileSpec: QuickSpec { var conf2: UnsafeMutablePointer? = nil expect(user_profile_init(&conf2, nil, 0, error2)).to(equal(0)) expect(config_needs_dump(conf2)).to(beFalse()) + error2?.deallocate() // Now imagine we just pulled down the `exp_push1` string from the swarm; we merge it into // conf2: - let mergeData: [[CChar]] = [expPush1] - var mergeDataPtr = mergeData.map { value in - let cStringCopy = UnsafeMutableBufferPointer.allocate(capacity: value.count) - _ = cStringCopy.initialize(from: value) - let ptr = UnsafePointer(cStringCopy.baseAddress) - return UnsafePointer(cStringCopy.baseAddress) - } + var mergeData: [UnsafePointer?] = [expPush1].unsafeCopy() var mergeSize: [Int] = [expPush1.count] - config_merge(conf2, &mergeDataPtr, &mergeSize, 1) + config_merge(conf2, &mergeData, &mergeSize, 1) + mergeData.forEach { $0?.deallocate() } // Our state has changed, so we need to dump: expect(config_needs_dump(conf2)).to(beTrue()) @@ -197,6 +196,7 @@ class ConfigUserProfileSpec: QuickSpec { var dump3Len: Int = 0 config_dump(conf2, &dump3, &dump3Len) // (store in db) + dump3?.deallocate() expect(config_needs_dump(conf2)).to(beFalse()) // We *don't* need to push: even though we updated, all we did is update to the merged data (and @@ -232,12 +232,12 @@ class ConfigUserProfileSpec: QuickSpec { expect(config_needs_push(conf2)).to(beTrue()) var toPush3: UnsafeMutablePointer? = nil var toPush3Len: Int = 0 - let seqno3 = config_push(conf, &toPush3, &toPush3Len) + let seqno3: Int64 = config_push(conf, &toPush3, &toPush3Len) expect(seqno3).to(equal(2)) // incremented, since we made a field change var toPush4: UnsafeMutablePointer? = nil var toPush4Len: Int = 0 - let seqno4 = config_push(conf2, &toPush4, &toPush4Len) + let seqno4: Int64 = config_push(conf2, &toPush4, &toPush4Len) expect(seqno4).to(equal(2)) // incremented, since we made a field change var dump4: UnsafeMutablePointer? = nil @@ -247,29 +247,34 @@ class ConfigUserProfileSpec: QuickSpec { var dump5Len: Int = 0 config_dump(conf2, &dump5, &dump5Len); // (store in db) + dump4?.deallocate() + dump5?.deallocate() // Since we set different things, we're going to get back different serialized data to be // pushed: - expect(String(cString: toPush3!)).toNot(equal(String(cString: toPush4!))) + expect(String(pointer: toPush3, length: toPush3Len, encoding: .ascii)) + .toNot(equal(String(pointer: toPush4, length: toPush4Len, encoding: .ascii))) // Now imagine that each client pushed its `seqno=2` config to the swarm, but then each client // also fetches new messages and pulls down the other client's `seqno=2` value. // Feed the new config into each other. (This array could hold multiple configs if we pulled // down more than one). - var mergeData2Ptr: [UnsafePointer?] = [UnsafePointer(toPush3)] + var mergeData2: [UnsafePointer?] = [UnsafePointer(toPush3)] var mergeSize2: [Int] = [toPush3Len] - config_merge(conf2, &mergeData2Ptr, &mergeSize2, 1) + config_merge(conf2, &mergeData2, &mergeSize2, 1) + toPush3?.deallocate() var mergeData3: [UnsafePointer?] = [UnsafePointer(toPush4)] var mergeSize3: [Int] = [toPush4Len] config_merge(conf, &mergeData3, &mergeSize3, 1) + toPush4?.deallocate() // Now after the merge we *will* want to push from both client, since both will have generated a // merge conflict update (with seqno = 3). expect(config_needs_push(conf)).to(beTrue()) expect(config_needs_push(conf2)).to(beTrue()) - let seqno5 = config_push(conf, &toPush3, &toPush3Len); - let seqno6 = config_push(conf2, &toPush4, &toPush4Len); + let seqno5: Int64 = config_push(conf, &toPush3, &toPush3Len); + let seqno6: Int64 = config_push(conf2, &toPush4, &toPush4Len); expect(seqno5).to(equal(3)) expect(seqno6).to(equal(3)) @@ -282,18 +287,16 @@ class ConfigUserProfileSpec: QuickSpec { // test). // Since only one of them set a profile pic there should be no conflict there: - let pic3 = user_profile_get_pic(conf) + let pic3: user_profile_pic = user_profile_get_pic(conf) expect(pic3.url).toNot(beNil()) expect(String(cString: pic3.url!)).to(equal("http://new.example.com/pic")) expect(pic3.key).toNot(beNil()) - expect(String(data: Data(bytes: pic3.key, count: pic3.keylen), encoding: .utf8)) - .to(equal("qwert\0yuio")) - let pic4 = user_profile_get_pic(conf2) + expect(String(pointer: pic3.key, length: pic3.keylen)).to(equal("qwert\0yuio")) + let pic4: user_profile_pic = user_profile_get_pic(conf2) expect(pic4.url).toNot(beNil()) expect(String(cString: pic4.url!)).to(equal("http://new.example.com/pic")) expect(pic4.key).toNot(beNil()) - expect(String(data: Data(bytes: pic4.key, count: pic4.keylen), encoding: .utf8)) - .to(equal("qwert\0yuio")) + expect(String(pointer: pic4.key, length: pic4.keylen)).to(equal("qwert\0yuio")) config_confirm_pushed(conf, seqno5) config_confirm_pushed(conf2, seqno6) @@ -305,11 +308,18 @@ class ConfigUserProfileSpec: QuickSpec { var dump7Len: Int = 0 config_dump(conf2, &dump7, &dump7Len); // (store in db) + dump6?.deallocate() + dump7?.deallocate() expect(config_needs_dump(conf)).to(beFalse()) expect(config_needs_dump(conf2)).to(beFalse()) expect(config_needs_push(conf)).to(beFalse()) expect(config_needs_push(conf2)).to(beFalse()) + + // Wouldn't do this in a normal session but doing it here to properly clean up + // after the test + conf?.deallocate() + conf2?.deallocate() } } } diff --git a/SessionUtilitiesKit/General/Collection+Subscripting.swift b/SessionUtilitiesKit/General/Collection+Subscripting.swift deleted file mode 100644 index 70ffcf61d..000000000 --- a/SessionUtilitiesKit/General/Collection+Subscripting.swift +++ /dev/null @@ -1,7 +0,0 @@ - -extension Collection { - - public subscript(ifValid index: Index) -> Iterator.Element? { - return self.indices.contains(index) ? self[index] : nil - } -} diff --git a/SessionUtilitiesKit/General/Collection+Utilities.swift b/SessionUtilitiesKit/General/Collection+Utilities.swift new file mode 100644 index 000000000..1c1954c26 --- /dev/null +++ b/SessionUtilitiesKit/General/Collection+Utilities.swift @@ -0,0 +1,22 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +extension Collection { + public subscript(ifValid index: Index) -> Iterator.Element? { + return self.indices.contains(index) ? self[index] : nil + } +} + +public extension Collection where Element == [CChar] { + /// This creates an array of UnsafePointer types to access data of the C strings in memory. This array provides no automated + /// memory management of it's children so after use you are responsible for handling the life cycle of the child elements and + /// need to call `deallocate()` on each child. + func unsafeCopy() -> [UnsafePointer?] { + return self.map { value in + let copy = UnsafeMutableBufferPointer.allocate(capacity: value.count) + _ = copy.initialize(from: value) + return UnsafePointer(copy.baseAddress) + } + } +} diff --git a/SessionUtilitiesKit/General/String+Utilities.swift b/SessionUtilitiesKit/General/String+Utilities.swift index 39bbbb913..be3bd36ad 100644 --- a/SessionUtilitiesKit/General/String+Utilities.swift +++ b/SessionUtilitiesKit/General/String+Utilities.swift @@ -74,6 +74,20 @@ public extension String { } } +// MARK: - Convenience + +public extension String { + /// Initialize with an optional pointer and a spoecific length + init?(pointer: UnsafeRawPointer?, length: Int, encoding: String.Encoding = .utf8) { + guard + let pointer: UnsafeRawPointer = pointer, + let result: String = String(data: Data(bytes: pointer, count: length), encoding: encoding) + else { return nil } + + self = result + } +} + // MARK: - Formatting public extension String { From f721178b49dd20402538765b39fd9068b9ca1eba Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 29 Nov 2022 14:40:41 +1100 Subject: [PATCH 004/135] Added in a migration to create the user profile config and trigger a sync # Conflicts: # Session.xcodeproj/project.pbxproj --- Session.xcodeproj/project.pbxproj | 5 + Session/Meta/AppDelegate.swift | 1 - .../Database/Models/SharedConfigDump.swift | 2 +- .../LibSessionUtil/SessionUtil.swift | 166 ++++++++++++------ .../LibSessionUtil/SessionUtilError.swift | 8 + .../General/Collection+Utilities.swift | 11 ++ SignalUtilitiesKit/Utilities/AppSetup.swift | 7 +- 7 files changed, 147 insertions(+), 53 deletions(-) create mode 100644 SessionMessagingKit/LibSessionUtil/SessionUtilError.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 6e4f6dc62..0426787fc 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -785,6 +785,7 @@ FD8ECF852934508B00C0D1BB /* BatchResponseSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF842934508B00C0D1BB /* BatchResponseSpec.swift */; }; FD8ECF8629346DA100C0D1BB /* HeaderSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C906127E411AF00CD579F /* HeaderSpec.swift */; }; FD8ECF8729346DB500C0D1BB /* RequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C906327E4122F00CD579F /* RequestSpec.swift */; }; + FD8ECF892935AB7200C0D1BB /* SessionUtilError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF882935AB7200C0D1BB /* SessionUtilError.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 */; }; @@ -1905,6 +1906,7 @@ FD8ECF7E2934298100C0D1BB /* SharedConfigDump.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedConfigDump.swift; sourceTree = ""; }; FD8ECF812934387A00C0D1BB /* ConfigUserProfileSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigUserProfileSpec.swift; sourceTree = ""; }; FD8ECF842934508B00C0D1BB /* BatchResponseSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchResponseSpec.swift; sourceTree = ""; }; + FD8ECF882935AB7200C0D1BB /* SessionUtilError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionUtilError.swift; sourceTree = ""; }; FD90040E2818AB6D00ABAAF6 /* GetSnodePoolJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetSnodePoolJob.swift; sourceTree = ""; }; FD9004132818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_SetupStandardJobs.swift; sourceTree = ""; }; FDA8EAFD280E8B78002B68E5 /* FailedMessageSendsJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailedMessageSendsJob.swift; sourceTree = ""; }; @@ -4027,6 +4029,7 @@ children = ( FD8ECF7829340F7100C0D1BB /* libsession-util.xcframework */, FD8ECF7A29340FFD00C0D1BB /* SessionUtil.swift */, + FD8ECF882935AB7200C0D1BB /* SessionUtilError.swift */, ); path = LibSessionUtil; sourceTree = ""; @@ -5564,6 +5567,8 @@ FD245C59285065FC00B966DD /* ControlMessage.swift in Sources */, B8DE1FB626C22FCB0079C9CE /* CallMessage.swift in Sources */, FD245C50285065C700B966DD /* VisibleMessage+Quote.swift in Sources */, + FD8ECF892935AB7200C0D1BB /* SessionUtilError.swift in Sources */, + FD8ECF42292B340D00C0D1BB /* SOGSBatchRequest.swift in Sources */, FD5C7307284F103B0029977D /* MessageReceiver+MessageRequests.swift in Sources */, C3A71D0B2558989C0043A11F /* MessageWrapper.swift in Sources */, FDE77F6B280FEB28002CFC5D /* ControlMessageProcessRecord.swift in Sources */, diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index e55a5cb2f..d91851597 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -27,7 +27,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD // MARK: - Lifecycle func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - SessionUtil.loadState() // TODO: Remove this (move to 'Configuration'? Or call directly as part of 'AppSetup'?) // These should be the first things we do (the startup process can fail without them) SetCurrentAppContext(MainAppContext()) verifyDBKeysAvailableBeforeBackgroundLaunch() diff --git a/SessionMessagingKit/Database/Models/SharedConfigDump.swift b/SessionMessagingKit/Database/Models/SharedConfigDump.swift index c9e8651df..c295b975f 100644 --- a/SessionMessagingKit/Database/Models/SharedConfigDump.swift +++ b/SessionMessagingKit/Database/Models/SharedConfigDump.swift @@ -13,7 +13,7 @@ internal struct ConfigDump: Codable, Equatable, Hashable, Identifiable, Fetchabl case data } - enum Variant: String, Codable, DatabaseValueConvertible { + enum Variant: String, Codable, DatabaseValueConvertible, CaseIterable { case userProfile } diff --git a/SessionMessagingKit/LibSessionUtil/SessionUtil.swift b/SessionMessagingKit/LibSessionUtil/SessionUtil.swift index 94f798ab2..b60f692a3 100644 --- a/SessionMessagingKit/LibSessionUtil/SessionUtil.swift +++ b/SessionMessagingKit/LibSessionUtil/SessionUtil.swift @@ -5,74 +5,140 @@ import GRDB import SessionUtil import SessionUtilitiesKit -/*internal*/public enum SessionUtil { // TODO: Rename this to be cleaner? +/*internal*/public enum SessionUtil { + typealias ConfResult = (needsPush: Bool, needsDump: Bool) + + // MARK: - Configs + + private static var userProfileConfig: Atomic?> = Atomic(nil) + + // MARK: - Variables + + public static var needsSync: Bool { + return ConfigDump.Variant.allCases.contains { variant in + switch variant { + case .userProfile: + return (userProfileConfig.wrappedValue.map { config_needs_push($0) } ?? false) + } + } + } + + // MARK: - Loading + /*internal*/public static func loadState() { + SessionUtil.userProfileConfig.mutate { $0 = loadState(for: .userProfile) } + } + + private static func loadState(for variant: ConfigDump.Variant) -> UnsafeMutablePointer? { + // Load any let storedDump: Data? = Storage.shared - .read { db in try ConfigDump.fetchOne(db, id: .userProfile) }? + .read { db in try ConfigDump.fetchOne(db, id: variant) }? .data - var dump: UnsafePointer? = nil // TODO: Load from DB/Disk - let dumpLen: size_t = 0 + + return try? loadState(for: variant, cachedData: storedDump) + } + + internal static func loadState( + for variant: ConfigDump.Variant, + cachedData: Data? = nil + ) throws -> UnsafeMutablePointer? { + // Setup initial variables (including getting the memory address for any cached data) var conf: UnsafeMutablePointer? = nil -// var confSetup: UnsafeMutablePointer?>? = nil - var error: UnsafeMutablePointer? = nil - // TODO: Will need to manually release any unsafe pointers - let result = user_profile_init(&conf, dump, dumpLen, error) - - guard result == 0 else { return } // TODO: Throw error - -// var conf: UnsafeMutablePointer? = confSetup?.pointee - - user_profile_set_name(conf, "TestName") // TODO: Confirm success - - let profileUrl: [CChar] = "http://example.org/omg-pic-123.bmp".bytes.map { CChar(bitPattern: $0) } - let profileKey: [CChar] = "secretNOTSECRET".bytes.map { CChar(bitPattern: $0) } - let profilePic: user_profile_pic = profileUrl.withUnsafeBufferPointer { profileUrlPtr in - profileKey.withUnsafeBufferPointer { profileKeyPtr in - user_profile_pic( - url: profileUrlPtr.baseAddress, - key: profileKeyPtr.baseAddress, - keylen: profileKey.count + let error: UnsafeMutablePointer? = nil + let cachedDump: (data: UnsafePointer, length: Int)? = cachedData?.withUnsafeBytes { unsafeBytes in + return unsafeBytes.baseAddress.map { + ( + $0.assumingMemoryBound(to: CChar.self), + unsafeBytes.count ) } } - user_profile_set_pic(conf, profilePic) // TODO: Confirm success - - if config_needs_push(conf) { - print("Needs Push!!!") + // No need to deallocate the `cachedDump.data` as it'll automatically be cleaned up by + // the `cachedData` lifecycle, but need to deallocate the `error` if it gets set + defer { + error?.deallocate() } - if config_needs_dump(conf) { - print("Needs Dump!!!") + // Try to create the object + let result: Int32 = { + switch variant { + case .userProfile: + return user_profile_init(&conf, cachedDump?.data, (cachedDump?.length ?? 0), error) + } + }() + + guard result == 0 else { + let errorString: String = (error.map { String(cString: $0) } ?? "unknown error") + SNLog("[SessionUtil Error] Unable to create \(variant.rawValue) config object: \(errorString)") + throw SessionUtilError.unableToCreateConfigObject } - var toPush: UnsafeMutablePointer? = nil - var pushLen: Int = 0 - let seqNo = config_push(conf, &toPush, &pushLen) + return conf + } + + internal static func saveState( + _ db: Database, + conf: UnsafeMutablePointer?, + for variant: ConfigDump.Variant + ) throws { + guard conf != nil else { throw SessionUtilError.nilConfigObject } - //var remoteAddr: [CChar] = remote.bytes.map { CChar(bitPattern: $0) } - //config_dump(conf, &dump1, &dump1len); - - free(toPush) // TODO: Confirm + // If it doesn't need a dump then do nothing + guard config_needs_dump(conf) else { return } var dumpResult: UnsafeMutablePointer? = nil var dumpResultLen: Int = 0 - config_dump(conf, &dumpResult, &dumpResultLen) - print("RAWR") - let str = String(cString: dumpResult!) - let stryBytes = str.bytes - let hexStr = stryBytes.toHexString() - let data = Data(bytes: dumpResult!, count: dumpResultLen) -// dumpResult. -// Storage.shared.write { db in -// try ConfigDump(variant: .userProfile, data: <#T##Data#>) -// .save(db) -// } -// - print("RAWR2") + guard let dumpResult: UnsafeMutablePointer = dumpResult else { return } - //String(cString: dumpResult!) + let dumpData: Data = Data(bytes: dumpResult, count: dumpResultLen) + dumpResult.deallocate() + + try ConfigDump( + variant: variant, + data: dumpData + ) + .save(db) + } + + // MARK: - UserProfile + + internal static func update( + conf: UnsafeMutablePointer?, + with profile: Profile + ) throws -> ConfResult { + guard conf != nil else { throw SessionUtilError.nilConfigObject } + + // Update the name + user_profile_set_name(conf, profile.name) + + let profilePic: user_profile_pic? = profile.profilePictureUrl? + .bytes + .map { CChar(bitPattern: $0) } + .withUnsafeBufferPointer { profileUrlPtr in + let profileKey: [CChar]? = profile.profileEncryptionKey? + .keyData + .bytes + .map { CChar(bitPattern: $0) } + + return profileKey?.withUnsafeBufferPointer { profileKeyPtr in + user_profile_pic( + url: profileUrlPtr.baseAddress, + key: profileKeyPtr.baseAddress, + keylen: (profileKey?.count ?? 0) + ) + } + } + + if let profilePic: user_profile_pic = profilePic { + user_profile_set_pic(conf, profilePic) + } + + return ( + needsPush: config_needs_push(conf), + needsDump: config_needs_dump(conf) + ) } } diff --git a/SessionMessagingKit/LibSessionUtil/SessionUtilError.swift b/SessionMessagingKit/LibSessionUtil/SessionUtilError.swift new file mode 100644 index 000000000..2425f7398 --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/SessionUtilError.swift @@ -0,0 +1,8 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public enum SessionUtilError: Error { + case unableToCreateConfigObject + case nilConfigObject +} diff --git a/SessionUtilitiesKit/General/Collection+Utilities.swift b/SessionUtilitiesKit/General/Collection+Utilities.swift index 1c1954c26..234de4c1a 100644 --- a/SessionUtilitiesKit/General/Collection+Utilities.swift +++ b/SessionUtilitiesKit/General/Collection+Utilities.swift @@ -8,6 +8,17 @@ extension Collection { } } +public extension Collection { + /// This creates an UnsafeMutableBufferPointer to access data in memory directly. This result pointer provides no automated + /// memory management so after use you are responsible for handling the life cycle and need to call `deallocate()`. + func unsafeCopy() -> UnsafeMutableBufferPointer { + let copy = UnsafeMutableBufferPointer.allocate(capacity: self.underestimatedCount) + _ = copy.initialize(from: self) + return copy + } +} + + public extension Collection where Element == [CChar] { /// This creates an array of UnsafePointer types to access data of the C strings in memory. This array provides no automated /// memory management of it's children so after use you are responsible for handling the life cycle of the child elements and diff --git a/SignalUtilitiesKit/Utilities/AppSetup.swift b/SignalUtilitiesKit/Utilities/AppSetup.swift index cec53720e..ab33661ae 100644 --- a/SignalUtilitiesKit/Utilities/AppSetup.swift +++ b/SignalUtilitiesKit/Utilities/AppSetup.swift @@ -74,8 +74,13 @@ public enum AppSetup { ], onProgressUpdate: migrationProgressChanged, onComplete: { result, needsConfigSync in + // After the migrations have run but before the migration completion we load the + // SessionUtil state and update the 'needsConfigSync' flag based on whether the + // configs also need to be sync'ed + SessionUtil.loadState() + DispatchQueue.main.async { - migrationsCompletion(result, needsConfigSync) + migrationsCompletion(result, (needsConfigSync || SessionUtil.needsSync)) // The 'if' is only there to prevent the "variable never read" warning from showing if backgroundTask != nil { backgroundTask = nil } From ba33d2c95eaaa0a56ce36fd3500fabde3eaaeb99 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 30 Nov 2022 08:49:53 +1100 Subject: [PATCH 005/135] Added the logic to create the UserProfile data dump in the migration --- .../Migrations/_011_SharedUtilChanges.swift | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/SessionMessagingKit/Database/Migrations/_011_SharedUtilChanges.swift b/SessionMessagingKit/Database/Migrations/_011_SharedUtilChanges.swift index 8206068f5..605dfe8d1 100644 --- a/SessionMessagingKit/Database/Migrations/_011_SharedUtilChanges.swift +++ b/SessionMessagingKit/Database/Migrations/_011_SharedUtilChanges.swift @@ -2,6 +2,7 @@ import Foundation import GRDB +import SessionUtil import SessionUtilitiesKit /// This migration recreates the interaction FTS table and adds the threadId so we can do a performant in-conversation @@ -9,7 +10,7 @@ import SessionUtilitiesKit enum _011_SharedUtilChanges: Migration { static let target: TargetMigrations.Identifier = .messagingKit static let identifier: String = "SharedUtilChanges" - static let needsConfigSync: Bool = false + static let needsConfigSync: Bool = true static let minExpectedRunDuration: TimeInterval = 0.1 static func migrate(_ db: Database) throws { @@ -21,7 +22,18 @@ enum _011_SharedUtilChanges: Migration { .notNull() } - // TODO: Create dumps for current data + // Create a dump for the user profile data + let userProfileConf: UnsafeMutablePointer? = try SessionUtil.loadState( + for: .userProfile + ) + let confResult: SessionUtil.ConfResult = try SessionUtil.update( + conf: userProfileConf, + with: Profile.fetchOrCreateCurrentUser(db) + ) + + if confResult.needsDump { + try SessionUtil.saveState(db, conf: userProfileConf, for: .userProfile) + } Storage.update(progress: 1, for: self, in: target) // In case this is the last migration } From f12191f85ed3d3c545c7b520aa5af43362fcecb9 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 30 Nov 2022 08:51:19 +1100 Subject: [PATCH 006/135] Updated the ProtoWrappers.py to use Python3 and support more types --- Scripts/ProtoWrappers.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/Scripts/ProtoWrappers.py b/Scripts/ProtoWrappers.py index d5af28fae..b346d1b1f 100755 --- a/Scripts/ProtoWrappers.py +++ b/Scripts/ProtoWrappers.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- import os @@ -202,10 +202,18 @@ class BaseContext(object): return 'UInt32' elif field.proto_type == 'fixed64': return 'UInt64' + elif field.proto_type == 'int64': + return 'Int64' + elif field.proto_type == 'int32': + return 'Int32' elif field.proto_type == 'bool': return 'Bool' elif field.proto_type == 'bytes': return 'Data' + elif field.proto_type == 'double': + return 'Double' + elif field.proto_type == 'float': + return 'Float' else: matching_context = self.context_for_proto_type(field) if matching_context is not None: @@ -236,7 +244,11 @@ class BaseContext(object): return field.proto_type in ('uint64', 'uint32', 'fixed64', - 'bool', ) + 'int64', + 'int32', + 'bool', + 'double', + 'float', ) def can_field_be_optional(self, field): if self.is_field_primitive(field): @@ -288,8 +300,16 @@ class BaseContext(object): return '0' elif field.proto_type == 'fixed64': return '0' + elif field.proto_type == 'int64': + return '0' + elif field.proto_type == 'int32': + return '0' elif field.proto_type == 'bool': return 'false' + elif field.proto_type == 'double': + return '0' + elif field.proto_type == 'float': + return '0' elif self.is_field_an_enum(field): # TODO: Assert that rules is empty. enum_context = self.context_for_proto_type(field) From 1345e898095468048d463373b2e984613a037b16 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 2 Dec 2022 14:00:10 +1100 Subject: [PATCH 007/135] Further config util logic Removed the usage of the OWSAES256Key (using CryptoKit and raw data instead) Removed the pre-compiled headers to speed up builds with minor changes (explicit imports instead) # Conflicts: # Session.xcodeproj/project.pbxproj # SessionMessagingKit/Database/Models/ClosedGroup.swift # SessionMessagingKit/Protos/Generated/SNProto.swift # SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift # SessionMessagingKit/Protos/SessionProtos.proto # SessionMessagingKit/Sending & Receiving/MessageSender.swift # SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift # SessionMessagingKit/Utilities/ProfileManager.swift # SessionSnodeKit/Models/DeleteAllMessagesRequest.swift # SessionSnodeKit/Models/GetMessagesRequest.swift # SessionSnodeKit/Models/SendMessageRequest.swift # SessionSnodeKit/Types/SnodeAPINamespace.swift --- Session.xcodeproj/project.pbxproj | 48 ++-- .../SessionCallManager+CXProvider.swift | 2 +- .../Call Management/SessionCallManager.swift | 1 + .../Calls/Views & Modals/CallVideoView.swift | 7 +- .../Conversations/ConversationSearch.swift | 1 + .../ConversationVC+Interaction.swift | 5 +- .../EmojiPickerCollectionView.swift | 1 + .../Emoji Picker/EmojiSkinTonePicker.swift | 1 + .../Content Views/LinkPreviewState.swift | 1 + .../Content Views/MediaAlbumView.swift | 1 + .../Content Views/MediaView.swift | 1 + .../Content Views/TypingIndicatorView.swift | 1 + .../Settings/OWSMessageTimerView.m | 1 + Session/Emoji/Emoji+Available.swift | 1 + .../GlobalSearch/EmptySearchResultCell.swift | 1 + .../GlobalSearchViewController.swift | 1 + .../AllMediaViewController.swift | 1 + .../CropScaleImageViewController.swift | 3 +- .../DocumentTitleViewController.swift | 1 + .../GIFs/GifPickerCell.swift | 1 + .../GIFs/GifPickerLayout.swift | 3 +- .../GIFs/GifPickerViewController.swift | 1 + .../GIFs/GiphyAPI.swift | 3 +- .../ImagePickerController.swift | 1 + .../MediaDetailViewController.swift | 1 + .../MediaPageViewController.swift | 1 + .../MediaTileViewController.swift | 1 + .../PhotoCapture.swift | 3 +- .../PhotoCaptureViewController.swift | 1 + .../PhotoGridViewCell.swift | 1 + .../PhotoLibrary.swift | 6 +- .../SendMediaNavigationController.swift | 1 + Session/Meta/AppDelegate.swift | 1 + Session/Meta/AppEnvironment.swift | 1 + Session/Meta/MainAppContext.m | 6 +- Session/Meta/Session-Prefix.pch | 15 - Session/Meta/SessionApp.swift | 1 + Session/Meta/Signal-Bridging-Header.h | 5 - Session/Notifications/AppNotifications.swift | 1 + .../PushRegistrationManager.swift | 1 + Session/Notifications/SyncPushTokensJob.swift | 2 +- .../UserNotificationsAdaptee.swift | 3 +- Session/Settings/HelpViewModel.swift | 1 + Session/Shared/OWSBezierPathView.m | 2 + Session/Shared/ScreenLockUI.swift | 1 + Session/Utilities/MockDataGenerator.swift | 1 - Session/Utilities/UIApplication+OWS.swift | 1 + .../Database/LegacyDatabase/SMKLegacy.swift | 22 +- .../Migrations/_003_YDBToGRDBMigration.swift | 24 +- .../Migrations/_011_SharedUtilChanges.swift | 4 +- .../Database/Models/ClosedGroup.swift | 1 + .../Database/Models/Profile.swift | 39 +-- .../Database/Models/SharedConfigDump.swift | 16 +- .../Jobs/Types/AttachmentUploadJob.swift | 1 - .../Jobs/Types/FailedMessageSendsJob.swift | 2 +- .../Jobs/Types/GarbageCollectionJob.swift | 1 - .../Jobs/Types/MessageReceiveJob.swift | 12 +- .../Jobs/Types/MessageSendJob.swift | 1 - .../RetrieveDefaultOpenGroupRoomsJob.swift | 1 - .../Jobs/Types/SendReadReceiptsJob.swift | 2 +- .../Jobs/Types/UpdateProfilePictureJob.swift | 2 +- .../SessionUtil+UserProfile.swift | 112 ++++++++ .../LibSessionUtil/SessionUtil.swift | 142 ++++++++-- .../ClosedGroupControlMessage.swift | 1 - .../ConfigurationMessage+Convenience.swift | 4 +- .../ConfigurationMessage.swift | 2 - .../SharedConfigMessage.swift | 126 +++++++++ .../Messages/Message+Destination.swift | 69 ++++- SessionMessagingKit/Messages/Message.swift | 7 +- .../VisibleMessage+Profile.swift | 2 +- .../Protos/Generated/SNProto.swift | 221 ++++++--------- .../Protos/Generated/SessionProtos.pb.swift | 267 ++++++------------ .../Protos/SessionProtos.proto | 30 +- .../Errors/MessageReceiverError.swift | 6 +- ...essageReceiver+ConfigurationMessages.swift | 9 +- .../MessageReceiver+MessageRequests.swift | 6 +- .../MessageReceiver+VisibleMessages.swift | 6 +- .../Sending & Receiving/MessageReceiver.swift | 10 +- .../MessageSender+Convenience.swift | 51 +++- .../Sending & Receiving/MessageSender.swift | 30 +- .../Pollers/CurrentUserPoller.swift | 114 ++++++++ .../Sending & Receiving/Pollers/Poller.swift | 1 - SessionMessagingKit/Utilities/AppReadiness.m | 20 +- .../Utilities/OWSAES256Key+Utilities.swift | 12 - .../Utilities/ProfileManager.swift | 120 +++++++- .../NotificationServiceExtension.swift | 4 + .../Meta/SessionShareExtension-Prefix.pch | 14 - .../SignalShareExtension-Bridging-Header.h | 3 - .../SAEScreenLockViewController.swift | 2 +- .../ShareAppExtensionContext.swift | 1 + SessionShareExtension/ShareVC.swift | 1 + SessionShareExtension/ThreadPickerVC.swift | 1 - SessionSnodeKit/GetSnodePoolJob.swift | 1 - .../Models/DeleteAllMessagesRequest.swift | 81 ++++++ .../Models/GetMessagesRequest.swift | 72 +++++ .../Models/SendMessageRequest.swift | 77 +++++ SessionSnodeKit/Types/SnodeAPINamespace.swift | 37 +++ SessionUtilitiesKit/Database/Storage.swift | 28 +- SessionUtilitiesKit/General/Atomic.swift | 6 + SessionUtilitiesKit/General/General.swift | 1 + .../General/Sodium+Utilities.swift | 1 - .../General/String+Utilities.swift | 1 + SessionUtilitiesKit/JobRunner/JobRunner.swift | 1 - .../Utilities/OWSBackgroundTask.m | 22 +- .../Utilities/Randomness.swift | 19 ++ SessionUtilitiesKit/Utilities/Threading.swift | 23 ++ ...AttachmentApprovalInputAccessoryView.swift | 1 + .../AttachmentCaptionToolbar.swift | 3 +- .../AttachmentItemCollection.swift | 3 +- .../AttachmentPrepViewController.swift | 1 + .../AttachmentTextToolbar.swift | 3 +- .../ImageEditorBrushViewController.swift | 1 + .../Image Editing/ImageEditorCanvasView.swift | 1 + .../Image Editing/ImageEditorContents.swift | 3 +- .../ImageEditorCropViewController.swift | 5 +- .../Image Editing/ImageEditorModel.swift | 5 +- .../ImageEditorPaletteView.swift | 1 + .../ImageEditorPanGestureRecognizer.swift | 3 +- .../ImageEditorPinchGestureRecognizer.swift | 3 +- .../ImageEditorTextViewController.swift | 5 +- .../Image Editing/ImageEditorView.swift | 3 +- .../MediaMessageView.swift | 3 +- .../OWSVideoPlayer.swift | 3 +- .../VideoPlayerView.swift | 1 + .../Meta/SignalUtilitiesKit-Prefix.pch | 17 -- .../Profile Pictures/PlaceholderIcon.swift | 1 + .../Screen Lock/ScreenLock.swift | 1 + ...ModalActivityIndicatorViewController.swift | 1 + .../OWSViewController.m | 1 + .../Shared Views/ApprovalRailCellView.swift | 1 + .../Shared Views/CircleView.swift | 4 +- .../Shared Views/TappableStackView.swift | 3 +- .../Shared Views/TappableView.swift | 3 +- SignalUtilitiesKit/Shared Views/Toast.swift | 1 + SignalUtilitiesKit/Utilities/AppSetup.swift | 4 +- SignalUtilitiesKit/Utilities/AppVersion.m | 1 + SignalUtilitiesKit/Utilities/Bench.swift | 3 +- SignalUtilitiesKit/Utilities/ByteParser.m | 1 + SignalUtilitiesKit/Utilities/FunctionalUtil.m | 1 + .../Utilities/NSAttributedString+OWS.h | 2 + .../Utilities/NSAttributedString+OWS.m | 2 +- .../Utilities/NoopNotificationsManager.swift | 1 + SignalUtilitiesKit/Utilities/OWSError.m | 1 + SignalUtilitiesKit/Utilities/OWSOperation.m | 5 +- .../Utilities/OrderedDictionary.swift | 3 +- .../Utilities/OutageDetection.swift | 127 --------- .../Utilities/ReachabilityManager.swift | 3 +- .../Utilities/ReverseDispatchQueue.swift | 3 +- SignalUtilitiesKit/Utilities/SSKAsserts.h | 2 - .../Utilities/SwiftSingletons.swift | 3 +- .../Utilities/UIAlertController+OWS.swift | 3 +- SignalUtilitiesKit/Utilities/UIFont+OWS.m | 1 + SignalUtilitiesKit/Utilities/UIView+OWS.swift | 3 +- 153 files changed, 1481 insertions(+), 806 deletions(-) delete mode 100644 Session/Meta/Session-Prefix.pch create mode 100644 SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift create mode 100644 SessionMessagingKit/Messages/Control Messages/SharedConfigMessage.swift create mode 100644 SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift delete mode 100644 SessionMessagingKit/Utilities/OWSAES256Key+Utilities.swift delete mode 100644 SessionShareExtension/Meta/SessionShareExtension-Prefix.pch create mode 100644 SessionSnodeKit/Models/DeleteAllMessagesRequest.swift create mode 100644 SessionSnodeKit/Models/GetMessagesRequest.swift create mode 100644 SessionSnodeKit/Models/SendMessageRequest.swift create mode 100644 SessionSnodeKit/Types/SnodeAPINamespace.swift create mode 100644 SessionUtilitiesKit/Utilities/Randomness.swift create mode 100644 SessionUtilitiesKit/Utilities/Threading.swift delete mode 100644 SignalUtilitiesKit/Meta/SignalUtilitiesKit-Prefix.pch delete mode 100644 SignalUtilitiesKit/Utilities/OutageDetection.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 0426787fc..5cb3838e0 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -320,7 +320,6 @@ C33FDC29255A581F00E217F9 /* ReachabilityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA6F255A57FA00E217F9 /* ReachabilityManager.swift */; }; C33FDC45255A581F00E217F9 /* AppVersion.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA8B255A57FD00E217F9 /* AppVersion.m */; }; 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 */; }; C33FDC78255A582000E217F9 /* TSConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDABE255A580100E217F9 /* TSConstants.m */; }; C33FDC7D255A582000E217F9 /* OWSDispatch.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDAC3255A580200E217F9 /* OWSDispatch.m */; }; @@ -524,7 +523,6 @@ FD09797927FAB7E800936362 /* ImageFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09797827FAB7E800936362 /* ImageFormat.swift */; }; FD09797B27FBB25900936362 /* Updatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09797A27FBB25900936362 /* Updatable.swift */; }; FD09797D27FBDB2000936362 /* Notification+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09797C27FBDB2000936362 /* Notification+Utilities.swift */; }; - FD09797F27FCFBFF00936362 /* OWSAES256Key+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09797E27FCFBFF00936362 /* OWSAES256Key+Utilities.swift */; }; FD09798127FCFEE800936362 /* SessionThread.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09798027FCFEE800936362 /* SessionThread.swift */; }; FD09798327FD1A1500936362 /* ClosedGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09798227FD1A1500936362 /* ClosedGroup.swift */; }; FD09798527FD1A6500936362 /* ClosedGroupKeyPair.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09798427FD1A6500936362 /* ClosedGroupKeyPair.swift */; }; @@ -786,6 +784,10 @@ FD8ECF8629346DA100C0D1BB /* HeaderSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C906127E411AF00CD579F /* HeaderSpec.swift */; }; FD8ECF8729346DB500C0D1BB /* RequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C906327E4122F00CD579F /* RequestSpec.swift */; }; FD8ECF892935AB7200C0D1BB /* SessionUtilError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF882935AB7200C0D1BB /* SessionUtilError.swift */; }; + FD8ECF8B2935DB4B00C0D1BB /* SharedConfigMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF8A2935DB4B00C0D1BB /* SharedConfigMessage.swift */; }; + FD8ECF9029381FC200C0D1BB /* SessionUtil+UserProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF8F29381FC200C0D1BB /* SessionUtil+UserProfile.swift */; }; + FD8ECF922938552800C0D1BB /* Threading.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF912938552800C0D1BB /* Threading.swift */; }; + FD8ECF94293856AF00C0D1BB /* Randomness.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF93293856AF00C0D1BB /* Randomness.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 */; }; @@ -1094,7 +1096,6 @@ 3427C64220F500DF00EEC730 /* OWSMessageTimerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSMessageTimerView.m; sourceTree = ""; }; 3430FE171F7751D4000EC51B /* GiphyAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GiphyAPI.swift; sourceTree = ""; }; 34480B371FD092A900BC14EF /* SignalShareExtension-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SignalShareExtension-Bridging-Header.h"; sourceTree = ""; }; - 34480B381FD092E300BC14EF /* SessionShareExtension-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SessionShareExtension-Prefix.pch"; sourceTree = ""; }; 34661FB720C1C0D60056EDD6 /* message_sent.aiff */ = {isa = PBXFileReference; lastKnownFileType = audio.aiff; name = message_sent.aiff; path = Session/Meta/AudioFiles/message_sent.aiff; sourceTree = SOURCE_ROOT; }; 346B66301F4E29B200E5122F /* CropScaleImageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CropScaleImageViewController.swift; sourceTree = ""; }; 3488F9352191CC4000E524CC /* MediaView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaView.swift; sourceTree = ""; }; @@ -1410,7 +1411,6 @@ C33FDA8B255A57FD00E217F9 /* AppVersion.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppVersion.m; sourceTree = ""; }; C33FDA8E255A57FD00E217F9 /* OWSFileSystem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSFileSystem.m; sourceTree = ""; }; C33FDA96255A57FE00E217F9 /* OWSDispatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSDispatch.h; sourceTree = ""; }; - C33FDA99255A57FE00E217F9 /* OutageDetection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutageDetection.swift; sourceTree = ""; }; C33FDA9E255A57FF00E217F9 /* ReverseDispatchQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReverseDispatchQueue.swift; sourceTree = ""; }; C33FDAA8255A57FF00E217F9 /* BuildConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BuildConfiguration.swift; sourceTree = ""; }; C33FDABE255A580100E217F9 /* TSConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSConstants.m; sourceTree = ""; }; @@ -1564,7 +1564,6 @@ C38EF3E9255B6DF6007E1867 /* Toast.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Toast.swift; path = "SignalUtilitiesKit/Shared Views/Toast.swift"; sourceTree = SOURCE_ROOT; }; C38EF3ED255B6DF6007E1867 /* TappableStackView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TappableStackView.swift; path = "SignalUtilitiesKit/Shared Views/TappableStackView.swift"; sourceTree = SOURCE_ROOT; }; C38EF3EE255B6DF6007E1867 /* GradientView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GradientView.swift; path = SessionUIKit/Components/GradientView.swift; sourceTree = SOURCE_ROOT; }; - C38EF458255B710A007E1867 /* SignalUtilitiesKit-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SignalUtilitiesKit-Prefix.pch"; sourceTree = ""; }; C396469C2509D3ED00B0B9F5 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; C396469D2509D3F400B0B9F5 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; C396469E2509D40400B0B9F5 /* vi-VN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "vi-VN"; path = "vi-VN.lproj/Localizable.strings"; sourceTree = ""; }; @@ -1646,7 +1645,6 @@ D221A08F169C9E5E00537ABF /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; D221A091169C9E5E00537ABF /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; D221A095169C9E5E00537ABF /* Session-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Session-Info.plist"; sourceTree = ""; }; - D221A09B169C9E5E00537ABF /* Session-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Session-Prefix.pch"; sourceTree = ""; }; D221A0E7169DFFC500537ABF /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = ../../../../../../System/Library/Frameworks/AVFoundation.framework; sourceTree = ""; }; D24B5BD4169F568C00681372 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = ../../../../../../System/Library/Frameworks/AudioToolbox.framework; sourceTree = ""; }; D2AEACDB16C426DA00C364C0 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; @@ -1674,7 +1672,6 @@ FD09797827FAB7E800936362 /* ImageFormat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageFormat.swift; sourceTree = ""; }; FD09797A27FBB25900936362 /* Updatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Updatable.swift; sourceTree = ""; }; FD09797C27FBDB2000936362 /* Notification+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Notification+Utilities.swift"; sourceTree = ""; }; - FD09797E27FCFBFF00936362 /* OWSAES256Key+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OWSAES256Key+Utilities.swift"; sourceTree = ""; }; FD09798027FCFEE800936362 /* SessionThread.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionThread.swift; sourceTree = ""; }; FD09798227FD1A1500936362 /* ClosedGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClosedGroup.swift; sourceTree = ""; }; FD09798427FD1A6500936362 /* ClosedGroupKeyPair.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClosedGroupKeyPair.swift; sourceTree = ""; }; @@ -1907,6 +1904,10 @@ FD8ECF812934387A00C0D1BB /* ConfigUserProfileSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigUserProfileSpec.swift; sourceTree = ""; }; FD8ECF842934508B00C0D1BB /* BatchResponseSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchResponseSpec.swift; sourceTree = ""; }; FD8ECF882935AB7200C0D1BB /* SessionUtilError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionUtilError.swift; sourceTree = ""; }; + FD8ECF8A2935DB4B00C0D1BB /* SharedConfigMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedConfigMessage.swift; sourceTree = ""; }; + FD8ECF8F29381FC200C0D1BB /* SessionUtil+UserProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionUtil+UserProfile.swift"; sourceTree = ""; }; + FD8ECF912938552800C0D1BB /* Threading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Threading.swift; sourceTree = ""; }; + FD8ECF93293856AF00C0D1BB /* Randomness.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Randomness.swift; sourceTree = ""; }; FD90040E2818AB6D00ABAAF6 /* GetSnodePoolJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetSnodePoolJob.swift; sourceTree = ""; }; FD9004132818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_SetupStandardJobs.swift; sourceTree = ""; }; FDA8EAFD280E8B78002B68E5 /* FailedMessageSendsJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailedMessageSendsJob.swift; sourceTree = ""; }; @@ -2710,6 +2711,8 @@ C3C2A7702553A41E00C340D1 /* ControlMessage.swift */, B8DE1FB526C22FCB0079C9CE /* CallMessage.swift */, C34A977325A3E34A00852C71 /* ClosedGroupControlMessage.swift */, + C34A977325A3E34A00852C71 /* LegacyClosedGroupControlMessage.swift */, + FD8ECF8A2935DB4B00C0D1BB /* SharedConfigMessage.swift */, C3DA9C0625AE7396008F7C7E /* ConfigurationMessage.swift */, 7B93D06E27CF194000811CB6 /* ConfigurationMessage+Convenience.swift */, B8F5F60225EDE16F003BF8D4 /* DataExtractionNotification.swift */, @@ -2770,7 +2773,6 @@ 4535186F1FC635DD00210559 /* Info.plist */, 34B0796E1FD07B1E00E248C2 /* SignalShareExtension.entitlements */, 34480B371FD092A900BC14EF /* SignalShareExtension-Bridging-Header.h */, - 34480B381FD092E300BC14EF /* SessionShareExtension-Prefix.pch */, ); path = Meta; sourceTree = ""; @@ -2926,7 +2928,6 @@ isa = PBXGroup; children = ( C33FD9AD255A548A00E217F9 /* SignalUtilitiesKit.h */, - C38EF458255B710A007E1867 /* SignalUtilitiesKit-Prefix.pch */, C33FD9AE255A548A00E217F9 /* Info.plist */, ); path = Meta; @@ -3202,7 +3203,6 @@ C38EF309255B6DBE007E1867 /* DeviceSleepManager.swift */, C3BBE0C62554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift */, C3A71D0A2558989C0043A11F /* MessageWrapper.swift */, - FD09797E27FCFBFF00936362 /* OWSAES256Key+Utilities.swift */, C38EF2F5255B6DBC007E1867 /* OWSAudioPlayer.h */, C38EF2F7255B6DBC007E1867 /* OWSAudioPlayer.m */, C38EF281255B6D84007E1867 /* OWSAudioSession.swift */, @@ -3382,7 +3382,6 @@ C3F0A52F255C80BC007BE2A3 /* NoopNotificationsManager.swift */, C33FDA8B255A57FD00E217F9 /* AppVersion.m */, C33FDB69255A580F00E217F9 /* FeatureFlags.swift */, - C33FDA99255A57FE00E217F9 /* OutageDetection.swift */, C33FDB80255A581100E217F9 /* Notification+Loki.swift */, C33FDC16255A581E00E217F9 /* FunctionalUtil.h */, C33FDB17255A580800E217F9 /* FunctionalUtil.m */, @@ -3436,7 +3435,6 @@ FDF2220A2818F38D000A4995 /* SessionApp.swift */, 45B201741DAECBFD00C461E0 /* Signal-Bridging-Header.h */, D221A095169C9E5E00537ABF /* Session-Info.plist */, - D221A09B169C9E5E00537ABF /* Session-Prefix.pch */, 4C63CBFF210A620B003AE45C /* SignalTSan.supp */, 4C6F527B20FFE8400097DEEE /* SignalUBSan.supp */, 34074F54203D0722004596AE /* Sounds */, @@ -3568,6 +3566,8 @@ FD09797127FAA2F500936362 /* Optional+Utilities.swift */, FD09797C27FBDB2000936362 /* Notification+Utilities.swift */, FDF222082818D2B0000A4995 /* NSAttributedString+Utilities.swift */, + FD8ECF912938552800C0D1BB /* Threading.swift */, + FD8ECF93293856AF00C0D1BB /* Randomness.swift */, C33FDB38255A580B00E217F9 /* OWSBackgroundTask.h */, C33FDC1B255A581F00E217F9 /* OWSBackgroundTask.m */, ); @@ -4027,9 +4027,10 @@ FD8ECF7529340F4800C0D1BB /* LibSessionUtil */ = { isa = PBXGroup; children = ( + FD8ECF8E29381FB200C0D1BB /* Config Handling */, FD8ECF7829340F7100C0D1BB /* libsession-util.xcframework */, - FD8ECF7A29340FFD00C0D1BB /* SessionUtil.swift */, FD8ECF882935AB7200C0D1BB /* SessionUtilError.swift */, + FD8ECF7A29340FFD00C0D1BB /* SessionUtil.swift */, ); path = LibSessionUtil; sourceTree = ""; @@ -4052,6 +4053,14 @@ path = Networking; sourceTree = ""; }; + FD8ECF8E29381FB200C0D1BB /* Config Handling */ = { + isa = PBXGroup; + children = ( + FD8ECF8F29381FC200C0D1BB /* SessionUtil+UserProfile.swift */, + ); + path = "Config Handling"; + sourceTree = ""; + }; FD9004102818ABB000ABAAF6 /* JobRunner */ = { isa = PBXGroup; children = ( @@ -5279,7 +5288,6 @@ C38EF245255B6D67007E1867 /* UIFont+OWS.m in Sources */, C38EF36F255B6DCC007E1867 /* OWSViewController.m in Sources */, C38EF3FB255B6DF7007E1867 /* UIAlertController+OWS.swift in Sources */, - C33FDC53255A582000E217F9 /* OutageDetection.swift in Sources */, C38EF30C255B6DBF007E1867 /* ScreenLock.swift in Sources */, C38EF363255B6DCC007E1867 /* ModalActivityIndicatorViewController.swift in Sources */, C38EF38A255B6DD2007E1867 /* AttachmentCaptionToolbar.swift in Sources */, @@ -5437,6 +5445,7 @@ FD17D7BA27F51F2100122BE0 /* TargetMigrations.swift in Sources */, FD17D7C327F5204C00122BE0 /* Database+Utilities.swift in Sources */, FD17D7C527F5206300122BE0 /* ColumnDefinition+Utilities.swift in Sources */, + FD8ECF94293856AF00C0D1BB /* Randomness.swift in Sources */, FDC438CD27BC641200C60D73 /* Set+Utilities.swift in Sources */, FD77289E284EF1C50018502F /* Sodium+Utilities.swift in Sources */, B8856DE6256F15F2001CE70E /* String+SSK.swift in Sources */, @@ -5458,6 +5467,7 @@ FD5D201E27B0D87C00FEA984 /* SessionId.swift in Sources */, C32C5A24256DB7DB003C73A2 /* SNUserDefaults.swift in Sources */, C3BBE0A72554D4DE0050F1E3 /* Promise+Retrying.swift in Sources */, + FD8ECF922938552800C0D1BB /* Threading.swift in Sources */, B8856D7B256F14F4001CE70E /* UIView+OWS.m in Sources */, FDF22211281B5E0B000A4995 /* TableRecord+Utilities.swift in Sources */, B88FA7FB26114EA70049422F /* Hex.swift in Sources */, @@ -5534,6 +5544,8 @@ FD245C6A2850666F00B966DD /* FileServerAPI.swift in Sources */, FDC4386927B4E6B800C60D73 /* String+Utlities.swift in Sources */, FD716E6628502EE200C96BF4 /* CurrentCallProtocol.swift in Sources */, + FD26FA512919F9CE005801D8 /* GroupDeleteMessage.swift in Sources */, + FD8ECF9029381FC200C0D1BB /* SessionUtil+UserProfile.swift in Sources */, FD09B7E5288670BB00ED0B66 /* _008_EmojiReacts.swift in Sources */, FDC4385F27B4C4A200C60D73 /* PinnedMessage.swift in Sources */, 7BFD1A8C2747150E00FB91B9 /* TurnServerInfo.swift in Sources */, @@ -5550,10 +5562,10 @@ FDC4386727B4E10E00C60D73 /* Capabilities.swift in Sources */, FDC438A427BB107F00C60D73 /* UserBanRequest.swift in Sources */, C379DCF4256735770002D4EB /* VisibleMessage+Attachment.swift in Sources */, - FD09797F27FCFBFF00936362 /* OWSAES256Key+Utilities.swift in Sources */, FDB4BBC72838B91E00B7C95D /* LinkPreviewError.swift in Sources */, FD09798327FD1A1500936362 /* ClosedGroup.swift in Sources */, B8B320B7258C30D70020074B /* HTMLMetadata.swift in Sources */, + FD8ECF8B2935DB4B00C0D1BB /* SharedConfigMessage.swift in Sources */, FD09798727FD1B7800936362 /* GroupMember.swift in Sources */, FDB4BBC92839BEF000B7C95D /* ProfileManagerError.swift in Sources */, FD3E0C84283B5835002A425C /* SessionThreadViewModel.swift in Sources */, @@ -6153,7 +6165,6 @@ GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "SessionShareExtension/Meta/SessionShareExtension-Prefix.pch"; GCC_PREPROCESSOR_DEFINITIONS = ( "$(inherited)", "COCOAPODS=1", @@ -6227,7 +6238,6 @@ GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "SessionShareExtension/Meta/SessionShareExtension-Prefix.pch"; GCC_PREPROCESSOR_DEFINITIONS = ( "$(inherited)", "COCOAPODS=1", @@ -6569,7 +6579,6 @@ GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREFIX_HEADER = "SignalUtilitiesKit/Meta/SignalUtilitiesKit-Prefix.pch"; GCC_PREPROCESSOR_DEFINITIONS = ( "$(inherited)", "COCOAPODS=1", @@ -6651,7 +6660,6 @@ ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; - GCC_PREFIX_HEADER = "SignalUtilitiesKit/Meta/SignalUtilitiesKit-Prefix.pch"; GCC_PREPROCESSOR_DEFINITIONS = ( "$(inherited)", "COCOAPODS=1", @@ -7296,7 +7304,6 @@ ); GCC_OPTIMIZATION_LEVEL = 0; GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "Session/Meta/Session-Prefix.pch"; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", @@ -7368,7 +7375,6 @@ ); GCC_OPTIMIZATION_LEVEL = 3; GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "Session/Meta/Session-Prefix.pch"; GCC_PREPROCESSOR_DEFINITIONS = ( "$(inherited)", HAVE_CONFIG_H, diff --git a/Session/Calls/Call Management/SessionCallManager+CXProvider.swift b/Session/Calls/Call Management/SessionCallManager+CXProvider.swift index 612742bc5..9f122ebcd 100644 --- a/Session/Calls/Call Management/SessionCallManager+CXProvider.swift +++ b/Session/Calls/Call Management/SessionCallManager+CXProvider.swift @@ -2,8 +2,8 @@ import Foundation import CallKit -import SignalCoreKit import SessionUtilitiesKit +import SignalCoreKit extension SessionCallManager: CXProviderDelegate { public func providerDidReset(_ provider: CXProvider) { diff --git a/Session/Calls/Call Management/SessionCallManager.swift b/Session/Calls/Call Management/SessionCallManager.swift index b33177ff7..f22ffdcab 100644 --- a/Session/Calls/Call Management/SessionCallManager.swift +++ b/Session/Calls/Call Management/SessionCallManager.swift @@ -4,6 +4,7 @@ import UIKit import CallKit import GRDB import SessionMessagingKit +import SignalCoreKit public final class SessionCallManager: NSObject, CallManagerProtocol { let provider: CXProvider? diff --git a/Session/Calls/Views & Modals/CallVideoView.swift b/Session/Calls/Views & Modals/CallVideoView.swift index 899732f66..8b4f152fc 100644 --- a/Session/Calls/Views & Modals/CallVideoView.swift +++ b/Session/Calls/Views & Modals/CallVideoView.swift @@ -1,6 +1,8 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + import WebRTC import Foundation +import SessionUtilitiesKit #if targetEnvironment(simulator) // Note: 'RTCMTLVideoView' doesn't seem to work on the simulator so use 'RTCEAGLVideoView' instead @@ -27,7 +29,7 @@ class RemoteVideoView: TargetView { return } - DispatchMainThreadSafe { + Threading.dispatchMainThreadSafe { let frameRatio = Double(frame.height) / Double(frame.width) let frameRotation = frame.rotation let deviceRotation = UIDevice.current.orientation @@ -93,7 +95,8 @@ class LocalVideoView: TargetView { override func renderFrame(_ frame: RTCVideoFrame?) { super.renderFrame(frame) - DispatchMainThreadSafe { + + Threading.dispatchMainThreadSafe { // This is a workaround for a weird issue that // sometimes the rotationOverride is not working // if it is only set once on initialization diff --git a/Session/Conversations/ConversationSearch.swift b/Session/Conversations/ConversationSearch.swift index 87a43e6a0..2b6f7e96a 100644 --- a/Session/Conversations/ConversationSearch.swift +++ b/Session/Conversations/ConversationSearch.swift @@ -3,6 +3,7 @@ import UIKit import GRDB import SignalUtilitiesKit +import SignalCoreKit public class StyledSearchController: UISearchController { public override var preferredStatusBarStyle: UIStatusBarStyle { diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 83cbbb45a..48d9302eb 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -4,7 +4,6 @@ import UIKit import CoreServices import Photos import PhotosUI -import Sodium import PromiseKit import GRDB import SessionMessagingKit @@ -1799,7 +1798,7 @@ extension ConversationVC: message: unsendRequest, threadId: threadId, interactionId: nil, - to: .contact(publicKey: userPublicKey) + to: .contact(publicKey: userPublicKey, namespace: .default) ) } return @@ -1818,7 +1817,7 @@ extension ConversationVC: message: unsendRequest, threadId: threadId, interactionId: nil, - to: .contact(publicKey: userPublicKey) + to: .contact(publicKey: userPublicKey, namespace: .default) ) } self?.showInputAccessoryView() diff --git a/Session/Conversations/Emoji Picker/EmojiPickerCollectionView.swift b/Session/Conversations/Emoji Picker/EmojiPickerCollectionView.swift index a8dceb7da..0f251c925 100644 --- a/Session/Conversations/Emoji Picker/EmojiPickerCollectionView.swift +++ b/Session/Conversations/Emoji Picker/EmojiPickerCollectionView.swift @@ -3,6 +3,7 @@ import UIKit import SessionUIKit import SessionUtilitiesKit +import SignalCoreKit protocol EmojiPickerCollectionViewDelegate: AnyObject { func emojiPicker(_ emojiPicker: EmojiPickerCollectionView?, didSelectEmoji emoji: EmojiWithSkinTones) diff --git a/Session/Conversations/Emoji Picker/EmojiSkinTonePicker.swift b/Session/Conversations/Emoji Picker/EmojiSkinTonePicker.swift index 4889dffa9..b771ec7d6 100644 --- a/Session/Conversations/Emoji Picker/EmojiSkinTonePicker.swift +++ b/Session/Conversations/Emoji Picker/EmojiSkinTonePicker.swift @@ -2,6 +2,7 @@ import UIKit import SessionUIKit +import SignalCoreKit class EmojiSkinTonePicker: UIView { let emoji: Emoji diff --git a/Session/Conversations/Message Cells/Content Views/LinkPreviewState.swift b/Session/Conversations/Message Cells/Content Views/LinkPreviewState.swift index 054d19271..20fbbe748 100644 --- a/Session/Conversations/Message Cells/Content Views/LinkPreviewState.swift +++ b/Session/Conversations/Message Cells/Content Views/LinkPreviewState.swift @@ -2,6 +2,7 @@ import UIKit import SessionMessagingKit +import SignalCoreKit protocol LinkPreviewState { var isLoaded: Bool { get } diff --git a/Session/Conversations/Message Cells/Content Views/MediaAlbumView.swift b/Session/Conversations/Message Cells/Content Views/MediaAlbumView.swift index 846e0aa72..c6ca54b4e 100644 --- a/Session/Conversations/Message Cells/Content Views/MediaAlbumView.swift +++ b/Session/Conversations/Message Cells/Content Views/MediaAlbumView.swift @@ -2,6 +2,7 @@ import UIKit import SessionMessagingKit +import SignalCoreKit public class MediaAlbumView: UIStackView { private let items: [Attachment] diff --git a/Session/Conversations/Message Cells/Content Views/MediaView.swift b/Session/Conversations/Message Cells/Content Views/MediaView.swift index 301818a8b..331a6a84f 100644 --- a/Session/Conversations/Message Cells/Content Views/MediaView.swift +++ b/Session/Conversations/Message Cells/Content Views/MediaView.swift @@ -4,6 +4,7 @@ import UIKit import YYImage import SessionUIKit import SessionMessagingKit +import SignalCoreKit public class MediaView: UIView { static let contentMode: UIView.ContentMode = .scaleAspectFill diff --git a/Session/Conversations/Message Cells/Content Views/TypingIndicatorView.swift b/Session/Conversations/Message Cells/Content Views/TypingIndicatorView.swift index 860388558..9af77393f 100644 --- a/Session/Conversations/Message Cells/Content Views/TypingIndicatorView.swift +++ b/Session/Conversations/Message Cells/Content Views/TypingIndicatorView.swift @@ -2,6 +2,7 @@ import UIKit import SessionUIKit +import SignalCoreKit @objc class TypingIndicatorView: UIStackView { // This represents the spacing between the dots diff --git a/Session/Conversations/Settings/OWSMessageTimerView.m b/Session/Conversations/Settings/OWSMessageTimerView.m index dc71b3b06..984973253 100644 --- a/Session/Conversations/Settings/OWSMessageTimerView.m +++ b/Session/Conversations/Settings/OWSMessageTimerView.m @@ -6,6 +6,7 @@ #import "OWSMath.h" #import "UIView+OWS.h" #import +#import #import #import diff --git a/Session/Emoji/Emoji+Available.swift b/Session/Emoji/Emoji+Available.swift index 5b17fa05e..3619859c1 100644 --- a/Session/Emoji/Emoji+Available.swift +++ b/Session/Emoji/Emoji+Available.swift @@ -1,4 +1,5 @@ import Foundation +import SignalCoreKit extension Emoji { private static let availableCache: Atomic<[Emoji:Bool]> = Atomic([:]) diff --git a/Session/Home/GlobalSearch/EmptySearchResultCell.swift b/Session/Home/GlobalSearch/EmptySearchResultCell.swift index 2c11764ae..5c82210bb 100644 --- a/Session/Home/GlobalSearch/EmptySearchResultCell.swift +++ b/Session/Home/GlobalSearch/EmptySearchResultCell.swift @@ -5,6 +5,7 @@ import PureLayout import SessionUIKit import SessionUtilitiesKit import NVActivityIndicatorView +import SignalCoreKit class EmptySearchResultCell: UITableViewCell { private lazy var messageLabel: UILabel = { diff --git a/Session/Home/GlobalSearch/GlobalSearchViewController.swift b/Session/Home/GlobalSearch/GlobalSearchViewController.swift index 139b5c383..674c3eda3 100644 --- a/Session/Home/GlobalSearch/GlobalSearchViewController.swift +++ b/Session/Home/GlobalSearch/GlobalSearchViewController.swift @@ -7,6 +7,7 @@ import SessionUIKit import SessionMessagingKit import SessionUtilitiesKit import SignalUtilitiesKit +import SignalCoreKit class GlobalSearchViewController: BaseVC, UITableViewDelegate, UITableViewDataSource { fileprivate typealias SectionModel = ArraySection diff --git a/Session/Media Viewing & Editing/AllMediaViewController.swift b/Session/Media Viewing & Editing/AllMediaViewController.swift index 8e3cbba03..a9e09d488 100644 --- a/Session/Media Viewing & Editing/AllMediaViewController.swift +++ b/Session/Media Viewing & Editing/AllMediaViewController.swift @@ -6,6 +6,7 @@ import GRDB import DifferenceKit import SessionUIKit import SignalUtilitiesKit +import SignalCoreKit public class AllMediaViewController: UIViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate { private let pageVC = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil) diff --git a/Session/Media Viewing & Editing/CropScaleImageViewController.swift b/Session/Media Viewing & Editing/CropScaleImageViewController.swift index f6ad5d8ff..2a7905764 100644 --- a/Session/Media Viewing & Editing/CropScaleImageViewController.swift +++ b/Session/Media Viewing & Editing/CropScaleImageViewController.swift @@ -1,11 +1,10 @@ -// // Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// import Foundation import MediaPlayer import SessionUIKit import SignalUtilitiesKit +import SignalCoreKit // This kind of view is tricky. I've tried to organize things in the // simplest possible way. diff --git a/Session/Media Viewing & Editing/DocumentTitleViewController.swift b/Session/Media Viewing & Editing/DocumentTitleViewController.swift index 7a1e34733..09b2673d1 100644 --- a/Session/Media Viewing & Editing/DocumentTitleViewController.swift +++ b/Session/Media Viewing & Editing/DocumentTitleViewController.swift @@ -6,6 +6,7 @@ import GRDB import DifferenceKit import SessionUIKit import SignalUtilitiesKit +import SignalCoreKit public class DocumentTileViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { diff --git a/Session/Media Viewing & Editing/GIFs/GifPickerCell.swift b/Session/Media Viewing & Editing/GIFs/GifPickerCell.swift index 6bf633e81..6c8a5ae08 100644 --- a/Session/Media Viewing & Editing/GIFs/GifPickerCell.swift +++ b/Session/Media Viewing & Editing/GIFs/GifPickerCell.swift @@ -7,6 +7,7 @@ import PromiseKit import SignalUtilitiesKit import SignalUtilitiesKit import YYImage +import SignalCoreKit class GifPickerCell: UICollectionViewCell { diff --git a/Session/Media Viewing & Editing/GIFs/GifPickerLayout.swift b/Session/Media Viewing & Editing/GIFs/GifPickerLayout.swift index 2587eb5a2..41e020085 100644 --- a/Session/Media Viewing & Editing/GIFs/GifPickerLayout.swift +++ b/Session/Media Viewing & Editing/GIFs/GifPickerLayout.swift @@ -1,8 +1,7 @@ -// // Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// import Foundation +import SignalCoreKit protocol GifPickerLayoutDelegate: AnyObject { func imageInfosForLayout() -> [GiphyImageInfo] diff --git a/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift b/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift index 43bd6dc68..672ce1f54 100644 --- a/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift +++ b/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift @@ -7,6 +7,7 @@ import Reachability import SignalUtilitiesKit import PromiseKit import SessionUIKit +import SignalCoreKit class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollectionViewDataSource, UICollectionViewDelegate, GifPickerLayoutDelegate { diff --git a/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift b/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift index 0dea671df..28c010523 100644 --- a/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift +++ b/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift @@ -1,11 +1,10 @@ -// // Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// import AFNetworking import Foundation import PromiseKit import CoreServices +import SignalCoreKit // There's no UTI type for webp! enum GiphyFormat { diff --git a/Session/Media Viewing & Editing/ImagePickerController.swift b/Session/Media Viewing & Editing/ImagePickerController.swift index 5c0666ec4..34c756f7e 100644 --- a/Session/Media Viewing & Editing/ImagePickerController.swift +++ b/Session/Media Viewing & Editing/ImagePickerController.swift @@ -7,6 +7,7 @@ import Photos import PromiseKit import SessionUIKit import SignalUtilitiesKit +import SignalCoreKit protocol ImagePickerGridControllerDelegate: AnyObject { func imagePickerDidCompleteSelection(_ imagePicker: ImagePickerGridController) diff --git a/Session/Media Viewing & Editing/MediaDetailViewController.swift b/Session/Media Viewing & Editing/MediaDetailViewController.swift index cb6cdec1c..1a7abeebe 100644 --- a/Session/Media Viewing & Editing/MediaDetailViewController.swift +++ b/Session/Media Viewing & Editing/MediaDetailViewController.swift @@ -5,6 +5,7 @@ import YYImage import SessionUIKit import SignalUtilitiesKit import SessionMessagingKit +import SignalCoreKit public enum MediaGalleryOption { case sliderEnabled diff --git a/Session/Media Viewing & Editing/MediaPageViewController.swift b/Session/Media Viewing & Editing/MediaPageViewController.swift index 49d9374e5..1cb0e1d95 100644 --- a/Session/Media Viewing & Editing/MediaPageViewController.swift +++ b/Session/Media Viewing & Editing/MediaPageViewController.swift @@ -6,6 +6,7 @@ import PromiseKit import SessionUIKit import SessionMessagingKit import SignalUtilitiesKit +import SignalCoreKit class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate, MediaDetailViewControllerDelegate, InteractivelyDismissableViewController { class DynamicallySizedView: UIView { diff --git a/Session/Media Viewing & Editing/MediaTileViewController.swift b/Session/Media Viewing & Editing/MediaTileViewController.swift index bbd5506c4..a7e5f2039 100644 --- a/Session/Media Viewing & Editing/MediaTileViewController.swift +++ b/Session/Media Viewing & Editing/MediaTileViewController.swift @@ -6,6 +6,7 @@ import GRDB import DifferenceKit import SessionUIKit import SignalUtilitiesKit +import SignalCoreKit public class MediaTileViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout { diff --git a/Session/Media Viewing & Editing/PhotoCapture.swift b/Session/Media Viewing & Editing/PhotoCapture.swift index 41ec6bb30..4a406c066 100644 --- a/Session/Media Viewing & Editing/PhotoCapture.swift +++ b/Session/Media Viewing & Editing/PhotoCapture.swift @@ -1,11 +1,10 @@ -// // Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// import Foundation import AVFoundation import PromiseKit import CoreServices +import SignalCoreKit protocol PhotoCaptureDelegate: AnyObject { func photoCapture(_ photoCapture: PhotoCapture, didFinishProcessingAttachment attachment: SignalAttachment) diff --git a/Session/Media Viewing & Editing/PhotoCaptureViewController.swift b/Session/Media Viewing & Editing/PhotoCaptureViewController.swift index 5314a0f5c..af039ea6c 100644 --- a/Session/Media Viewing & Editing/PhotoCaptureViewController.swift +++ b/Session/Media Viewing & Editing/PhotoCaptureViewController.swift @@ -5,6 +5,7 @@ import AVFoundation import PromiseKit import SessionUIKit import SignalUtilitiesKit +import SignalCoreKit protocol PhotoCaptureViewControllerDelegate: AnyObject { func photoCaptureViewController(_ photoCaptureViewController: PhotoCaptureViewController, didFinishProcessingAttachment attachment: SignalAttachment) diff --git a/Session/Media Viewing & Editing/PhotoGridViewCell.swift b/Session/Media Viewing & Editing/PhotoGridViewCell.swift index bfbf0268a..9c33ca7be 100644 --- a/Session/Media Viewing & Editing/PhotoGridViewCell.swift +++ b/Session/Media Viewing & Editing/PhotoGridViewCell.swift @@ -4,6 +4,7 @@ import UIKit import SessionUIKit +import SignalCoreKit public enum PhotoGridItemType { case photo, animated, video diff --git a/Session/Media Viewing & Editing/PhotoLibrary.swift b/Session/Media Viewing & Editing/PhotoLibrary.swift index dcd5bf13b..6638c04c8 100644 --- a/Session/Media Viewing & Editing/PhotoLibrary.swift +++ b/Session/Media Viewing & Editing/PhotoLibrary.swift @@ -6,6 +6,8 @@ import Foundation import Photos import PromiseKit import CoreServices +import SignalUtilitiesKit +import SignalCoreKit protocol PhotoLibraryDelegate: AnyObject { func photoLibraryDidChange(_ photoLibrary: PhotoLibrary) @@ -53,7 +55,7 @@ class PhotoPickerAssetItem: PhotoGridItem { // Surprisingly, iOS will opportunistically run the completion block sync if the image is // already available. photoCollectionContents.requestThumbnail(for: self.asset, thumbnailSize: photoMediaSize.thumbnailSize) { image, _ in - DispatchMainThreadSafe({ + Threading.dispatchMainThreadSafe { // Once we've _successfully_ completed (e.g. invoked the completion with // a non-nil image), don't invoke the completion again with a nil argument. if !hasLoadedImage || image != nil { @@ -63,7 +65,7 @@ class PhotoPickerAssetItem: PhotoGridItem { hasLoadedImage = true } } - }) + } } } } diff --git a/Session/Media Viewing & Editing/SendMediaNavigationController.swift b/Session/Media Viewing & Editing/SendMediaNavigationController.swift index 71e8f231b..415f930bd 100644 --- a/Session/Media Viewing & Editing/SendMediaNavigationController.swift +++ b/Session/Media Viewing & Editing/SendMediaNavigationController.swift @@ -4,6 +4,7 @@ import UIKit import Photos import PromiseKit import SignalUtilitiesKit +import SignalCoreKit class SendMediaNavigationController: UINavigationController { public override var preferredStatusBarStyle: UIStatusBarStyle { diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index d91851597..71bf2e252 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -12,6 +12,7 @@ import SessionUIKit import UserNotifications import UIKit import SignalUtilitiesKit +import SignalCoreKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate { diff --git a/Session/Meta/AppEnvironment.swift b/Session/Meta/AppEnvironment.swift index 1dbb82087..3efc61905 100644 --- a/Session/Meta/AppEnvironment.swift +++ b/Session/Meta/AppEnvironment.swift @@ -2,6 +2,7 @@ import Foundation import SignalUtilitiesKit +import SignalCoreKit public class AppEnvironment { diff --git a/Session/Meta/MainAppContext.m b/Session/Meta/MainAppContext.m index d7116c5e5..3d0b92d1d 100644 --- a/Session/Meta/MainAppContext.m +++ b/Session/Meta/MainAppContext.m @@ -4,7 +4,7 @@ #import "MainAppContext.h" #import "Session-Swift.h" -#import +#import #import NS_ASSUME_NONNULL_BEGIN @@ -259,7 +259,7 @@ NSString *const ReportedApplicationStateDidChangeNotification = @"ReportedApplic { OWSAssertDebug(block); - DispatchMainThreadSafe(^{ + [Threading dispatchMainThreadSafe:^{ if (self.isMainAppAndActive) { // App active blocks typically will be used to safely access the // shared data container, so use a background task to protect this @@ -273,7 +273,7 @@ NSString *const ReportedApplicationStateDidChangeNotification = @"ReportedApplic } [self.appActiveBlocks addObject:block]; - }); + }]; } - (void)runAppActiveBlocks diff --git a/Session/Meta/Session-Prefix.pch b/Session/Meta/Session-Prefix.pch deleted file mode 100644 index 8998c4792..000000000 --- a/Session/Meta/Session-Prefix.pch +++ /dev/null @@ -1,15 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import - -#ifdef __OBJC__ - #import - #import - - #import - #import - #import - #import -#endif diff --git a/Session/Meta/SessionApp.swift b/Session/Meta/SessionApp.swift index c17905cf2..14e13aa73 100644 --- a/Session/Meta/SessionApp.swift +++ b/Session/Meta/SessionApp.swift @@ -3,6 +3,7 @@ import Foundation import SessionUtilitiesKit import SessionMessagingKit +import SignalCoreKit public struct SessionApp { static let homeViewController: Atomic = Atomic(nil) diff --git a/Session/Meta/Signal-Bridging-Header.h b/Session/Meta/Signal-Bridging-Header.h index 26368c90c..270acb8e1 100644 --- a/Session/Meta/Signal-Bridging-Header.h +++ b/Session/Meta/Signal-Bridging-Header.h @@ -16,11 +16,6 @@ #import #import #import -#import -#import -#import -#import -#import #import #import #import diff --git a/Session/Notifications/AppNotifications.swift b/Session/Notifications/AppNotifications.swift index cdc0cb67a..b957baeae 100644 --- a/Session/Notifications/AppNotifications.swift +++ b/Session/Notifications/AppNotifications.swift @@ -5,6 +5,7 @@ import GRDB import PromiseKit import SessionMessagingKit import SignalUtilitiesKit +import SignalCoreKit /// There are two primary components in our system notification integration: /// diff --git a/Session/Notifications/PushRegistrationManager.swift b/Session/Notifications/PushRegistrationManager.swift index 171ae9617..62ac77c31 100644 --- a/Session/Notifications/PushRegistrationManager.swift +++ b/Session/Notifications/PushRegistrationManager.swift @@ -7,6 +7,7 @@ import PromiseKit import PushKit import SignalUtilitiesKit import GRDB +import SignalCoreKit public enum PushRegistrationError: Error { case assertionError(description: String) diff --git a/Session/Notifications/SyncPushTokensJob.swift b/Session/Notifications/SyncPushTokensJob.swift index e18bc19b7..bf6fbadf6 100644 --- a/Session/Notifications/SyncPushTokensJob.swift +++ b/Session/Notifications/SyncPushTokensJob.swift @@ -3,9 +3,9 @@ import Foundation import GRDB import PromiseKit -import SignalCoreKit import SessionMessagingKit import SessionUtilitiesKit +import SignalCoreKit public enum SyncPushTokensJob: JobExecutor { public static let maxFailureCount: Int = -1 diff --git a/Session/Notifications/UserNotificationsAdaptee.swift b/Session/Notifications/UserNotificationsAdaptee.swift index c6da6323b..3b436499a 100644 --- a/Session/Notifications/UserNotificationsAdaptee.swift +++ b/Session/Notifications/UserNotificationsAdaptee.swift @@ -1,11 +1,10 @@ -// // Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// import Foundation import UserNotifications import PromiseKit import SessionMessagingKit +import SignalCoreKit class UserNotificationConfig { diff --git a/Session/Settings/HelpViewModel.swift b/Session/Settings/HelpViewModel.swift index 1ebae32c7..acfe2e089 100644 --- a/Session/Settings/HelpViewModel.swift +++ b/Session/Settings/HelpViewModel.swift @@ -6,6 +6,7 @@ import DifferenceKit import SessionUIKit import SessionMessagingKit import SessionUtilitiesKit +import SignalCoreKit class HelpViewModel: SessionTableViewModel { // MARK: - Section diff --git a/Session/Shared/OWSBezierPathView.m b/Session/Shared/OWSBezierPathView.m index eaac80fc0..eaddf67a7 100644 --- a/Session/Shared/OWSBezierPathView.m +++ b/Session/Shared/OWSBezierPathView.m @@ -2,7 +2,9 @@ // Copyright (c) 2018 Open Whisper Systems. All rights reserved. // +#import #import "OWSBezierPathView.h" +#import NS_ASSUME_NONNULL_BEGIN diff --git a/Session/Shared/ScreenLockUI.swift b/Session/Shared/ScreenLockUI.swift index 30db060b9..e358a2833 100644 --- a/Session/Shared/ScreenLockUI.swift +++ b/Session/Shared/ScreenLockUI.swift @@ -4,6 +4,7 @@ import UIKit import SessionMessagingKit import SessionUtilitiesKit import SignalUtilitiesKit +import SignalCoreKit class ScreenLockUI { public static let shared: ScreenLockUI = ScreenLockUI() diff --git a/Session/Utilities/MockDataGenerator.swift b/Session/Utilities/MockDataGenerator.swift index 457a50b89..8c7e12e5c 100644 --- a/Session/Utilities/MockDataGenerator.swift +++ b/Session/Utilities/MockDataGenerator.swift @@ -2,7 +2,6 @@ import Foundation import GRDB -import Sodium import Curve25519Kit import SessionMessagingKit diff --git a/Session/Utilities/UIApplication+OWS.swift b/Session/Utilities/UIApplication+OWS.swift index 3980dbe57..7c28cc7cd 100644 --- a/Session/Utilities/UIApplication+OWS.swift +++ b/Session/Utilities/UIApplication+OWS.swift @@ -3,6 +3,7 @@ import Foundation import SessionUtilitiesKit import UIKit +import SignalCoreKit @objc public extension UIApplication { diff --git a/SessionMessagingKit/Database/LegacyDatabase/SMKLegacy.swift b/SessionMessagingKit/Database/LegacyDatabase/SMKLegacy.swift index 713499ae7..65d49217c 100644 --- a/SessionMessagingKit/Database/LegacyDatabase/SMKLegacy.swift +++ b/SessionMessagingKit/Database/LegacyDatabase/SMKLegacy.swift @@ -3,7 +3,6 @@ import Foundation import Sodium import YapDatabase -import SignalCoreKit import SessionUtilitiesKit public enum SMKLegacy { @@ -89,10 +88,23 @@ public enum SMKLegacy { @objc(SNContact) public class _Contact: NSObject, NSCoding { + @objc(SNLegacyProfileKey) + public class _LegacyProfileKey: NSObject, NSCoding { + let keyData: Data + + public required init?(coder: NSCoder) { + keyData = coder.decodeObject(forKey: "keyData") as! Data + } + + public func encode(with coder: NSCoder) { + fatalError("encode(with:) should never be called for legacy types") + } + } + public let sessionID: String public var profilePictureURL: String? public var profilePictureFileName: String? - public var profileEncryptionKey: OWSAES256Key? + public var profileEncryptionKey: _LegacyProfileKey? public var threadID: String? public var isTrusted = false public var isApproved = false @@ -112,7 +124,7 @@ public enum SMKLegacy { if let nickname = coder.decodeObject(forKey: "nickname") as! String? { self.nickname = nickname } if let profilePictureURL = coder.decodeObject(forKey: "profilePictureURL") as! String? { self.profilePictureURL = profilePictureURL } if let profilePictureFileName = coder.decodeObject(forKey: "profilePictureFileName") as! String? { self.profilePictureFileName = profilePictureFileName } - if let profileEncryptionKey = coder.decodeObject(forKey: "profilePictureEncryptionKey") as! OWSAES256Key? { self.profileEncryptionKey = profileEncryptionKey } + if let profileEncryptionKey = coder.decodeObject(forKey: "profilePictureEncryptionKey") as! _LegacyProfileKey? { self.profileEncryptionKey = profileEncryptionKey } if let threadID = coder.decodeObject(forKey: "threadID") as! String? { self.threadID = threadID } let isBlockedFlag: Bool = coder.decodeBool(forKey: "isBlocked") @@ -1627,10 +1639,10 @@ public enum SMKLegacy { self.message = message if let destString: String = _MessageSendJob.process(rawDestination, type: "contact") { - destination = .contact(publicKey: destString) + destination = .contact(publicKey: destString, namespace: .default) } else if let destString: String = _MessageSendJob.process(rawDestination, type: "closedGroup") { - destination = .closedGroup(groupPublicKey: destString) + destination = .closedGroup(groupPublicKey: destString, namespace: .legacyClosedGroup) } else if _MessageSendJob.process(rawDestination, type: "openGroup") != nil { // We can no longer support sending messages to legacy open groups diff --git a/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift b/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift index d87a10ef2..00f08a2f5 100644 --- a/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift +++ b/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift @@ -421,7 +421,7 @@ enum _003_YDBToGRDBMigration: Migration { nickname: legacyContact.nickname, profilePictureUrl: legacyContact.profilePictureURL, profilePictureFileName: legacyContact.profilePictureFileName, - profileEncryptionKey: legacyContact.profileEncryptionKey + profileEncryptionKey: legacyContact.profileEncryptionKey?.keyData ).migrationSafeInsert(db) /// **Note:** The blow "shouldForce" flags are here to allow us to avoid having to run legacy migrations they @@ -526,7 +526,7 @@ enum _003_YDBToGRDBMigration: Migration { let recipientString: String = { if let destination: Message.Destination = destination { switch destination { - case .contact(let publicKey): return publicKey + case .contact(let publicKey, _): return publicKey default: break } } @@ -974,7 +974,10 @@ enum _003_YDBToGRDBMigration: Migration { .keys .map { $0 }) .defaulting(to: []), - destination: (threadVariant == .contact ? .contact(publicKey: threadId) : nil), + destination: (threadVariant == .contact ? + .contact(publicKey: threadId, namespace: .default) : + nil + ), variant: variant, useFallback: false ) @@ -986,7 +989,10 @@ enum _003_YDBToGRDBMigration: Migration { .keys .map { $0 }) .defaulting(to: []), - destination: (threadVariant == .contact ? .contact(publicKey: threadId) : nil), + destination: (threadVariant == .contact ? + .contact(publicKey: threadId, namespace: .default) : + nil + ), variant: variant, useFallback: true ) @@ -1273,8 +1279,8 @@ enum _003_YDBToGRDBMigration: Migration { // Fetch the threadId and interactionId this job should be associated with let threadId: String = { switch legacyJob.destination { - case .contact(let publicKey): return publicKey - case .closedGroup(let groupPublicKey): return groupPublicKey + case .contact(let publicKey, _): return publicKey + case .closedGroup(let groupPublicKey, _): return groupPublicKey case .openGroup(let roomToken, let server, _, _, _): return OpenGroup.idFor(roomToken: roomToken, server: server) @@ -1430,7 +1436,7 @@ enum _003_YDBToGRDBMigration: Migration { behaviour: .recurring, threadId: threadId, details: SendReadReceiptsJob.Details( - destination: .contact(publicKey: threadId), + destination: .contact(publicKey: threadId, namespace: .default), timestampMsValues: timestampsMs ) )?.migrationSafeInserted(db) @@ -1856,6 +1862,10 @@ enum _003_YDBToGRDBMigration: Migration { SMKLegacy._MessageRequestResponse.self, forClassName: "SNMessageRequestResponse" ) + NSKeyedUnarchiver.setClass( + SMKLegacy._Contact._LegacyProfileKey.self, + forClassName: "OWSAES256Key" + ) } } diff --git a/SessionMessagingKit/Database/Migrations/_011_SharedUtilChanges.swift b/SessionMessagingKit/Database/Migrations/_011_SharedUtilChanges.swift index 605dfe8d1..548f1f0d1 100644 --- a/SessionMessagingKit/Database/Migrations/_011_SharedUtilChanges.swift +++ b/SessionMessagingKit/Database/Migrations/_011_SharedUtilChanges.swift @@ -27,8 +27,8 @@ enum _011_SharedUtilChanges: Migration { for: .userProfile ) let confResult: SessionUtil.ConfResult = try SessionUtil.update( - conf: userProfileConf, - with: Profile.fetchOrCreateCurrentUser(db) + profile: Profile.fetchOrCreateCurrentUser(db), + in: .custom(conf: Atomic(userProfileConf)) ) if confResult.needsDump { diff --git a/SessionMessagingKit/Database/Models/ClosedGroup.swift b/SessionMessagingKit/Database/Models/ClosedGroup.swift index 48be3511a..00210ff12 100644 --- a/SessionMessagingKit/Database/Models/ClosedGroup.swift +++ b/SessionMessagingKit/Database/Models/ClosedGroup.swift @@ -2,6 +2,7 @@ import Foundation import GRDB +import DifferenceKit import SessionUtilitiesKit public struct ClosedGroup: Codable, Identifiable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { diff --git a/SessionMessagingKit/Database/Models/Profile.swift b/SessionMessagingKit/Database/Models/Profile.swift index 38f7c4cd9..f2e6c5237 100644 --- a/SessionMessagingKit/Database/Models/Profile.swift +++ b/SessionMessagingKit/Database/Models/Profile.swift @@ -3,7 +3,6 @@ import Foundation import GRDB import DifferenceKit -import SignalCoreKit import SessionUtilitiesKit public struct Profile: Codable, Identifiable, Equatable, Hashable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible, CustomStringConvertible, Differentiable { @@ -42,7 +41,7 @@ public struct Profile: Codable, Identifiable, Equatable, Hashable, FetchableReco public let profilePictureFileName: String? /// The key with which the profile is encrypted. - public let profileEncryptionKey: OWSAES256Key? + public let profileEncryptionKey: Data? // MARK: - Initialization @@ -52,7 +51,7 @@ public struct Profile: Codable, Identifiable, Equatable, Hashable, FetchableReco nickname: String? = nil, profilePictureUrl: String? = nil, profilePictureFileName: String? = nil, - profileEncryptionKey: OWSAES256Key? = nil + profileEncryptionKey: Data? = nil ) { self.id = id self.name = name @@ -68,7 +67,7 @@ public struct Profile: Codable, Identifiable, Equatable, Hashable, FetchableReco """ Profile( name: \(name), - profileKey: \(profileEncryptionKey?.keyData.description ?? "null"), + profileKey: \(profileEncryptionKey?.description ?? "null"), profilePictureUrl: \(profilePictureUrl ?? "null") ) """ @@ -81,7 +80,7 @@ public extension Profile { init(from decoder: Decoder) throws { let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) - var profileKey: OWSAES256Key? + var profileKey: Data? var profilePictureUrl: String? // If we have both a `profileKey` and a `profilePicture` then the key MUST be valid @@ -89,12 +88,7 @@ public extension Profile { let profileKeyData: Data = try? container.decode(Data.self, forKey: .profileEncryptionKey), let profilePictureUrlValue: String = try? container.decode(String.self, forKey: .profilePictureUrl) { - guard let validProfileKey: OWSAES256Key = OWSAES256Key(data: profileKeyData) else { - owsFailDebug("Failed to make profile key for key data") - throw StorageError.decodingFailed - } - - profileKey = validProfileKey + profileKey = profileKeyData profilePictureUrl = profilePictureUrlValue } @@ -113,10 +107,10 @@ public extension Profile { try container.encode(id, forKey: .id) try container.encode(name, forKey: .name) - try container.encode(nickname, forKey: .nickname) - try container.encode(profilePictureUrl, forKey: .profilePictureUrl) - try container.encode(profilePictureFileName, forKey: .profilePictureFileName) - try container.encode(profileEncryptionKey?.keyData, forKey: .profileEncryptionKey) + try container.encodeIfPresent(nickname, forKey: .nickname) + try container.encodeIfPresent(profilePictureUrl, forKey: .profilePictureUrl) + try container.encodeIfPresent(profilePictureFileName, forKey: .profilePictureFileName) + try container.encodeIfPresent(profileEncryptionKey, forKey: .profileEncryptionKey) } } @@ -126,17 +120,12 @@ public extension Profile { static func fromProto(_ proto: SNProtoDataMessage, id: String) -> Profile? { guard let profileProto = proto.profile, let displayName = profileProto.displayName else { return nil } - var profileKey: OWSAES256Key? + var profileKey: Data? var profilePictureUrl: String? // If we have both a `profileKey` and a `profilePicture` then the key MUST be valid if let profileKeyData: Data = proto.profileKey, profileProto.profilePicture != nil { - guard let validProfileKey: OWSAES256Key = OWSAES256Key(data: profileKeyData) else { - owsFailDebug("Failed to make profile key for key data") - return nil - } - - profileKey = validProfileKey + profileKey = profileKeyData profilePictureUrl = profileProto.profilePicture } @@ -155,8 +144,8 @@ public extension Profile { let profileProto = SNProtoLokiProfile.builder() profileProto.setDisplayName(name) - if let profileKey: OWSAES256Key = profileEncryptionKey, let profilePictureUrl: String = profilePictureUrl { - dataMessageProto.setProfileKey(profileKey.keyData) + if let profileKey: Data = profileEncryptionKey, let profilePictureUrl: String = profilePictureUrl { + dataMessageProto.setProfileKey(profileKey) profileProto.setProfilePicture(profilePictureUrl) } @@ -178,7 +167,7 @@ public extension Profile { name: String? = nil, profilePictureUrl: Updatable = .existing, profilePictureFileName: Updatable = .existing, - profileEncryptionKey: Updatable = .existing + profileEncryptionKey: Updatable = .existing ) -> Profile { return Profile( id: id, diff --git a/SessionMessagingKit/Database/Models/SharedConfigDump.swift b/SessionMessagingKit/Database/Models/SharedConfigDump.swift index c295b975f..c5278a68f 100644 --- a/SessionMessagingKit/Database/Models/SharedConfigDump.swift +++ b/SessionMessagingKit/Database/Models/SharedConfigDump.swift @@ -4,7 +4,7 @@ import Foundation import GRDB import SessionUtilitiesKit -internal struct ConfigDump: Codable, Equatable, Hashable, Identifiable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { +public struct ConfigDump: Codable, Equatable, Hashable, Identifiable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { public static var databaseTableName: String { "configDump" } public typealias Columns = CodingKeys @@ -13,11 +13,11 @@ internal struct ConfigDump: Codable, Equatable, Hashable, Identifiable, Fetchabl case data } - enum Variant: String, Codable, DatabaseValueConvertible, CaseIterable { + public enum Variant: String, Codable, DatabaseValueConvertible, CaseIterable { case userProfile } - var id: Variant { variant } + public var id: Variant { variant } /// The type of config this dump is for public let variant: Variant @@ -25,3 +25,13 @@ internal struct ConfigDump: Codable, Equatable, Hashable, Identifiable, Fetchabl /// The data for this dump public let data: Data } + +// MARK: - Convenience + +public extension ConfigDump.Variant { + var configMessageKind: SharedConfigMessage.Kind { + switch self { + case .userProfile: return .userProfile + } + } +} diff --git a/SessionMessagingKit/Jobs/Types/AttachmentUploadJob.swift b/SessionMessagingKit/Jobs/Types/AttachmentUploadJob.swift index 88e72891f..fac908cc0 100644 --- a/SessionMessagingKit/Jobs/Types/AttachmentUploadJob.swift +++ b/SessionMessagingKit/Jobs/Types/AttachmentUploadJob.swift @@ -3,7 +3,6 @@ import Foundation import GRDB import PromiseKit -import SignalCoreKit import SessionUtilitiesKit public enum AttachmentUploadJob: JobExecutor { diff --git a/SessionMessagingKit/Jobs/Types/FailedMessageSendsJob.swift b/SessionMessagingKit/Jobs/Types/FailedMessageSendsJob.swift index b83e2e31e..244c8ecf4 100644 --- a/SessionMessagingKit/Jobs/Types/FailedMessageSendsJob.swift +++ b/SessionMessagingKit/Jobs/Types/FailedMessageSendsJob.swift @@ -2,7 +2,7 @@ import Foundation import GRDB -import SignalCoreKit +//import SignalCoreKit import SessionUtilitiesKit public enum FailedMessageSendsJob: JobExecutor { diff --git a/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift b/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift index 6c47e876f..0f74c2209 100644 --- a/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift +++ b/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift @@ -3,7 +3,6 @@ import Foundation import GRDB import PromiseKit -import SignalCoreKit import SessionUtilitiesKit import SessionSnodeKit diff --git a/SessionMessagingKit/Jobs/Types/MessageReceiveJob.swift b/SessionMessagingKit/Jobs/Types/MessageReceiveJob.swift index 582c5aae1..5ec15e21e 100644 --- a/SessionMessagingKit/Jobs/Types/MessageReceiveJob.swift +++ b/SessionMessagingKit/Jobs/Types/MessageReceiveJob.swift @@ -27,11 +27,19 @@ public enum MessageReceiveJob: JobExecutor { var updatedJob: Job = job var leastSevereError: Error? + let nonConfigMessages: [Details.MessageInfo] = details.messages + .filter { $0.variant != .sharedConfigMessage } + let sharedConfigMessages: [SharedConfigMessage] = details.messages + .compactMap { $0.message as? SharedConfigMessage } Storage.shared.write { db in + // Send any SharedConfigMessages to the SessionUtil to handle it + try SessionUtil.handleConfigMessages(db, messages: sharedConfigMessages) + + // Handle the remaining messages var remainingMessagesToProcess: [Details.MessageInfo] = [] - for messageInfo in details.messages { + for messageInfo in nonConfigMessages { do { try MessageReceiver.handle( db, @@ -100,6 +108,8 @@ public enum MessageReceiveJob: JobExecutor { extension MessageReceiveJob { public struct Details: Codable { + typealias SharedConfigInfo = (message: SharedConfigMessage, serializedProtoData: Data) + public struct MessageInfo: Codable { private enum CodingKeys: String, CodingKey { case message diff --git a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift index e8d3e0265..d41b71b9d 100644 --- a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift +++ b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift @@ -3,7 +3,6 @@ import Foundation import GRDB import PromiseKit -import SignalCoreKit import SessionUtilitiesKit import SessionSnodeKit diff --git a/SessionMessagingKit/Jobs/Types/RetrieveDefaultOpenGroupRoomsJob.swift b/SessionMessagingKit/Jobs/Types/RetrieveDefaultOpenGroupRoomsJob.swift index 01c244019..5d33b0fed 100644 --- a/SessionMessagingKit/Jobs/Types/RetrieveDefaultOpenGroupRoomsJob.swift +++ b/SessionMessagingKit/Jobs/Types/RetrieveDefaultOpenGroupRoomsJob.swift @@ -2,7 +2,6 @@ import Foundation import GRDB -import SignalCoreKit import SessionUtilitiesKit public enum RetrieveDefaultOpenGroupRoomsJob: JobExecutor { diff --git a/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift b/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift index bf27ef218..33d369fc8 100644 --- a/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift +++ b/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift @@ -156,7 +156,7 @@ public extension SendReadReceiptsJob { behaviour: .recurring, threadId: threadId, details: Details( - destination: .contact(publicKey: threadId), + destination: .contact(publicKey: threadId, namespace: .default), timestampMsValues: timestampMsValues.asSet() ) ) diff --git a/SessionMessagingKit/Jobs/Types/UpdateProfilePictureJob.swift b/SessionMessagingKit/Jobs/Types/UpdateProfilePictureJob.swift index 0a79dfde9..d8dfc6b91 100644 --- a/SessionMessagingKit/Jobs/Types/UpdateProfilePictureJob.swift +++ b/SessionMessagingKit/Jobs/Types/UpdateProfilePictureJob.swift @@ -2,7 +2,6 @@ import Foundation import GRDB -import SignalCoreKit import SessionUtilitiesKit public enum UpdateProfilePictureJob: JobExecutor { @@ -37,6 +36,7 @@ public enum UpdateProfilePictureJob: JobExecutor { .updateAll(db, Job.Columns.nextRunTimestamp.set(to: 0)) } } + SNLog("[UpdateProfilePictureJob] Deferred as not enough time has passed since the last update") deferred(job) return } diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift new file mode 100644 index 000000000..38a616b59 --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift @@ -0,0 +1,112 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import GRDB +import SessionUtil +import SessionUtilitiesKit + +internal extension SessionUtil { + static func handleUserProfileUpdate( + _ db: Database, + in target: Target, + needsDump: Bool, + latestConfigUpdateSentTimestamp: TimeInterval + ) throws { + typealias ProfileData = (profileName: String, profilePictureUrl: String?, profilePictureKey: Data?) + + guard needsDump else { return } + guard target.conf.wrappedValue != nil else { throw SessionUtilError.nilConfigObject } + + let userPublicKey: String = getUserHexEncodedPublicKey(db) + + // Since we are doing direct memory manipulation we are using an `Atomic` type which has + // blocking access in it's `mutate` closure + let maybeProfileData: ProfileData? = target.conf.mutate { conf -> ProfileData? in + // A profile must have a name so if this is null then it's invalid and can be ignored + guard let profileNamePtr: UnsafePointer = user_profile_get_name(conf) else { + return nil + } + + let profileName: String = String(cString: profileNamePtr) + let profilePic: user_profile_pic = user_profile_get_pic(conf) + var profilePictureUrl: String? = nil + var profilePictureKey: Data? = nil + + // Make sure the url and key exist before reading the memory + if + profilePic.keylen > 0, + let profilePictureUrlPtr: UnsafePointer = profilePic.url, + let profilePictureKeyPtr: UnsafePointer = profilePic.key + { + profilePictureUrl = String(cString: profilePictureUrlPtr) + profilePictureKey = Data(bytes: profilePictureKeyPtr, count: profilePic.keylen) + } + + return ( + profileName: profileName, + profilePictureUrl: profilePictureUrl, + profilePictureKey: profilePictureKey + ) + } + + // Only save the data in the database if it's valid + guard let profileData: ProfileData = maybeProfileData else { return } + + // Profile (also force-approve the current user in case the account got into a weird state or + // restored directly from a migration) + try MessageReceiver.updateProfileIfNeeded( + db, + publicKey: userPublicKey, + name: profileData.profileName, + profilePictureUrl: profileData.profilePictureUrl, + profileKey: profileData.profilePictureKey, + sentTimestamp: latestConfigUpdateSentTimestamp + ) + try Contact(id: userPublicKey) + .with( + isApproved: true, + didApproveMe: true + ) + .save(db) + } + + @discardableResult static func update( + profile: Profile, + in target: Target + ) throws -> ConfResult { + guard target.conf.wrappedValue != nil else { throw SessionUtilError.nilConfigObject } + + // Since we are doing direct memory manipulation we are using an `Atomic` type which has + // blocking access in it's `mutate` closure + return target.conf.mutate { conf in + // Update the name + user_profile_set_name(conf, profile.name) + + let profilePic: user_profile_pic? = profile.profilePictureUrl? + .bytes + .map { CChar(bitPattern: $0) } + .withUnsafeBufferPointer { profileUrlPtr in + let profileKey: [CChar]? = profile.profileEncryptionKey? + .bytes + .map { CChar(bitPattern: $0) } + + return profileKey?.withUnsafeBufferPointer { profileKeyPtr in + user_profile_pic( + url: profileUrlPtr.baseAddress, + key: profileKeyPtr.baseAddress, + keylen: (profileKey?.count ?? 0) + ) + } + } + + if let profilePic: user_profile_pic = profilePic { + user_profile_set_pic(conf, profilePic) + } + + return ( + needsPush: config_needs_push(conf), + needsDump: config_needs_dump(conf) + ) + } + } +} diff --git a/SessionMessagingKit/LibSessionUtil/SessionUtil.swift b/SessionMessagingKit/LibSessionUtil/SessionUtil.swift index b60f692a3..1d3247ee4 100644 --- a/SessionMessagingKit/LibSessionUtil/SessionUtil.swift +++ b/SessionMessagingKit/LibSessionUtil/SessionUtil.swift @@ -6,7 +6,20 @@ import SessionUtil import SessionUtilitiesKit /*internal*/public enum SessionUtil { - typealias ConfResult = (needsPush: Bool, needsDump: Bool) + public typealias ConfResult = (needsPush: Bool, needsDump: Bool) + public typealias IncomingConfResult = (needsPush: Bool, needsDump: Bool, latestSentTimestamp: TimeInterval) + + enum Target { + case global(variant: ConfigDump.Variant) + case custom(conf: Atomic?>) + + var conf: Atomic?> { + switch self { + case .global(let variant): return SessionUtil.config(for: variant) + case .custom(let conf): return conf + } + } + } // MARK: - Configs @@ -23,6 +36,13 @@ import SessionUtilitiesKit } } + // MARK: - Convenience + private static func config(for variant: ConfigDump.Variant) -> Atomic?> { + switch variant { + case .userProfile: return SessionUtil.userProfileConfig + } + } + // MARK: - Loading /*internal*/public static func loadState() { @@ -103,42 +123,104 @@ import SessionUtilitiesKit .save(db) } - // MARK: - UserProfile + // MARK: - Pushes - internal static func update( - conf: UnsafeMutablePointer?, - with profile: Profile - ) throws -> ConfResult { - guard conf != nil else { throw SessionUtilError.nilConfigObject } + public static func getChanges( + for variants: [ConfigDump.Variant] = ConfigDump.Variant.allCases + ) -> [SharedConfigMessage] { + return variants + .compactMap { variant -> SharedConfigMessage? in + let conf = SessionUtil.config(for: variant) + + // Check if the config needs to be pushed + guard config_needs_push(conf.wrappedValue) else { return nil } + + var toPush: UnsafeMutablePointer? = nil + var toPushLen: Int = 0 + let seqNo: Int64 = conf.mutate { config_push($0, &toPush, &toPushLen) } + + guard let toPush: UnsafeMutablePointer = toPush else { return nil } + + let pushData: Data = Data(bytes: toPush, count: toPushLen) + toPush.deallocate() + + return SharedConfigMessage( + kind: variant.configMessageKind, + seqNo: seqNo, + data: pushData + ) + } + } + + public static func markAsPushed(messages: [SharedConfigMessage]) -> [ConfigDump.Variant: Bool] { + messages.reduce(into: [:]) { result, message in + let conf = SessionUtil.config(for: message.kind.configDumpVariant) + + // Mark the config as pushed + config_confirm_pushed(conf.wrappedValue, message.seqNo) + + // Update the result to indicate whether the config needs to be dumped + result[message.kind.configDumpVariant] = config_needs_dump(conf.wrappedValue) + } + } + + // MARK: - Receiving + + public static func handleConfigMessages( + _ db: Database, + messages: [SharedConfigMessage] + ) throws { + let groupedMessages: [SharedConfigMessage.Kind: [SharedConfigMessage]] = messages + .grouped(by: \.kind) - // Update the name - user_profile_set_name(conf, profile.name) - - let profilePic: user_profile_pic? = profile.profilePictureUrl? - .bytes - .map { CChar(bitPattern: $0) } - .withUnsafeBufferPointer { profileUrlPtr in - let profileKey: [CChar]? = profile.profileEncryptionKey? - .keyData - .bytes - .map { CChar(bitPattern: $0) } + // Merge the config messages into the current state + let results: [ConfigDump.Variant: IncomingConfResult] = groupedMessages + .reduce(into: [:]) { result, next in + let atomicConf = SessionUtil.config(for: next.key.configDumpVariant) + var needsPush: Bool = false + var needsDump: Bool = false + let messageSentTimestamp: TimeInterval = TimeInterval( + (next.value.compactMap { $0.sentTimestamp }.max() ?? 0) / 1000 + ) + + // Block the config while we are merging + atomicConf.mutate { conf in + var mergeData: [UnsafePointer?] = next.value + .map { message -> [CChar] in + message.data + .bytes + .map { CChar(bitPattern: $0) } + } + .unsafeCopy() + var mergeSize: [Int] = messages.map { $0.data.count } + config_merge(conf, &mergeData, &mergeSize, messages.count) + mergeData.forEach { $0?.deallocate() } - return profileKey?.withUnsafeBufferPointer { profileKeyPtr in - user_profile_pic( - url: profileUrlPtr.baseAddress, - key: profileKeyPtr.baseAddress, - keylen: (profileKey?.count ?? 0) - ) + // Get the state of this variant + needsPush = config_needs_push(conf) + needsDump = config_needs_dump(conf) } + + // Return the current state of the config + result[next.key.configDumpVariant] = ( + needsPush: needsPush, + needsDump: needsDump, + latestSentTimestamp: messageSentTimestamp + ) } - if let profilePic: user_profile_pic = profilePic { - user_profile_set_pic(conf, profilePic) + // If the data needs to be dumped then apply the relevant local changes + try results.forEach { variant, result in + switch variant { + case .userProfile: + try SessionUtil.handleUserProfileUpdate( + db, + in: .global(variant: variant), + needsDump: result.needsDump, + latestConfigUpdateSentTimestamp: result.latestSentTimestamp + ) + } } - return ( - needsPush: config_needs_push(conf), - needsDump: config_needs_dump(conf) - ) } } diff --git a/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift b/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift index 9f2214fe9..ec17d153e 100644 --- a/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift +++ b/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift @@ -3,7 +3,6 @@ import Foundation import GRDB import Sodium -import Curve25519Kit import SessionUtilitiesKit public final class ClosedGroupControlMessage: ControlMessage { diff --git a/SessionMessagingKit/Messages/Control Messages/ConfigurationMessage+Convenience.swift b/SessionMessagingKit/Messages/Control Messages/ConfigurationMessage+Convenience.swift index 72289c200..52dde7478 100644 --- a/SessionMessagingKit/Messages/Control Messages/ConfigurationMessage+Convenience.swift +++ b/SessionMessagingKit/Messages/Control Messages/ConfigurationMessage+Convenience.swift @@ -9,7 +9,7 @@ extension ConfigurationMessage { let currentUserProfile: Profile = Profile.fetchOrCreateCurrentUser(db) let displayName: String = currentUserProfile.name let profilePictureUrl: String? = currentUserProfile.profilePictureUrl - let profileKey: Data? = currentUserProfile.profileEncryptionKey?.keyData + let profileKey: Data? = currentUserProfile.profileEncryptionKey let closedGroups: Set = try ClosedGroup.fetchAll(db) .compactMap { closedGroup -> CMClosedGroup? in guard let latestKeyPair: ClosedGroupKeyPair = try closedGroup.fetchLatestKeyPair(db) else { @@ -62,7 +62,7 @@ extension ConfigurationMessage { publicKey: contact.id, displayName: (profile?.name ?? contact.id), profilePictureUrl: profile?.profilePictureUrl, - profileKey: profile?.profileEncryptionKey?.keyData, + profileKey: profile?.profileEncryptionKey, hasIsApproved: true, isApproved: contact.isApproved, hasIsBlocked: true, diff --git a/SessionMessagingKit/Messages/Control Messages/ConfigurationMessage.swift b/SessionMessagingKit/Messages/Control Messages/ConfigurationMessage.swift index 9ffa5e6ce..44977dd52 100644 --- a/SessionMessagingKit/Messages/Control Messages/ConfigurationMessage.swift +++ b/SessionMessagingKit/Messages/Control Messages/ConfigurationMessage.swift @@ -1,9 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation -import Sodium import GRDB -import Curve25519Kit import SessionUtilitiesKit public final class ConfigurationMessage: ControlMessage { diff --git a/SessionMessagingKit/Messages/Control Messages/SharedConfigMessage.swift b/SessionMessagingKit/Messages/Control Messages/SharedConfigMessage.swift new file mode 100644 index 000000000..ad4054fb4 --- /dev/null +++ b/SessionMessagingKit/Messages/Control Messages/SharedConfigMessage.swift @@ -0,0 +1,126 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import GRDB +import SessionUtilitiesKit + +public final class SharedConfigMessage: ControlMessage { + private enum CodingKeys: String, CodingKey { + case kind + case seqNo + case data + } + + public var kind: Kind + public var seqNo: Int64 + public var data: Data + + public override var isSelfSendValid: Bool { true } + + // MARK: - Kind + + public enum Kind: CustomStringConvertible, Codable { + case userProfile + + public var description: String { + switch self { + case .userProfile: return "userProfile" + } + } + } + + // MARK: - Initialization + + public init( + kind: Kind, + seqNo: Int64, + data: Data + ) { + self.kind = kind + self.seqNo = seqNo + self.data = data + + super.init() + } + + // MARK: - Codable + + required init(from decoder: Decoder) throws { + let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) + + kind = try container.decode(Kind.self, forKey: .kind) + seqNo = try container.decode(Int64.self, forKey: .seqNo) + data = try container.decode(Data.self, forKey: .data) + + try super.init(from: decoder) + } + + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + + var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(kind, forKey: .kind) + try container.encode(seqNo, forKey: .seqNo) + try container.encode(data, forKey: .data) + } + + // MARK: - Proto Conversion + + public override class func fromProto(_ proto: SNProtoContent, sender: String) -> SharedConfigMessage? { + guard let sharedConfigMessage = proto.sharedConfigMessage else { return nil } + + return SharedConfigMessage( + kind: { + switch sharedConfigMessage.kind { + case .userProfile: return .userProfile + } + }(), + seqNo: sharedConfigMessage.seqno, + data: sharedConfigMessage.data + ) + } + + public override func toProto(_ db: Database) -> SNProtoContent? { + do { + let sharedConfigMessage: SNProtoSharedConfigMessage.SNProtoSharedConfigMessageBuilder = SNProtoSharedConfigMessage.builder( + kind: { + switch self.kind { + case .userProfile: return .userProfile + } + }(), + seqno: self.seqNo, + data: self.data + ) + + let contentProto = SNProtoContent.builder() + contentProto.setSharedConfigMessage(try sharedConfigMessage.build()) + return try contentProto.build() + } catch { + SNLog("Couldn't construct data extraction notification proto from: \(self).") + return nil + } + } + + // MARK: - Description + + public var description: String { + """ + SharedConfigMessage( + kind: \(kind.description), + seqNo: \(seqNo), + data: \(data.count) bytes + ) + """ + } +} + +// MARK: - Convenience + +public extension SharedConfigMessage.Kind { + var configDumpVariant: ConfigDump.Variant { + switch self { + case .userProfile: return .userProfile + } + } +} diff --git a/SessionMessagingKit/Messages/Message+Destination.swift b/SessionMessagingKit/Messages/Message+Destination.swift index e1eaad9bc..3a04f5ac3 100644 --- a/SessionMessagingKit/Messages/Message+Destination.swift +++ b/SessionMessagingKit/Messages/Message+Destination.swift @@ -2,12 +2,19 @@ import Foundation import GRDB +import SessionSnodeKit import SessionUtilitiesKit public extension Message { enum Destination: Codable { - case contact(publicKey: String) - case closedGroup(groupPublicKey: String) + case contact( + publicKey: String, + namespace: SnodeAPI.Namespace + ) + case closedGroup( + groupPublicKey: String, + namespace: SnodeAPI.Namespace + ) case openGroup( roomToken: String, server: String, @@ -36,10 +43,10 @@ public extension Message { ) } - return .contact(publicKey: thread.id) + return .contact(publicKey: thread.id, namespace: .default) case .closedGroup: - return .closedGroup(groupPublicKey: thread.id) + return .closedGroup(groupPublicKey: thread.id, namespace: .legacyClosedGroup) case .openGroup: guard let openGroup: OpenGroup = try thread.openGroup.fetchOne(db) else { @@ -65,5 +72,59 @@ public extension Message { default: return self } } + + // MARK: - Codable + + // FIXME: Remove this custom implementation after enough time has passed (added the 'namespace' properties) + public init(from decoder: Decoder) throws { + let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) + + // Should only have a single root key so we can just switch on it to have cleaner code + switch container.allKeys.first { + case .contact: + let childContainer: KeyedDecodingContainer = try container.nestedContainer(keyedBy: ContactCodingKeys.self, forKey: .contact) + + self = .contact( + publicKey: try childContainer.decode(String.self, forKey: .publicKey), + namespace: ( + (try? childContainer.decode(SnodeAPI.Namespace.self, forKey: .namespace)) ?? + .default + ) + ) + + case .closedGroup: + let childContainer: KeyedDecodingContainer = try container.nestedContainer(keyedBy: ClosedGroupCodingKeys.self, forKey: .closedGroup) + + self = .closedGroup( + groupPublicKey: try childContainer.decode(String.self, forKey: .groupPublicKey), + namespace: ( + (try? childContainer.decode(SnodeAPI.Namespace.self, forKey: .namespace)) ?? + .legacyClosedGroup + ) + ) + + case .openGroup: + let childContainer: KeyedDecodingContainer = try container.nestedContainer(keyedBy: OpenGroupCodingKeys.self, forKey: .openGroup) + + self = .openGroup( + roomToken: try childContainer.decode(String.self, forKey: .roomToken), + server: try childContainer.decode(String.self, forKey: .server), + whisperTo: try? childContainer.decode(String.self, forKey: .whisperTo), + whisperMods: try childContainer.decode(Bool.self, forKey: .whisperMods), + fileIds: try? childContainer.decode([String].self, forKey: .fileIds) + ) + + case .openGroupInbox: + let childContainer: KeyedDecodingContainer = try container.nestedContainer(keyedBy: OpenGroupInboxCodingKeys.self, forKey: .openGroupInbox) + + self = .openGroupInbox( + server: try childContainer.decode(String.self, forKey: .server), + openGroupPublicKey: try childContainer.decode(String.self, forKey: .openGroupPublicKey), + blindedPublicKey: try childContainer.decode(String.self, forKey: .blindedPublicKey) + ) + + default: throw MessageReceiverError.invalidMessage + } + } } } diff --git a/SessionMessagingKit/Messages/Message.swift b/SessionMessagingKit/Messages/Message.swift index bac6dbb5e..6d8c63a64 100644 --- a/SessionMessagingKit/Messages/Message.swift +++ b/SessionMessagingKit/Messages/Message.swift @@ -99,6 +99,7 @@ public extension Message { case messageRequestResponse case visibleMessage case callMessage + case sharedConfigMessage init?(from type: Message) { switch type { @@ -112,6 +113,7 @@ public extension Message { case is MessageRequestResponse: self = .messageRequestResponse case is VisibleMessage: self = .visibleMessage case is CallMessage: self = .callMessage + case is SharedConfigMessage: self = .sharedConfigMessage default: return nil } } @@ -128,6 +130,7 @@ public extension Message { case .messageRequestResponse: return MessageRequestResponse.self case .visibleMessage: return VisibleMessage.self case .callMessage: return CallMessage.self + case .sharedConfigMessage: return SharedConfigMessage.self } } @@ -148,6 +151,7 @@ public extension Message { case .messageRequestResponse: return try container.decode(MessageRequestResponse.self, forKey: key) case .visibleMessage: return try container.decode(VisibleMessage.self, forKey: key) case .callMessage: return try container.decode(CallMessage.self, forKey: key) + case .sharedConfigMessage: return try container.decode(SharedConfigMessage.self, forKey: key) } } } @@ -165,7 +169,8 @@ public extension Message { .unsendRequest, .messageRequestResponse, .visibleMessage, - .callMessage + .callMessage, + .sharedConfigMessage ] return prioritisedVariants diff --git a/SessionMessagingKit/Messages/Visible Messages/VisibleMessage+Profile.swift b/SessionMessagingKit/Messages/Visible Messages/VisibleMessage+Profile.swift index dcec57fc5..8f63ed5a9 100644 --- a/SessionMessagingKit/Messages/Visible Messages/VisibleMessage+Profile.swift +++ b/SessionMessagingKit/Messages/Visible Messages/VisibleMessage+Profile.swift @@ -114,7 +114,7 @@ public extension VisibleMessage { extension VisibleMessage.VMProfile { init(profile: Profile) { self.displayName = profile.name - self.profileKey = profile.profileEncryptionKey?.keyData + self.profileKey = profile.profileEncryptionKey self.profilePictureUrl = profile.profilePictureUrl } } diff --git a/SessionMessagingKit/Protos/Generated/SNProto.swift b/SessionMessagingKit/Protos/Generated/SNProto.swift index 22a8dd6b2..26060b939 100644 --- a/SessionMessagingKit/Protos/Generated/SNProto.swift +++ b/SessionMessagingKit/Protos/Generated/SNProto.swift @@ -618,6 +618,9 @@ extension SNProtoMessageRequestResponse.SNProtoMessageRequestResponseBuilder { if let _value = messageRequestResponse { builder.setMessageRequestResponse(_value) } + if let _value = sharedConfigMessage { + builder.setSharedConfigMessage(_value) + } return builder } @@ -659,6 +662,10 @@ extension SNProtoMessageRequestResponse.SNProtoMessageRequestResponseBuilder { proto.messageRequestResponse = valueParam.proto } + @objc public func setSharedConfigMessage(_ valueParam: SNProtoSharedConfigMessage) { + proto.sharedConfigMessage = valueParam.proto + } + @objc public func build() throws -> SNProtoContent { return try SNProtoContent.parseProto(proto) } @@ -686,6 +693,8 @@ extension SNProtoMessageRequestResponse.SNProtoMessageRequestResponseBuilder { @objc public let messageRequestResponse: SNProtoMessageRequestResponse? + @objc public let sharedConfigMessage: SNProtoSharedConfigMessage? + private init(proto: SessionProtos_Content, dataMessage: SNProtoDataMessage?, callMessage: SNProtoCallMessage?, @@ -694,7 +703,8 @@ extension SNProtoMessageRequestResponse.SNProtoMessageRequestResponseBuilder { configurationMessage: SNProtoConfigurationMessage?, dataExtractionNotification: SNProtoDataExtractionNotification?, unsendRequest: SNProtoUnsendRequest?, - messageRequestResponse: SNProtoMessageRequestResponse?) { + messageRequestResponse: SNProtoMessageRequestResponse?, + sharedConfigMessage: SNProtoSharedConfigMessage?) { self.proto = proto self.dataMessage = dataMessage self.callMessage = callMessage @@ -704,6 +714,7 @@ extension SNProtoMessageRequestResponse.SNProtoMessageRequestResponseBuilder { self.dataExtractionNotification = dataExtractionNotification self.unsendRequest = unsendRequest self.messageRequestResponse = messageRequestResponse + self.sharedConfigMessage = sharedConfigMessage } @objc @@ -757,6 +768,11 @@ extension SNProtoMessageRequestResponse.SNProtoMessageRequestResponseBuilder { messageRequestResponse = try SNProtoMessageRequestResponse.parseProto(proto.messageRequestResponse) } + var sharedConfigMessage: SNProtoSharedConfigMessage? = nil + if proto.hasSharedConfigMessage { + sharedConfigMessage = try SNProtoSharedConfigMessage.parseProto(proto.sharedConfigMessage) + } + // MARK: - Begin Validation Logic for SNProtoContent - // MARK: - End Validation Logic for SNProtoContent - @@ -769,7 +785,8 @@ extension SNProtoMessageRequestResponse.SNProtoMessageRequestResponseBuilder { configurationMessage: configurationMessage, dataExtractionNotification: dataExtractionNotification, unsendRequest: unsendRequest, - messageRequestResponse: messageRequestResponse) + messageRequestResponse: messageRequestResponse, + sharedConfigMessage: sharedConfigMessage) return result } @@ -2449,9 +2466,6 @@ extension SNProtoDataMessageClosedGroupControlMessage.SNProtoDataMessageClosedGr builder.setBody(_value) } builder.setAttachments(attachments) - if let _value = group { - builder.setGroup(_value) - } if hasFlags { builder.setFlags(flags) } @@ -2506,10 +2520,6 @@ extension SNProtoDataMessageClosedGroupControlMessage.SNProtoDataMessageClosedGr proto.attachments = wrappedItems.map { $0.proto } } - @objc public func setGroup(_ valueParam: SNProtoGroupContext) { - proto.group = valueParam.proto - } - @objc public func setFlags(_ valueParam: UInt32) { proto.flags = valueParam } @@ -2573,8 +2583,6 @@ extension SNProtoDataMessageClosedGroupControlMessage.SNProtoDataMessageClosedGr @objc public let attachments: [SNProtoAttachmentPointer] - @objc public let group: SNProtoGroupContext? - @objc public let quote: SNProtoDataMessageQuote? @objc public let preview: [SNProtoDataMessagePreview] @@ -2640,7 +2648,6 @@ extension SNProtoDataMessageClosedGroupControlMessage.SNProtoDataMessageClosedGr private init(proto: SessionProtos_DataMessage, attachments: [SNProtoAttachmentPointer], - group: SNProtoGroupContext?, quote: SNProtoDataMessageQuote?, preview: [SNProtoDataMessagePreview], reaction: SNProtoDataMessageReaction?, @@ -2649,7 +2656,6 @@ extension SNProtoDataMessageClosedGroupControlMessage.SNProtoDataMessageClosedGr closedGroupControlMessage: SNProtoDataMessageClosedGroupControlMessage?) { self.proto = proto self.attachments = attachments - self.group = group self.quote = quote self.preview = preview self.reaction = reaction @@ -2672,11 +2678,6 @@ extension SNProtoDataMessageClosedGroupControlMessage.SNProtoDataMessageClosedGr var attachments: [SNProtoAttachmentPointer] = [] attachments = try proto.attachments.map { try SNProtoAttachmentPointer.parseProto($0) } - var group: SNProtoGroupContext? = nil - if proto.hasGroup { - group = try SNProtoGroupContext.parseProto(proto.group) - } - var quote: SNProtoDataMessageQuote? = nil if proto.hasQuote { quote = try SNProtoDataMessageQuote.parseProto(proto.quote) @@ -2711,7 +2712,6 @@ extension SNProtoDataMessageClosedGroupControlMessage.SNProtoDataMessageClosedGr let result = SNProtoDataMessage(proto: proto, attachments: attachments, - group: group, quote: quote, preview: preview, reaction: reaction, @@ -3706,152 +3706,91 @@ extension SNProtoAttachmentPointer.SNProtoAttachmentPointerBuilder { #endif -// MARK: - SNProtoGroupContext +// MARK: - SNProtoSharedConfigMessage -@objc public class SNProtoGroupContext: NSObject { +@objc public class SNProtoSharedConfigMessage: NSObject { - // MARK: - SNProtoGroupContextType + // MARK: - SNProtoSharedConfigMessageKind - @objc public enum SNProtoGroupContextType: Int32 { - case unknown = 0 - case update = 1 - case deliver = 2 - case quit = 3 - case requestInfo = 4 + @objc public enum SNProtoSharedConfigMessageKind: Int32 { + case userProfile = 1 } - private class func SNProtoGroupContextTypeWrap(_ value: SessionProtos_GroupContext.TypeEnum) -> SNProtoGroupContextType { + private class func SNProtoSharedConfigMessageKindWrap(_ value: SessionProtos_SharedConfigMessage.Kind) -> SNProtoSharedConfigMessageKind { switch value { - case .unknown: return .unknown - case .update: return .update - case .deliver: return .deliver - case .quit: return .quit - case .requestInfo: return .requestInfo + case .userProfile: return .userProfile } } - private class func SNProtoGroupContextTypeUnwrap(_ value: SNProtoGroupContextType) -> SessionProtos_GroupContext.TypeEnum { + private class func SNProtoSharedConfigMessageKindUnwrap(_ value: SNProtoSharedConfigMessageKind) -> SessionProtos_SharedConfigMessage.Kind { switch value { - case .unknown: return .unknown - case .update: return .update - case .deliver: return .deliver - case .quit: return .quit - case .requestInfo: return .requestInfo + case .userProfile: return .userProfile } } - // MARK: - SNProtoGroupContextBuilder + // MARK: - SNProtoSharedConfigMessageBuilder - @objc public class func builder(id: Data, type: SNProtoGroupContextType) -> SNProtoGroupContextBuilder { - return SNProtoGroupContextBuilder(id: id, type: type) + @objc public class func builder(kind: SNProtoSharedConfigMessageKind, seqno: Int64, data: Data) -> SNProtoSharedConfigMessageBuilder { + return SNProtoSharedConfigMessageBuilder(kind: kind, seqno: seqno, data: data) } // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SNProtoGroupContextBuilder { - let builder = SNProtoGroupContextBuilder(id: id, type: type) - if let _value = name { - builder.setName(_value) - } - builder.setMembers(members) - if let _value = avatar { - builder.setAvatar(_value) - } - builder.setAdmins(admins) + @objc public func asBuilder() -> SNProtoSharedConfigMessageBuilder { + let builder = SNProtoSharedConfigMessageBuilder(kind: kind, seqno: seqno, data: data) return builder } - @objc public class SNProtoGroupContextBuilder: NSObject { + @objc public class SNProtoSharedConfigMessageBuilder: NSObject { - private var proto = SessionProtos_GroupContext() + private var proto = SessionProtos_SharedConfigMessage() @objc fileprivate override init() {} - @objc fileprivate init(id: Data, type: SNProtoGroupContextType) { + @objc fileprivate init(kind: SNProtoSharedConfigMessageKind, seqno: Int64, data: Data) { super.init() - setId(id) - setType(type) + setKind(kind) + setSeqno(seqno) + setData(data) } - @objc public func setId(_ valueParam: Data) { - proto.id = valueParam + @objc public func setKind(_ valueParam: SNProtoSharedConfigMessageKind) { + proto.kind = SNProtoSharedConfigMessageKindUnwrap(valueParam) } - @objc public func setType(_ valueParam: SNProtoGroupContextType) { - proto.type = SNProtoGroupContextTypeUnwrap(valueParam) + @objc public func setSeqno(_ valueParam: Int64) { + proto.seqno = valueParam } - @objc public func setName(_ valueParam: String) { - proto.name = valueParam + @objc public func setData(_ valueParam: Data) { + proto.data = valueParam } - @objc public func addMembers(_ valueParam: String) { - var items = proto.members - items.append(valueParam) - proto.members = items - } - - @objc public func setMembers(_ wrappedItems: [String]) { - proto.members = wrappedItems - } - - @objc public func setAvatar(_ valueParam: SNProtoAttachmentPointer) { - proto.avatar = valueParam.proto - } - - @objc public func addAdmins(_ valueParam: String) { - var items = proto.admins - items.append(valueParam) - proto.admins = items - } - - @objc public func setAdmins(_ wrappedItems: [String]) { - proto.admins = wrappedItems - } - - @objc public func build() throws -> SNProtoGroupContext { - return try SNProtoGroupContext.parseProto(proto) + @objc public func build() throws -> SNProtoSharedConfigMessage { + return try SNProtoSharedConfigMessage.parseProto(proto) } @objc public func buildSerializedData() throws -> Data { - return try SNProtoGroupContext.parseProto(proto).serializedData() + return try SNProtoSharedConfigMessage.parseProto(proto).serializedData() } } - fileprivate let proto: SessionProtos_GroupContext + fileprivate let proto: SessionProtos_SharedConfigMessage - @objc public let id: Data + @objc public let kind: SNProtoSharedConfigMessageKind - @objc public let type: SNProtoGroupContextType + @objc public let seqno: Int64 - @objc public let avatar: SNProtoAttachmentPointer? + @objc public let data: Data - @objc public var name: String? { - guard proto.hasName else { - return nil - } - return proto.name - } - @objc public var hasName: Bool { - return proto.hasName - } - - @objc public var members: [String] { - return proto.members - } - - @objc public var admins: [String] { - return proto.admins - } - - private init(proto: SessionProtos_GroupContext, - id: Data, - type: SNProtoGroupContextType, - avatar: SNProtoAttachmentPointer?) { + private init(proto: SessionProtos_SharedConfigMessage, + kind: SNProtoSharedConfigMessageKind, + seqno: Int64, + data: Data) { self.proto = proto - self.id = id - self.type = type - self.avatar = avatar + self.kind = kind + self.seqno = seqno + self.data = data } @objc @@ -3859,35 +3798,35 @@ extension SNProtoAttachmentPointer.SNProtoAttachmentPointerBuilder { return try self.proto.serializedData() } - @objc public class func parseData(_ serializedData: Data) throws -> SNProtoGroupContext { - let proto = try SessionProtos_GroupContext(serializedData: serializedData) + @objc public class func parseData(_ serializedData: Data) throws -> SNProtoSharedConfigMessage { + let proto = try SessionProtos_SharedConfigMessage(serializedData: serializedData) return try parseProto(proto) } - fileprivate class func parseProto(_ proto: SessionProtos_GroupContext) throws -> SNProtoGroupContext { - guard proto.hasID else { - throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: id") + fileprivate class func parseProto(_ proto: SessionProtos_SharedConfigMessage) throws -> SNProtoSharedConfigMessage { + guard proto.hasKind else { + throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: kind") } - let id = proto.id + let kind = SNProtoSharedConfigMessageKindWrap(proto.kind) - guard proto.hasType else { - throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: type") + guard proto.hasSeqno else { + throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: seqno") } - let type = SNProtoGroupContextTypeWrap(proto.type) + let seqno = proto.seqno - var avatar: SNProtoAttachmentPointer? = nil - if proto.hasAvatar { - avatar = try SNProtoAttachmentPointer.parseProto(proto.avatar) + guard proto.hasData else { + throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: data") } + let data = proto.data - // MARK: - Begin Validation Logic for SNProtoGroupContext - + // MARK: - Begin Validation Logic for SNProtoSharedConfigMessage - - // MARK: - End Validation Logic for SNProtoGroupContext - + // MARK: - End Validation Logic for SNProtoSharedConfigMessage - - let result = SNProtoGroupContext(proto: proto, - id: id, - type: type, - avatar: avatar) + let result = SNProtoSharedConfigMessage(proto: proto, + kind: kind, + seqno: seqno, + data: data) return result } @@ -3898,14 +3837,14 @@ extension SNProtoAttachmentPointer.SNProtoAttachmentPointerBuilder { #if DEBUG -extension SNProtoGroupContext { +extension SNProtoSharedConfigMessage { @objc public func serializedDataIgnoringErrors() -> Data? { return try! self.serializedData() } } -extension SNProtoGroupContext.SNProtoGroupContextBuilder { - @objc public func buildIgnoringErrors() -> SNProtoGroupContext? { +extension SNProtoSharedConfigMessage.SNProtoSharedConfigMessageBuilder { + @objc public func buildIgnoringErrors() -> SNProtoSharedConfigMessage? { return try! self.build() } } diff --git a/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift b/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift index 488fc61f9..0d8cd6e48 100644 --- a/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift +++ b/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift @@ -348,6 +348,15 @@ struct SessionProtos_Content { /// Clears the value of `messageRequestResponse`. Subsequent reads from it will return its default value. mutating func clearMessageRequestResponse() {_uniqueStorage()._messageRequestResponse = nil} + var sharedConfigMessage: SessionProtos_SharedConfigMessage { + get {return _storage._sharedConfigMessage ?? SessionProtos_SharedConfigMessage()} + set {_uniqueStorage()._sharedConfigMessage = newValue} + } + /// Returns true if `sharedConfigMessage` has been explicitly set. + var hasSharedConfigMessage: Bool {return _storage._sharedConfigMessage != nil} + /// Clears the value of `sharedConfigMessage`. Subsequent reads from it will return its default value. + mutating func clearSharedConfigMessage() {_uniqueStorage()._sharedConfigMessage = nil} + var unknownFields = SwiftProtobuf.UnknownStorage() init() {} @@ -591,15 +600,7 @@ struct SessionProtos_DataMessage { set {_uniqueStorage()._attachments = newValue} } - var group: SessionProtos_GroupContext { - get {return _storage._group ?? SessionProtos_GroupContext()} - set {_uniqueStorage()._group = newValue} - } - /// Returns true if `group` has been explicitly set. - var hasGroup: Bool {return _storage._group != nil} - /// Clears the value of `group`. Subsequent reads from it will return its default value. - mutating func clearGroup() {_uniqueStorage()._group = nil} - + /// optional GroupContext group = 3; // No longer used var flags: UInt32 { get {return _storage._flags ?? 0} set {_uniqueStorage()._flags = newValue} @@ -1580,91 +1581,61 @@ extension SessionProtos_AttachmentPointer.Flags: CaseIterable { #endif // swift(>=4.2) -struct SessionProtos_GroupContext { +struct SessionProtos_SharedConfigMessage { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. /// @required - var id: Data { - get {return _storage._id ?? Data()} - set {_uniqueStorage()._id = newValue} + var kind: SessionProtos_SharedConfigMessage.Kind { + get {return _kind ?? .userProfile} + set {_kind = newValue} } - /// Returns true if `id` has been explicitly set. - var hasID: Bool {return _storage._id != nil} - /// Clears the value of `id`. Subsequent reads from it will return its default value. - mutating func clearID() {_uniqueStorage()._id = nil} + /// Returns true if `kind` has been explicitly set. + var hasKind: Bool {return self._kind != nil} + /// Clears the value of `kind`. Subsequent reads from it will return its default value. + mutating func clearKind() {self._kind = nil} /// @required - var type: SessionProtos_GroupContext.TypeEnum { - get {return _storage._type ?? .unknown} - set {_uniqueStorage()._type = newValue} + var seqno: Int64 { + get {return _seqno ?? 0} + set {_seqno = newValue} } - /// Returns true if `type` has been explicitly set. - var hasType: Bool {return _storage._type != nil} - /// Clears the value of `type`. Subsequent reads from it will return its default value. - mutating func clearType() {_uniqueStorage()._type = nil} + /// Returns true if `seqno` has been explicitly set. + var hasSeqno: Bool {return self._seqno != nil} + /// Clears the value of `seqno`. Subsequent reads from it will return its default value. + mutating func clearSeqno() {self._seqno = nil} - var name: String { - get {return _storage._name ?? String()} - set {_uniqueStorage()._name = newValue} - } - /// Returns true if `name` has been explicitly set. - var hasName: Bool {return _storage._name != nil} - /// Clears the value of `name`. Subsequent reads from it will return its default value. - mutating func clearName() {_uniqueStorage()._name = nil} - - var members: [String] { - get {return _storage._members} - set {_uniqueStorage()._members = newValue} - } - - var avatar: SessionProtos_AttachmentPointer { - get {return _storage._avatar ?? SessionProtos_AttachmentPointer()} - set {_uniqueStorage()._avatar = newValue} - } - /// Returns true if `avatar` has been explicitly set. - var hasAvatar: Bool {return _storage._avatar != nil} - /// Clears the value of `avatar`. Subsequent reads from it will return its default value. - mutating func clearAvatar() {_uniqueStorage()._avatar = nil} - - var admins: [String] { - get {return _storage._admins} - set {_uniqueStorage()._admins = newValue} + /// @required + var data: Data { + get {return _data ?? Data()} + set {_data = newValue} } + /// Returns true if `data` has been explicitly set. + var hasData: Bool {return self._data != nil} + /// Clears the value of `data`. Subsequent reads from it will return its default value. + mutating func clearData() {self._data = nil} var unknownFields = SwiftProtobuf.UnknownStorage() - enum TypeEnum: SwiftProtobuf.Enum { + enum Kind: SwiftProtobuf.Enum { typealias RawValue = Int - case unknown // = 0 - case update // = 1 - case deliver // = 2 - case quit // = 3 - case requestInfo // = 4 + case userProfile // = 1 init() { - self = .unknown + self = .userProfile } init?(rawValue: Int) { switch rawValue { - case 0: self = .unknown - case 1: self = .update - case 2: self = .deliver - case 3: self = .quit - case 4: self = .requestInfo + case 1: self = .userProfile default: return nil } } var rawValue: Int { switch self { - case .unknown: return 0 - case .update: return 1 - case .deliver: return 2 - case .quit: return 3 - case .requestInfo: return 4 + case .userProfile: return 1 } } @@ -1672,12 +1643,14 @@ struct SessionProtos_GroupContext { init() {} - fileprivate var _storage = _StorageClass.defaultInstance + fileprivate var _kind: SessionProtos_SharedConfigMessage.Kind? = nil + fileprivate var _seqno: Int64? = nil + fileprivate var _data: Data? = nil } #if swift(>=4.2) -extension SessionProtos_GroupContext.TypeEnum: CaseIterable { +extension SessionProtos_SharedConfigMessage.Kind: CaseIterable { // Support synthesized by the compiler. } @@ -1933,6 +1906,7 @@ extension SessionProtos_Content: SwiftProtobuf.Message, SwiftProtobuf._MessageIm 8: .same(proto: "dataExtractionNotification"), 9: .same(proto: "unsendRequest"), 10: .same(proto: "messageRequestResponse"), + 11: .same(proto: "sharedConfigMessage"), ] fileprivate class _StorageClass { @@ -1944,6 +1918,7 @@ extension SessionProtos_Content: SwiftProtobuf.Message, SwiftProtobuf._MessageIm var _dataExtractionNotification: SessionProtos_DataExtractionNotification? = nil var _unsendRequest: SessionProtos_UnsendRequest? = nil var _messageRequestResponse: SessionProtos_MessageRequestResponse? = nil + var _sharedConfigMessage: SessionProtos_SharedConfigMessage? = nil static let defaultInstance = _StorageClass() @@ -1958,6 +1933,7 @@ extension SessionProtos_Content: SwiftProtobuf.Message, SwiftProtobuf._MessageIm _dataExtractionNotification = source._dataExtractionNotification _unsendRequest = source._unsendRequest _messageRequestResponse = source._messageRequestResponse + _sharedConfigMessage = source._sharedConfigMessage } } @@ -1978,6 +1954,7 @@ extension SessionProtos_Content: SwiftProtobuf.Message, SwiftProtobuf._MessageIm if let v = _storage._dataExtractionNotification, !v.isInitialized {return false} if let v = _storage._unsendRequest, !v.isInitialized {return false} if let v = _storage._messageRequestResponse, !v.isInitialized {return false} + if let v = _storage._sharedConfigMessage, !v.isInitialized {return false} return true } } @@ -1998,6 +1975,7 @@ extension SessionProtos_Content: SwiftProtobuf.Message, SwiftProtobuf._MessageIm case 8: try { try decoder.decodeSingularMessageField(value: &_storage._dataExtractionNotification) }() case 9: try { try decoder.decodeSingularMessageField(value: &_storage._unsendRequest) }() case 10: try { try decoder.decodeSingularMessageField(value: &_storage._messageRequestResponse) }() + case 11: try { try decoder.decodeSingularMessageField(value: &_storage._sharedConfigMessage) }() default: break } } @@ -2034,6 +2012,9 @@ extension SessionProtos_Content: SwiftProtobuf.Message, SwiftProtobuf._MessageIm try { if let v = _storage._messageRequestResponse { try visitor.visitSingularMessageField(value: v, fieldNumber: 10) } }() + try { if let v = _storage._sharedConfigMessage { + try visitor.visitSingularMessageField(value: v, fieldNumber: 11) + } }() } try unknownFields.traverse(visitor: &visitor) } @@ -2051,6 +2032,7 @@ extension SessionProtos_Content: SwiftProtobuf.Message, SwiftProtobuf._MessageIm if _storage._dataExtractionNotification != rhs_storage._dataExtractionNotification {return false} if _storage._unsendRequest != rhs_storage._unsendRequest {return false} if _storage._messageRequestResponse != rhs_storage._messageRequestResponse {return false} + if _storage._sharedConfigMessage != rhs_storage._sharedConfigMessage {return false} return true } if !storagesAreEqual {return false} @@ -2286,7 +2268,6 @@ extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "body"), 2: .same(proto: "attachments"), - 3: .same(proto: "group"), 4: .same(proto: "flags"), 5: .same(proto: "expireTimer"), 6: .same(proto: "profileKey"), @@ -2303,7 +2284,6 @@ extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa fileprivate class _StorageClass { var _body: String? = nil var _attachments: [SessionProtos_AttachmentPointer] = [] - var _group: SessionProtos_GroupContext? = nil var _flags: UInt32? = nil var _expireTimer: UInt32? = nil var _profileKey: Data? = nil @@ -2323,7 +2303,6 @@ extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa init(copying source: _StorageClass) { _body = source._body _attachments = source._attachments - _group = source._group _flags = source._flags _expireTimer = source._expireTimer _profileKey = source._profileKey @@ -2348,7 +2327,6 @@ extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa public var isInitialized: Bool { return withExtendedLifetime(_storage) { (_storage: _StorageClass) in if !SwiftProtobuf.Internal.areAllInitialized(_storage._attachments) {return false} - if let v = _storage._group, !v.isInitialized {return false} if let v = _storage._quote, !v.isInitialized {return false} if !SwiftProtobuf.Internal.areAllInitialized(_storage._preview) {return false} if let v = _storage._reaction, !v.isInitialized {return false} @@ -2368,7 +2346,6 @@ extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa switch fieldNumber { case 1: try { try decoder.decodeSingularStringField(value: &_storage._body) }() case 2: try { try decoder.decodeRepeatedMessageField(value: &_storage._attachments) }() - case 3: try { try decoder.decodeSingularMessageField(value: &_storage._group) }() case 4: try { try decoder.decodeSingularUInt32Field(value: &_storage._flags) }() case 5: try { try decoder.decodeSingularUInt32Field(value: &_storage._expireTimer) }() case 6: try { try decoder.decodeSingularBytesField(value: &_storage._profileKey) }() @@ -2398,9 +2375,6 @@ extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa if !_storage._attachments.isEmpty { try visitor.visitRepeatedMessageField(value: _storage._attachments, fieldNumber: 2) } - try { if let v = _storage._group { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } }() try { if let v = _storage._flags { try visitor.visitSingularUInt32Field(value: v, fieldNumber: 4) } }() @@ -2445,7 +2419,6 @@ extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa let rhs_storage = _args.1 if _storage._body != rhs_storage._body {return false} if _storage._attachments != rhs_storage._attachments {return false} - if _storage._group != rhs_storage._group {return false} if _storage._flags != rhs_storage._flags {return false} if _storage._expireTimer != rhs_storage._expireTimer {return false} if _storage._profileKey != rhs_storage._profileKey {return false} @@ -3301,127 +3274,63 @@ extension SessionProtos_AttachmentPointer.Flags: SwiftProtobuf._ProtoNameProvidi ] } -extension SessionProtos_GroupContext: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".GroupContext" +extension SessionProtos_SharedConfigMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".SharedConfigMessage" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "id"), - 2: .same(proto: "type"), - 3: .same(proto: "name"), - 4: .same(proto: "members"), - 5: .same(proto: "avatar"), - 6: .same(proto: "admins"), + 1: .same(proto: "kind"), + 2: .same(proto: "seqno"), + 3: .same(proto: "data"), ] - fileprivate class _StorageClass { - var _id: Data? = nil - var _type: SessionProtos_GroupContext.TypeEnum? = nil - var _name: String? = nil - var _members: [String] = [] - var _avatar: SessionProtos_AttachmentPointer? = nil - var _admins: [String] = [] - - static let defaultInstance = _StorageClass() - - private init() {} - - init(copying source: _StorageClass) { - _id = source._id - _type = source._type - _name = source._name - _members = source._members - _avatar = source._avatar - _admins = source._admins - } - } - - fileprivate mutating func _uniqueStorage() -> _StorageClass { - if !isKnownUniquelyReferenced(&_storage) { - _storage = _StorageClass(copying: _storage) - } - return _storage - } - public var isInitialized: Bool { - return withExtendedLifetime(_storage) { (_storage: _StorageClass) in - if let v = _storage._avatar, !v.isInitialized {return false} - return true - } + if self._kind == nil {return false} + if self._seqno == nil {return false} + if self._data == nil {return false} + return true } mutating func decodeMessage(decoder: inout D) throws { - _ = _uniqueStorage() - try withExtendedLifetime(_storage) { (_storage: _StorageClass) in - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularBytesField(value: &_storage._id) }() - case 2: try { try decoder.decodeSingularEnumField(value: &_storage._type) }() - case 3: try { try decoder.decodeSingularStringField(value: &_storage._name) }() - case 4: try { try decoder.decodeRepeatedStringField(value: &_storage._members) }() - case 5: try { try decoder.decodeSingularMessageField(value: &_storage._avatar) }() - case 6: try { try decoder.decodeRepeatedStringField(value: &_storage._admins) }() - default: break - } + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularEnumField(value: &self._kind) }() + case 2: try { try decoder.decodeSingularInt64Field(value: &self._seqno) }() + case 3: try { try decoder.decodeSingularBytesField(value: &self._data) }() + default: break } } } func traverse(visitor: inout V) throws { - try withExtendedLifetime(_storage) { (_storage: _StorageClass) in - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - try { if let v = _storage._id { - try visitor.visitSingularBytesField(value: v, fieldNumber: 1) - } }() - try { if let v = _storage._type { - try visitor.visitSingularEnumField(value: v, fieldNumber: 2) - } }() - try { if let v = _storage._name { - try visitor.visitSingularStringField(value: v, fieldNumber: 3) - } }() - if !_storage._members.isEmpty { - try visitor.visitRepeatedStringField(value: _storage._members, fieldNumber: 4) - } - try { if let v = _storage._avatar { - try visitor.visitSingularMessageField(value: v, fieldNumber: 5) - } }() - if !_storage._admins.isEmpty { - try visitor.visitRepeatedStringField(value: _storage._admins, fieldNumber: 6) - } - } + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._kind { + try visitor.visitSingularEnumField(value: v, fieldNumber: 1) + } }() + try { if let v = self._seqno { + try visitor.visitSingularInt64Field(value: v, fieldNumber: 2) + } }() + try { if let v = self._data { + try visitor.visitSingularBytesField(value: v, fieldNumber: 3) + } }() try unknownFields.traverse(visitor: &visitor) } - static func ==(lhs: SessionProtos_GroupContext, rhs: SessionProtos_GroupContext) -> Bool { - if lhs._storage !== rhs._storage { - let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in - let _storage = _args.0 - let rhs_storage = _args.1 - if _storage._id != rhs_storage._id {return false} - if _storage._type != rhs_storage._type {return false} - if _storage._name != rhs_storage._name {return false} - if _storage._members != rhs_storage._members {return false} - if _storage._avatar != rhs_storage._avatar {return false} - if _storage._admins != rhs_storage._admins {return false} - return true - } - if !storagesAreEqual {return false} - } + static func ==(lhs: SessionProtos_SharedConfigMessage, rhs: SessionProtos_SharedConfigMessage) -> Bool { + if lhs._kind != rhs._kind {return false} + if lhs._seqno != rhs._seqno {return false} + if lhs._data != rhs._data {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } -extension SessionProtos_GroupContext.TypeEnum: SwiftProtobuf._ProtoNameProviding { +extension SessionProtos_SharedConfigMessage.Kind: SwiftProtobuf._ProtoNameProviding { static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 0: .same(proto: "UNKNOWN"), - 1: .same(proto: "UPDATE"), - 2: .same(proto: "DELIVER"), - 3: .same(proto: "QUIT"), - 4: .same(proto: "REQUEST_INFO"), + 1: .same(proto: "USER_PROFILE"), ] } diff --git a/SessionMessagingKit/Protos/SessionProtos.proto b/SessionMessagingKit/Protos/SessionProtos.proto index b642c5270..13ec388c2 100644 --- a/SessionMessagingKit/Protos/SessionProtos.proto +++ b/SessionMessagingKit/Protos/SessionProtos.proto @@ -57,6 +57,7 @@ message Content { optional DataExtractionNotification dataExtractionNotification = 8; optional UnsendRequest unsendRequest = 9; optional MessageRequestResponse messageRequestResponse = 10; + optional SharedConfigMessage sharedConfigMessage = 11; } message CallMessage { @@ -193,7 +194,7 @@ message DataMessage { optional string body = 1; repeated AttachmentPointer attachments = 2; - optional GroupContext group = 3; + // optional GroupContext group = 3; // No longer used optional uint32 flags = 4; optional uint32 expireTimer = 5; optional bytes profileKey = 6; @@ -271,22 +272,15 @@ message AttachmentPointer { optional string url = 101; } -message GroupContext { +message SharedConfigMessage { + enum Kind { + USER_PROFILE = 1; + } - enum Type { - UNKNOWN = 0; - UPDATE = 1; - DELIVER = 2; - QUIT = 3; - REQUEST_INFO = 4; - } - - // @required - optional bytes id = 1; - // @required - optional Type type = 2; - optional string name = 3; - repeated string members = 4; - optional AttachmentPointer avatar = 5; - repeated string admins = 6; + // @required + required Kind kind = 1; + // @required + required int64 seqno = 2; + // @required + required bytes data = 3; } diff --git a/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift b/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift index 74ff59dfc..b5d43eed6 100644 --- a/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift +++ b/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift @@ -19,12 +19,14 @@ public enum MessageReceiverError: LocalizedError { case decryptionFailed case invalidGroupPublicKey case noGroupKeyPair + case invalidSharedConfigMessageHandling public var isRetryable: Bool { switch self { case .duplicateMessage, .duplicateMessageNewSnode, .duplicateControlMessage, .invalidMessage, .unknownMessage, .unknownEnvelopeType, .invalidSignature, - .noData, .senderBlocked, .noThread, .selfSend, .decryptionFailed: + .noData, .senderBlocked, .noThread, .selfSend, .decryptionFailed, + .invalidSharedConfigMessageHandling: return false default: return true @@ -51,6 +53,8 @@ public enum MessageReceiverError: LocalizedError { // Shared sender keys case .invalidGroupPublicKey: return "Invalid group public key." case .noGroupKeyPair: return "Missing group key pair." + + case .invalidSharedConfigMessageHandling: return "Invalid handling of a shared config message" } } } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift index 1259720ae..71a56d0f3 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift @@ -3,7 +3,6 @@ import Foundation import GRDB import Sodium -import SignalCoreKit import SessionUtilitiesKit extension MessageReceiver { @@ -29,7 +28,7 @@ extension MessageReceiver { publicKey: userPublicKey, name: message.displayName, profilePictureUrl: message.profilePictureUrl, - profileKey: OWSAES256Key(data: message.profileKey), + profileKey: message.profileKey, sentTimestamp: messageSentTimestamp ) try Contact(id: userPublicKey) @@ -73,15 +72,13 @@ extension MessageReceiver { if profile.name != contactInfo.displayName || profile.profilePictureUrl != contactInfo.profilePictureUrl || - profile.profileEncryptionKey != contactInfo.profileKey.map({ OWSAES256Key(data: $0) }) + profile.profileEncryptionKey != contactInfo.profileKey { try profile .with( name: contactInfo.displayName, profilePictureUrl: .updateIf(contactInfo.profilePictureUrl), - profileEncryptionKey: .updateIf( - contactInfo.profileKey.map { OWSAES256Key(data: $0) } - ) + profileEncryptionKey: .updateIf(contactInfo.profileKey) ) .save(db) } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift index d16adbe95..d930b4239 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift @@ -2,7 +2,6 @@ import Foundation import GRDB -import SignalCoreKit import SessionUtilitiesKit extension MessageReceiver { @@ -21,17 +20,14 @@ extension MessageReceiver { // Update profile if needed (want to do this regardless of whether the message exists or // not to ensure the profile info gets sync between a users devices at every chance) if let profile = message.profile { - var contactProfileKey: OWSAES256Key? = nil let messageSentTimestamp: TimeInterval = (TimeInterval(message.sentTimestamp ?? 0) / 1000) - if let profileKey = profile.profileKey { contactProfileKey = OWSAES256Key(data: profileKey) } - try MessageReceiver.updateProfileIfNeeded( db, publicKey: senderId, name: profile.displayName, profilePictureUrl: profile.profilePictureUrl, - profileKey: contactProfileKey, + profileKey: profile.profileKey, sentTimestamp: messageSentTimestamp ) } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift index 12e15fc9c..8e6963210 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift @@ -3,7 +3,6 @@ import Foundation import GRDB import Sodium -import SignalCoreKit import SessionUtilitiesKit extension MessageReceiver { @@ -26,15 +25,12 @@ extension MessageReceiver { // Update profile if needed (want to do this regardless of whether the message exists or // not to ensure the profile info gets sync between a users devices at every chance) if let profile = message.profile { - var contactProfileKey: OWSAES256Key? = nil - if let profileKey = profile.profileKey { contactProfileKey = OWSAES256Key(data: profileKey) } - try MessageReceiver.updateProfileIfNeeded( db, publicKey: sender, name: profile.displayName, profilePictureUrl: profile.profilePictureUrl, - profileKey: contactProfileKey, + profileKey: profile.profileKey, sentTimestamp: messageSentTimestamp ) } diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index f3b6f674c..357a9b915 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -3,7 +3,6 @@ import Foundation import GRDB import Sodium -import SignalCoreKit import SessionUtilitiesKit public enum MessageReceiver { @@ -218,6 +217,9 @@ public enum MessageReceiver { openGroupId: openGroupId ) + // SharedConfigMessages should be handled by the 'SharedUtil' instead of this + case is SharedConfigMessage: throw MessageReceiverError.invalidSharedConfigMessageHandling + default: fatalError() } @@ -309,7 +311,7 @@ public enum MessageReceiver { publicKey: String, name: String?, profilePictureUrl: String?, - profileKey: OWSAES256Key?, + profileKey: Data?, sentTimestamp: TimeInterval, dependencies: Dependencies = Dependencies() ) throws { @@ -341,9 +343,9 @@ public enum MessageReceiver { // Profile picture & profile key if - let profileKey: OWSAES256Key = profileKey, + let profileKey: Data = profileKey, let profilePictureUrl: String = profilePictureUrl, - profileKey.keyData.count == kAES256_KeyByteLength, + profileKey.count == ProfileManager.avatarAES256KeyByteLength, profileKey != profile.profileEncryptionKey { let shouldUpdate: Bool diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift b/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift index 4f95ea77c..d59cc1621 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift @@ -106,8 +106,8 @@ extension MessageSender { if let interactionId: Int64 = interactionId { let threadId: String = { switch destination { - case .contact(let publicKey): return publicKey - case .closedGroup(let groupPublicKey): return groupPublicKey + case .contact(let publicKey, _): return publicKey + case .closedGroup(let groupPublicKey, _): return groupPublicKey case .openGroup(let roomToken, let server, _, _, _): return OpenGroup.idFor(roomToken: roomToken, server: server) @@ -194,16 +194,53 @@ extension MessageSender { guard Identity.userExists(db) else { return Promise(error: StorageError.generic) } let publicKey: String = getUserHexEncodedPublicKey(db) - let destination: Message.Destination = Message.Destination.contact(publicKey: publicKey) - let configurationMessage = try ConfigurationMessage.getCurrent(db) + let legacyDestination: Message.Destination = Message.Destination.contact( + publicKey: publicKey, + namespace: .default + ) + let legacyConfigurationMessage = try ConfigurationMessage.getCurrent(db) let (promise, seal) = Promise.pending() + let userConfigMessageChanges: [SharedConfigMessage] = SessionUtil.getChanges() + let destination: Message.Destination = Message.Destination.contact( + publicKey: publicKey, + namespace: .userProfileConfig + ) + if forceSyncNow { try MessageSender - .sendImmediate(db, message: configurationMessage, to: destination, interactionId: nil) + .sendImmediate(db, message: legacyConfigurationMessage, to: legacyDestination, interactionId: nil) .done { seal.fulfill(()) } .catch { _ in seal.reject(StorageError.generic) } .retainUntilComplete() + when( + resolved: try userConfigMessageChanges.map { message in + try MessageSender + .sendImmediate( + db, + message: message, + to: destination, + interactionId: nil + ) + } + ) + .done { results in + let hadError: Bool = results.contains { result in + switch result { + case .fulfilled: return false + case .rejected: return true + } + } + + guard !hadError else { + seal.reject(StorageError.generic) + return + } + + seal.fulfill(()) + } + .catch { _ in seal.reject(StorageError.generic) } + .retainUntilComplete() } else { JobRunner.add( @@ -212,8 +249,8 @@ extension MessageSender { variant: .messageSend, threadId: publicKey, details: MessageSendJob.Details( - destination: destination, - message: configurationMessage + destination: legacyDestination, + message: legacyConfigurationMessage ) ) ) diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index 547ab9521..6a05a0e38 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -77,8 +77,8 @@ public final class MessageSender { message.sender = userPublicKey message.recipient = { switch destination { - case .contact(let publicKey): return publicKey - case .closedGroup(let groupPublicKey): return groupPublicKey + case .contact(let publicKey, _): return publicKey + case .closedGroup(let groupPublicKey, _): return groupPublicKey case .openGroup, .openGroupInbox: preconditionFailure() } }() @@ -112,7 +112,7 @@ public final class MessageSender { if var messageWithProfile: MessageWithProfile = message as? MessageWithProfile { let profile: Profile = Profile.fetchOrCreateCurrentUser(db) - if let profileKey: Data = profile.profileEncryptionKey?.keyData, let profilePictureUrl: String = profile.profilePictureUrl { + if let profileKey: Data = profile.profileEncryptionKey, let profilePictureUrl: String = profile.profilePictureUrl { messageWithProfile.profile = VisibleMessage.VMProfile( displayName: profile.name, profileKey: profileKey, @@ -146,10 +146,10 @@ public final class MessageSender { let ciphertext: Data do { switch destination { - case .contact(let publicKey): + case .contact(let publicKey, _): ciphertext = try encryptWithSessionProtocol(plaintext, for: publicKey) - case .closedGroup(let groupPublicKey): + case .closedGroup(let groupPublicKey, _): guard let encryptionKeyPair: ClosedGroupKeyPair = try? ClosedGroupKeyPair.fetchLatestKeyPair(db, threadId: groupPublicKey) else { throw MessageSenderError.noKeyPair } @@ -171,15 +171,18 @@ public final class MessageSender { // Wrap the result let kind: SNProtoEnvelope.SNProtoEnvelopeType let senderPublicKey: String + let namespace: SnodeAPI.Namespace switch destination { - case .contact: + case .contact(_, let targetNamespace): kind = .sessionMessage senderPublicKey = "" + namespace = targetNamespace - case .closedGroup(let groupPublicKey): + case .closedGroup(let groupPublicKey, let targetNamespace): kind = .closedGroupMessage senderPublicKey = groupPublicKey + namespace = targetNamespace case .openGroup, .openGroupInbox: preconditionFailure() } @@ -208,8 +211,7 @@ public final class MessageSender { SnodeAPI .sendMessage( snodeMessage, - isClosedGroupMessage: (kind == .closedGroupMessage), - isConfigMessage: (message is ConfigurationMessage) + in: namespace ) .done(on: DispatchQueue.global(qos: .default)) { promises in let promiseCount = promises.count @@ -488,7 +490,7 @@ public final class MessageSender { if let message: VisibleMessage = message as? VisibleMessage { let profile: Profile = Profile.fetchOrCreateCurrentUser(db) - if let profileKey: Data = profile.profileEncryptionKey?.keyData, let profilePictureUrl: String = profile.profilePictureUrl { + if let profileKey: Data = profile.profileEncryptionKey, let profilePictureUrl: String = profile.profilePictureUrl { message.profile = VisibleMessage.VMProfile( displayName: profile.name, profileKey: profileKey, @@ -627,8 +629,8 @@ public final class MessageSender { try? ControlMessageProcessRecord( threadId: { switch destination { - case .contact(let publicKey): return publicKey - case .closedGroup(let groupPublicKey): return groupPublicKey + case .contact(let publicKey, _): return publicKey + case .closedGroup(let groupPublicKey, _): return groupPublicKey case .openGroup(let roomToken, let server, _, _, _): return OpenGroup.idFor(roomToken: roomToken, server: server) @@ -644,7 +646,7 @@ public final class MessageSender { // • the destination was a contact // • we didn't sync it already let userPublicKey = getUserHexEncodedPublicKey(db) - if case .contact(let publicKey) = destination, !isSyncMessage { + if case .contact(let publicKey, let namespace) = destination, !isSyncMessage { if let message = message as? VisibleMessage { message.syncTarget = publicKey } if let message = message as? ExpirationTimerUpdate { message.syncTarget = publicKey } @@ -652,7 +654,7 @@ public final class MessageSender { try sendToSnodeDestination( db, message: message, - to: .contact(publicKey: userPublicKey), + to: .contact(publicKey: userPublicKey, namespace: namespace), interactionId: interactionId, isSyncMessage: true ).retainUntilComplete() diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift new file mode 100644 index 000000000..378d6fcc2 --- /dev/null +++ b/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift @@ -0,0 +1,114 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import GRDB +import PromiseKit +import SessionSnodeKit +import SessionUtilitiesKit + +public final class CurrentUserPoller: Poller { + public static var namespaces: [SnodeAPI.Namespace] = [.default, .userProfileConfig] + + private var targetSnode: Atomic = Atomic(nil) + private var usedSnodes: Atomic> = Atomic([]) + + // MARK: - Settings + + override var namespaces: [SnodeAPI.Namespace] { CurrentUserPoller.namespaces } + + /// After polling a given snode this many times we always switch to a new one. + /// + /// The reason for doing this is that sometimes a snode will be giving us successful responses while + /// it isn't actually getting messages from other snodes. + override var maxNodePollCount: UInt { 6 } + + private let pollInterval: TimeInterval = 1.5 + private let retryInterval: TimeInterval = 0.25 + private let maxRetryInterval: TimeInterval = 15 + + // MARK: - Convenience Functions + + public func start() { + let publicKey: String = getUserHexEncodedPublicKey() + + guard isPolling.wrappedValue[publicKey] != true else { return } + + SNLog("Started polling.") + super.startIfNeeded(for: publicKey) + } + + public func stop() { + SNLog("Stopped polling.") + super.stopAllPollers() + } + + // MARK: - Abstract Methods + + override func pollerName(for publicKey: String) -> String { + return "Main Poller" + } + + override func nextPollDelay(for publicKey: String) -> TimeInterval { + let failureCount: TimeInterval = TimeInterval(failureCount.wrappedValue[publicKey] ?? 0) + + // If there have been no failures then just use the 'minPollInterval' + guard failureCount > 0 else { return pollInterval } + + // Otherwise use a simple back-off with the 'retryInterval' + let nextDelay: TimeInterval = (retryInterval * (failureCount * 1.2)) + + return min(maxRetryInterval, nextDelay) + } + + override func getSnodeForPolling( + for publicKey: String, + on queue: DispatchQueue + ) -> Promise { + if let targetSnode: Snode = self.targetSnode.wrappedValue { + return Promise.value(targetSnode) + } + + // Used the cached swarm for the given key and update the list of unusedSnodes + let swarm: Set = (SnodeAPI.swarmCache.wrappedValue[publicKey] ?? []) + let unusedSnodes: Set = swarm.subtracting(usedSnodes.wrappedValue) + + // randomElement() uses the system's default random generator, which is cryptographically secure + if let nextSnode: Snode = unusedSnodes.randomElement() { + self.targetSnode.mutate { $0 = nextSnode } + self.usedSnodes.mutate { $0.insert(nextSnode) } + + return Promise.value(nextSnode) + } + + // If we haven't retrieved a target snode at this point then either the cache + // is empty or we have used all of the snodes and need to start from scratch + return SnodeAPI.getSwarm(for: publicKey) + .then(on: queue) { [weak self] _ -> Promise in + guard let strongSelf = self else { return Promise(error: SnodeAPIError.generic) } + + self?.targetSnode.mutate { $0 = nil } + self?.usedSnodes.mutate { $0.removeAll() } + + return strongSelf.getSnodeForPolling(for: publicKey, on: queue) + } + } + + override func handlePollError(_ error: Error, for publicKey: String) { + if UserDefaults.sharedLokiProject?[.isMainAppActive] != true { + // Do nothing when an error gets throws right after returning from the background (happens frequently) + } + else if let targetSnode: Snode = targetSnode.wrappedValue { + SNLog("Polling \(targetSnode) failed; dropping it and switching to next snode.") + self.targetSnode.mutate { $0 = nil } + SnodeAPI.dropSnodeFromSwarmIfNeeded(targetSnode, publicKey: publicKey) + } + else { + SNLog("Polling failed due to having no target service node.") + } + + // Try to restart the poller from scratch + Threading.pollerQueue.async { [weak self] in + self?.setUpPolling(for: publicKey) + } + } +} diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift index 677acbaf5..7fc7d7b3f 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift @@ -3,7 +3,6 @@ import Foundation import GRDB import PromiseKit -import Sodium import SessionSnodeKit import SessionUtilitiesKit diff --git a/SessionMessagingKit/Utilities/AppReadiness.m b/SessionMessagingKit/Utilities/AppReadiness.m index 46ee6c2b9..2d1ef8176 100755 --- a/SessionMessagingKit/Utilities/AppReadiness.m +++ b/SessionMessagingKit/Utilities/AppReadiness.m @@ -4,8 +4,6 @@ #import "AppReadiness.h" #import "AppContext.h" -#import -#import NS_ASSUME_NONNULL_BEGIN @@ -53,9 +51,14 @@ NS_ASSUME_NONNULL_BEGIN + (void)runNowOrWhenAppWillBecomeReady:(AppReadyBlock)block { - DispatchMainThreadSafe(^{ + if ([NSThread isMainThread]) { [self.sharedManager runNowOrWhenAppWillBecomeReady:block]; - }); + } + else { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.sharedManager runNowOrWhenAppWillBecomeReady:block]; + }); + } } - (void)runNowOrWhenAppWillBecomeReady:(AppReadyBlock)block @@ -75,9 +78,14 @@ NS_ASSUME_NONNULL_BEGIN + (void)runNowOrWhenAppDidBecomeReady:(AppReadyBlock)block { - DispatchMainThreadSafe(^{ + if ([NSThread isMainThread]) { [self.sharedManager runNowOrWhenAppDidBecomeReady:block]; - }); + } + else { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.sharedManager runNowOrWhenAppDidBecomeReady:block]; + }); + } } - (void)runNowOrWhenAppDidBecomeReady:(AppReadyBlock)block diff --git a/SessionMessagingKit/Utilities/OWSAES256Key+Utilities.swift b/SessionMessagingKit/Utilities/OWSAES256Key+Utilities.swift deleted file mode 100644 index 295c78ed9..000000000 --- a/SessionMessagingKit/Utilities/OWSAES256Key+Utilities.swift +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import Foundation -import SignalCoreKit - -public extension OWSAES256Key { - convenience init?(data: Data?) { - guard let existingData: Data = data else { return nil } - - self.init(data: existingData) - } -} diff --git a/SessionMessagingKit/Utilities/ProfileManager.swift b/SessionMessagingKit/Utilities/ProfileManager.swift index ba5212d30..79c3435ae 100644 --- a/SessionMessagingKit/Utilities/ProfileManager.swift +++ b/SessionMessagingKit/Utilities/ProfileManager.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit +import CryptoKit import GRDB import PromiseKit import SignalCoreKit @@ -11,6 +12,9 @@ public struct ProfileManager { // Before encrypting and submitting we NULL pad the name data to this length. private static let nameDataLength: UInt = 64 public static let maxAvatarDiameter: CGFloat = 640 + internal static let avatarAES256KeyByteLength: Int = 32 + private static let avatarNonceLength: Int = 12 + private static let avatarTagLength: Int = 16 private static var profileAvatarCache: Atomic<[String: Data]> = Atomic([:]) private static var currentAvatarDownloads: Atomic> = Atomic([]) @@ -82,16 +86,40 @@ public struct ProfileManager { // MARK: - Profile Encryption - private static func encryptProfileData(data: Data, key: OWSAES256Key) -> Data? { - guard key.keyData.count == kAES256_KeyByteLength else { return nil } + private static func encryptData(data: Data, key: Data) -> Data? { + // The key structure is: nonce || ciphertext || authTag + guard + key.count == ProfileManager.avatarAES256KeyByteLength, + let nonceData: Data = try? Randomness.generateRandomBytes(numberBytes: ProfileManager.avatarNonceLength), + let nonce: AES.GCM.Nonce = try? AES.GCM.Nonce(data: nonceData), + let sealedData: AES.GCM.SealedBox = try? AES.GCM.seal( + data, + using: SymmetricKey(data: key), + nonce: nonce + ), + let encryptedContent: Data = sealedData.combined + else { return nil } - return Cryptography.encryptAESGCMProfileData(plainTextData: data, key: key) + return encryptedContent } - private static func decryptProfileData(data: Data, key: OWSAES256Key) -> Data? { - guard key.keyData.count == kAES256_KeyByteLength else { return nil } + private static func decryptData(data: Data, key: Data) -> Data? { + guard key.count == ProfileManager.avatarAES256KeyByteLength else { return nil } - return Cryptography.decryptAESGCMProfileData(encryptedData: data, key: key) + // The key structure is: nonce || ciphertext || authTag + let cipherTextLength: Int = (data.count - (ProfileManager.avatarNonceLength + ProfileManager.avatarTagLength)) + + guard + cipherTextLength > 0, + let sealedData: AES.GCM.SealedBox = try? AES.GCM.SealedBox( + nonce: AES.GCM.Nonce(data: data.subdata(in: 0.. 0 + let profileKeyAtStart: Data = profile.profileEncryptionKey, + profileKeyAtStart.count > 0 else { return } @@ -178,8 +206,8 @@ public struct ProfileManager { } guard - let latestProfileKey: OWSAES256Key = latestProfile.profileEncryptionKey, - !latestProfileKey.keyData.isEmpty, + let latestProfileKey: Data = latestProfile.profileEncryptionKey, + !latestProfileKey.isEmpty, latestProfileKey == profileKeyAtStart else { OWSLogger.warn("Ignoring avatar download for obsolete user profile.") @@ -239,15 +267,85 @@ public struct ProfileManager { imageFilePath: String?, success: ((Database, Profile) throws -> ())? = nil, failure: ((ProfileManagerError) -> ())? = nil + ) { + prepareAndUploadAvatarImage( + queue: queue, + image: image, + imageFilePath: imageFilePath, + success: { fileInfo, newProfileKey in + // If we have no download url the we are removing the profile image + guard let (downloadUrl, fileName): (String, String) = fileInfo else { + Storage.shared.writeAsync { db in + let existingProfile: Profile = Profile.fetchOrCreateCurrentUser(db) + + OWSLogger.verbose(existingProfile.profilePictureUrl != nil ? + "Updating local profile on service with cleared avatar." : + "Updating local profile on service with no avatar." + ) + + let updatedProfile: Profile = try existingProfile + .with( + name: profileName, + profilePictureUrl: nil, + profilePictureFileName: nil, + profileEncryptionKey: (existingProfile.profilePictureUrl != nil ? + .update(newProfileKey) : + .existing + ) + ) + .saved(db) + + try SessionUtil.update( + profile: updatedProfile, + in: .global(variant: .userProfile) + ) + + SNLog("Successfully updated service with profile.") + try success?(db, updatedProfile) + } + return + } + + // Update user defaults + UserDefaults.standard[.lastProfilePictureUpload] = Date() + + // Update the profile + Storage.shared.writeAsync { db in + let profile: Profile = try Profile + .fetchOrCreateCurrentUser(db) + .with( + name: profileName, + profilePictureUrl: .update(downloadUrl), + profilePictureFileName: .update(fileName), + profileEncryptionKey: .update(newProfileKey) + ) + .saved(db) + + SNLog("Successfully updated service with profile.") + try success?(db, profile) + } + }, + failure: failure + ) + } + + public static func prepareAndUploadAvatarImage( + queue: DispatchQueue, + image: UIImage?, + imageFilePath: String?, + success: @escaping ((downloadUrl: String, fileName: String)?, Data) -> (), + failure: ((ProfileManagerError) -> ())? = nil ) { queue.async { // If the profile avatar was updated or removed then encrypt with a new profile key // to ensure that other users know that our profile picture was updated - let newProfileKey: OWSAES256Key = OWSAES256Key.generateRandom() + let newProfileKey: Data let maxAvatarBytes: UInt = (5 * 1000 * 1000) let avatarImageData: Data? do { + newProfileKey = try Randomness.generateRandomBytes(numberBytes: ProfileManager.avatarAES256KeyByteLength) + avatarImageData = try { guard var image: UIImage = image else { guard let imageFilePath: String = imageFilePath else { return nil } diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index 7cfeae747..5ff7d12f9 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -8,6 +8,7 @@ import BackgroundTasks import PromiseKit import SessionMessagingKit import SignalUtilitiesKit +import SignalCoreKit public final class NotificationServiceExtension: UNNotificationServiceExtension { private var didPerformSetup = false @@ -137,6 +138,9 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension self.handleSuccessForIncomingCall(db, for: callMessage) + case let sharedConfigMessage as SharedConfigMessage: + try SessionUtil.handleConfigMessages(db, messages: [sharedConfigMessage]) + default: break } diff --git a/SessionShareExtension/Meta/SessionShareExtension-Prefix.pch b/SessionShareExtension/Meta/SessionShareExtension-Prefix.pch deleted file mode 100644 index 1fa2513a1..000000000 --- a/SessionShareExtension/Meta/SessionShareExtension-Prefix.pch +++ /dev/null @@ -1,14 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import - -#ifdef __OBJC__ - #import - #import - - #import - #import - #import -#endif diff --git a/SessionShareExtension/Meta/SignalShareExtension-Bridging-Header.h b/SessionShareExtension/Meta/SignalShareExtension-Bridging-Header.h index b69a7ab21..9334672ce 100644 --- a/SessionShareExtension/Meta/SignalShareExtension-Bridging-Header.h +++ b/SessionShareExtension/Meta/SignalShareExtension-Bridging-Header.h @@ -6,9 +6,6 @@ #import // Separate iOS Frameworks from other imports. -#import -#import -#import #import #import #import diff --git a/SessionShareExtension/SAEScreenLockViewController.swift b/SessionShareExtension/SAEScreenLockViewController.swift index ab735e459..36fac11ea 100644 --- a/SessionShareExtension/SAEScreenLockViewController.swift +++ b/SessionShareExtension/SAEScreenLockViewController.swift @@ -2,10 +2,10 @@ import UIKit import PromiseKit -import SignalCoreKit import SignalUtilitiesKit import SessionUIKit import SessionUtilitiesKit +import SignalCoreKit final class SAEScreenLockViewController: ScreenLockViewController { private var hasShownAuthUIOnce: Bool = false diff --git a/SessionShareExtension/ShareAppExtensionContext.swift b/SessionShareExtension/ShareAppExtensionContext.swift index 5f1445a53..213d8178b 100644 --- a/SessionShareExtension/ShareAppExtensionContext.swift +++ b/SessionShareExtension/ShareAppExtensionContext.swift @@ -4,6 +4,7 @@ import UIKit import SignalUtilitiesKit import SessionUtilitiesKit import SessionMessagingKit +import SignalCoreKit /// This is _NOT_ a singleton and will be instantiated each time that the SAE is used. final class ShareAppExtensionContext: NSObject, AppContext { diff --git a/SessionShareExtension/ShareVC.swift b/SessionShareExtension/ShareVC.swift index 07c83873c..47d431c1e 100644 --- a/SessionShareExtension/ShareVC.swift +++ b/SessionShareExtension/ShareVC.swift @@ -5,6 +5,7 @@ import CoreServices import PromiseKit import SignalUtilitiesKit import SessionUIKit +import SignalCoreKit final class ShareVC: UINavigationController, ShareViewDelegate { private var areVersionMigrationsComplete = false diff --git a/SessionShareExtension/ThreadPickerVC.swift b/SessionShareExtension/ThreadPickerVC.swift index 7db42407d..cae2bcf95 100644 --- a/SessionShareExtension/ThreadPickerVC.swift +++ b/SessionShareExtension/ThreadPickerVC.swift @@ -4,7 +4,6 @@ import UIKit import GRDB import PromiseKit import DifferenceKit -import Sodium import SessionUIKit import SignalUtilitiesKit import SessionMessagingKit diff --git a/SessionSnodeKit/GetSnodePoolJob.swift b/SessionSnodeKit/GetSnodePoolJob.swift index af0c61d07..0d792437f 100644 --- a/SessionSnodeKit/GetSnodePoolJob.swift +++ b/SessionSnodeKit/GetSnodePoolJob.swift @@ -2,7 +2,6 @@ import Foundation import GRDB -import SignalCoreKit import SessionUtilitiesKit public enum GetSnodePoolJob: JobExecutor { diff --git a/SessionSnodeKit/Models/DeleteAllMessagesRequest.swift b/SessionSnodeKit/Models/DeleteAllMessagesRequest.swift new file mode 100644 index 000000000..b90ce4bcf --- /dev/null +++ b/SessionSnodeKit/Models/DeleteAllMessagesRequest.swift @@ -0,0 +1,81 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +extension SnodeAPI { + public class DeleteAllMessagesRequest: SnodeAuthenticatedRequestBody { + enum CodingKeys: String, CodingKey { + case namespace + } + + /// The message namespace from which to delete messages. The request will delete all messages + /// from the specific namespace, or from all namespaces when not provided + /// + /// **Note:** If omitted when sending the request, messages are deleted from the default namespace + /// only (namespace 0) + let namespace: SnodeAPI.Namespace? + + // MARK: - Init + + public init( + namespace: SnodeAPI.Namespace?, + pubkey: String, + timestampMs: UInt64, + ed25519PublicKey: [UInt8], + ed25519SecretKey: [UInt8] + ) { + self.namespace = namespace + + super.init( + pubkey: pubkey, + ed25519PublicKey: ed25519PublicKey, + ed25519SecretKey: ed25519SecretKey, + timestampMs: timestampMs + ) + } + + // MARK: - Coding + + override public func encode(to encoder: Encoder) throws { + var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) + + // If no namespace is specified it defaults to the default namespace only (namespace + // 0), so instead in this case we want to explicitly delete from `all` namespaces + switch namespace { + case .some(let namespace): try container.encode(namespace, forKey: .namespace) + case .none: try container.encode("all", forKey: .namespace) + } + + try super.encode(to: encoder) + } + + // MARK: - Abstract Methods + + override func generateSignature() throws -> [UInt8] { + /// Ed25519 signature of `( "delete_all" || namespace || timestamp )`, where + /// `namespace` is the empty string for the default namespace (whether explicitly specified or + /// not), and otherwise the stringified version of the namespace parameter (i.e. "99" or "-42" or "all"). + /// The signature must be signed by the ed25519 pubkey in `pubkey` (omitting the leading prefix). + /// Must be base64 encoded for json requests; binary for OMQ requests. + let verificationBytes: [UInt8] = SnodeAPI.Endpoint.deleteAll.rawValue.bytes + .appending( + contentsOf: (namespace == nil ? + "all" : + namespace?.verificationString + )?.bytes + ) + .appending(contentsOf: timestampMs.map { "\($0)" }?.data(using: .utf8)?.bytes) + + guard + let signatureBytes: [UInt8] = sodium.wrappedValue.sign.signature( + message: verificationBytes, + secretKey: ed25519SecretKey + ) + else { + throw SnodeAPIError.signingFailed + } + + return signatureBytes + } + } +} diff --git a/SessionSnodeKit/Models/GetMessagesRequest.swift b/SessionSnodeKit/Models/GetMessagesRequest.swift new file mode 100644 index 000000000..6eeedf406 --- /dev/null +++ b/SessionSnodeKit/Models/GetMessagesRequest.swift @@ -0,0 +1,72 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +extension SnodeAPI { + public class GetMessagesRequest: SnodeAuthenticatedRequestBody { + enum CodingKeys: String, CodingKey { + case lastHash = "last_hash" + case namespace + } + + let lastHash: String + let namespace: SnodeAPI.Namespace? + + // MARK: - Init + + public init( + lastHash: String, + namespace: SnodeAPI.Namespace?, + pubkey: String, + subkey: String?, + timestampMs: UInt64, + ed25519PublicKey: [UInt8], + ed25519SecretKey: [UInt8] + ) { + self.lastHash = lastHash + self.namespace = namespace + + super.init( + pubkey: pubkey, + ed25519PublicKey: ed25519PublicKey, + ed25519SecretKey: ed25519SecretKey, + subkey: subkey, + timestampMs: timestampMs + ) + } + + // MARK: - Coding + + override public func encode(to encoder: Encoder) throws { + var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(lastHash, forKey: .lastHash) + try container.encodeIfPresent(namespace, forKey: .namespace) + + try super.encode(to: encoder) + } + + // MARK: - Abstract Methods + + override func generateSignature() throws -> [UInt8] { + /// Ed25519 signature of `("retrieve" || namespace || timestamp)` (if using a non-0 + /// namespace), or `("retrieve" || timestamp)` when fetching from the default namespace. Both + /// namespace and timestamp are the base10 expressions of the relevant values. Must be base64 + /// encoded for json requests; binary for OMQ requests. + let verificationBytes: [UInt8] = SnodeAPI.Endpoint.getMessages.rawValue.bytes + .appending(contentsOf: namespace?.verificationString.bytes) + .appending(contentsOf: timestampMs.map { "\($0)" }?.data(using: .utf8)?.bytes) + + guard + let signatureBytes: [UInt8] = sodium.wrappedValue.sign.signature( + message: verificationBytes, + secretKey: ed25519SecretKey + ) + else { + throw SnodeAPIError.signingFailed + } + + return signatureBytes + } + } +} diff --git a/SessionSnodeKit/Models/SendMessageRequest.swift b/SessionSnodeKit/Models/SendMessageRequest.swift new file mode 100644 index 000000000..541768a64 --- /dev/null +++ b/SessionSnodeKit/Models/SendMessageRequest.swift @@ -0,0 +1,77 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +extension SnodeAPI { + public class SendMessageRequest: SnodeAuthenticatedRequestBody { + enum CodingKeys: String, CodingKey { + case namespace + case signatureTimestamp = "sig_timestamp" + } + + let message: SnodeMessage + let namespace: SnodeAPI.Namespace + + // MARK: - Init + + public init( + message: SnodeMessage, + namespace: SnodeAPI.Namespace, + subkey: String?, + timestampMs: UInt64, + ed25519PublicKey: [UInt8], + ed25519SecretKey: [UInt8] + ) { + self.message = message + self.namespace = namespace + + super.init( + pubkey: message.recipient, + ed25519PublicKey: ed25519PublicKey, + ed25519SecretKey: ed25519SecretKey, + subkey: subkey, + timestampMs: timestampMs + ) + } + + // MARK: - Coding + + override public func encode(to encoder: Encoder) throws { + var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) + + try super.encode(to: encoder) + + /// **Note:** We **MUST** do the `message.encode` after we call `super.encode` because it will + /// override the `timestampMs` value with the value in the message (if we do it the other way around then + /// the the API call timestamp will be sent instead which is incorrect. For this specific request type we have a + /// separate `signatureTimestamp` value to store the timestamp used to generate the signature + try message.encode(to: encoder) + try container.encode(namespace, forKey: .namespace) + try container.encode(timestampMs, forKey: .signatureTimestamp) + } + + // MARK: - Abstract Methods + + override func generateSignature() throws -> [UInt8] { + /// Ed25519 signature of `("store" || namespace || sig_timestamp)`, where namespace and + /// `sig_timestamp` are the base10 expression of the namespace and `sig_timestamp` values. Must be + /// base64 encoded for json requests; binary for OMQ requests. For non-05 type pubkeys (i.e. non + /// session ids) the signature will be verified using `pubkey`. For 05 pubkeys, see the following + /// option. + let verificationBytes: [UInt8] = SnodeAPI.Endpoint.sendMessage.rawValue.bytes + .appending(contentsOf: namespace.verificationString.bytes) + .appending(contentsOf: timestampMs.map { "\($0)" }?.data(using: .utf8)?.bytes) + + guard + let signatureBytes: [UInt8] = sodium.wrappedValue.sign.signature( + message: verificationBytes, + secretKey: ed25519SecretKey + ) + else { + throw SnodeAPIError.signingFailed + } + + return signatureBytes + } + } +} diff --git a/SessionSnodeKit/Types/SnodeAPINamespace.swift b/SessionSnodeKit/Types/SnodeAPINamespace.swift new file mode 100644 index 000000000..da038aeca --- /dev/null +++ b/SessionSnodeKit/Types/SnodeAPINamespace.swift @@ -0,0 +1,37 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public extension SnodeAPI { + enum Namespace: Int, Codable { + case `default` = 0 + + case userProfileConfig = 2 + + case legacyClosedGroup = -10 + + // MARK: Variables + + var requiresReadAuthentication: Bool { + switch self { + case .legacyClosedGroup: return false + default: return true + } + } + + var requiresWriteAuthentication: Bool { + switch self { + // Not in use until we can batch delete and store config messages + case .default, .legacyClosedGroup: return false + default: return true + } + } + + var verificationString: String { + switch self { + case .`default`: return "" + default: return "\(self.rawValue)" + } + } + } +} diff --git a/SessionUtilitiesKit/Database/Storage.swift b/SessionUtilitiesKit/Database/Storage.swift index c3db2cd39..b022f3fdf 100644 --- a/SessionUtilitiesKit/Database/Storage.swift +++ b/SessionUtilitiesKit/Database/Storage.swift @@ -4,13 +4,12 @@ import Foundation import Combine import GRDB import PromiseKit -import SignalCoreKit open class Storage { private static let dbFileName: String = "Session.sqlite" private static let keychainService: String = "TSKeyChainService" private static let dbCipherKeySpecKey: String = "GRDBDatabaseCipherKeySpec" - private static let kSQLCipherKeySpecLength: Int32 = 48 + private static let kSQLCipherKeySpecLength: Int = 48 private static var sharedDatabaseDirectoryPath: String { "\(OWSFileSystem.appSharedDataDirectoryPath())/database" } private static var databasePath: String { "\(Storage.sharedDatabaseDirectoryPath)/\(Storage.dbFileName)" } @@ -103,7 +102,7 @@ open class Storage { migrations: [TargetMigrations], async: Bool = true, onProgressUpdate: ((CGFloat, TimeInterval) -> ())?, - onComplete: @escaping (Swift.Result, Bool) -> () + onComplete: @escaping (Swift.Result, Bool) -> () ) { guard isValid, let dbWriter: DatabaseWriter = dbWriter else { return } @@ -179,7 +178,7 @@ open class Storage { } // Store the logic to run when the migration completes - let migrationCompleted: (Swift.Result) -> () = { [weak self] result in + let migrationCompleted: (Swift.Result) -> () = { [weak self] result in self?.hasCompletedMigrations = true self?.migrationProgressUpdater = nil SUKLegacy.clearLegacyDatabaseInstance() @@ -194,16 +193,23 @@ open class Storage { // Note: The non-async migration should only be used for unit tests guard async else { do { try self.migrator?.migrate(dbWriter) } - catch { - try? dbWriter.read { db in - migrationCompleted(Swift.Result.failure(error)) - } - } + catch { migrationCompleted(Swift.Result.failure(error)) } return } self.migrator?.asyncMigrate(dbWriter) { result in - migrationCompleted(result) + let finalResult: Swift.Result = { + switch result { + case .failure(let error): return .failure(error) + case .success: return .success(()) + } + }() + + // Note: We need to dispatch this to the next run toop to prevent any potential re-entrancy + // issues since the 'asyncMigrate' returns a result containing a DB instance + DispatchQueue.global(qos: .userInitiated).async { + migrationCompleted(finalResult) + } } } @@ -251,7 +257,7 @@ open class Storage { case (_, errSecItemNotFound): // No keySpec was found so we need to generate a new one do { - var keySpec: Data = Randomness.generateRandomBytes(kSQLCipherKeySpecLength) + var keySpec: Data = try Randomness.generateRandomBytes(numberBytes: kSQLCipherKeySpecLength) defer { keySpec.resetBytes(in: 0.. { return mutation(&value) } } + + @discardableResult public func mutate(_ mutation: (inout Value) throws -> T) throws -> T { + return try queue.sync { + return try mutation(&value) + } + } } extension Atomic where Value: CustomDebugStringConvertible { diff --git a/SessionUtilitiesKit/General/General.swift b/SessionUtilitiesKit/General/General.swift index 714184ee3..2931e47c2 100644 --- a/SessionUtilitiesKit/General/General.swift +++ b/SessionUtilitiesKit/General/General.swift @@ -20,6 +20,7 @@ public enum General { public enum GeneralError: Error { case keyGenerationFailed + case randomGenerationFailed } public func getUserHexEncodedPublicKey(_ db: Database? = nil, dependencies: Dependencies = Dependencies()) -> String { diff --git a/SessionUtilitiesKit/General/Sodium+Utilities.swift b/SessionUtilitiesKit/General/Sodium+Utilities.swift index b9161af12..fcdf1d3db 100644 --- a/SessionUtilitiesKit/General/Sodium+Utilities.swift +++ b/SessionUtilitiesKit/General/Sodium+Utilities.swift @@ -3,7 +3,6 @@ import Foundation import Clibsodium import Sodium -import Curve25519Kit extension Sign { diff --git a/SessionUtilitiesKit/General/String+Utilities.swift b/SessionUtilitiesKit/General/String+Utilities.swift index be3bd36ad..4294375db 100644 --- a/SessionUtilitiesKit/General/String+Utilities.swift +++ b/SessionUtilitiesKit/General/String+Utilities.swift @@ -1,5 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +import Foundation import SignalCoreKit public extension String { diff --git a/SessionUtilitiesKit/JobRunner/JobRunner.swift b/SessionUtilitiesKit/JobRunner/JobRunner.swift index 065ad3ba1..71e4d7b3a 100644 --- a/SessionUtilitiesKit/JobRunner/JobRunner.swift +++ b/SessionUtilitiesKit/JobRunner/JobRunner.swift @@ -2,7 +2,6 @@ import Foundation import GRDB -import SignalCoreKit public protocol JobExecutor { /// The maximum number of times the job can fail before it fails permanently diff --git a/SessionUtilitiesKit/Utilities/OWSBackgroundTask.m b/SessionUtilitiesKit/Utilities/OWSBackgroundTask.m index 7602df9e6..9e4dc76b5 100644 --- a/SessionUtilitiesKit/Utilities/OWSBackgroundTask.m +++ b/SessionUtilitiesKit/Utilities/OWSBackgroundTask.m @@ -4,8 +4,8 @@ #import "OWSBackgroundTask.h" #import "AppContext.h" -#import #import +#import NS_ASSUME_NONNULL_BEGIN @@ -237,7 +237,7 @@ typedef NSNumber *OWSTaskId; // always be called on the main thread, so we use DispatchSyncMainThreadSafe() // to ensure that. We thereby ensure that we don't end the background task // until all of the completion blocks have completed. - DispatchSyncMainThreadSafe(^{ + [Threading dispatchSyncMainThreadSafe:^{ for (BackgroundTaskExpirationBlock expirationBlock in expirationMap.allValues) { expirationBlock(); } @@ -245,7 +245,7 @@ typedef NSNumber *OWSTaskId; // Apparently we need to "end" even expired background tasks. [CurrentAppContext() endBackgroundTask:backgroundTaskId]; } - }); + }]; } - (void)timerDidFire @@ -329,7 +329,7 @@ typedef NSNumber *OWSTaskId; { __weak typeof(self) weakSelf = self; self.taskId = [OWSBackgroundTaskManager.sharedManager addTaskWithExpirationBlock:^{ - DispatchMainThreadSafe(^{ + [Threading dispatchMainThreadSafe:^{ OWSBackgroundTask *strongSelf = weakSelf; if (!strongSelf) { return; @@ -353,7 +353,7 @@ typedef NSNumber *OWSTaskId; if (completionBlock) { completionBlock(BackgroundTaskState_Expired); } - }); + }]; }]; // If a background task could not be begun, call the completion block. @@ -368,9 +368,9 @@ typedef NSNumber *OWSTaskId; self.completionBlock = nil; } if (completionBlock) { - DispatchMainThreadSafe(^{ + [Threading dispatchMainThreadSafe:^{ completionBlock(BackgroundTaskState_CouldNotStart); - }); + }]; } } } @@ -393,11 +393,11 @@ typedef NSNumber *OWSTaskId; } // endBackgroundTask must be called on the main thread. - DispatchMainThreadSafe(^{ + [Threading dispatchMainThreadSafe:^{ if (completionBlock) { completionBlock(BackgroundTaskState_Cancelled); } - }); + }]; } - (void)endBackgroundTask @@ -418,11 +418,11 @@ typedef NSNumber *OWSTaskId; } // endBackgroundTask must be called on the main thread. - DispatchMainThreadSafe(^{ + [Threading dispatchMainThreadSafe:^{ if (completionBlock) { completionBlock(BackgroundTaskState_Success); } - }); + }]; } @end diff --git a/SessionUtilitiesKit/Utilities/Randomness.swift b/SessionUtilitiesKit/Utilities/Randomness.swift new file mode 100644 index 000000000..a242d93dc --- /dev/null +++ b/SessionUtilitiesKit/Utilities/Randomness.swift @@ -0,0 +1,19 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public enum Randomness { + public static func generateRandomBytes(numberBytes: Int) throws -> Data { + var randomByes: Data = Data(count: numberBytes) + let result = randomByes.withUnsafeMutableBytes { + SecRandomCopyBytes(kSecRandomDefault, numberBytes, $0.baseAddress!) + } + + guard result == errSecSuccess, randomByes.count == numberBytes else { + print("Problem generating random bytes") + throw GeneralError.randomGenerationFailed + } + + return randomByes + } +} diff --git a/SessionUtilitiesKit/Utilities/Threading.swift b/SessionUtilitiesKit/Utilities/Threading.swift new file mode 100644 index 000000000..e894bbf3c --- /dev/null +++ b/SessionUtilitiesKit/Utilities/Threading.swift @@ -0,0 +1,23 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +@objc public class Threading: NSObject { + @objc public static func dispatchMainThreadSafe(_ closure: @escaping () -> ()) { + guard Thread.isMainThread else { + DispatchQueue.main.async { dispatchMainThreadSafe(closure) } + return + } + + closure() + } + + @objc public static func dispatchSyncMainThreadSafe(_ closure: @escaping () -> ()) { + guard Thread.isMainThread else { + DispatchQueue.main.sync { dispatchSyncMainThreadSafe(closure) } + return + } + + closure() + } +} diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalInputAccessoryView.swift b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalInputAccessoryView.swift index 16c9bf731..aa778f9e4 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalInputAccessoryView.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalInputAccessoryView.swift @@ -3,6 +3,7 @@ import Foundation import UIKit import SessionUIKit +import SignalCoreKit protocol AttachmentApprovalInputAccessoryViewDelegate: AnyObject { func attachmentApprovalInputUpdateMediaRail() diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentCaptionToolbar.swift b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentCaptionToolbar.swift index 9c6eec082..534c7321c 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentCaptionToolbar.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentCaptionToolbar.swift @@ -1,10 +1,9 @@ -// // Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// import Foundation import UIKit import SessionUIKit +import SignalCoreKit protocol AttachmentCaptionToolbarDelegate: AnyObject { func attachmentCaptionToolbarDidEdit(_ attachmentCaptionToolbar: AttachmentCaptionToolbar) diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentItemCollection.swift b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentItemCollection.swift index 778c0bf13..2bdc5377e 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentItemCollection.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentItemCollection.swift @@ -1,10 +1,9 @@ -// // Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// import Foundation import PromiseKit import SessionMessagingKit +import SignalCoreKit class AddMoreRailItem: GalleryRailItem { func buildRailItemView() -> UIView { diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentPrepViewController.swift b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentPrepViewController.swift index 8e4d3be2c..db65cd7e2 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentPrepViewController.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentPrepViewController.swift @@ -4,6 +4,7 @@ import Foundation import UIKit import AVFoundation import SessionUIKit +import SignalCoreKit protocol AttachmentPrepViewControllerDelegate: AnyObject { func prepViewControllerUpdateNavigationBar() diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentTextToolbar.swift b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentTextToolbar.swift index 532d363ec..b1274a8f5 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentTextToolbar.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentTextToolbar.swift @@ -1,10 +1,9 @@ -// // Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// import Foundation import UIKit import SessionUIKit +import SignalCoreKit // Coincides with Android's max text message length let kMaxMessageBodyCharacterCount = 2000 diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorBrushViewController.swift b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorBrushViewController.swift index 733be4cbd..ff4dd9f60 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorBrushViewController.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorBrushViewController.swift @@ -2,6 +2,7 @@ import UIKit import SessionUIKit +import SignalCoreKit @objc public protocol ImageEditorBrushViewControllerDelegate: AnyObject { diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorCanvasView.swift b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorCanvasView.swift index fdbfc3fce..e0acf8bba 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorCanvasView.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorCanvasView.swift @@ -2,6 +2,7 @@ import UIKit import SessionUIKit +import SignalCoreKit public class EditorTextLayer: CATextLayer { let itemId: String diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorContents.swift b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorContents.swift index e5ce28f6d..76eb079dc 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorContents.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorContents.swift @@ -1,8 +1,7 @@ -// // Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// import UIKit +import SignalCoreKit // ImageEditorContents represents a snapshot of canvas // state. diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorCropViewController.swift b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorCropViewController.swift index 8ac563f0b..ad415e5e6 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorCropViewController.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorCropViewController.swift @@ -1,11 +1,10 @@ -// // Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// import UIKit import SessionUIKit +import SignalCoreKit -public protocol ImageEditorCropViewControllerDelegate: class { +public protocol ImageEditorCropViewControllerDelegate: AnyObject { func cropDidComplete(transform: ImageEditorTransform) func cropDidCancel() } diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorModel.swift b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorModel.swift index b52629052..d2c8b062b 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorModel.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorModel.swift @@ -1,8 +1,7 @@ -// // Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// import UIKit +import SignalCoreKit // Used to represent undo/redo operations. // @@ -25,7 +24,7 @@ private class ImageEditorOperation: NSObject { // MARK: - @objc -public protocol ImageEditorModelObserver: class { +public protocol ImageEditorModelObserver: AnyObject { // Used for large changes to the model, when the entire // model should be reloaded. func imageEditorModelDidChange(before: ImageEditorContents, diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorPaletteView.swift b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorPaletteView.swift index a4b015925..0cd26364e 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorPaletteView.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorPaletteView.swift @@ -2,6 +2,7 @@ import UIKit import SessionUIKit +import SignalCoreKit public protocol ImageEditorPaletteViewDelegate: AnyObject { func selectedColorDidChange() diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorPanGestureRecognizer.swift b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorPanGestureRecognizer.swift index 699a2c832..919a343ca 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorPanGestureRecognizer.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorPanGestureRecognizer.swift @@ -1,8 +1,7 @@ -// // Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// import UIKit +import SignalCoreKit // This GR: // diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorPinchGestureRecognizer.swift b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorPinchGestureRecognizer.swift index c76f49475..01e580ecb 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorPinchGestureRecognizer.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorPinchGestureRecognizer.swift @@ -1,8 +1,7 @@ -// // Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// import UIKit +import SignalCoreKit public struct ImageEditorPinchState { public let centroid: CGPoint diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorTextViewController.swift b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorTextViewController.swift index 02bcab923..2e9e5b998 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorTextViewController.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorTextViewController.swift @@ -1,12 +1,11 @@ -// // Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// import UIKit import SessionUIKit +import SignalCoreKit @objc -public protocol VAlignTextViewDelegate: class { +public protocol VAlignTextViewDelegate: AnyObject { func textViewDidComplete() } diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorView.swift b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorView.swift index 1fdb229f2..ef9bb9c64 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorView.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorView.swift @@ -1,9 +1,8 @@ -// // Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// import UIKit import SessionUtilitiesKit +import SignalCoreKit @objc public protocol ImageEditorViewDelegate: AnyObject { diff --git a/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift b/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift index 99cd2c61b..7a8a1b156 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift @@ -1,6 +1,4 @@ -// // Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// import Foundation import MediaPlayer @@ -8,6 +6,7 @@ import YYImage import NVActivityIndicatorView import SessionUIKit import SessionMessagingKit +import SignalCoreKit public protocol MediaMessageViewAudioDelegate: AnyObject { func progressChanged(_ progressSeconds: CGFloat, durationSeconds: CGFloat) diff --git a/SignalUtilitiesKit/Media Viewing & Editing/OWSVideoPlayer.swift b/SignalUtilitiesKit/Media Viewing & Editing/OWSVideoPlayer.swift index c99513091..f24b12262 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/OWSVideoPlayer.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/OWSVideoPlayer.swift @@ -1,10 +1,9 @@ -// // Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// import Foundation import AVFoundation import SessionMessagingKit +import SignalCoreKit public protocol OWSVideoPlayerDelegate: AnyObject { func videoPlayerDidPlayToCompletion(_ videoPlayer: OWSVideoPlayer) diff --git a/SignalUtilitiesKit/Media Viewing & Editing/VideoPlayerView.swift b/SignalUtilitiesKit/Media Viewing & Editing/VideoPlayerView.swift index bebeda88c..373de082e 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/VideoPlayerView.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/VideoPlayerView.swift @@ -3,6 +3,7 @@ import UIKit import AVFoundation import SessionUIKit +import SignalCoreKit @objc public class VideoPlayerView: UIView { diff --git a/SignalUtilitiesKit/Meta/SignalUtilitiesKit-Prefix.pch b/SignalUtilitiesKit/Meta/SignalUtilitiesKit-Prefix.pch deleted file mode 100644 index 4ea96ba51..000000000 --- a/SignalUtilitiesKit/Meta/SignalUtilitiesKit-Prefix.pch +++ /dev/null @@ -1,17 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import - -#ifdef __OBJC__ - #import - #import - - @import PureLayout; - @import SignalCoreKit; - - @import SessionMessagingKit; - @import SessionSnodeKit; - @import SessionUtilitiesKit; -#endif diff --git a/SignalUtilitiesKit/Profile Pictures/PlaceholderIcon.swift b/SignalUtilitiesKit/Profile Pictures/PlaceholderIcon.swift index 33246ebce..32f0c60f4 100644 --- a/SignalUtilitiesKit/Profile Pictures/PlaceholderIcon.swift +++ b/SignalUtilitiesKit/Profile Pictures/PlaceholderIcon.swift @@ -3,6 +3,7 @@ import UIKit import CryptoSwift import SessionUIKit +import SignalCoreKit public class PlaceholderIcon { private let seed: Int diff --git a/SignalUtilitiesKit/Screen Lock/ScreenLock.swift b/SignalUtilitiesKit/Screen Lock/ScreenLock.swift index a4158d358..c1ed4654a 100644 --- a/SignalUtilitiesKit/Screen Lock/ScreenLock.swift +++ b/SignalUtilitiesKit/Screen Lock/ScreenLock.swift @@ -4,6 +4,7 @@ import Foundation import GRDB import LocalAuthentication import SessionMessagingKit +import SignalCoreKit public class ScreenLock { public enum Outcome { diff --git a/SignalUtilitiesKit/Shared View Controllers/ModalActivityIndicatorViewController.swift b/SignalUtilitiesKit/Shared View Controllers/ModalActivityIndicatorViewController.swift index 07d12166d..779cfb5c8 100644 --- a/SignalUtilitiesKit/Shared View Controllers/ModalActivityIndicatorViewController.swift +++ b/SignalUtilitiesKit/Shared View Controllers/ModalActivityIndicatorViewController.swift @@ -4,6 +4,7 @@ import Foundation import MediaPlayer import SessionUIKit import NVActivityIndicatorView +import SignalCoreKit // A modal view that be used during blocking interactions (e.g. waiting on response from // service or on the completion of a long-running local operation). diff --git a/SignalUtilitiesKit/Shared View Controllers/OWSViewController.m b/SignalUtilitiesKit/Shared View Controllers/OWSViewController.m index 6d2ea863b..0f842a238 100644 --- a/SignalUtilitiesKit/Shared View Controllers/OWSViewController.m +++ b/SignalUtilitiesKit/Shared View Controllers/OWSViewController.m @@ -6,6 +6,7 @@ #import "UIView+OWS.h" #import #import "AppContext.h" +#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalUtilitiesKit/Shared Views/ApprovalRailCellView.swift b/SignalUtilitiesKit/Shared Views/ApprovalRailCellView.swift index 67c745389..30d626382 100644 --- a/SignalUtilitiesKit/Shared Views/ApprovalRailCellView.swift +++ b/SignalUtilitiesKit/Shared Views/ApprovalRailCellView.swift @@ -2,6 +2,7 @@ import UIKit import SessionUIKit +import SignalCoreKit protocol ApprovalRailCellViewDelegate: AnyObject { func approvalRailCellView(_ approvalRailCellView: ApprovalRailCellView, didRemoveItem attachmentItem: SignalAttachmentItem) diff --git a/SignalUtilitiesKit/Shared Views/CircleView.swift b/SignalUtilitiesKit/Shared Views/CircleView.swift index 3cec26ba5..957f17da7 100644 --- a/SignalUtilitiesKit/Shared Views/CircleView.swift +++ b/SignalUtilitiesKit/Shared Views/CircleView.swift @@ -1,7 +1,7 @@ -// // Copyright (c) 2020 Open Whisper Systems. All rights reserved. -// + import UIKit +import SignalCoreKit @objc (OWSCircleView) public class CircleView: UIView { diff --git a/SignalUtilitiesKit/Shared Views/TappableStackView.swift b/SignalUtilitiesKit/Shared Views/TappableStackView.swift index 27d66af81..cd5af8000 100644 --- a/SignalUtilitiesKit/Shared Views/TappableStackView.swift +++ b/SignalUtilitiesKit/Shared Views/TappableStackView.swift @@ -1,8 +1,7 @@ -// // Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// import Foundation +import SignalCoreKit @objc public class TappableStackView: UIStackView { diff --git a/SignalUtilitiesKit/Shared Views/TappableView.swift b/SignalUtilitiesKit/Shared Views/TappableView.swift index 585410e45..d08a04ca5 100644 --- a/SignalUtilitiesKit/Shared Views/TappableView.swift +++ b/SignalUtilitiesKit/Shared Views/TappableView.swift @@ -1,8 +1,7 @@ -// // Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// import Foundation +import SignalCoreKit public class TappableView: UIView { let actionBlock : (() -> Void) diff --git a/SignalUtilitiesKit/Shared Views/Toast.swift b/SignalUtilitiesKit/Shared Views/Toast.swift index 231dd800b..591842fe0 100644 --- a/SignalUtilitiesKit/Shared Views/Toast.swift +++ b/SignalUtilitiesKit/Shared Views/Toast.swift @@ -2,6 +2,7 @@ import UIKit import SessionUIKit +import SignalCoreKit public class ToastController: ToastViewDelegate { static var currentToastController: ToastController? diff --git a/SignalUtilitiesKit/Utilities/AppSetup.swift b/SignalUtilitiesKit/Utilities/AppSetup.swift index ab33661ae..9e70fdbfa 100644 --- a/SignalUtilitiesKit/Utilities/AppSetup.swift +++ b/SignalUtilitiesKit/Utilities/AppSetup.swift @@ -12,7 +12,7 @@ public enum AppSetup { public static func setupEnvironment( appSpecificBlock: @escaping () -> (), migrationProgressChanged: ((CGFloat, TimeInterval) -> ())? = nil, - migrationsCompletion: @escaping (Result, Bool) -> () + migrationsCompletion: @escaping (Result, Bool) -> () ) { guard !AppSetup.hasRun else { return } @@ -61,7 +61,7 @@ public enum AppSetup { public static func runPostSetupMigrations( backgroundTask: OWSBackgroundTask? = nil, migrationProgressChanged: ((CGFloat, TimeInterval) -> ())? = nil, - migrationsCompletion: @escaping (Result, Bool) -> () + migrationsCompletion: @escaping (Result, Bool) -> () ) { var backgroundTask: OWSBackgroundTask? = (backgroundTask ?? OWSBackgroundTask(labelStr: #function)) diff --git a/SignalUtilitiesKit/Utilities/AppVersion.m b/SignalUtilitiesKit/Utilities/AppVersion.m index efea79e1d..993e99475 100755 --- a/SignalUtilitiesKit/Utilities/AppVersion.m +++ b/SignalUtilitiesKit/Utilities/AppVersion.m @@ -4,6 +4,7 @@ #import "AppVersion.h" #import "NSUserDefaults+OWS.h" +#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalUtilitiesKit/Utilities/Bench.swift b/SignalUtilitiesKit/Utilities/Bench.swift index 298ab6b01..cfcf5d7ad 100644 --- a/SignalUtilitiesKit/Utilities/Bench.swift +++ b/SignalUtilitiesKit/Utilities/Bench.swift @@ -1,8 +1,7 @@ -// // Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// import Foundation +import SignalCoreKit /// Benchmark async code by calling the passed in block parameter when the work /// is done. diff --git a/SignalUtilitiesKit/Utilities/ByteParser.m b/SignalUtilitiesKit/Utilities/ByteParser.m index f8f1f6b10..4dd7c38db 100644 --- a/SignalUtilitiesKit/Utilities/ByteParser.m +++ b/SignalUtilitiesKit/Utilities/ByteParser.m @@ -3,6 +3,7 @@ // #import "ByteParser.h" +#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalUtilitiesKit/Utilities/FunctionalUtil.m b/SignalUtilitiesKit/Utilities/FunctionalUtil.m index 830b47812..65fe6dc5c 100644 --- a/SignalUtilitiesKit/Utilities/FunctionalUtil.m +++ b/SignalUtilitiesKit/Utilities/FunctionalUtil.m @@ -3,6 +3,7 @@ // #import "FunctionalUtil.h" +#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalUtilitiesKit/Utilities/NSAttributedString+OWS.h b/SignalUtilitiesKit/Utilities/NSAttributedString+OWS.h index 601d4b35d..d78b1744f 100644 --- a/SignalUtilitiesKit/Utilities/NSAttributedString+OWS.h +++ b/SignalUtilitiesKit/Utilities/NSAttributedString+OWS.h @@ -2,6 +2,8 @@ // Copyright (c) 2018 Open Whisper Systems. All rights reserved. // +#import + NS_ASSUME_NONNULL_BEGIN @interface NSAttributedString (OWS) diff --git a/SignalUtilitiesKit/Utilities/NSAttributedString+OWS.m b/SignalUtilitiesKit/Utilities/NSAttributedString+OWS.m index 936a15b2f..3762543d2 100644 --- a/SignalUtilitiesKit/Utilities/NSAttributedString+OWS.m +++ b/SignalUtilitiesKit/Utilities/NSAttributedString+OWS.m @@ -3,8 +3,8 @@ // #import "NSAttributedString+OWS.h" -#import "UIView+OWS.h" #import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalUtilitiesKit/Utilities/NoopNotificationsManager.swift b/SignalUtilitiesKit/Utilities/NoopNotificationsManager.swift index f6a399a6f..775319890 100644 --- a/SignalUtilitiesKit/Utilities/NoopNotificationsManager.swift +++ b/SignalUtilitiesKit/Utilities/NoopNotificationsManager.swift @@ -3,6 +3,7 @@ import Foundation import GRDB import SessionMessagingKit +import SignalCoreKit public class NoopNotificationsManager: NotificationsProtocol { public init() {} diff --git a/SignalUtilitiesKit/Utilities/OWSError.m b/SignalUtilitiesKit/Utilities/OWSError.m index 2577bb11a..f8096d70e 100644 --- a/SignalUtilitiesKit/Utilities/OWSError.m +++ b/SignalUtilitiesKit/Utilities/OWSError.m @@ -3,6 +3,7 @@ // #import "OWSError.h" +#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalUtilitiesKit/Utilities/OWSOperation.m b/SignalUtilitiesKit/Utilities/OWSOperation.m index 5c496949a..47e511990 100644 --- a/SignalUtilitiesKit/Utilities/OWSOperation.m +++ b/SignalUtilitiesKit/Utilities/OWSOperation.m @@ -3,8 +3,11 @@ // #import "OWSOperation.h" -#import "OWSBackgroundTask.h" #import "OWSError.h" +#import +#import +#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalUtilitiesKit/Utilities/OrderedDictionary.swift b/SignalUtilitiesKit/Utilities/OrderedDictionary.swift index 5e38f9e4d..549e05aec 100644 --- a/SignalUtilitiesKit/Utilities/OrderedDictionary.swift +++ b/SignalUtilitiesKit/Utilities/OrderedDictionary.swift @@ -1,8 +1,7 @@ -// // Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// import Foundation +import SignalCoreKit public class OrderedDictionary { diff --git a/SignalUtilitiesKit/Utilities/OutageDetection.swift b/SignalUtilitiesKit/Utilities/OutageDetection.swift deleted file mode 100644 index 8f680505f..000000000 --- a/SignalUtilitiesKit/Utilities/OutageDetection.swift +++ /dev/null @@ -1,127 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import Foundation -import os - -@objc -public class OutageDetection: NSObject { - @objc(sharedManager) - public static let shared = OutageDetection() - - @objc public static let outageStateDidChange = Notification.Name("OutageStateDidChange") - - // These properties should only be accessed on the main thread. - @objc - public var hasOutage = false { - didSet { - AssertIsOnMainThread() - - if hasOutage != oldValue { - Logger.info("hasOutage: \(hasOutage).") - - NotificationCenter.default.postNotificationNameAsync(OutageDetection.outageStateDidChange, object: nil) - } - } - } - private var shouldCheckForOutage = false { - didSet { - AssertIsOnMainThread() - ensureCheckTimer() - } - } - - // We only show the outage warning when we're certain there's an outage. - // DNS lookup failures, etc. are not considered an outage. - private func checkForOutageSync() -> Bool { - let host = CFHostCreateWithName(nil, "uptime.signal.org" as CFString).takeRetainedValue() - CFHostStartInfoResolution(host, .addresses, nil) - var success: DarwinBoolean = false - guard let addresses = CFHostGetAddressing(host, &success)?.takeUnretainedValue() as NSArray? else { - Logger.error("CFHostGetAddressing failed: no addresses.") - return false - } - guard success.boolValue else { - Logger.error("CFHostGetAddressing failed.") - return false - } - var isOutageDetected = false - for case let address as NSData in addresses { - var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST)) - if getnameinfo(address.bytes.assumingMemoryBound(to: sockaddr.self), socklen_t(address.length), - &hostname, socklen_t(hostname.count), nil, 0, NI_NUMERICHOST) == 0 { - let addressString = String(cString: hostname) - let kHealthyAddress = "127.0.0.1" - let kOutageAddress = "127.0.0.2" - if addressString == kHealthyAddress { - // Do nothing. - } else if addressString == kOutageAddress { - isOutageDetected = true - } else { - owsFailDebug("unexpected address: \(addressString)") - } - } - } - return isOutageDetected - } - - private func checkForOutageAsync() { - Logger.info("") - - DispatchQueue.global().async { - let isOutageDetected = self.checkForOutageSync() - DispatchQueue.main.async { - self.hasOutage = isOutageDetected - } - } - } - - private var checkTimer: Timer? - private func ensureCheckTimer() { - // Only monitor for outages in the main app. - guard CurrentAppContext().isMainApp else { - return - } - - if shouldCheckForOutage { - if checkTimer != nil { - // Already has timer. - return - } - - // The TTL of the DNS record is 60 seconds. - checkTimer = WeakTimer.scheduledTimer(timeInterval: 60, target: self, userInfo: nil, repeats: true) { [weak self] _ in - AssertIsOnMainThread() - - guard CurrentAppContext().isMainAppAndActive else { - return - } - - guard let strongSelf = self else { - return - } - - strongSelf.checkForOutageAsync() - } - } else { - checkTimer?.invalidate() - checkTimer = nil - } - } - - @objc - public func reportConnectionSuccess() { - DispatchMainThreadSafe { - self.shouldCheckForOutage = false - self.hasOutage = false - } - } - - @objc - public func reportConnectionFailure() { - DispatchMainThreadSafe { - self.shouldCheckForOutage = true - } - } -} diff --git a/SignalUtilitiesKit/Utilities/ReachabilityManager.swift b/SignalUtilitiesKit/Utilities/ReachabilityManager.swift index 90e7d2e34..cafa683ec 100644 --- a/SignalUtilitiesKit/Utilities/ReachabilityManager.swift +++ b/SignalUtilitiesKit/Utilities/ReachabilityManager.swift @@ -1,9 +1,8 @@ -// // Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// import Foundation import Reachability +import SignalCoreKit @objc public class SSKReachabilityManagerImpl: NSObject, SSKReachabilityManager { diff --git a/SignalUtilitiesKit/Utilities/ReverseDispatchQueue.swift b/SignalUtilitiesKit/Utilities/ReverseDispatchQueue.swift index 1133f4384..4eb689a4b 100644 --- a/SignalUtilitiesKit/Utilities/ReverseDispatchQueue.swift +++ b/SignalUtilitiesKit/Utilities/ReverseDispatchQueue.swift @@ -1,8 +1,7 @@ -// // Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// import Foundation +import SignalCoreKit // This is intended to be a drop-in replacement for DispatchQueue // that processes its queue in reverse order. diff --git a/SignalUtilitiesKit/Utilities/SSKAsserts.h b/SignalUtilitiesKit/Utilities/SSKAsserts.h index 8fcbd48eb..d6c18e4a8 100755 --- a/SignalUtilitiesKit/Utilities/SSKAsserts.h +++ b/SignalUtilitiesKit/Utilities/SSKAsserts.h @@ -3,8 +3,6 @@ // #import -#import -#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalUtilitiesKit/Utilities/SwiftSingletons.swift b/SignalUtilitiesKit/Utilities/SwiftSingletons.swift index 346069bf1..2209fcf52 100644 --- a/SignalUtilitiesKit/Utilities/SwiftSingletons.swift +++ b/SignalUtilitiesKit/Utilities/SwiftSingletons.swift @@ -1,8 +1,7 @@ -// // Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// import Foundation +import SignalCoreKit public class SwiftSingletons: NSObject { public static let shared = SwiftSingletons() diff --git a/SignalUtilitiesKit/Utilities/UIAlertController+OWS.swift b/SignalUtilitiesKit/Utilities/UIAlertController+OWS.swift index a8dec91da..7ef870767 100644 --- a/SignalUtilitiesKit/Utilities/UIAlertController+OWS.swift +++ b/SignalUtilitiesKit/Utilities/UIAlertController+OWS.swift @@ -1,8 +1,7 @@ -// // Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// import Foundation +import SignalCoreKit extension UIAlertController { @objc diff --git a/SignalUtilitiesKit/Utilities/UIFont+OWS.m b/SignalUtilitiesKit/Utilities/UIFont+OWS.m index 39617cd13..78497bd44 100644 --- a/SignalUtilitiesKit/Utilities/UIFont+OWS.m +++ b/SignalUtilitiesKit/Utilities/UIFont+OWS.m @@ -3,6 +3,7 @@ // #import "UIFont+OWS.h" +#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalUtilitiesKit/Utilities/UIView+OWS.swift b/SignalUtilitiesKit/Utilities/UIView+OWS.swift index b3a9978e4..de1a7c5cd 100644 --- a/SignalUtilitiesKit/Utilities/UIView+OWS.swift +++ b/SignalUtilitiesKit/Utilities/UIView+OWS.swift @@ -1,9 +1,8 @@ -// // Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// import Foundation import SessionUIKit +import SignalCoreKit public extension UIEdgeInsets { init(top: CGFloat, leading: CGFloat, bottom: CGFloat, trailing: CGFloat) { From f5933bdf750b0db90a9bcc3905f3868d7de3604b Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 7 Dec 2022 14:22:47 +1100 Subject: [PATCH 008/135] Updated to the latest version of the shared util library # Conflicts: # Session/Onboarding/Onboarding.swift --- Session/Onboarding/Onboarding.swift | 33 ++--- .../Migrations/_011_SharedUtilChanges.swift | 10 +- .../Models/ControlMessageProcessRecord.swift | 2 +- .../SessionUtil+UserProfile.swift | 6 +- .../LibSessionUtil/SessionUtil.swift | 40 +++--- .../ios-arm64/libsession-util.a | Bin 833504 -> 863920 bytes .../libsession-util.a | Bin 1879632 -> 1947080 bytes .../module.modulemap | 3 + .../session/config.h | 13 ++ .../session/config.hpp | 59 ++------- .../session/config/base.h | 55 +++++++-- .../session/config/base.hpp | 115 ++++++++++++++---- .../session/config/encrypt.h | 36 ++++++ .../session/config/encrypt.hpp | 69 +++++++++++ .../session/config/user_profile.h | 31 +++-- .../session/config/user_profile.hpp | 40 ++++-- .../session/export.h | 8 ++ .../session/types.hpp | 18 +++ .../session/util.hpp | 27 ++++ .../MessageSender+Convenience.swift | 9 +- .../ConfigUserProfileSpec.swift | 115 ++++++++++++------ .../Models/SendMessageRequest.swift | 2 +- .../General/Collection+Utilities.swift | 13 ++ SignalUtilitiesKit/Utilities/AppSetup.swift | 4 +- 24 files changed, 534 insertions(+), 174 deletions(-) create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config.h create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/encrypt.h create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/encrypt.hpp create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/export.h create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/types.hpp create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/util.hpp diff --git a/Session/Onboarding/Onboarding.swift b/Session/Onboarding/Onboarding.swift index 3814a223c..d7caf5c80 100644 --- a/Session/Onboarding/Onboarding.swift +++ b/Session/Onboarding/Onboarding.swift @@ -24,22 +24,25 @@ enum Onboarding { didApproveMe: true ) .save(db) + + // Create the 'Note to Self' thread (not visible by default) + try SessionThread + .fetchOrCreate(db, id: x25519PublicKey, variant: .contact) + .save(db) + + // Create the initial shared util state (won't have been created on + // launch due to lack of ed25519 key) + SessionUtil.loadState(ed25519SecretKey: ed25519KeyPair.secretKey) + + // No need to show the seed again if the user is restoring or linking + db[.hasViewedSeed] = (self == .recover || self == .link) } - - switch self { - case .register: - Storage.shared.write { db in db[.hasViewedSeed] = false } - // Set hasSyncedInitialConfiguration to true so that when we hit the - // home screen a configuration sync is triggered (yes, the logic is a - // bit weird). This is needed so that if the user registers and - // immediately links a device, there'll be a configuration in their swarm. - userDefaults[.hasSyncedInitialConfiguration] = true - - case .recover, .link: - // No need to show it again if the user is restoring or linking - Storage.shared.write { db in db[.hasViewedSeed] = true } - userDefaults[.hasSyncedInitialConfiguration] = false - } + + // Set hasSyncedInitialConfiguration to true so that when we hit the + // home screen a configuration sync is triggered (yes, the logic is a + // bit weird). This is needed so that if the user registers and + // immediately links a device, there'll be a configuration in their swarm. + userDefaults[.hasSyncedInitialConfiguration] = (self == .register) switch self { case .register, .recover: diff --git a/SessionMessagingKit/Database/Migrations/_011_SharedUtilChanges.swift b/SessionMessagingKit/Database/Migrations/_011_SharedUtilChanges.swift index 548f1f0d1..c9b33cb43 100644 --- a/SessionMessagingKit/Database/Migrations/_011_SharedUtilChanges.swift +++ b/SessionMessagingKit/Database/Migrations/_011_SharedUtilChanges.swift @@ -22,9 +22,17 @@ enum _011_SharedUtilChanges: Migration { .notNull() } + // If we don't have an ed25519 key then no need to create cached dump data + guard let secretKey: [UInt8] = Identity.fetchUserEd25519KeyPair(db)?.secretKey else { + Storage.update(progress: 1, for: self, in: target) // In case this is the last migration + return + } + // Create a dump for the user profile data let userProfileConf: UnsafeMutablePointer? = try SessionUtil.loadState( - for: .userProfile + for: .userProfile, + secretKey: secretKey, + cachedData: nil ) let confResult: SessionUtil.ConfResult = try SessionUtil.update( profile: Profile.fetchOrCreateCurrentUser(db), diff --git a/SessionMessagingKit/Database/Models/ControlMessageProcessRecord.swift b/SessionMessagingKit/Database/Models/ControlMessageProcessRecord.swift index cafc4645f..02bb36173 100644 --- a/SessionMessagingKit/Database/Models/ControlMessageProcessRecord.swift +++ b/SessionMessagingKit/Database/Models/ControlMessageProcessRecord.swift @@ -102,7 +102,7 @@ public struct ControlMessageProcessRecord: Codable, FetchableRecord, Persistable case is ClosedGroupControlMessage: return .closedGroupControlMessage case is DataExtractionNotification: return .dataExtractionNotification case is ExpirationTimerUpdate: return .expirationTimerUpdate - case is ConfigurationMessage: return .configurationMessage + case is ConfigurationMessage, is SharedConfigMessage: return .configurationMessage // TODO: Confirm this is desired case is UnsendRequest: return .unsendRequest case is MessageRequestResponse: return .messageRequestResponse case is CallMessage: return .call diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift index 38a616b59..35fa57e27 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift @@ -36,7 +36,7 @@ internal extension SessionUtil { if profilePic.keylen > 0, let profilePictureUrlPtr: UnsafePointer = profilePic.url, - let profilePictureKeyPtr: UnsafePointer = profilePic.key + let profilePictureKeyPtr: UnsafePointer = profilePic.key { profilePictureUrl = String(cString: profilePictureUrlPtr) profilePictureKey = Data(bytes: profilePictureKeyPtr, count: profilePic.keylen) @@ -86,9 +86,7 @@ internal extension SessionUtil { .bytes .map { CChar(bitPattern: $0) } .withUnsafeBufferPointer { profileUrlPtr in - let profileKey: [CChar]? = profile.profileEncryptionKey? - .bytes - .map { CChar(bitPattern: $0) } + let profileKey: [UInt8]? = profile.profileEncryptionKey?.bytes return profileKey?.withUnsafeBufferPointer { profileKeyPtr in user_profile_pic( diff --git a/SessionMessagingKit/LibSessionUtil/SessionUtil.swift b/SessionMessagingKit/LibSessionUtil/SessionUtil.swift index 1d3247ee4..32c29557d 100644 --- a/SessionMessagingKit/LibSessionUtil/SessionUtil.swift +++ b/SessionMessagingKit/LibSessionUtil/SessionUtil.swift @@ -45,22 +45,30 @@ import SessionUtilitiesKit // MARK: - Loading - /*internal*/public static func loadState() { - SessionUtil.userProfileConfig.mutate { $0 = loadState(for: .userProfile) } + /*internal*/public static func loadState(ed25519SecretKey: [UInt8]?) { + guard let secretKey: [UInt8] = ed25519SecretKey else { return } + + SessionUtil.userProfileConfig.mutate { $0 = loadState(for: .userProfile, secretKey: secretKey) } } - private static func loadState(for variant: ConfigDump.Variant) -> UnsafeMutablePointer? { + private static func loadState( + for variant: ConfigDump.Variant, + secretKey ed25519SecretKey: [UInt8]? + ) -> UnsafeMutablePointer? { + guard let secretKey: [UInt8] = ed25519SecretKey else { return nil } + // Load any let storedDump: Data? = Storage.shared .read { db in try ConfigDump.fetchOne(db, id: variant) }? .data - return try? loadState(for: variant, cachedData: storedDump) + return try? loadState(for: variant, secretKey: secretKey, cachedData: storedDump) } internal static func loadState( for variant: ConfigDump.Variant, - cachedData: Data? = nil + secretKey ed25519SecretKey: [UInt8], + cachedData: Data? ) throws -> UnsafeMutablePointer? { // Setup initial variables (including getting the memory address for any cached data) var conf: UnsafeMutablePointer? = nil @@ -81,10 +89,11 @@ import SessionUtilitiesKit } // Try to create the object + var secretKey: [UInt8] = ed25519SecretKey let result: Int32 = { switch variant { case .userProfile: - return user_profile_init(&conf, cachedDump?.data, (cachedDump?.length ?? 0), error) + return user_profile_init(&conf, &secretKey, cachedDump?.data, (cachedDump?.length ?? 0), error) } }() @@ -107,11 +116,11 @@ import SessionUtilitiesKit // If it doesn't need a dump then do nothing guard config_needs_dump(conf) else { return } - var dumpResult: UnsafeMutablePointer? = nil + var dumpResult: UnsafeMutablePointer? = nil var dumpResultLen: Int = 0 config_dump(conf, &dumpResult, &dumpResultLen) - guard let dumpResult: UnsafeMutablePointer = dumpResult else { return } + guard let dumpResult: UnsafeMutablePointer = dumpResult else { return } let dumpData: Data = Data(bytes: dumpResult, count: dumpResultLen) dumpResult.deallocate() @@ -126,7 +135,8 @@ import SessionUtilitiesKit // MARK: - Pushes public static func getChanges( - for variants: [ConfigDump.Variant] = ConfigDump.Variant.allCases + for variants: [ConfigDump.Variant] = ConfigDump.Variant.allCases, + ed25519SecretKey: [UInt8] ) -> [SharedConfigMessage] { return variants .compactMap { variant -> SharedConfigMessage? in @@ -135,11 +145,11 @@ import SessionUtilitiesKit // Check if the config needs to be pushed guard config_needs_push(conf.wrappedValue) else { return nil } - var toPush: UnsafeMutablePointer? = nil + var toPush: UnsafeMutablePointer? = nil var toPushLen: Int = 0 let seqNo: Int64 = conf.mutate { config_push($0, &toPush, &toPushLen) } - guard let toPush: UnsafeMutablePointer = toPush else { return nil } + guard let toPush: UnsafeMutablePointer = toPush else { return nil } let pushData: Data = Data(bytes: toPush, count: toPushLen) toPush.deallocate() @@ -185,12 +195,8 @@ import SessionUtilitiesKit // Block the config while we are merging atomicConf.mutate { conf in - var mergeData: [UnsafePointer?] = next.value - .map { message -> [CChar] in - message.data - .bytes - .map { CChar(bitPattern: $0) } - } + var mergeData: [UnsafePointer?] = next.value + .map { message -> [UInt8] in message.data.bytes } .unsafeCopy() var mergeSize: [Int] = messages.map { $0.data.count } config_merge(conf, &mergeData, &mergeSize, messages.count) diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64/libsession-util.a b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64/libsession-util.a index 6d20b8a98cf56097e401c223b89b42aad9bccb62..5e58f5b33459945aa3200c0bec0d318957eb4ba5 100644 GIT binary patch delta 132653 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

vN<%Aj6t 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 diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64_x86_64-simulator/libsession-util.a b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64_x86_64-simulator/libsession-util.a index 284fc504fcd04b7ff3a0f8186a09374b1d6f2040..8c1e89f238583fdbe03c3619b9ff30608ff576df 100644 GIT binary patch delta 268354 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

  • 1UVB^(#~=ALA@N+PH*0n*+~D!a=}?<2U%O# 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 TC^GQycF(qcv8L|uq)Pt}3CH~= delta 219683 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 + +typedef int64_t seqno_t; + +#ifdef __cplusplus +} +#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config.hpp index 90ab8676a..c59809861 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config.hpp +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config.hpp @@ -11,8 +11,11 @@ #include #include +#include "types.hpp" + namespace session::config { +// FIXME: for multi-message we encode to longer and then split it up inline constexpr int MAX_MESSAGE_SIZE = 76800; // 76.8kB = Storage server's limit // Application data data types: @@ -35,13 +38,9 @@ constexpr inline const dict_variant& unwrap(const dict_value& v) { return static_cast(v); } -using seqno_t = std::int64_t; using hash_t = std::array; using seqno_hash_t = std::pair; -using ustring = std::basic_string; -using ustring_view = std::basic_string_view; - class MutableConfigMessage; /// Base type for all errors that can happen during config parsing @@ -103,7 +102,7 @@ class ConfigMessage { using verify_callable = std::function; /// Signing function: this is passed the data to be signed and returns the 64-byte signature. - using sign_callable = std::function; + using sign_callable = std::function; ConfigMessage(); ConfigMessage(const ConfigMessage&) = default; @@ -116,7 +115,7 @@ class ConfigMessage { /// Initializes a config message by parsing a serialized message. Throws on any error. See the /// vector version below for argument descriptions. explicit ConfigMessage( - std::string_view serialized, + ustring_view serialized, verify_callable verifier = nullptr, sign_callable signer = nullptr, int lag = DEFAULT_DIFF_LAGS, @@ -155,7 +154,7 @@ class ConfigMessage { /// parse. A simple handler such as `[](const auto& e) { throw e; }` can be used to make any /// parse error of any message fatal. explicit ConfigMessage( - const std::vector& configs, + const std::vector& configs, verify_callable verifier = nullptr, sign_callable signer = nullptr, int lag = DEFAULT_DIFF_LAGS, @@ -218,10 +217,10 @@ class ConfigMessage { /// typically for a local serialization value that isn't being pushed to the server). Note that /// signing is always disabled if there is no signing callback set, regardless of the value of /// this argument. - virtual std::string serialize(bool enable_signing = true); + virtual ustring serialize(bool enable_signing = true); protected: - std::string serialize_impl(const oxenc::bt_dict& diff, bool enable_signing = true); + ustring serialize_impl(const oxenc::bt_dict& diff, bool enable_signing = true); }; // Constructor tag @@ -267,7 +266,7 @@ class MutableConfigMessage : public ConfigMessage { /// constructor only increments seqno once while the indirect version would increment twice in /// the case of a required merge conflict resolution. explicit MutableConfigMessage( - const std::vector& configs, + const std::vector& configs, verify_callable verifier = nullptr, sign_callable signer = nullptr, int lag = DEFAULT_DIFF_LAGS, @@ -278,7 +277,7 @@ class MutableConfigMessage : public ConfigMessage { /// take an error handler and instead always throws on parse errors (the above also throws for /// an erroneous single message, but with a less specific "no valid config messages" error). explicit MutableConfigMessage( - std::string_view config, + ustring_view config, verify_callable verifier = nullptr, sign_callable signer = nullptr, int lag = DEFAULT_DIFF_LAGS, @@ -319,46 +318,10 @@ class MutableConfigMessage : public ConfigMessage { const hash_t& hash() override; protected: - const hash_t& hash(std::string_view serialized); + const hash_t& hash(ustring_view serialized); void increment_impl(); }; -/// Encrypts a config message using XChaCha20-Poly1305, using a blake2b keyed hash of the message -/// for the nonce (rather than pure random) so that different clients will encrypt the same data to -/// the same encrypted value (thus allowing for server-side deduplication of identical messages). -/// -/// `key_base` must be 32 bytes. This value is a fixed key that all clients that might receive this -/// message can calculate independently (for instance a value derived from a secret key, or a shared -/// random key). This key will be hashed with the message size and domain suffix (see below) to -/// determine the actual encryption key. -/// -/// `domain` is a short string (1-24 chars) used for the keyed hash. Typically this is the type of -/// config, e.g. "closed-group" or "contacts". The full key will be -/// "session-config-encrypted-message-[domain]". This value is also used for the encrypted key (see -/// above). -/// -/// The returned result will consist of encrypted data with authentication tag and appended nonce, -/// suitable for being passed to decrypt() to authenticate and decrypt. -/// -/// Throw std::invalid_argument on bad input (i.e. from invalid key_base or domain). -ustring encrypt(ustring_view message, ustring_view key_base, std::string_view domain); - -/// Same as above but works with strings/string_views instead of ustring/ustring_view -std::string encrypt(std::string_view message, std::string_view key_base, std::string_view domain); - -/// Thrown if decrypt() fails. -struct decrypt_error : std::runtime_error { - using std::runtime_error::runtime_error; -}; - -/// Takes a value produced by `encrypt()` and decrypts it. `key_base` and `domain` must be the same -/// given to encrypt or else decryption fails. Upon decryption failure a std:: -ustring decrypt(ustring_view ciphertext, ustring_view key_base, std::string_view domain); - -/// Same as above but using std::string/string_view -std::string decrypt( - std::string_view ciphertext, std::string_view key_base, std::string_view domain); - } // namespace session::config namespace oxenc::detail { diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.h index 55649ecc8..2619511dd 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.h +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.h @@ -8,14 +8,7 @@ extern "C" { #include #include -#if defined(_WIN32) || defined(WIN32) -#define LIBSESSION_EXPORT __declspec(dllexport) -#else -#define LIBSESSION_EXPORT __attribute__((visibility("default"))) -#endif -#define LIBSESSION_C_API extern "C" LIBSESSION_EXPORT - -typedef int64_t seqno_t; +#include "../config.h" // Config object base type: this type holds the internal object and is initialized by the various // config-dependent settings (e.g. config_user_profile_init) then passed to the various functions. @@ -33,6 +26,28 @@ typedef struct config_object { /// user_profile_init). void config_free(config_object* conf); +typedef enum config_log_level { + LOG_LEVEL_DEBUG = 0, + LOG_LEVEL_INFO, + LOG_LEVEL_WARNING, + LOG_LEVEL_ERROR +} config_log_level; + +/// Sets a logging function; takes the log function pointer and a context pointer (which can be NULL +/// if not needed). The given function pointer will be invoked with one of the above values, a +/// null-terminated c string containing the log message, and the void* context object given when +/// setting the logger (this is for caller-specific state data and won't be touched). +/// +/// The logging function must have signature: +/// +/// void log(config_log_level lvl, const char* msg, void* ctx); +/// +/// Can be called with callback set to NULL to clear an existing logger. +/// +/// The config object itself has no log level: the caller should filter by level as needed. +void config_set_logger( + config_object* conf, void (*callback)(config_log_level, const char*, void*), void* ctx); + /// Returns the numeric namespace in which config messages of this type should be stored. int16_t config_storage_namespace(const config_object* conf); @@ -42,7 +57,8 @@ int16_t config_storage_namespace(const config_object* conf); /// /// `configs` is an array of pointers to the start of the strings; `lengths` is an array of string /// lengths; `count` is the length of those two arrays. -void config_merge(config_object* conf, const char** configs, const size_t* lengths, size_t count); +int config_merge( + config_object* conf, const unsigned char** configs, const size_t* lengths, size_t count); /// Returns true if this config object contains updated data that has not yet been confirmed stored /// on the server. @@ -59,7 +75,7 @@ bool config_needs_push(const config_object* conf); /// /// NB: The returned buffer belongs to the caller: that is, the caller *MUST* free() it when done /// with it. -seqno_t config_push(config_object* conf, char** out, size_t* outlen); +seqno_t config_push(config_object* conf, unsigned char** out, size_t* outlen); /// Reports that data obtained from `config_push` has been successfully stored on the server. The /// seqno value is the one returned by the config_push call that yielded the config data. @@ -74,12 +90,29 @@ void config_confirm_pushed(config_object* conf, seqno_t seqno); /// /// Immediately after this is called `config_needs_dump` will start returning true (until the /// configuration is next modified). -void config_dump(config_object* conf, char** out, size_t* outlen); +void config_dump(config_object* conf, unsigned char** out, size_t* outlen); /// Returns true if something has changed since the last call to `dump()` that requires calling /// and saving the `config_dump()` data again. bool config_needs_dump(const config_object* conf); +/// Config key management; see the corresponding method docs in base.hpp. All `key` arguments here +/// are 32-byte binary buffers (and since fixed-length, there is no keylen argument). +void config_add_key(config_object* conf, const unsigned char* key); +void config_add_key_low_prio(config_object* conf, const unsigned char* key); +int config_clear_keys(config_object* conf); +bool config_remove_key(config_object* conf, const unsigned char* key); +int config_key_count(const config_object* conf); +bool config_has_key(const config_object* conf, const unsigned char* key); +// Returns a pointer to the 32-byte binary key at position i. This is *not* null terminated (and is +// exactly 32 bytes long). `i < config_key_count(conf)` must be satisfied. Ownership of the data +// remains in the object (that is: the caller must not attempt to free it). +const unsigned char* config_key(const config_object* conf, size_t i); + +/// Returns the encryption domain C-str used to encrypt values for this config object. (This is +/// here only for debugging/testing). +const char* config_encryption_domain(const config_object* conf); + #ifdef __cplusplus } // extern "C" #endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.hpp index dc14a37bc..86f42533d 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.hpp +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.hpp @@ -1,9 +1,11 @@ #pragma once +#include #include #include #include #include +#include #include "base.h" #include "namespaces.hpp" @@ -23,7 +25,7 @@ static constexpr bool is_dict_value = is_dict_subtype || is_one_of; // Levels for the logging callback -enum class LogLevel { debug, info, warning, error }; +enum class LogLevel { debug = 0, info, warning, error }; /// Our current config state enum class ConfigState : int { @@ -52,12 +54,21 @@ class ConfigBase { // Tracks our current state ConfigState _state = ConfigState::Clean; - protected: - // Constructs an empty base config with no config settings and seqno set to 0. - ConfigBase(); + static constexpr size_t KEY_SIZE = 32; - // Constructs a base config by loading the data from a dump as produced by `dump()`. - explicit ConfigBase(std::string_view dump); + // Contains the base key(s) we use to encrypt/decrypt messages. If non-empty, the .front() + // element will be used when encrypting a new message to push. When decrypting, we attempt each + // of them, starting with .front(), until decryption succeeds. + using Key = std::array; + Key* _keys = nullptr; + size_t _keys_size = 0; + size_t _keys_capacity = 0; + + protected: + // Constructs a base config by loading the data from a dump as produced by `dump()`. If the + // dump is nullopt then an empty base config is constructed with no config settings and seqno + // set to 0. + explicit ConfigBase(std::optional dump = std::nullopt); // Tracks whether we need to dump again; most mutating methods should set this to true (unless // calling set_state, which sets to to true implicitly). @@ -69,10 +80,7 @@ class ConfigBase { _needs_dump = true; } - // If set then we log things by calling this callback - std::function logger; - - // Invokes the above if set, does nothing if there is no logger. + // Invokes the `logger` callback if set, does nothing if there is no logger. void log(LogLevel lvl, std::string msg) { if (logger) logger(lvl, std::move(msg)); @@ -371,15 +379,24 @@ class ConfigBase { virtual void load_extra_data(oxenc::bt_dict extra) {} public: - virtual ~ConfigBase() = default; + virtual ~ConfigBase(); // Proxy class providing read and write access to the contained config data. const DictFieldRoot data{*this}; + // If set then we log things by calling this callback + std::function logger; + // Accesses the storage namespace where this config type is to be stored/loaded from. See // namespaces.hpp for the underlying integer values. virtual Namespace storage_namespace() const = 0; + /// Subclasses must override this to return a constant string that is unique per config type; + /// this value is used for domain separation in encryption. The string length must be between 1 + /// and 24 characters; use the class name (e.g. "UserProfile") unless you have something better + /// to use. This is rarely needed externally; it is public merely for testing purposes. + virtual const char* encryption_domain() const = 0; + // How many config lags should be used for this object; default to 5. Implementing subclasses // can override to return a different constant if desired. More lags require more "diff" // storage in the config messages, but also allow for a higher tolerance of simultaneous message @@ -392,9 +409,15 @@ class ConfigBase { // After this call the caller should check `needs_push()` to see if the data on hand was updated // and needs to be pushed to the server again. // + // Returns the number of the given config messages that were successfully parsed. + // // Will throw on serious error (i.e. if neither the current nor any of the given configs are - // parseable). - virtual void merge(const std::vector& configs); + // parseable). This should not happen (the current config, at least, should always be + // re-parseable). + virtual int merge(const std::vector& configs); + + // Same as above but takes a vector of ustring's as sometimes that is more convenient. + int merge(const std::vector& configs); // Returns true if we are currently dirty (i.e. have made changes that haven't been serialized // yet). @@ -408,13 +431,13 @@ class ConfigBase { // the server. This will be true whenever `is_clean()` is false: that is, if we are currently // "dirty" (i.e. have changes that haven't been pushed) or are still awaiting confirmation of // storage of the most recent serialized push data. - virtual bool needs_push() const; + bool needs_push() const; - // Returns the data to push to the server along with the seqno value of the data. If the config - // is currently dirty (i.e. has previously unsent modifications) then this marks it as - // awaiting-confirmation instead of dirty so that any future change immediately increments the - // seqno. - virtual std::pair push(); + // Returns the data messages to push to the server along with the seqno value of the data. If + // the config is currently dirty (i.e. has previously unsent modifications) then this marks it + // as awaiting-confirmation instead of dirty so that any future change immediately increments + // the seqno. + std::pair push(); // Should be called after the push is confirmed stored on the storage server swarm to let the // object know the data is stored. (Once this is called `needs_push` will start returning false @@ -431,11 +454,61 @@ class ConfigBase { // into the constructor to reconstitute the object (including the push/not pushed status). This // method is *not* virtual: if subclasses need to store extra data they should set it in the // `subclass_data` field. - std::string dump(); + ustring dump(); // Returns true if something has changed since the last call to `dump()` that requires calling // and saving the `dump()` data again. virtual bool needs_dump() const { return _needs_dump; } + + // Encryption key methods. For classes that have a single, static key (such as user profile + // storage types) these methods typically don't need to be used: the subclass calls them + // automatically. + + // Adds an encryption/decryption key, without removing existing keys. They key must be exactly + // 32 bytes long. The newly added key becomes the highest priority key (unless the + // `high_priority` argument is set to false' see below): it will be used for encryption of + // config pushes after the call, and will be tried first when decrypting, followed by keys + // present (if any) before this call. If the given key is already present in the key list then + // this call moves it to the front of the list (if not already at the front). + // + // If the `high_priority` argument is specified and false, then the key is added to the *end* of + // the key list instead of the beginning: that is, it will not replace the current + // highest-priority key used for encryption, but will still be usable for decryption of new + // incoming messages (after trying keys present before the call). If the key already exists + // then nothing happens with `high_priority=false` (in particular, it is *not* repositioned, in + // contrast to high_priority=true behaviour). + // + // Will throw a std::invalid_argument if the key is not 32 bytes. + void add_key(ustring_view key, bool high_priority = true); + + // Clears all stored encryption/decryption keys. This is typically immediately followed with + // one or more `add_key` call to replace existing keys. Returns the number of keys removed. + int clear_keys(); + + // Removes the given encryption/decryption key, if present. Returns true if it was found and + // removed, false if it was not in the key list. + // + // The optional second argument removes the key only from position `from` or higher. It is + // mainly for internal use and is usually omitted. + bool remove_key(ustring_view key, size_t from = 0); + + // Returns a vector of encryption keys, in priority order (i.e. element 0 is the encryption key, + // and the first decryption key). + std::vector get_keys() const; + + // Returns the number of encryption keys. + int key_count() const; + + // Returns true if the given key is already in the keys list. + bool has_key(ustring_view key) const; + + // Accesses the key at position i (0 if omitted). There must be at least one key, and i must be + // less than key_count(). The key at position 0 is used for encryption; for decryption all keys + // are tried in order, starting from position 0. + ustring_view key(size_t i = 0) const { + assert(i < _keys_size); + return {_keys[i].data(), _keys[i].size()}; + } }; // The C++ struct we hold opaquely inside the C internals struct. This is designed so that any @@ -498,6 +571,6 @@ inline int set_error(config_object* conf, int errcode, const std::exception& e) // Copies a value contained in a string into a new malloced char buffer, returning the buffer and // size via the two pointer arguments. -void copy_out(const std::string& data, char** out, size_t* outlen); +void copy_out(ustring_view data, unsigned char** out, size_t* outlen); } // namespace session::config diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/encrypt.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/encrypt.h new file mode 100644 index 000000000..b29848929 --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/encrypt.h @@ -0,0 +1,36 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/// Wrapper around session::config::encrypt. message and key_base are binary: message has the +/// length provided, key_base must be exactly 32 bytes. domain is a c string. Returns a newly +/// allocated buffer containing the encrypted data, and sets the data's length into +/// `ciphertext_size`. It is the caller's responsibility to `free()` the returned buffer! +/// +/// Returns nullptr on error. +unsigned char* config_encrypt( + const unsigned char* message, + size_t mlen, + const unsigned char* key_base, + const char* domain, + size_t* ciphertext_size); + +/// Works just like config_encrypt, but in reverse. +unsigned char* config_decrypt( + const unsigned char* ciphertext, + size_t clen, + const unsigned char* key_base, + const char* domain, + size_t* plaintext_size); + +/// Returns the amount of padding needed for a plaintext of size s with encryption overhead +/// `overhead`. +size_t config_padded_size(size_t s, size_t overhead); + +#ifdef __cplusplus +} +#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/encrypt.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/encrypt.hpp new file mode 100644 index 000000000..75c9f9aff --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/encrypt.hpp @@ -0,0 +1,69 @@ +#pragma once + +#include + +#include "../types.hpp" + +namespace session::config { + +/// Encrypts a config message using XChaCha20-Poly1305, using a blake2b keyed hash of the message +/// for the nonce (rather than pure random) so that different clients will encrypt the same data to +/// the same encrypted value (thus allowing for server-side deduplication of identical messages). +/// +/// `key_base` must be 32 bytes. This value is a fixed key that all clients that might receive this +/// message can calculate independently (for instance a value derived from a secret key, or a shared +/// random key). This key will be hashed with the message size and domain suffix (see below) to +/// determine the actual encryption key. +/// +/// `domain` is a short string (1-24 chars) used for the keyed hash. Typically this is the type of +/// config, e.g. "closed-group" or "contacts". The full key will be +/// "session-config-encrypted-message-[domain]". This value is also used for the encrypted key (see +/// above). +/// +/// The returned result will consist of encrypted data with authentication tag and appended nonce, +/// suitable for being passed to decrypt() to authenticate and decrypt. +/// +/// Throw std::invalid_argument on bad input (i.e. from invalid key_base or domain). +ustring encrypt(ustring_view message, ustring_view key_base, std::string_view domain); + +/// Same as above, but modifies `message` in place. `message` gets encrypted plus has the extra +/// data and nonce appended. +void encrypt_inplace(ustring& message, ustring_view key_base, std::string_view domain); + +/// Constant amount of extra bytes required to be appended when encrypting. +constexpr size_t ENCRYPT_DATA_OVERHEAD = 40; // ABYTES + NPUBBYTES + +/// Thrown if decrypt() fails. +struct decrypt_error : std::runtime_error { + using std::runtime_error::runtime_error; +}; + +/// Takes a value produced by `encrypt()` and decrypts it. `key_base` and `domain` must be the same +/// given to encrypt or else decryption fails. Upon decryption failure a `decrypt_error` exception +/// is thrown. +ustring decrypt(ustring_view ciphertext, ustring_view key_base, std::string_view domain); + +/// Same as above, but does in in-place. The string gets shortend to the plaintext after this call. +void decrypt_inplace(ustring& ciphertext, ustring_view key_base, std::string_view domain); + +/// Returns the target size of the message with padding, assuming an additional `overhead` bytes of +/// overhead (e.g. from encrypt() overhead) will be appended. Will always return a value >= s + +/// overhead. +/// +/// Padding increments we use: 256 byte increments up to 5120; 1024 byte increments up to 20480, +/// 2048 increments up to 40960, then 5120 from there up. +inline constexpr size_t padded_size(size_t s, size_t overhead = ENCRYPT_DATA_OVERHEAD) { + size_t s2 = s + overhead; + size_t chunk = s2 < 5120 ? 256 : s2 < 20480 ? 1024 : s2 < 40960 ? 2048 : 5120; + return (s2 + chunk - 1) / chunk * chunk - overhead; +} + +/// Inserts null byte padding to the beginning of a message to make the final message size granular. +/// See the above function for the sizes. +/// +/// \param data - the data; this is modified in place. +/// \param overhead - encryption overhead to account for to reach the desired padded size. The +/// default, if omitted, is the space used by the `encrypt()` function defined above. +void pad_message(ustring& data, size_t overhead = ENCRYPT_DATA_OVERHEAD); + +} // namespace session::config diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.h index 6a0123027..8b438591e 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.h +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.h @@ -6,19 +6,32 @@ extern "C" { #include "base.h" -/// Constructs a user profile config object and sets a pointer to it in `conf`. To restore an -/// existing dump produced by a past instantiation's call to `dump()` pass the dump value via `dump` -/// and `dumplen`; to construct a new, empty profile pass NULL and 0. +/// Constructs a user profile config object and sets a pointer to it in `conf`. /// -/// `error` must either be NULL or a pointer to a buffer of at least 256 bytes. +/// \param ed25519_secretkey must be the 32-byte secret key seed value. (You can also pass the +/// pointer to the beginning of the 64-byte value libsodium calls the "secret key" as the first 32 +/// bytes of that are the seed). This field cannot be null. /// -/// Returns 0 on success; returns a non-zero error code and sets error (if not NULL) to the -/// exception message on failure. +/// \param dump - if non-NULL this restores the state from the dumped byte string produced by a past +/// instantiation's call to `dump()`. To construct a new, empty profile this should be NULL. +/// +/// \param dumplen - the length of `dump` when restoring from a dump, or 0 when `dump` is NULL. +/// +/// \param error - the pointer to a buffer in which we will write an error string if an error +/// occurs; error messages are discarded if this is given as NULL. If non-NULL this must be a +/// buffer of at least 256 bytes. +/// +/// Returns 0 on success; returns a non-zero error code and write the exception message as a +/// C-string into `error` (if not NULL) on failure. /// /// When done with the object the `config_object` must be destroyed by passing the pointer to /// config_free() (in `session/config/base.h`). -int user_profile_init(config_object** conf, const char* dump, size_t dumplen, char* error) - __attribute__((warn_unused_result)); +int user_profile_init( + config_object** conf, + const unsigned char* ed25519_secretkey, + const unsigned char* dump, + size_t dumplen, + char* error) __attribute__((warn_unused_result)); /// Returns a pointer to the currently-set name (null-terminated), or NULL if there is no name at /// all. Should be copied right away as the pointer may not remain valid beyond other API calls. @@ -34,7 +47,7 @@ typedef struct user_profile_pic { const char* url; // The profile pic decryption key, in bytes. This is a byte buffer of length `keylen`, *not* a // null-terminated C string. Will be NULL if there is no profile pic. - const char* key; + const unsigned char* key; size_t keylen; } user_profile_pic; diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.hpp index b4cdba80b..f13a83867 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.hpp +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.hpp @@ -14,30 +14,52 @@ namespace session::config { /// p - user profile url /// q - user profile decryption key (binary) +// Profile pic info. Note that `url` is null terminated (though the null lies just beyond the end +// of the string view: that is, it views into a full std::string). +struct profile_pic { + std::string_view url; + ustring_view key; +}; + class UserProfile final : public ConfigBase { public: - /// Constructs a new, blank user profile. - UserProfile() = default; + // No default constructor + UserProfile() = delete; - /// Constructs a user profile from existing data - explicit UserProfile(std::string_view dumped) : ConfigBase{dumped} {} + /// Constructs a user profile from existing data (stored from `dump()`) and the user's secret + /// key for generating the data encryption key. To construct a blank profile (i.e. with no + /// pre-existing dumped data to load) pass `std::nullopt` as the second argument. + /// + /// \param ed25519_secretkey - contains the libsodium secret key used to encrypt/decrypt user + /// profile messages; these can either be the full 64-byte value (which is technically the + /// 32-byte seed followed by the 32-byte pubkey), or just the 32-byte seed of the secret key. + /// + /// \param dumped - either `std::nullopt` to construct a new, empty user profile; or binary + /// state data that was previously dumped from a UserProfile object by calling `dump()`. + UserProfile(ustring_view ed25519_secretkey, std::optional dumped); Namespace storage_namespace() const override { return Namespace::UserProfile; } - /// Returns the user profile name, or nullptr if there is no profile name set. - const std::string* get_name() const; + const char* encryption_domain() const override { return "UserProfile"; } - /// Sets the user profile name + /// Returns the user profile name, or std::nullopt if there is no profile name set. + const std::optional get_name() const; + + /// Sets the user profile name; if given an empty string then the name is removed. void set_name(std::string_view new_name); /// Gets the user's current profile pic URL and decryption key. Returns nullptr for *both* /// values if *either* value is unset or empty in the config data. - std::pair get_profile_pic() const; + std::optional get_profile_pic() const; /// Sets the user's current profile pic to a new URL and decryption key. Clears both if either /// one is empty. - void set_profile_pic(std::string url, std::string key); + void set_profile_pic(std::string_view url, ustring_view key); + void set_profile_pic(profile_pic pic); + + private: + void load_key(ustring_view ed25519_secretkey); }; } // namespace session::config diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/export.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/export.h new file mode 100644 index 000000000..ab307f6f2 --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/export.h @@ -0,0 +1,8 @@ +#pragma once + +#if defined(_WIN32) || defined(WIN32) +#define LIBSESSION_EXPORT __declspec(dllexport) +#else +#define LIBSESSION_EXPORT __attribute__((visibility("default"))) +#endif +#define LIBSESSION_C_API extern "C" LIBSESSION_EXPORT diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/types.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/types.hpp new file mode 100644 index 000000000..d63fe470e --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/types.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include + +namespace session { + +using ustring = std::basic_string; +using ustring_view = std::basic_string_view; + +namespace config { + + using seqno_t = std::int64_t; + +} // namespace config + +} // namespace session diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/util.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/util.hpp new file mode 100644 index 000000000..8348d908e --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/util.hpp @@ -0,0 +1,27 @@ +#pragma once +#include "types.hpp" + +namespace session { + +// Helper function to go to/from char pointers to unsigned char pointers: +inline const unsigned char* to_unsigned(const char* x) { + return reinterpret_cast(x); +} +inline unsigned char* to_unsigned(char* x) { + return reinterpret_cast(x); +} +inline const char* from_unsigned(const unsigned char* x) { + return reinterpret_cast(x); +} +inline char* from_unsigned(unsigned char* x) { + return reinterpret_cast(x); +} +// Helper function to switch between string_view and ustring_view +inline ustring_view to_unsigned_sv(std::string_view v) { + return {to_unsigned(v.data()), v.size()}; +} +inline std::string_view from_unsigned_sv(ustring_view v) { + return {from_unsigned(v.data()), v.size()}; +} + +} // namespace session diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift b/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift index d59cc1621..ac1160084 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift @@ -191,7 +191,10 @@ extension MessageSender { // If we don't have a userKeyPair yet then there is no need to sync the configuration // as the user doesn't exist yet (this will get triggered on the first launch of a // fresh install due to the migrations getting run) - guard Identity.userExists(db) else { return Promise(error: StorageError.generic) } + guard + Identity.userExists(db), + let ed25519SecretKey: [UInt8] = Identity.fetchUserEd25519KeyPair(db)?.secretKey + else { return Promise(error: StorageError.generic) } let publicKey: String = getUserHexEncodedPublicKey(db) let legacyDestination: Message.Destination = Message.Destination.contact( @@ -201,7 +204,9 @@ extension MessageSender { let legacyConfigurationMessage = try ConfigurationMessage.getCurrent(db) let (promise, seal) = Promise.pending() - let userConfigMessageChanges: [SharedConfigMessage] = SessionUtil.getChanges() + let userConfigMessageChanges: [SharedConfigMessage] = SessionUtil.getChanges( + ed25519SecretKey: ed25519SecretKey + ) let destination: Message.Destination = Message.Destination.contact( publicKey: publicKey, namespace: .userProfileConfig diff --git a/SessionMessagingKitTests/LibSessionUtil/ConfigUserProfileSpec.swift b/SessionMessagingKitTests/LibSessionUtil/ConfigUserProfileSpec.swift index e08840408..93ba46791 100644 --- a/SessionMessagingKitTests/LibSessionUtil/ConfigUserProfileSpec.swift +++ b/SessionMessagingKitTests/LibSessionUtil/ConfigUserProfileSpec.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import Sodium import SessionUtil import SessionUtilitiesKit @@ -13,10 +14,21 @@ class ConfigUserProfileSpec: QuickSpec { override func spec() { it("generates configs correctly") { + let seed: Data = Data(hex: "0123456789abcdef0123456789abcdef") + + // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately + let identity = try! Identity.generate(from: seed) + var edSK: [UInt8] = identity.ed25519KeyPair.secretKey + expect(edSK.toHexString().suffix(64)) + .to(equal("4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7")) + expect(identity.x25519KeyPair.publicKey.toHexString()) + .to(equal("d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72")) + expect(String(edSK.toHexString().prefix(32))).to(equal(seed.toHexString())) + // Initialize a brand new, empty config because we have no dump data to deal with. let error: UnsafeMutablePointer? = nil var conf: UnsafeMutablePointer? = nil - expect(user_profile_init(&conf, nil, 0, error)).to(equal(0)) + expect(user_profile_init(&conf, &edSK, nil, 0, error)).to(equal(0)) error?.deallocate() // We don't need to push anything, since this is an empty config @@ -28,15 +40,31 @@ class ConfigUserProfileSpec: QuickSpec { let namePtr: UnsafePointer? = user_profile_get_name(conf) expect(namePtr).to(beNil()) - var toPush: UnsafeMutablePointer? = nil + var toPush: UnsafeMutablePointer? = nil var toPushLen: Int = 0 // We don't need to push since we haven't changed anything, so this call is mainly just for // testing: let seqno: Int64 = config_push(conf, &toPush, &toPushLen) expect(toPush).toNot(beNil()) expect(seqno).to(equal(0)) - expect(String(pointer: toPush, length: toPushLen)).to(equal("d1:#i0e1:&de1:? = config_decrypt(toPush, toPushLen, edSK, encDomain, &toPushDecSize) + let prefixPadding: String = (0..<193) + .map { _ in "\0" } + .joined() + expect(toPushDecrypted).toNot(beNil()) + expect(toPushDecSize).to(equal(216)) // 256 - 40 overhead + expect(String(pointer: toPushDecrypted, length: toPushDecSize)) + .to(equal("\(prefixPadding)d1:#i0e1:&de1:? = nil + var toPush2: UnsafeMutablePointer? = nil var toPush2Len: Int = 0 let seqno2: Int64 = config_push(conf, &toPush2, &toPush2Len); // incremented since we made changes (this only increments once between @@ -90,11 +116,10 @@ class ConfigUserProfileSpec: QuickSpec { // Note: This hex value differs from the value in the library tests because // it looks like the library has an "end of cell mark" character added at the // end (0x07 or '0007') so we need to manually add it to work - let expHash0: [CChar] = Data(hex: "ea173b57beca8af18c3519a7bbf69c3e7a05d1c049fa9558341d8ebb48b0c965") + let expHash0: [UInt8] = Data(hex: "ea173b57beca8af18c3519a7bbf69c3e7a05d1c049fa9558341d8ebb48b0c965") .bytes - .map { CChar(bitPattern: $0) } // The data to be actually pushed, expanded like this to make it somewhat human-readable: - let expPush1: [CChar] = [""" + let expPush1Decrypted: [UInt8] = [""" d 1:#i1e 1:& d @@ -105,8 +130,7 @@ class ConfigUserProfileSpec: QuickSpec { 1:< l l i0e 32: """.removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) // For readability - .bytes - .map { CChar(bitPattern: $0) }, + .bytes, expHash0, """ de e @@ -119,20 +143,44 @@ class ConfigUserProfileSpec: QuickSpec { e """.removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) // For readability .bytes - .map { CChar(bitPattern: $0) } ] .flatMap { $0 } + let expPush1Encrypted: [UInt8] = Data(hex: [ + "a2952190dcb9797bc48e48f6dc7b3254d004bde9091cfc9ec3433cbc5939a3726deb04f58a546d7d79e6f8", + "0ea185d43bf93278398556304998ae882304075c77f15c67f9914c4d10005a661f29ff7a79e0a9de7f2172", + "5ba3b5a6c19eaa3797671b8fa4008d62e9af2744629cbb46664c4d8048e2867f66ed9254120371bdb24e95", + "b2d92341fa3b1f695046113a768ceb7522269f937ead5591bfa8a5eeee3010474002f2db9de043f0f0d1cf", + "b1066a03e7b5d6cfb70a8f84a20cd2df5a510cd3d175708015a52dd4a105886d916db0005dbea5706e5a5d", + "c37ffd0a0ca2824b524da2e2ad181a48bb38e21ed9abe136014a4ee1e472cb2f53102db2a46afa9d68" + ].joined()).bytes expect(String(pointer: toPush2, length: toPush2Len, encoding: .ascii)) - .to(equal(String(pointer: expPush1, length: expPush1.count, encoding: .ascii))) + .to(equal(String(pointer: expPush1Encrypted, length: expPush1Encrypted.count, encoding: .ascii))) + + // Raw decryption doesn't unpad (i.e. the padding is part of the encrypted data) + var toPush2DecSize: Int = 0 + let toPush2Decrypted: UnsafeMutablePointer? = config_decrypt( + toPush2, + toPush2Len, + edSK, + encDomain, + &toPush2DecSize + ) + let prefixPadding2: String = (0..<(256 - 40 - expPush1Decrypted.count)) + .map { _ in "\0" } + .joined() + expect(toPush2DecSize).to(equal(216)) // 256 - 40 overhead + expect(String(pointer: toPush2Decrypted, length: toPush2DecSize, encoding: .ascii)) + .to(equal(String(pointer: expPush1Decrypted, length: expPush1Decrypted.count, encoding: .ascii).map { "\(prefixPadding2)\($0)" })) toPush2?.deallocate() + toPush2Decrypted?.deallocate() // We haven't dumped, so still need to dump: expect(config_needs_dump(conf)).to(beTrue()) // We did call push, but we haven't confirmed it as stored yet, so this will still return true: expect(config_needs_push(conf)).to(beTrue()) - var dump1: UnsafeMutablePointer? = nil + var dump1: UnsafeMutablePointer? = nil var dump1Len: Int = 0 config_dump(conf, &dump1, &dump1Len) @@ -144,12 +192,13 @@ class ConfigUserProfileSpec: QuickSpec { """ d 1:! i2e - 1:$ \(expPush1.count): + 1:$ \(expPush1Decrypted.count): """ .removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) .bytes .map { CChar(bitPattern: $0) }, - expPush1, + expPush1Decrypted + .map { CChar(bitPattern: $0) }, """ e """.removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) @@ -167,7 +216,7 @@ class ConfigUserProfileSpec: QuickSpec { expect(config_needs_push(conf)).to(beFalse()) expect(config_needs_dump(conf)).to(beTrue()) // The confirmation changes state, so this makes us need a dump - var dump2: UnsafeMutablePointer? = nil + var dump2: UnsafeMutablePointer? = nil var dump2Len: Int = 0 config_dump(conf, &dump2, &dump2Len) dump2?.deallocate() @@ -179,20 +228,20 @@ class ConfigUserProfileSpec: QuickSpec { // Start with an empty config, as above: let error2: UnsafeMutablePointer? = nil var conf2: UnsafeMutablePointer? = nil - expect(user_profile_init(&conf2, nil, 0, error2)).to(equal(0)) + expect(user_profile_init(&conf2, &edSK, nil, 0, error2)).to(equal(0)) expect(config_needs_dump(conf2)).to(beFalse()) error2?.deallocate() // Now imagine we just pulled down the `exp_push1` string from the swarm; we merge it into // conf2: - var mergeData: [UnsafePointer?] = [expPush1].unsafeCopy() - var mergeSize: [Int] = [expPush1.count] - config_merge(conf2, &mergeData, &mergeSize, 1) + var mergeData: [UnsafePointer?] = [expPush1Encrypted].unsafeCopy() + var mergeSize: [Int] = [expPush1Encrypted.count] + expect(config_merge(conf2, &mergeData, &mergeSize, 1)).to(equal(1)) mergeData.forEach { $0?.deallocate() } // Our state has changed, so we need to dump: expect(config_needs_dump(conf2)).to(beTrue()) - var dump3: UnsafeMutablePointer? = nil + var dump3: UnsafeMutablePointer? = nil var dump3Len: Int = 0 config_dump(conf2, &dump3, &dump3Len) // (store in db) @@ -213,9 +262,7 @@ class ConfigUserProfileSpec: QuickSpec { let profile2Url: [CChar] = "http://new.example.com/pic" .bytes .map { CChar(bitPattern: $0) } - let profile2Key: [CChar] = "qwert\0yuio" - .bytes - .map { CChar(bitPattern: $0) } + let profile2Key: [UInt8] = "qwert\0yuio".bytes let p2: user_profile_pic = profile2Url.withUnsafeBufferPointer { profile2UrlPtr in profile2Key.withUnsafeBufferPointer { profile2KeyPtr in user_profile_pic( @@ -230,20 +277,20 @@ class ConfigUserProfileSpec: QuickSpec { // Both have changes, so push need a push expect(config_needs_push(conf)).to(beTrue()) expect(config_needs_push(conf2)).to(beTrue()) - var toPush3: UnsafeMutablePointer? = nil + var toPush3: UnsafeMutablePointer? = nil var toPush3Len: Int = 0 let seqno3: Int64 = config_push(conf, &toPush3, &toPush3Len) expect(seqno3).to(equal(2)) // incremented, since we made a field change - var toPush4: UnsafeMutablePointer? = nil + var toPush4: UnsafeMutablePointer? = nil var toPush4Len: Int = 0 let seqno4: Int64 = config_push(conf2, &toPush4, &toPush4Len) expect(seqno4).to(equal(2)) // incremented, since we made a field change - var dump4: UnsafeMutablePointer? = nil + var dump4: UnsafeMutablePointer? = nil var dump4Len: Int = 0 config_dump(conf, &dump4, &dump4Len); - var dump5: UnsafeMutablePointer? = nil + var dump5: UnsafeMutablePointer? = nil var dump5Len: Int = 0 config_dump(conf2, &dump5, &dump5Len); // (store in db) @@ -260,11 +307,11 @@ class ConfigUserProfileSpec: QuickSpec { // Feed the new config into each other. (This array could hold multiple configs if we pulled // down more than one). - var mergeData2: [UnsafePointer?] = [UnsafePointer(toPush3)] + var mergeData2: [UnsafePointer?] = [UnsafePointer(toPush3)] var mergeSize2: [Int] = [toPush3Len] config_merge(conf2, &mergeData2, &mergeSize2, 1) toPush3?.deallocate() - var mergeData3: [UnsafePointer?] = [UnsafePointer(toPush4)] + var mergeData3: [UnsafePointer?] = [UnsafePointer(toPush4)] var mergeSize3: [Int] = [toPush4Len] config_merge(conf, &mergeData3, &mergeSize3, 1) toPush4?.deallocate() @@ -301,10 +348,10 @@ class ConfigUserProfileSpec: QuickSpec { config_confirm_pushed(conf, seqno5) config_confirm_pushed(conf2, seqno6) - var dump6: UnsafeMutablePointer? = nil + var dump6: UnsafeMutablePointer? = nil var dump6Len: Int = 0 config_dump(conf, &dump6, &dump6Len); - var dump7: UnsafeMutablePointer? = nil + var dump7: UnsafeMutablePointer? = nil var dump7Len: Int = 0 config_dump(conf2, &dump7, &dump7Len); // (store in db) diff --git a/SessionSnodeKit/Models/SendMessageRequest.swift b/SessionSnodeKit/Models/SendMessageRequest.swift index 541768a64..94c5ab0a9 100644 --- a/SessionSnodeKit/Models/SendMessageRequest.swift +++ b/SessionSnodeKit/Models/SendMessageRequest.swift @@ -6,7 +6,7 @@ extension SnodeAPI { public class SendMessageRequest: SnodeAuthenticatedRequestBody { enum CodingKeys: String, CodingKey { case namespace - case signatureTimestamp = "sig_timestamp" + case signatureTimestamp = "timestamp"//"sig_timestamp" // TODO: Add this back once the snodes are fixed } let message: SnodeMessage diff --git a/SessionUtilitiesKit/General/Collection+Utilities.swift b/SessionUtilitiesKit/General/Collection+Utilities.swift index 234de4c1a..f757a9a8d 100644 --- a/SessionUtilitiesKit/General/Collection+Utilities.swift +++ b/SessionUtilitiesKit/General/Collection+Utilities.swift @@ -31,3 +31,16 @@ public extension Collection where Element == [CChar] { } } } + +public extension Collection where Element == [UInt8] { + /// This creates an array of UnsafePointer types to access data of the C strings in memory. This array provides no automated + /// memory management of it's children so after use you are responsible for handling the life cycle of the child elements and + /// need to call `deallocate()` on each child. + func unsafeCopy() -> [UnsafePointer?] { + return self.map { value in + let copy = UnsafeMutableBufferPointer.allocate(capacity: value.count) + _ = copy.initialize(from: value) + return UnsafePointer(copy.baseAddress) + } + } +} diff --git a/SignalUtilitiesKit/Utilities/AppSetup.swift b/SignalUtilitiesKit/Utilities/AppSetup.swift index 9e70fdbfa..f3dcac986 100644 --- a/SignalUtilitiesKit/Utilities/AppSetup.swift +++ b/SignalUtilitiesKit/Utilities/AppSetup.swift @@ -77,7 +77,9 @@ public enum AppSetup { // After the migrations have run but before the migration completion we load the // SessionUtil state and update the 'needsConfigSync' flag based on whether the // configs also need to be sync'ed - SessionUtil.loadState() + SessionUtil.loadState( + ed25519SecretKey: Identity.fetchUserEd25519KeyPair()?.secretKey + ) DispatchQueue.main.async { migrationsCompletion(result, (needsConfigSync || SessionUtil.needsSync)) From c9fdee9f24c865227c599bc33e53fa675fceb732 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Mon, 28 Nov 2022 08:32:32 +1100 Subject: [PATCH 009/135] Work on the PromiseKit refactor # Conflicts: # Session.xcodeproj/project.pbxproj # Session/Conversations/ConversationVC+Interaction.swift # Session/Home/Message Requests/MessageRequestsViewModel.swift # Session/Notifications/AppNotifications.swift # Session/Notifications/PushRegistrationManager.swift # Session/Notifications/SyncPushTokensJob.swift # Session/Notifications/UserNotificationsAdaptee.swift # Session/Settings/BlockedContactsViewModel.swift # Session/Settings/NukeDataModal.swift # Session/Settings/SettingsViewModel.swift # Session/Utilities/BackgroundPoller.swift # SessionMessagingKit/Database/Models/ClosedGroup.swift # SessionMessagingKit/File Server/FileServerAPI.swift # SessionMessagingKit/Open Groups/OpenGroupAPI.swift # SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift # SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+UnsendRequests.swift # SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift # SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift # SessionMessagingKit/Sending & Receiving/MessageSender.swift # SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift # SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift # SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift # SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift # SessionMessagingKit/Utilities/ProfileManager.swift # SessionSnodeKit/Networking/SnodeAPI.swift # SessionSnodeKit/OnionRequestAPI.swift # SessionUtilitiesKit/Networking/HTTP.swift --- Podfile.lock | 16 +- Session.xcodeproj/project.pbxproj | 1 + Session/Closed Groups/NewClosedGroupVC.swift | 4 +- .../Context Menu/ContextMenuWindow.swift | 3 + .../ConversationVC+Interaction.swift | 442 +++--- .../Conversations/ConversationViewModel.swift | 2 +- .../Settings/ThreadSettingsViewModel.swift | 4 +- Session/Home/HomeVC.swift | 2 +- Session/Home/HomeViewModel.swift | 4 +- .../MessageRequestsViewController.swift | 102 +- .../MessageRequestsViewModel.swift | 92 ++ Session/Home/New Conversation/NewDMVC.swift | 70 +- Session/Meta/AppDelegate.swift | 27 +- Session/Notifications/AppNotifications.swift | 146 +- .../PushRegistrationManager.swift | 235 ++-- Session/Notifications/SyncPushTokensJob.swift | 112 +- .../UserNotificationsAdaptee.swift | 120 +- Session/Open Groups/JoinOpenGroupVC.swift | 58 +- .../Open Groups/OpenGroupSuggestionGrid.swift | 74 +- .../Settings/BlockedContactsViewModel.swift | 84 +- Session/Settings/NukeDataModal.swift | 105 +- Session/Settings/SettingsViewModel.swift | 45 +- Session/Utilities/BackgroundPoller.swift | 210 ++- Session/Utilities/CGRect+Utilities.swift | 4 +- .../Database/Models/Attachment.swift | 90 +- .../Database/Models/ClosedGroup.swift | 73 + .../File Server/FileServerAPI.swift | 56 +- .../Jobs/Types/AttachmentDownloadJob.swift | 243 ++-- .../Jobs/Types/AttachmentUploadJob.swift | 2 + .../Jobs/Types/MessageSendJob.swift | 92 +- .../Jobs/Types/NotifyPushServerJob.swift | 18 +- .../RetrieveDefaultOpenGroupRoomsJob.swift | 13 +- .../Jobs/Types/SendReadReceiptsJob.swift | 79 +- .../Jobs/Types/UpdateProfilePictureJob.swift | 2 +- .../Messages/Message+Destination.swift | 2 +- .../Open Groups/Models/SOGSBatchRequest.swift | 24 +- .../Open Groups/OpenGroupAPI.swift | 232 ++-- .../Open Groups/OpenGroupManager.swift | 333 +++-- .../MessageReceiver+ClosedGroups.swift | 1 + ...essageReceiver+ConfigurationMessages.swift | 2 +- .../MessageReceiver+MessageRequests.swift | 3 +- .../MessageReceiver+UnsendRequests.swift | 7 +- .../MessageSender+ClosedGroups.swift | 549 ++++---- .../MessageSender+Convenience.swift | 282 +++- .../Sending & Receiving/MessageSender.swift | 957 +++++++++---- .../Notifications/PushNotificationAPI.swift | 313 +++-- .../Pollers/ClosedGroupPoller.swift | 291 +--- .../Pollers/CurrentUserPoller.swift | 26 +- .../Pollers/OpenGroupPoller.swift | 227 +-- .../Sending & Receiving/Pollers/Poller.swift | 348 +++-- .../Utilities/ProfileManager.swift | 88 +- .../NotificationServiceExtension.swift | 31 +- SessionShareExtension/ShareVC.swift | 2 +- SessionSnodeKit/GetSnodePoolJob.swift | 16 +- SessionSnodeKit/Models/SnodeAPIError.swift | 2 + SessionSnodeKit/Networking/SnodeAPI.swift | 1213 +++++++++++++++++ .../OnionRequestAPI+Encryption.swift | 101 +- SessionSnodeKit/OnionRequestAPI.swift | 770 ++++++++--- .../Combine/Publisher+Utilities.swift | 79 ++ SessionUtilitiesKit/Database/Storage.swift | 34 + .../Networking/BatchResponse.swift | 99 +- SessionUtilitiesKit/Networking/HTTP.swift | 149 +- 62 files changed, 5919 insertions(+), 2892 deletions(-) create mode 100644 SessionSnodeKit/Networking/SnodeAPI.swift diff --git a/Podfile.lock b/Podfile.lock index c47cf02c1..8cc73eae6 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -43,14 +43,14 @@ PODS: - NVActivityIndicatorView/Base (= 5.1.1) - NVActivityIndicatorView/Base (5.1.1) - OpenSSL-Universal (1.1.1300) - - PromiseKit (6.15.3): - - PromiseKit/CorePromise (= 6.15.3) - - PromiseKit/Foundation (= 6.15.3) - - PromiseKit/UIKit (= 6.15.3) - - PromiseKit/CorePromise (6.15.3) - - PromiseKit/Foundation (6.15.3): + - PromiseKit (6.18.1): + - PromiseKit/CorePromise (= 6.18.1) + - PromiseKit/Foundation (= 6.18.1) + - PromiseKit/UIKit (= 6.18.1) + - PromiseKit/CorePromise (6.18.1) + - PromiseKit/Foundation (6.18.1): - PromiseKit/CorePromise - - PromiseKit/UIKit (6.15.3): + - PromiseKit/UIKit (6.18.1): - PromiseKit/CorePromise - PureLayout (3.1.9) - Quick (5.0.1) @@ -227,7 +227,7 @@ SPEC CHECKSUMS: Nimble: 5316ef81a170ce87baf72dd961f22f89a602ff84 NVActivityIndicatorView: 1f6c5687f1171810aa27a3296814dc2d7dec3667 OpenSSL-Universal: e7311447fd2419f57420c79524b641537387eff2 - PromiseKit: 3b2b6995e51a954c46dbc550ce3da44fbfb563c5 + PromiseKit: 49d70c53d5d20e346beaea4b276b5dd2ab446bb4 PureLayout: 5fb5e5429519627d60d079ccb1eaa7265ce7cf88 Quick: 749aa754fd1e7d984f2000fe051e18a3a9809179 Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 5cb3838e0..ac13f45d1 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -1724,6 +1724,7 @@ FD245C612850664300B966DD /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; FD28A4F527EAD44C00FF65E7 /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = ""; }; FD2AAAEF28ED57B500A49611 /* SynchronousStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SynchronousStorage.swift; sourceTree = ""; }; + FD2AAAF328EE882B00A49611 /* HTTPError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPError.swift; sourceTree = ""; }; FD37E9C228A1C6F3003AE748 /* ThemeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = ""; }; FD37E9C528A1D4EC003AE748 /* Theme+ClassicDark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+ClassicDark.swift"; sourceTree = ""; }; FD37E9C728A1D73F003AE748 /* Theme+Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+Colors.swift"; sourceTree = ""; }; diff --git a/Session/Closed Groups/NewClosedGroupVC.swift b/Session/Closed Groups/NewClosedGroupVC.swift index 49ed7ae16..ee417e542 100644 --- a/Session/Closed Groups/NewClosedGroupVC.swift +++ b/Session/Closed Groups/NewClosedGroupVC.swift @@ -322,7 +322,9 @@ final class NewClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegate } .done(on: DispatchQueue.main) { thread in Storage.shared.writeAsync { db in - try? MessageSender.syncConfiguration(db, forceSyncNow: true).retainUntilComplete() + try? MessageSender + .syncConfiguration(db, forceSyncNow: true) + .sinkUntilComplete() } self?.presentingViewController?.dismiss(animated: true, completion: nil) diff --git a/Session/Conversations/Context Menu/ContextMenuWindow.swift b/Session/Conversations/Context Menu/ContextMenuWindow.swift index 7e309c199..7970811fc 100644 --- a/Session/Conversations/Context Menu/ContextMenuWindow.swift +++ b/Session/Conversations/Context Menu/ContextMenuWindow.swift @@ -1,3 +1,6 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import UIKit final class ContextMenuWindow : UIWindow { diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 48d9302eb..e9f9568b0 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -1,10 +1,11 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit +import Combine import CoreServices import Photos import PhotosUI -import PromiseKit +import Sodium import GRDB import SessionMessagingKit import SessionUtilitiesKit @@ -1160,7 +1161,7 @@ extension ConversationVC: guard cellViewModel.threadVariant == .openGroup else { return } Storage.shared - .read { db -> Promise in + .readPublisherFlatMap { db -> AnyPublisher<(OpenGroupAPI.ReactionRemoveAllResponse, OpenGroupAPI.PendingChange), Error> in guard let openGroup: OpenGroup = try? OpenGroup .fetchOne(db, id: cellViewModel.threadId), @@ -1170,10 +1171,11 @@ extension ConversationVC: .asRequest(of: Int64.self) .fetchOne(db) else { - return Promise(error: StorageError.objectNotFound) + return Fail(error: StorageError.objectNotFound) + .eraseToAnyPublisher() } - let pendingChange = OpenGroupManager + let pendingChange: OpenGroupAPI.PendingChange = OpenGroupManager .addPendingReaction( emoji: emoji, id: openGroupServerMessageId, @@ -1190,23 +1192,28 @@ extension ConversationVC: in: openGroup.roomToken, on: openGroup.server ) - .map { _, response in - OpenGroupManager - .updatePendingChange( - pendingChange, - seqNo: response.seqNo - ) - } + .map { _, response in (response, pendingChange) } + .eraseToAnyPublisher() } - .done { _ in - Storage.shared.writeAsync { db in - _ = try Reaction - .filter(Reaction.Columns.interactionId == cellViewModel.id) - .filter(Reaction.Columns.emoji == emoji) - .deleteAll(db) + .handleEvents( + receiveOutput: { response, pendingChange in + OpenGroupManager + .updatePendingChange( + pendingChange, + seqNo: response.seqNo + ) } - } - .retainUntilComplete() + ) + .sinkUntilComplete( + receiveCompletion: { _ in + Storage.shared.writeAsync { db in + _ = try Reaction + .filter(Reaction.Columns.interactionId == cellViewModel.id) + .filter(Reaction.Columns.emoji == emoji) + .deleteAll(db) + } + } + ) } func react(_ cellViewModel: MessageViewModel, with emoji: String, remove: Bool) { @@ -1242,12 +1249,14 @@ extension ConversationVC: .suffix(19)) .appending(sentTimestamp) } - + // TODO: Need to test emoji reacts for both open groups and one-to-one to make sure this isn't broken // Perform the sending logic - Storage.shared.writeAsync( - updates: { db in + Storage.shared + .writePublisherFlatMap { db -> AnyPublisher in guard let thread: SessionThread = try SessionThread.fetchOne(db, id: cellViewModel.threadId) else { - return + return Just(nil) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } // Update the thread to be visible @@ -1296,92 +1305,12 @@ extension ConversationVC: Emoji.addRecent(db, emoji: emoji) } - if let openGroup: OpenGroup = try? OpenGroup.fetchOne(db, id: cellViewModel.threadId), - OpenGroupManager.isOpenGroupSupport(.reactions, on: openGroup.server) - { - // Send reaction to open groups - guard - let openGroupServerMessageId: Int64 = try? Interaction - .select(.openGroupServerMessageId) - .filter(id: cellViewModel.id) - .asRequest(of: Int64.self) - .fetchOne(db) - else { return } - - if remove { - let pendingChange = OpenGroupManager - .addPendingReaction( - emoji: emoji, - id: openGroupServerMessageId, - in: openGroup.roomToken, - on: openGroup.server, - type: .remove - ) - OpenGroupAPI - .reactionDelete( - db, - emoji: emoji, - id: openGroupServerMessageId, - in: openGroup.roomToken, - on: openGroup.server - ) - .map { _, response in - OpenGroupManager - .updatePendingChange( - pendingChange, - seqNo: response.seqNo - ) - } - .catch { [weak self] _ in - OpenGroupManager.removePendingChange(pendingChange) - - self?.handleReactionSentFailure( - pendingReaction, - remove: remove - ) - - } - .retainUntilComplete() - } - else { - let pendingChange = OpenGroupManager - .addPendingReaction( - emoji: emoji, - id: openGroupServerMessageId, - in: openGroup.roomToken, - on: openGroup.server, - type: .add - ) - - OpenGroupAPI - .reactionAdd( - db, - emoji: emoji, - id: openGroupServerMessageId, - in: openGroup.roomToken, - on: openGroup.server - ) - .map { _, response in - OpenGroupManager - .updatePendingChange( - pendingChange, - seqNo: response.seqNo - ) - } - .catch { [weak self] _ in - OpenGroupManager.removePendingChange(pendingChange) - - self?.handleReactionSentFailure( - pendingReaction, - remove: remove - ) - } - .retainUntilComplete() - } - } + // If it's not an OpenGroup then send the message directly to the thread + guard + let openGroup: OpenGroup = try? OpenGroup.fetchOne(db, id: cellViewModel.threadId), + OpenGroupManager.isOpenGroupSupport(.reactions, on: openGroup.server) else { - // Send the actual message - try MessageSender.send( + let sendData: MessageSender.PreparedSendData = try MessageSender.preparedSendData( db, message: VisibleMessage( sentTimestamp: UInt64(sentTimestamp), @@ -1399,12 +1328,100 @@ extension ConversationVC: kind: (remove ? .remove : .react) ) ), - interactionId: cellViewModel.id, - in: thread + to: try Message.Destination.from(db, thread: thread), + interactionId: cellViewModel.id ) + + return Just(sendData) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } + + // Otherwise we need to make an API call to the OpenGroup + // Send reaction to open groups + guard + let openGroupServerMessageId: Int64 = try? Interaction + .select(.openGroupServerMessageId) + .filter(id: cellViewModel.id) + .asRequest(of: Int64.self) + .fetchOne(db) + else { + return Fail(error: MessageSenderError.invalidMessage) + .eraseToAnyPublisher() + } + + let pendingChange: OpenGroupAPI.PendingChange = OpenGroupManager + .addPendingReaction( + emoji: emoji, + id: openGroupServerMessageId, + in: openGroup.roomToken, + on: openGroup.server, + type: (remove ? .remove : .add) + ) + + let request: AnyPublisher = { + switch remove { + case true: + return OpenGroupAPI + .reactionDelete( + db, + emoji: emoji, + id: openGroupServerMessageId, + in: openGroup.roomToken, + on: openGroup.server + ) + .map { _, response in response.seqNo } + .eraseToAnyPublisher() + + case false: + return OpenGroupAPI + .reactionAdd( + db, + emoji: emoji, + id: openGroupServerMessageId, + in: openGroup.roomToken, + on: openGroup.server + ) + .map { _, response in response.seqNo } + .eraseToAnyPublisher() + } + }() + + return request + .handleEvents( + receiveOutput: { seqNo in + OpenGroupManager + .updatePendingChange( + pendingChange, + seqNo: seqNo + ) + }, + receiveCompletion: { [weak self] result in + switch result { + case .finished: break + case .failure: + OpenGroupManager.removePendingChange(pendingChange) + + self?.handleReactionSentFailure( + pendingReaction, + remove: remove + ) + } + } + ) + .map { _ in nil } + .eraseToAnyPublisher() } - ) + .flatMap { maybeSendData -> AnyPublisher in + guard let sendData: MessageSender.PreparedSendData = maybeSendData else { + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + + return MessageSender.sendImmediate(data: sendData) + } + .sinkUntilComplete() } func handleReactionSentFailure(_ pendingReaction: Reaction?, remove: Bool) { @@ -1520,7 +1537,7 @@ extension ConversationVC: } Storage.shared - .writeAsync { db in + .writePublisherFlatMap { db in OpenGroupManager.shared.add( db, roomToken: room, @@ -1529,24 +1546,31 @@ extension ConversationVC: isConfigMessage: false ) } - .done(on: DispatchQueue.main) { _ in - Storage.shared.writeAsync { db in - try MessageSender.syncConfiguration(db, forceSyncNow: true).retainUntilComplete() // FIXME: It's probably cleaner to do this inside addOpenGroup(...) + .receive(on: DispatchQueue.main) + .sinkUntilComplete( + receiveCompletion: { result in + switch result { + case .finished: + Storage.shared.writeAsync { db in + try MessageSender + .syncConfiguration(db, forceSyncNow: true) + .sinkUntilComplete() // FIXME: It's probably cleaner to do this inside addOpenGroup(...) + } + + case .failure(let error): + let errorModal: ConfirmationModal = ConfirmationModal( + info: ConfirmationModal.Info( + title: "COMMUNITY_ERROR_GENERIC".localized(), + explanation: error.localizedDescription, + cancelTitle: "BUTTON_OK".localized(), + cancelStyle: .alert_text + ) + ) + + presentingViewController.present(errorModal, animated: true, completion: nil) + } } - } - .catch(on: DispatchQueue.main) { error in - let errorModal: ConfirmationModal = ConfirmationModal( - info: ConfirmationModal.Info( - title: "COMMUNITY_ERROR_GENERIC".localized(), - explanation: error.localizedDescription, - cancelTitle: "BUTTON_OK".localized(), - cancelStyle: .alert_text - ) - ) - - presentingViewController.present(errorModal, animated: true, completion: nil) - } - .retainUntilComplete() + ) } ) ) @@ -1640,34 +1664,38 @@ extension ConversationVC: let userPublicKey: String = getUserHexEncodedPublicKey() // Remote deletion logic - func deleteRemotely(from viewController: UIViewController?, request: Promise, onComplete: (() -> ())?) { + func deleteRemotely(from viewController: UIViewController?, request: AnyPublisher, onComplete: (() -> ())?) { + // TODO: Test that this works // Show a loading indicator - let (promise, seal) = Promise.pending() - - ModalActivityIndicatorViewController.present(fromViewController: viewController, canCancel: false) { _ in - seal.fulfill(()) + Future { resolver in + ModalActivityIndicatorViewController.present(fromViewController: viewController, canCancel: false) { _ in + // TODO: Remove the 'Swift.' + resolver(Swift.Result.success(())) + } } - - promise - .then { _ -> Promise in request } - .done { _ in - // Delete the interaction (and associated data) from the database - Storage.shared.writeAsync { db in - _ = try Interaction - .filter(id: cellViewModel.id) - .deleteAll(db) + .flatMap { _ in request } + .receive(on: DispatchQueue.main) + .sinkUntilComplete( + receiveCompletion: { [weak self] result in + switch result { + case .failure: break + case .finished: + // Delete the interaction (and associated data) from the database + Storage.shared.writeAsync { db in + _ = try Interaction + .filter(id: cellViewModel.id) + .deleteAll(db) + } } - } - .ensure { - DispatchQueue.main.async { [weak self] in - if self?.presentedViewController is ModalActivityIndicatorViewController { - self?.dismiss(animated: true, completion: nil) // Dismiss the loader - } - - onComplete?() + + // Regardless of success we should dismiss and callback + if self?.presentedViewController is ModalActivityIndicatorViewController { + self?.dismiss(animated: true, completion: nil) // Dismiss the loader } + + onComplete?() } - .retainUntilComplete() + ) } // How we delete the message differs depending on the type of thread @@ -1752,7 +1780,7 @@ extension ConversationVC: // Delete the message from the open group deleteRemotely( from: self, - request: Storage.shared.read { db in + request: Storage.shared.readPublisherFlatMap { db in OpenGroupAPI.messageDelete( db, id: openGroupServerMessageId, @@ -1760,6 +1788,7 @@ extension ConversationVC: on: openGroup.server ) .map { _ in () } + .eraseToAnyPublisher() } ) { [weak self] in self?.showInputAccessoryView() @@ -1838,6 +1867,7 @@ extension ConversationVC: serverHashes: [serverHash] ) .map { _ in () } + .eraseToAnyPublisher() ) { [weak self] in Storage.shared.writeAsync { db in guard let thread: SessionThread = try SessionThread.fetchOne(db, id: threadId) else { @@ -1939,9 +1969,10 @@ extension ConversationVC: cancelStyle: .alert_text, onConfirm: { [weak self] _ in Storage.shared - .read { db -> Promise in + .readPublisherFlatMap { db -> AnyPublisher in guard let openGroup: OpenGroup = try OpenGroup.fetchOne(db, id: threadId) else { - return Promise(error: StorageError.objectNotFound) + return Fail(error: StorageError.objectNotFound) + .eraseToAnyPublisher() } return OpenGroupAPI @@ -1952,20 +1983,27 @@ extension ConversationVC: on: openGroup.server ) .map { _ in () } + .eraseToAnyPublisher() } - .catch(on: DispatchQueue.main) { _ in - let modal: ConfirmationModal = ConfirmationModal( - targetView: self?.view, - info: ConfirmationModal.Info( - title: CommonStrings.errorAlertTitle, - explanation: "context_menu_ban_user_error_alert_message".localized(), - cancelTitle: "BUTTON_OK".localized(), - cancelStyle: .alert_text - ) - ) - self?.present(modal, animated: true) - } - .retainUntilComplete() + .receive(on: DispatchQueue.main) + .sinkUntilComplete( + receiveCompletion: { result in + switch result { + case .finished: break + case .failure: + let modal: ConfirmationModal = ConfirmationModal( + targetView: self?.view, + info: ConfirmationModal.Info( + title: CommonStrings.errorAlertTitle, + explanation: "context_menu_ban_user_error_alert_message".localized(), + cancelTitle: "BUTTON_OK".localized(), + cancelStyle: .alert_text + ) + ) + self?.present(modal, animated: true) + } + } + ) self?.becomeFirstResponder() }, @@ -1988,9 +2026,10 @@ extension ConversationVC: cancelStyle: .alert_text, onConfirm: { [weak self] _ in Storage.shared - .read { db -> Promise in + .readPublisherFlatMap { db -> AnyPublisher in guard let openGroup: OpenGroup = try OpenGroup.fetchOne(db, id: threadId) else { - return Promise(error: StorageError.objectNotFound) + return Fail(error: StorageError.objectNotFound) + .eraseToAnyPublisher() } return OpenGroupAPI @@ -2001,20 +2040,27 @@ extension ConversationVC: on: openGroup.server ) .map { _ in () } + .eraseToAnyPublisher() } - .catch(on: DispatchQueue.main) { _ in - let modal: ConfirmationModal = ConfirmationModal( - targetView: self?.view, - info: ConfirmationModal.Info( - title: CommonStrings.errorAlertTitle, - explanation: "context_menu_ban_user_error_alert_message".localized(), - cancelTitle: "BUTTON_OK".localized(), - cancelStyle: .alert_text - ) - ) - self?.present(modal, animated: true) - } - .retainUntilComplete() + .receive(on: DispatchQueue.main) + .sinkUntilComplete( + receiveCompletion: { result in + switch result { + case .finished: break + case .failure: + let modal: ConfirmationModal = ConfirmationModal( + targetView: self?.view, + info: ConfirmationModal.Info( + title: CommonStrings.errorAlertTitle, + explanation: "context_menu_ban_user_error_alert_message".localized(), + cancelTitle: "BUTTON_OK".localized(), + cancelStyle: .alert_text + ) + ) + self?.present(modal, animated: true) + } + } + ) self?.becomeFirstResponder() }, @@ -2215,6 +2261,21 @@ extension ConversationVC { timestampMs: Int64 ) { guard threadVariant == .contact else { return } + + let updateNavigationBackStack: () -> Void = { + // Remove the 'MessageRequestsViewController' from the nav hierarchy if present + DispatchQueue.main.async { [weak self] in + if + let viewControllers: [UIViewController] = self?.navigationController?.viewControllers, + let messageRequestsIndex = viewControllers.firstIndex(where: { $0 is MessageRequestsViewController }), + messageRequestsIndex > 0 + { + var newViewControllers = viewControllers + newViewControllers.remove(at: messageRequestsIndex) + self?.navigationController?.viewControllers = newViewControllers + } + } + } // If the contact doesn't exist then we should create it so we can store the 'isApproved' state // (it'll be updated with correct profile info if they accept the message request so this @@ -2262,20 +2323,7 @@ extension ConversationVC { .syncConfiguration(db, forceSyncNow: true) .retainUntilComplete() }, - completion: { _, _ in - // Remove the 'MessageRequestsViewController' from the nav hierarchy if present - DispatchQueue.main.async { [weak self] in - if - let viewControllers: [UIViewController] = self?.navigationController?.viewControllers, - let messageRequestsIndex = viewControllers.firstIndex(where: { $0 is MessageRequestsViewController }), - messageRequestsIndex > 0 - { - var newViewControllers = viewControllers - newViewControllers.remove(at: messageRequestsIndex) - self?.navigationController?.viewControllers = newViewControllers - } - } - } + completion: { _, _ in updateNavigationBackStack() } ) } diff --git a/Session/Conversations/ConversationViewModel.swift b/Session/Conversations/ConversationViewModel.swift index cdb434a40..43134d1b3 100644 --- a/Session/Conversations/ConversationViewModel.swift +++ b/Session/Conversations/ConversationViewModel.swift @@ -485,7 +485,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { try MessageSender .syncConfiguration(db, forceSyncNow: true) - .retainUntilComplete() + .sinkUntilComplete() } } diff --git a/Session/Conversations/Settings/ThreadSettingsViewModel.swift b/Session/Conversations/Settings/ThreadSettingsViewModel.swift index bc410492b..760e639e9 100644 --- a/Session/Conversations/Settings/ThreadSettingsViewModel.swift +++ b/Session/Conversations/Settings/ThreadSettingsViewModel.swift @@ -658,7 +658,9 @@ class ThreadSettingsViewModel: SessionTableViewModel Void)? = nil + ) { + let modal: ConfirmationModal = ConfirmationModal( + info: ConfirmationModal.Info( + title: "MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON".localized(), + confirmTitle: "TXT_DELETE_TITLE".localized(), + confirmStyle: .danger, + cancelStyle: .alert_text + ) { _ in + Storage.shared.write { db in + switch threadVariant { + case .contact, .openGroup: + _ = try SessionThread + .filter(id: threadId) + .deleteAll(db) + + case .closedGroup: + try ClosedGroup.removeKeysAndUnsubscribe( + db, + threadId: threadId, + removeGroupData: true + ) + + // Force a config sync + try MessageSender + .syncConfiguration(db, forceSyncNow: true) + .sinkUntilComplete() + } + } + + completion?() + } + ) + + viewController?.present(modal, animated: true, completion: nil) + } + + static func blockMessageRequest( + threadId: String, + threadVariant: SessionThread.Variant, + viewController: UIViewController?, + completion: (() -> Void)? = nil + ) { + guard threadVariant == .contact else { return } + + let modal: ConfirmationModal = ConfirmationModal( + info: ConfirmationModal.Info( + title: "MESSAGE_REQUESTS_BLOCK_CONFIRMATION_ACTON".localized(), + confirmTitle: "BLOCK_LIST_BLOCK_BUTTON".localized(), + confirmStyle: .danger, + cancelStyle: .alert_text + ) { _ in + Storage.shared.writeAsync( + updates: { db in + // Update the contact + _ = try Contact + .fetchOrCreate(db, id: threadId) + .with( + isApproved: false, + isBlocked: true, + + // Note: We set this to true so the current user will be able to send a + // message to the person who originally sent them the message request in + // the future if they unblock them + didApproveMe: true + ) + .saved(db) + + // Remove the thread + _ = try SessionThread + .filter(id: threadId) + .deleteAll(db) + + // Force a config sync + try MessageSender + .syncConfiguration(db, forceSyncNow: true) + .sinkUntilComplete() + }, + completion: { _, _ in completion?() } + ) + } + ) + + viewController?.present(modal, animated: true, completion: nil) + } } diff --git a/Session/Home/New Conversation/NewDMVC.swift b/Session/Home/New Conversation/NewDMVC.swift index ca5673833..88381416e 100644 --- a/Session/Home/New Conversation/NewDMVC.swift +++ b/Session/Home/New Conversation/NewDMVC.swift @@ -183,45 +183,45 @@ final class NewDMVC: BaseVC, UIPageViewControllerDataSource, UIPageViewControlle ModalActivityIndicatorViewController .present(fromViewController: navigationController!, canCancel: false) { [weak self] modalActivityIndicator in SnodeAPI - .getSessionID(for: onsNameOrPublicKey) - .done { sessionID in - modalActivityIndicator.dismiss { - self?.startNewDM(with: sessionID) - } + .getSessionID(for: onsNameOrPublicKey) + .done { sessionID in + modalActivityIndicator.dismiss { + self?.startNewDM(with: sessionID) } - .catch { error in - modalActivityIndicator.dismiss { - var messageOrNil: String? - if let error = error as? SnodeAPIError { - switch error { - case .decryptionFailed, .hashingFailed, .validationFailed: - messageOrNil = error.errorDescription - default: break - } + } + .catch { error in + modalActivityIndicator.dismiss { + var messageOrNil: String? + if let error = error as? SnodeAPIError { + switch error { + case .decryptionFailed, .hashingFailed, .validationFailed: + messageOrNil = error.errorDescription + default: break } - let message: String = { - if let messageOrNil: String = messageOrNil { - return messageOrNil - } - - return (maybeSessionId?.prefix == .blinded ? - "DM_ERROR_DIRECT_BLINDED_ID".localized() : - "DM_ERROR_INVALID".localized() - ) - }() - - let modal: ConfirmationModal = ConfirmationModal( - targetView: self?.view, - info: ConfirmationModal.Info( - title: "ALERT_ERROR_TITLE".localized(), - explanation: message, - cancelTitle: "BUTTON_OK".localized(), - cancelStyle: .alert_text - ) - ) - self?.present(modal, animated: true) } + let message: String = { + if let messageOrNil: String = messageOrNil { + return messageOrNil + } + + return (maybeSessionId?.prefix == .blinded ? + "DM_ERROR_DIRECT_BLINDED_ID".localized() : + "DM_ERROR_INVALID".localized() + ) + }() + + let modal: ConfirmationModal = ConfirmationModal( + targetView: self?.view, + info: ConfirmationModal.Info( + title: "ALERT_ERROR_TITLE".localized(), + explanation: message, + cancelTitle: "BUTTON_OK".localized(), + cancelStyle: .alert_text + ) + ) + self?.present(modal, animated: true) } + } } } diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 71bf2e252..7dfa06113 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -298,7 +298,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD appVersion.lastAppVersion != appVersion.currentAppVersion ) { - try MessageSender.syncConfiguration(db, forceSyncNow: true).retainUntilComplete() + try MessageSender + .syncConfiguration(db, forceSyncNow: true) + .sinkUntilComplete() } } } @@ -658,15 +660,20 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD guard Date().timeIntervalSince(lastSync) > (7 * 24 * 60 * 60) else { return } // Sync every 2 days Storage.shared - .writeAsync { db in try MessageSender.syncConfiguration(db, forceSyncNow: false) } - .done { - // Only update the 'lastConfigurationSync' timestamp if we have done the - // first sync (Don't want a new device config sync to override config - // syncs from other devices) - if UserDefaults.standard[.hasSyncedInitialConfiguration] { - UserDefaults.standard[.lastConfigurationSync] = Date() + .writePublisherFlatMap { db in try MessageSender.syncConfiguration(db, forceSyncNow: false) } + .sinkUntilComplete( + receiveCompletion: { result in + switch result { + case .failure: break + case .finished: + // Only update the 'lastConfigurationSync' timestamp if we have done the + // first sync (Don't want a new device config sync to override config + // syncs from other devices) + if UserDefaults.standard[.hasSyncedInitialConfiguration] { + UserDefaults.standard[.lastConfigurationSync] = Date() + } + } } - } - .retainUntilComplete() + ) } } diff --git a/Session/Notifications/AppNotifications.swift b/Session/Notifications/AppNotifications.swift index b957baeae..2b9da963b 100644 --- a/Session/Notifications/AppNotifications.swift +++ b/Session/Notifications/AppNotifications.swift @@ -1,8 +1,8 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import Combine import GRDB -import PromiseKit import SessionMessagingKit import SignalUtilitiesKit import SignalCoreKit @@ -88,7 +88,7 @@ let kAudioNotificationsThrottleInterval: TimeInterval = 5 protocol NotificationPresenterAdaptee: AnyObject { - func registerNotificationSettings() -> Promise + func registerNotificationSettings() -> Future func notify( category: AppNotificationCategory, @@ -148,8 +148,9 @@ public class NotificationPresenter: NSObject, NotificationsProtocol { // MARK: - Presenting Notifications - func registerNotificationSettings() -> Promise { + func registerNotificationSettings() -> AnyPublisher { return adaptee.registerNotificationSettings() + .eraseToAnyPublisher() } public func notifyUser(_ db: Database, for interaction: Interaction, in thread: SessionThread) { @@ -504,73 +505,84 @@ class NotificationActionHandler { // MARK: - - func markAsRead(userInfo: [AnyHashable: Any]) throws -> Promise { + func markAsRead(userInfo: [AnyHashable: Any]) -> AnyPublisher { guard let threadId: String = userInfo[AppNotificationUserInfoKey.threadId] as? String else { - throw NotificationError.failDebug("threadId was unexpectedly nil") + return Fail(error: NotificationError.failDebug("threadId was unexpectedly nil")) + .eraseToAnyPublisher() } guard let thread: SessionThread = Storage.shared.read({ db in try SessionThread.fetchOne(db, id: threadId) }) else { - throw NotificationError.failDebug("unable to find thread with id: \(threadId)") + return Fail(error: NotificationError.failDebug("unable to find thread with id: \(threadId)")) + .eraseToAnyPublisher() } return markAsRead(thread: thread) } - func reply(userInfo: [AnyHashable: Any], replyText: String) throws -> Promise { + func reply(userInfo: [AnyHashable: Any], replyText: String) -> AnyPublisher { guard let threadId = userInfo[AppNotificationUserInfoKey.threadId] as? String else { - throw NotificationError.failDebug("threadId was unexpectedly nil") + return Fail(error: NotificationError.failDebug("threadId was unexpectedly nil")) + .eraseToAnyPublisher() } - + guard let thread: SessionThread = Storage.shared.read({ db in try SessionThread.fetchOne(db, id: threadId) }) else { - throw NotificationError.failDebug("unable to find thread with id: \(threadId)") + return Fail(error: NotificationError.failDebug("unable to find thread with id: \(threadId)")) + .eraseToAnyPublisher() } - let (promise, seal) = Promise.pending() - - Storage.shared.writeAsync { db in - let interaction: Interaction = try Interaction( - threadId: thread.id, - authorId: getUserHexEncodedPublicKey(db), - variant: .standardOutgoing, - body: replyText, - timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)), - hasMention: Interaction.isUserMentioned(db, threadId: threadId, body: replyText), - expiresInSeconds: try? DisappearingMessagesConfiguration - .select(.durationSeconds) - .filter(id: threadId) - .filter(DisappearingMessagesConfiguration.Columns.isEnabled == true) - .asRequest(of: TimeInterval.self) - .fetchOne(db) - ).inserted(db) - - try Interaction.markAsRead( - db, - interactionId: interaction.id, - threadId: thread.id, - includingOlder: true, - trySendReadReceipt: true - ) - - return try MessageSender.sendNonDurably( - db, - interaction: interaction, - in: thread - ) - } - .done { seal.fulfill(()) } - .catch { error in - Storage.shared.read { [weak self] db in - self?.notificationPresenter.notifyForFailedSend(db, in: thread) + return Storage.shared + .writePublisher { db in + let interaction: Interaction = try Interaction( + threadId: thread.id, + authorId: getUserHexEncodedPublicKey(db), + variant: .standardOutgoing, + body: replyText, + timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)), + hasMention: Interaction.isUserMentioned( + db, + threadId: threadId, + threadVariant: thread.variant, + body: replyText + ), + expiresInSeconds: try? DisappearingMessagesConfiguration + .select(.durationSeconds) + .filter(id: threadId) + .filter(DisappearingMessagesConfiguration.Columns.isEnabled == true) + .asRequest(of: TimeInterval.self) + .fetchOne(db) + ).inserted(db) + + try Interaction.markAsRead( + db, + interactionId: interaction.id, + threadId: thread.id, + includingOlder: true, + trySendReadReceipt: true + ) + // TODO: Will need to split the attachment upload from the message preparation logic + return try MessageSender.preparedSendData( + db, + interaction: interaction, + in: thread + ) } - - seal.reject(error) - } - .retainUntilComplete() - - return promise + .flatMap { MessageSender.performUploadsIfNeeded(preparedSendData: $0) } + .flatMap { MessageSender.sendImmediate(data: $0) } + .handleEvents( + receiveCompletion: { result in + switch result { + case .finished: break + case .failure: + Storage.shared.read { [weak self] db in + self?.notificationPresenter.notifyForFailedSend(db, in: thread) + } + } + } + ) + .eraseToAnyPublisher() } - func showThread(userInfo: [AnyHashable: Any]) throws -> Promise { + func showThread(userInfo: [AnyHashable: Any]) -> AnyPublisher { guard let threadId = userInfo[AppNotificationUserInfoKey.threadId] as? String else { return showHomeVC() } @@ -580,19 +592,19 @@ class NotificationActionHandler { // it animate in from the homescreen. let shouldAnimate: Bool = (UIApplication.shared.applicationState == .active) SessionApp.presentConversation(for: threadId, animated: shouldAnimate) - return Promise.value(()) + return Just(()) + .eraseToAnyPublisher() } - func showHomeVC() -> Promise { + func showHomeVC() -> AnyPublisher { SessionApp.showHomeView() - return Promise.value(()) + return Just(()) + .eraseToAnyPublisher() } - - private func markAsRead(thread: SessionThread) -> Promise { - let (promise, seal) = Promise.pending() - - Storage.shared.writeAsync( - updates: { db in + + private func markAsRead(thread: SessionThread) -> AnyPublisher { + return Storage.shared + .writePublisher { db in try Interaction.markAsRead( db, interactionId: try thread.interactions @@ -604,16 +616,8 @@ class NotificationActionHandler { includingOlder: true, trySendReadReceipt: true ) - }, - completion: { _, result in - switch result { - case .success: seal.fulfill(()) - case .failure(let error): seal.reject(error) - } } - ) - - return promise + .eraseToAnyPublisher() } } diff --git a/Session/Notifications/PushRegistrationManager.swift b/Session/Notifications/PushRegistrationManager.swift index 62ac77c31..7ef51b3a6 100644 --- a/Session/Notifications/PushRegistrationManager.swift +++ b/Session/Notifications/PushRegistrationManager.swift @@ -1,18 +1,16 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation -import PromiseKit +import Combine import PushKit -import SignalUtilitiesKit import GRDB -import SignalCoreKit +import SignalUtilitiesKit public enum PushRegistrationError: Error { case assertionError(description: String) case pushNotSupported(description: String) case timeout + case publisherNoLongerExists } /** @@ -41,64 +39,68 @@ public enum PushRegistrationError: Error { SwiftSingletons.register(self) } - private var vanillaTokenPromise: Promise? - private var vanillaTokenResolver: Resolver? + private var vanillaTokenPublisher: AnyPublisher? + private var vanillaTokenResolver: ((Result) -> ())? private var voipRegistry: PKPushRegistry? - private var voipTokenPromise: Promise? - private var voipTokenResolver: Resolver? + private var voipTokenPublisher: AnyPublisher? + private var voipTokenResolver: ((Result) -> ())? - // MARK: Public interface + // MARK: - Public interface - public func requestPushTokens() -> Promise<(pushToken: String, voipToken: String)> { + public func requestPushTokens() -> AnyPublisher<(pushToken: String, voipToken: String), Error> { Logger.info("") + + return registerUserNotificationSettings() + .setFailureType(to: Error.self) + .flatMap { _ -> AnyPublisher<(pushToken: String, voipToken: String), Error> in + #if targetEnvironment(simulator) + return Fail(error: PushRegistrationError.pushNotSupported(description: "Push not supported on simulators")) + .eraseToAnyPublisher() + #endif - return firstly { () -> Promise in - self.registerUserNotificationSettings() - }.then { (_) -> Promise<(pushToken: String, voipToken: String)> in - #if targetEnvironment(simulator) - throw PushRegistrationError.pushNotSupported(description: "Push not supported on simulators") - #endif - - return self.registerForVanillaPushToken().then { vanillaPushToken -> Promise<(pushToken: String, voipToken: String)> in - self.registerForVoipPushToken().map { voipPushToken in - (pushToken: vanillaPushToken, voipToken: voipPushToken ?? "") - } + return self.registerForVanillaPushToken() + .flatMap { vanillaPushToken -> AnyPublisher<(pushToken: String, voipToken: String), Error> in + self.registerForVoipPushToken() + .map { voipPushToken in (vanillaPushToken, (voipPushToken ?? "")) } + .eraseToAnyPublisher() + } + .eraseToAnyPublisher() } - } + .eraseToAnyPublisher() } // MARK: Vanilla push token // Vanilla push token is obtained from the system via AppDelegate - @objc public func didReceiveVanillaPushToken(_ tokenData: Data) { guard let vanillaTokenResolver = self.vanillaTokenResolver else { - owsFailDebug("promise completion in \(#function) unexpectedly nil") + owsFailDebug("publisher completion in \(#function) unexpectedly nil") return } - vanillaTokenResolver.fulfill(tokenData) + vanillaTokenResolver(Result.success(tokenData)) } // Vanilla push token is obtained from the system via AppDelegate @objc public func didFailToReceiveVanillaPushToken(error: Error) { guard let vanillaTokenResolver = self.vanillaTokenResolver else { - owsFailDebug("promise completion in \(#function) unexpectedly nil") + owsFailDebug("publisher completion in \(#function) unexpectedly nil") return } - vanillaTokenResolver.reject(error) + vanillaTokenResolver(Result.failure(error)) } // MARK: helpers // User notification settings must be registered *before* AppDelegate will // return any requested push tokens. - public func registerUserNotificationSettings() -> Promise { + public func registerUserNotificationSettings() -> AnyPublisher { AssertIsOnMainThread() return notificationPresenter.registerNotificationSettings() + .eraseToAnyPublisher() } /** @@ -126,52 +128,72 @@ public enum PushRegistrationError: Error { return true } - private func registerForVanillaPushToken() -> Promise { + private func registerForVanillaPushToken() -> AnyPublisher { AssertIsOnMainThread() - - guard self.vanillaTokenPromise == nil else { - let promise = vanillaTokenPromise! - assert(promise.isPending) - return promise.map { $0.hexEncodedString } + + // Use the existing publisher if it exists + if let vanillaTokenPublisher: AnyPublisher = self.vanillaTokenPublisher { + return vanillaTokenPublisher + .map { $0.toHexString() } + .eraseToAnyPublisher() } - - // No pending vanilla token yet; create a new promise - let (promise, resolver) = Promise.pending() - self.vanillaTokenPromise = promise - self.vanillaTokenResolver = resolver - + UIApplication.shared.registerForRemoteNotifications() - - let kTimeout: TimeInterval = 10 - let timeout: Promise = after(seconds: kTimeout).map { throw PushRegistrationError.timeout } - let promiseWithTimeout: Promise = race(promise, timeout) - - return promiseWithTimeout.recover { error -> Promise in - switch error { - case PushRegistrationError.timeout: - if self.isSusceptibleToFailedPushRegistration { - // If we've timed out on a device known to be susceptible to failures, quit trying - // so the user doesn't remain indefinitely hung for no good reason. - throw PushRegistrationError.pushNotSupported(description: "Device configuration disallows push notifications") - } else { - // Sometimes registration can just take a while. - // If we're not on a device known to be susceptible to push registration failure, - // just return the original promise. - return promise + + // No pending vanilla token yet; create a new publisher + let publisher: AnyPublisher = Future { self.vanillaTokenResolver = $0 } + .eraseToAnyPublisher() + self.vanillaTokenPublisher = publisher + + return publisher + .timeout( + .seconds(10), + scheduler: DispatchQueue.main, + customError: { PushRegistrationError.timeout } + ) + .catch { error -> AnyPublisher in + switch error { + case PushRegistrationError.timeout: + guard self.isSusceptibleToFailedPushRegistration else { + // Sometimes registration can just take a while. + // If we're not on a device known to be susceptible to push registration failure, + // just return the original publisher. + guard let originalPublisher: AnyPublisher = self.vanillaTokenPublisher else { + return Fail(error: PushRegistrationError.publisherNoLongerExists) + .eraseToAnyPublisher() + } + + return originalPublisher + } + + // If we've timed out on a device known to be susceptible to failures, quit trying + // so the user doesn't remain indefinitely hung for no good reason. + return Fail( + error: PushRegistrationError.pushNotSupported( + description: "Device configuration disallows push notifications" + ) + ).eraseToAnyPublisher() + + default: + return Fail(error: error) + .eraseToAnyPublisher() } - default: - throw error } - }.map { (pushTokenData: Data) -> String in - if self.isSusceptibleToFailedPushRegistration { - // Sentinal in case this bug is fixed - OWSLogger.debug("Device was unexpectedly able to complete push registration even though it was susceptible to failure.") + .map { tokenData -> String in + if self.isSusceptibleToFailedPushRegistration { + // Sentinal in case this bug is fixed + OWSLogger.debug("Device was unexpectedly able to complete push registration even though it was susceptible to failure.") + } + + return tokenData.toHexString() } - - return pushTokenData.hexEncodedString - }.ensure { - self.vanillaTokenPromise = nil - } + .handleEvents( + receiveCompletion: { _ in + self.vanillaTokenPublisher = nil + self.vanillaTokenResolver = nil + } + ) + .eraseToAnyPublisher() } public func createVoipRegistryIfNecessary() { @@ -179,61 +201,68 @@ public enum PushRegistrationError: Error { guard voipRegistry == nil else { return } let voipRegistry = PKPushRegistry(queue: nil) - self.voipRegistry = voipRegistry + self.voipRegistry = voipRegistry voipRegistry.desiredPushTypes = [.voIP] voipRegistry.delegate = self } - private func registerForVoipPushToken() -> Promise { + private func registerForVoipPushToken() -> AnyPublisher { AssertIsOnMainThread() - - guard self.voipTokenPromise == nil else { - let promise = self.voipTokenPromise! - return promise.map { $0?.hexEncodedString } + + // Use the existing publisher if it exists + if let voipTokenPublisher: AnyPublisher = self.voipTokenPublisher { + return voipTokenPublisher + .map { $0?.toHexString() } + .eraseToAnyPublisher() } - - // No pending voip token yet. Create a new promise - let (promise, resolver) = Promise.pending() - self.voipTokenPromise = promise - self.voipTokenResolver = resolver - + // We don't create the voip registry in init, because it immediately requests the voip token, // potentially before we're ready to handle it. createVoipRegistryIfNecessary() - - guard let voipRegistry = self.voipRegistry else { + + guard let voipRegistry: PKPushRegistry = self.voipRegistry else { owsFailDebug("failed to initialize voipRegistry") - resolver.reject(PushRegistrationError.assertionError(description: "failed to initialize voipRegistry")) - return promise.map { _ in - // coerce expected type of returned promise - we don't really care about the value, - // since this promise has been rejected. In practice this shouldn't happen - String() - } + return Fail( + error: PushRegistrationError.assertionError(description: "failed to initialize voipRegistry") + ).eraseToAnyPublisher() } - + // If we've already completed registering for a voip token, resolve it immediately, // rather than waiting for the delegate method to be called. - if let voipTokenData = voipRegistry.pushToken(for: .voIP) { + if let voipTokenData: Data = voipRegistry.pushToken(for: .voIP) { Logger.info("using pre-registered voIP token") - resolver.fulfill(voipTokenData) - } - - return promise.map { (voipTokenData: Data?) -> String? in - Logger.info("successfully registered for voip push notifications") - return voipTokenData?.hexEncodedString - }.ensure { - self.voipTokenPromise = nil + return Just(voipTokenData.toHexString()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } + + // No pending voip token yet. Create a new publisher + let publisher: AnyPublisher = Future { self.voipTokenResolver = $0 } + .eraseToAnyPublisher() + self.voipTokenPublisher = publisher + + return publisher + .map { voipTokenData -> String? in + Logger.info("successfully registered for voip push notifications") + return voipTokenData?.toHexString() + } + .handleEvents( + receiveCompletion: { _ in + self.voipTokenPublisher = nil + self.voipTokenResolver = nil + } + ) + .eraseToAnyPublisher() } - // MARK: PKPushRegistryDelegate + // MARK: - PKPushRegistryDelegate + public func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) { Logger.info("") owsAssertDebug(type == .voIP) owsAssertDebug(pushCredentials.type == .voIP) - guard let voipTokenResolver = voipTokenResolver else { return } - voipTokenResolver.fulfill(pushCredentials.token) + voipTokenResolver?(Result.success(pushCredentials.token)) } // NOTE: This function MUST report an incoming call. diff --git a/Session/Notifications/SyncPushTokensJob.swift b/Session/Notifications/SyncPushTokensJob.swift index bf6fbadf6..c2fe63429 100644 --- a/Session/Notifications/SyncPushTokensJob.swift +++ b/Session/Notifications/SyncPushTokensJob.swift @@ -1,8 +1,9 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import Combine import GRDB -import PromiseKit +import SignalCoreKit import SessionMessagingKit import SessionUtilitiesKit import SignalCoreKit @@ -54,7 +55,8 @@ public enum SyncPushTokensJob: JobExecutor { let currentAppVersion: String? = AppVersion.sharedInstance().currentAppVersion PushRegistrationManager.shared.requestPushTokens() - .then(on: queue) { (pushToken: String, voipToken: String) -> Promise in + .subscribe(on: queue) + .flatMap { (pushToken: String, voipToken: String) -> AnyPublisher in let lastPushToken: String? = Storage.shared[.lastRecordedPushToken] let lastVoipToken: String? = Storage.shared[.lastRecordedVoipToken] let shouldUploadTokens: Bool = ( @@ -65,30 +67,44 @@ public enum SyncPushTokensJob: JobExecutor { lastAppVersion != currentAppVersion ) - guard shouldUploadTokens else { return Promise.value(()) } + guard shouldUploadTokens else { + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } - let (promise, seal) = Promise.pending() - - SyncPushTokensJob.registerForPushNotifications( - pushToken: pushToken, - voipToken: voipToken, - isForcedUpdate: shouldUploadTokens, - success: { seal.fulfill(()) }, - failure: seal.reject - ) - - return promise - .done(on: queue) { _ in - Logger.warn("Recording push tokens locally. pushToken: \(redact(pushToken)), voipToken: \(redact(voipToken))") + return Future { resolver in + SyncPushTokensJob.registerForPushNotifications( + pushToken: pushToken, + voipToken: voipToken, + isForcedUpdate: shouldUploadTokens, + // TODO: Remove the 'Swift.' + success: { resolver(Swift.Result.success(())) }, + // TODO: Remove the 'Swift.' + failure: { resolver(Swift.Result.failure($0)) } + ) + } + .handleEvents( + receiveCompletion: { result in + switch result { + case .failure: break + case .finished: + Logger.warn("Recording push tokens locally. pushToken: \(redact(pushToken)), voipToken: \(redact(voipToken))") - Storage.shared.write { db in - db[.lastRecordedPushToken] = pushToken - db[.lastRecordedVoipToken] = voipToken + Storage.shared.write { db in + db[.lastRecordedPushToken] = pushToken + db[.lastRecordedVoipToken] = voipToken + } } } + ) + .eraseToAnyPublisher() } - .ensure(on: queue) { success(job, false) } // We want to complete this job regardless of success or failure - .retainUntilComplete() + .sinkUntilComplete( + // We want to complete this job regardless of success or failure + receiveCompletion: { _ in success(job, false) }, + receiveValue: { _ in } + ) } public static func run(uploadOnlyIfStale: Bool) { @@ -134,19 +150,26 @@ extension SyncPushTokensJob { remainingRetries: Int = 3 ) { let isUsingFullAPNs: Bool = UserDefaults.standard[.isUsingFullAPNs] - let pushTokenAsData = Data(hex: pushToken) - let promise: Promise = (isUsingFullAPNs ? - PushNotificationAPI.register( - with: pushTokenAsData, - publicKey: getUserHexEncodedPublicKey(), - isForcedUpdate: isForcedUpdate - ) : - PushNotificationAPI.unregister(pushTokenAsData) - ) - promise - .done { success() } - .catch { error in + Just(Data(hex: pushToken)) + .setFailureType(to: Error.self) + .flatMap { pushTokenAsData -> AnyPublisher in + guard isUsingFullAPNs else { + return PushNotificationAPI.unregister(pushTokenAsData) + .map { _ in true } + .eraseToAnyPublisher() + } + + return PushNotificationAPI + .register( + with: pushTokenAsData, + publicKey: getUserHexEncodedPublicKey(), + isForcedUpdate: isForcedUpdate + ) + .map { _ in true } + .eraseToAnyPublisher() + } + .catch { error -> AnyPublisher in guard remainingRetries == 0 else { SyncPushTokensJob.registerForPushNotifications( pushToken: pushToken, @@ -156,11 +179,28 @@ extension SyncPushTokensJob { failure: failure, remainingRetries: (remainingRetries - 1) ) - return + + return Just(false) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } - failure(error) + return Fail(error: error) + .eraseToAnyPublisher() } - .retainUntilComplete() + .sinkUntilComplete( + receiveCompletion: { result in + // TODO: Test these are called correctly + switch result { + case .finished: break + case .failure(let error): failure(error) + } + }, + receiveValue: { didComplete in + guard didComplete else { return } + + success() + } + ) } } diff --git a/Session/Notifications/UserNotificationsAdaptee.swift b/Session/Notifications/UserNotificationsAdaptee.swift index 3b436499a..525edc592 100644 --- a/Session/Notifications/UserNotificationsAdaptee.swift +++ b/Session/Notifications/UserNotificationsAdaptee.swift @@ -1,10 +1,11 @@ -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import Combine import UserNotifications -import PromiseKit import SessionMessagingKit import SignalCoreKit +import SignalUtilitiesKit class UserNotificationConfig { @@ -18,39 +19,47 @@ class UserNotificationConfig { } class func notificationCategory(_ category: AppNotificationCategory) -> UNNotificationCategory { - return UNNotificationCategory(identifier: category.identifier, - actions: notificationActions(for: category), - intentIdentifiers: [], - options: []) + return UNNotificationCategory( + identifier: category.identifier, + actions: notificationActions(for: category), + intentIdentifiers: [], + options: [] + ) } class func notificationAction(_ action: AppNotificationAction) -> UNNotificationAction { switch action { - case .markAsRead: - return UNNotificationAction(identifier: action.identifier, - title: MessageStrings.markAsReadNotificationAction, - options: []) - case .reply: - return UNTextInputNotificationAction(identifier: action.identifier, - title: MessageStrings.replyNotificationAction, - options: [], - textInputButtonTitle: MessageStrings.sendButton, - textInputPlaceholder: "") - case .showThread: - return UNNotificationAction(identifier: action.identifier, - title: CallStrings.showThreadButtonTitle, - options: [.foreground]) + case .markAsRead: + return UNNotificationAction( + identifier: action.identifier, + title: MessageStrings.markAsReadNotificationAction, + options: [] + ) + + case .reply: + return UNTextInputNotificationAction( + identifier: action.identifier, + title: MessageStrings.replyNotificationAction, + options: [], + textInputButtonTitle: MessageStrings.sendButton, + textInputPlaceholder: "" + ) + + case .showThread: + return UNNotificationAction( + identifier: action.identifier, + title: CallStrings.showThreadButtonTitle, + options: [.foreground] + ) } } class func action(identifier: String) -> AppNotificationAction? { return AppNotificationAction.allCases.first { notificationAction($0).identifier == identifier } } - } class UserNotificationPresenterAdaptee: NSObject, UNUserNotificationCenterDelegate { - private let notificationCenter: UNUserNotificationCenter private var notifications: [String: UNNotificationRequest] = [:] @@ -64,24 +73,23 @@ class UserNotificationPresenterAdaptee: NSObject, UNUserNotificationCenterDelega } extension UserNotificationPresenterAdaptee: NotificationPresenterAdaptee { + func registerNotificationSettings() -> Future { + return Future { [weak self] resolver in + self?.notificationCenter.requestAuthorization(options: [.badge, .sound, .alert]) { (granted, error) in + self?.notificationCenter.setNotificationCategories(UserNotificationConfig.allNotificationCategories) - func registerNotificationSettings() -> Promise { - return Promise { resolver in - notificationCenter.requestAuthorization(options: [.badge, .sound, .alert]) { (granted, error) in - self.notificationCenter.setNotificationCategories(UserNotificationConfig.allNotificationCategories) - - if granted { - - } else if error != nil { - Logger.error("failed with error: \(error!)") - } else { + if granted {} + else if let error: Error = error { + Logger.error("failed with error: \(error)") + } + else { Logger.error("failed without error.") } // Note that the promise is fulfilled regardless of if notification permssions were // granted. This promise only indicates that the user has responded, so we can // proceed with requesting push tokens and complete registration. - resolver.fulfill(()) + resolver(Result.success(())) } } } @@ -240,18 +248,22 @@ public class UserNotificationActionHandler: NSObject { @objc func handleNotificationResponse( _ response: UNNotificationResponse, completionHandler: @escaping () -> Void) { AssertIsOnMainThread() - firstly { - try handleNotificationResponse(response) - }.done { - completionHandler() - }.catch { error in - completionHandler() - owsFailDebug("error: \(error)") - Logger.error("error: \(error)") - }.retainUntilComplete() + handleNotificationResponse(response) + .sinkUntilComplete( + receiveCompletion: { result in + switch result { + case .finished: break + case .failure(let error): + completionHandler() + owsFailDebug("error: \(error)") + Logger.error("error: \(error)") + } + }, + receiveValue: { _ in completionHandler() } + ) } - func handleNotificationResponse( _ response: UNNotificationResponse) throws -> Promise { + func handleNotificationResponse( _ response: UNNotificationResponse) -> AnyPublisher { AssertIsOnMainThread() assert(AppReadiness.isAppReady()) @@ -260,12 +272,16 @@ public class UserNotificationActionHandler: NSObject { switch response.actionIdentifier { case UNNotificationDefaultActionIdentifier: Logger.debug("default action") - return try actionHandler.showThread(userInfo: userInfo) + return actionHandler.showThread(userInfo: userInfo) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() case UNNotificationDismissActionIdentifier: // TODO - mark as read? Logger.debug("dismissed notification") - return Promise.value(()) + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() default: // proceed @@ -273,22 +289,26 @@ public class UserNotificationActionHandler: NSObject { } guard let action = UserNotificationConfig.action(identifier: response.actionIdentifier) else { - throw NotificationError.failDebug("unable to find action for actionIdentifier: \(response.actionIdentifier)") + return Fail(error: NotificationError.failDebug("unable to find action for actionIdentifier: \(response.actionIdentifier)")) + .eraseToAnyPublisher() } switch action { case .markAsRead: - return try actionHandler.markAsRead(userInfo: userInfo) + return actionHandler.markAsRead(userInfo: userInfo) case .reply: guard let textInputResponse = response as? UNTextInputNotificationResponse else { - throw NotificationError.failDebug("response had unexpected type: \(response)") + return Fail(error: NotificationError.failDebug("response had unexpected type: \(response)")) + .eraseToAnyPublisher() } - return try actionHandler.reply(userInfo: userInfo, replyText: textInputResponse.userText) + return actionHandler.reply(userInfo: userInfo, replyText: textInputResponse.userText) case .showThread: - return try actionHandler.showThread(userInfo: userInfo) + return actionHandler.showThread(userInfo: userInfo) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } } } diff --git a/Session/Open Groups/JoinOpenGroupVC.swift b/Session/Open Groups/JoinOpenGroupVC.swift index 2b84bb734..8e2ff7134 100644 --- a/Session/Open Groups/JoinOpenGroupVC.swift +++ b/Session/Open Groups/JoinOpenGroupVC.swift @@ -168,7 +168,7 @@ final class JoinOpenGroupVC: BaseVC, UIPageViewControllerDataSource, UIPageViewC ModalActivityIndicatorViewController.present(fromViewController: navigationController, canCancel: false) { [weak self] _ in Storage.shared - .writeAsync { db in + .writePublisher { db in OpenGroupManager.shared.add( db, roomToken: roomToken, @@ -177,31 +177,39 @@ final class JoinOpenGroupVC: BaseVC, UIPageViewControllerDataSource, UIPageViewC isConfigMessage: false ) } - .done(on: DispatchQueue.main) { [weak self] _ in - Storage.shared.writeAsync { db in - try MessageSender.syncConfiguration(db, forceSyncNow: true).retainUntilComplete() // FIXME: It's probably cleaner to do this inside addOpenGroup(...) + .receive(on: DispatchQueue.main) + .sinkUntilComplete( + receiveCompletion: { result in + switch result { + case .failure(let error): + self?.dismiss(animated: true, completion: nil) // Dismiss the loader + let title = "COMMUNITY_ERROR_GENERIC".localized() + let message = error.localizedDescription + self?.isJoining = false + self?.showError(title: title, message: message) + + case .finished: + Storage.shared.writeAsync { db in + try MessageSender + .syncConfiguration(db, forceSyncNow: true) + .sinkUntilComplete() // FIXME: It's probably cleaner to do this inside addOpenGroup(...) + } + + self?.presentingViewController?.dismiss(animated: true, completion: nil) + + if shouldOpenCommunity { + SessionApp.presentConversation( + for: OpenGroup.idFor(roomToken: roomToken, server: server), + threadVariant: .openGroup, + isMessageRequest: false, + action: .compose, + focusInteractionId: nil, + animated: false + ) + } + } } - - self?.presentingViewController?.dismiss(animated: true, completion: nil) - - if shouldOpenCommunity { - SessionApp.presentConversation( - for: OpenGroup.idFor(roomToken: roomToken, server: server), - threadVariant: .openGroup, - isMessageRequest: false, - action: .compose, - focusInteractionId: nil, - animated: false - ) - } - } - .catch(on: DispatchQueue.main) { [weak self] error in - self?.dismiss(animated: true, completion: nil) // Dismiss the loader - let title = "COMMUNITY_ERROR_GENERIC".localized() - let message = error.localizedDescription - self?.isJoining = false - self?.showError(title: title, message: message) - } + ) } } diff --git a/Session/Open Groups/OpenGroupSuggestionGrid.swift b/Session/Open Groups/OpenGroupSuggestionGrid.swift index 9ee5c5b67..e62f8ec18 100644 --- a/Session/Open Groups/OpenGroupSuggestionGrid.swift +++ b/Session/Open Groups/OpenGroupSuggestionGrid.swift @@ -1,4 +1,7 @@ -import PromiseKit +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import Combine import NVActivityIndicatorView import SessionMessagingKit import SessionUIKit @@ -140,12 +143,10 @@ final class OpenGroupSuggestionGrid: UIView, UICollectionViewDataSource, UIColle widthAnchor.constraint(greaterThanOrEqualToConstant: OpenGroupSuggestionGrid.cellHeight).isActive = true OpenGroupManager.getDefaultRoomsIfNeeded() - .done { [weak self] rooms in - self?.rooms = rooms - } - .catch { [weak self] _ in - self?.update() - } + .sinkUntilComplete( + receiveCompletion: { [weak self] _ in self?.update() }, + receiveValue: { [weak self] rooms in self?.rooms = rooms } + ) } // MARK: - Updating @@ -315,24 +316,55 @@ extension OpenGroupSuggestionGrid { return } - let promise = Storage.shared.read { db in - OpenGroupManager.roomImage(db, fileId: imageId, for: room.token, on: OpenGroupAPI.defaultServer) - } + imageView.image = nil // TODO: Test this - if let imageData: Data = promise.value { - imageView.image = UIImage(data: imageData) - imageView.isHidden = (imageView.image == nil) - } - else { - imageView.isHidden = true - - _ = promise.done { [weak self] imageData in - DispatchQueue.main.async { + Publishers + .MergeMany( + Storage.shared + .readPublisherFlatMap { db in + OpenGroupManager + .roomImage(db, fileId: imageId, for: room.token, on: OpenGroupAPI.defaultServer) + } + .map { ($0, true) } + .eraseToAnyPublisher(), + // If we have already received the room image then the above will emit first and + // we can ignore this 'Just' call which is used to hide the image while loading + Just((Data(), false)) + .setFailureType(to: Error.self) +// .delay(for: .milliseconds(10), scheduler: DispatchQueue.main) + .eraseToAnyPublisher() + ) + .receiveOnMain(immediately: true) + .sinkUntilComplete( + receiveValue: { [weak self] imageData, hasData in + // TODO: Test this behaviour + guard hasData else { + self?.imageView.isHidden = true + return + } + self?.imageView.image = UIImage(data: imageData) self?.imageView.isHidden = (self?.imageView.image == nil) } - } - } + ) + +// OpenGroupManager.roomImage(db, fileId: imageId, for: room.token, on: OpenGroupAPI.defaultServer) +// .values +// +// if let imageData: Data = promise.value { +// imageView.image = UIImage(data: imageData) +// imageView.isHidden = (imageView.image == nil) +// } +// else { +// imageView.isHidden = true +// +// _ = promise.done { [weak self] imageData in +// DispatchQueue.main.async { +// self?.imageView.image = UIImage(data: imageData) +// self?.imageView.isHidden = (self?.imageView.image == nil) +// } +// } +// } } } } diff --git a/Session/Settings/BlockedContactsViewModel.swift b/Session/Settings/BlockedContactsViewModel.swift index 1a414edf3..18957a07a 100644 --- a/Session/Settings/BlockedContactsViewModel.swift +++ b/Session/Settings/BlockedContactsViewModel.swift @@ -141,8 +141,88 @@ public class BlockedContactsViewModel { ].flatMap { $0 } } - public func updateContactData(_ updatedData: [SectionModel]) { - self.contactData = updatedData + private func unblockTapped() { + guard !selectedContactIdsSubject.value.isEmpty else { return } + + let contactIds: Set = selectedContactIdsSubject.value + let contactNames: [String] = contactIds + .compactMap { contactId in + guard + let section: BlockedContactsViewModel.SectionModel = self.tableData + .first(where: { section in section.model == .contacts }), + let info: SessionCell.Info = section.elements + .first(where: { info in info.id.id == contactId }) + else { return contactId } + + return info.title?.text + } + let confirmationTitle: String = { + guard contactNames.count > 1 else { + // Show a single users name + return String( + format: "CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_SINGLE".localized(), + ( + contactNames.first ?? + "CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_FALLBACK".localized() + ) + ) + } + guard contactNames.count > 3 else { + // Show up to three users names + let initialNames: [String] = Array(contactNames.prefix(upTo: (contactNames.count - 1))) + let lastName: String = contactNames[contactNames.count - 1] + + return [ + String( + format: "CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_MULTIPLE_1".localized(), + initialNames.joined(separator: ", ") + ), + String( + format: "CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_MULTIPLE_2_SINGLE".localized(), + lastName + ) + ] + .reversed(if: CurrentAppContext().isRTL) + .joined(separator: " ") + } + + // If we have exactly 4 users, show the first two names followed by 'and X others', for + // more than 4 users, show the first 3 names followed by 'and X others' + let numNamesToShow: Int = (contactNames.count == 4 ? 2 : 3) + let initialNames: [String] = Array(contactNames.prefix(upTo: numNamesToShow)) + + return [ + String( + format: "CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_MULTIPLE_1".localized(), + initialNames.joined(separator: ", ") + ), + String( + format: "CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_MULTIPLE_3".localized(), + (contactNames.count - numNamesToShow) + ) + ] + .reversed(if: CurrentAppContext().isRTL) + .joined(separator: " ") + }() + let confirmationModal: ConfirmationModal = ConfirmationModal( + info: ConfirmationModal.Info( + title: confirmationTitle, + confirmTitle: "CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_ACTON".localized(), + confirmStyle: .danger, + cancelStyle: .alert_text + ) { _ in + // Unblock the contacts + Storage.shared.write { db in + _ = try Contact + .filter(ids: contactIds) + .updateAll(db, Contact.Columns.isBlocked.set(to: false)) + + // Force a config sync + try MessageSender.syncConfiguration(db, forceSyncNow: true).sinkUntilComplete() + } + } + ) + self.transitionToScreen(confirmationModal, transitionType: .present) } // MARK: - DataModel diff --git a/Session/Settings/NukeDataModal.swift b/Session/Settings/NukeDataModal.swift index d8a791a70..34b0b84b5 100644 --- a/Session/Settings/NukeDataModal.swift +++ b/Session/Settings/NukeDataModal.swift @@ -150,62 +150,73 @@ final class NukeDataModal: Modal { private func clearDeviceOnly() { ModalActivityIndicatorViewController.present(fromViewController: self, canCancel: false) { [weak self] _ in Storage.shared - .writeAsync { db in try MessageSender.syncConfiguration(db, forceSyncNow: true) } - .ensure(on: DispatchQueue.main) { - self?.deleteAllLocalData() - self?.dismiss(animated: true, completion: nil) // Dismiss the loader - } - .retainUntilComplete() + .writePublisherFlatMap { db in try MessageSender.syncConfiguration(db, forceSyncNow: true) } + .receive(on: DispatchQueue.main) + .sinkUntilComplete( + receiveCompletion: { _ in + self?.deleteAllLocalData() + self?.dismiss(animated: true, completion: nil) // Dismiss the loader + } + ) } } private func clearEntireAccount(presentedViewController: UIViewController) { ModalActivityIndicatorViewController .present(fromViewController: presentedViewController, canCancel: false) { [weak self] _ in - SnodeAPI.clearAllData() - .done(on: DispatchQueue.main) { confirmations in - self?.dismiss(animated: true, completion: nil) // Dismiss the loader - - let potentiallyMaliciousSnodes = confirmations.compactMap { $0.value == false ? $0.key : nil } - - if potentiallyMaliciousSnodes.isEmpty { - self?.deleteAllLocalData() - } - else { - let message: String - if potentiallyMaliciousSnodes.count == 1 { - message = String(format: "dialog_clear_all_data_deletion_failed_1".localized(), potentiallyMaliciousSnodes[0]) + SnodeAPI.deleteAllMessages() + .subscribe(on: DispatchQueue.global(qos: .default)) + .receive(on: DispatchQueue.main) + .sinkUntilComplete( + receiveCompletion: { result in + switch result { + case .finished: break + case .failure(let error): + self?.dismiss(animated: true, completion: nil) // Dismiss the loader + + let modal: ConfirmationModal = ConfirmationModal( + targetView: self?.view, + info: ConfirmationModal.Info( + title: "ALERT_ERROR_TITLE".localized(), + explanation: error.localizedDescription, + cancelTitle: "BUTTON_OK".localized(), + cancelStyle: .alert_text + ) + ) + self?.present(modal, animated: true) + } + }, + receiveValue: { confirmations in + self?.dismiss(animated: true, completion: nil) // Dismiss the loader + + let potentiallyMaliciousSnodes = confirmations + .compactMap { ($0.value == false ? $0.key : nil) } + + if potentiallyMaliciousSnodes.isEmpty { + self?.deleteAllLocalData() } else { - message = String(format: "dialog_clear_all_data_deletion_failed_2".localized(), String(potentiallyMaliciousSnodes.count), potentiallyMaliciousSnodes.joined(separator: ", ")) - } - - let modal: ConfirmationModal = ConfirmationModal( - targetView: self?.view, - info: ConfirmationModal.Info( - title: "ALERT_ERROR_TITLE".localized(), - explanation: message, - cancelTitle: "BUTTON_OK".localized(), - cancelStyle: .alert_text + let message: String + if potentiallyMaliciousSnodes.count == 1 { + message = String(format: "dialog_clear_all_data_deletion_failed_1".localized(), potentiallyMaliciousSnodes[0]) + } + else { + message = String(format: "dialog_clear_all_data_deletion_failed_2".localized(), String(potentiallyMaliciousSnodes.count), potentiallyMaliciousSnodes.joined(separator: ", ")) + } + + let modal: ConfirmationModal = ConfirmationModal( + targetView: self?.view, + info: ConfirmationModal.Info( + title: "ALERT_ERROR_TITLE".localized(), + explanation: message, + cancelTitle: "BUTTON_OK".localized(), + cancelStyle: .alert_text + ) ) - ) - self?.present(modal, animated: true) + self?.present(modal, animated: true) + } } - } - .catch(on: DispatchQueue.main) { error in - self?.dismiss(animated: true, completion: nil) // Dismiss the loader - - let modal: ConfirmationModal = ConfirmationModal( - targetView: self?.view, - info: ConfirmationModal.Info( - title: "ALERT_ERROR_TITLE".localized(), - explanation: error.localizedDescription, - cancelTitle: "BUTTON_OK".localized(), - cancelStyle: .alert_text - ) - ) - self?.present(modal, animated: true) - } + ) } } @@ -216,7 +227,7 @@ final class NukeDataModal: Modal { if isUsingFullAPNs, let deviceToken: String = maybeDeviceToken { let data: Data = Data(hex: deviceToken) - PushNotificationAPI.unregister(data).retainUntilComplete() + PushNotificationAPI.unregister(data).sinkUntilComplete() } // Clear the app badge and notifications diff --git a/Session/Settings/SettingsViewModel.swift b/Session/Settings/SettingsViewModel.swift index 476277407..a1277a29c 100644 --- a/Session/Settings/SettingsViewModel.swift +++ b/Session/Settings/SettingsViewModel.swift @@ -421,6 +421,47 @@ class SettingsViewModel: SessionTableViewModel] = [] + private static var publishers: [AnyPublisher] = [] public static var isValid: Bool = false public static func poll(completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { - promises = [] - .appending(pollForMessages()) - .appending(contentsOf: pollForClosedGroupMessages()) - .appending( - contentsOf: Storage.shared - .read { db in - // The default room promise creates an OpenGroup with an empty `roomToken` value, - // we don't want to start a poller for this as the user hasn't actually joined a room - try OpenGroup - .select(.server) - .filter(OpenGroup.Columns.roomToken != "") - .filter(OpenGroup.Columns.isActive) - .distinct() - .asRequest(of: String.self) - .fetchSet(db) - } - .defaulting(to: []) - .map { server in - let poller: OpenGroupAPI.Poller = OpenGroupAPI.Poller(for: server) - poller.stop() - - return poller.poll( - calledFromBackgroundPoller: true, - isBackgroundPollerValid: { BackgroundPoller.isValid }, - isPostCapabilitiesRetry: false - ) - } + // TODO: Test this works + Publishers + .MergeMany( + [pollForMessages()] + .appending(contentsOf: pollForClosedGroupMessages()) + .appending( + contentsOf: Storage.shared + .read { db in + // The default room promise creates an OpenGroup with an empty + // `roomToken` value, we don't want to start a poller for this + // as the user hasn't actually joined a room + try OpenGroup + .select(.server) + .filter(OpenGroup.Columns.roomToken != "") + .filter(OpenGroup.Columns.isActive) + .distinct() + .asRequest(of: String.self) + .fetchSet(db) + } + .defaulting(to: []) + .map { server -> AnyPublisher in + let poller: OpenGroupAPI.Poller = OpenGroupAPI.Poller(for: server) + poller.stop() + + return poller.poll( + calledFromBackgroundPoller: true, + isBackgroundPollerValid: { BackgroundPoller.isValid }, + isPostCapabilitiesRetry: false + ) + } + ) + ) + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .collect() + .sinkUntilComplete( + receiveCompletion: { result in + // If we have already invalidated the timer then do nothing (we essentially timed out) + guard BackgroundPoller.isValid else { return } + + switch result { + case .finished: completionHandler(.newData) + case .failure(let error): + SNLog("Background poll failed due to error: \(error)") + completionHandler(.failed) + } + } ) - - when(resolved: promises) - .done { _ in - // If we have already invalidated the timer then do nothing (we essentially timed out) - guard BackgroundPoller.isValid else { return } - - completionHandler(.newData) - } - .catch { error in - // If we have already invalidated the timer then do nothing (we essentially timed out) - guard BackgroundPoller.isValid else { return } - - SNLog("Background poll failed due to error: \(error)") - completionHandler(.failed) - } } - private static func pollForMessages() -> Promise { + private static func pollForMessages() -> AnyPublisher { let userPublicKey: String = getUserHexEncodedPublicKey() - return getMessages(for: userPublicKey) + + return SnodeAPI.getSwarm(for: userPublicKey) + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .flatMap { swarm -> AnyPublisher in + guard let snode = swarm.randomElement() else { + return Fail(error: SnodeAPIError.generic) + .eraseToAnyPublisher() + } + + return CurrentUserPoller.poll( + namespaces: CurrentUserPoller.namespaces, + from: snode, + for: userPublicKey, + on: DispatchQueue.main, + calledFromBackgroundPoller: true, + isBackgroundPollValid: { BackgroundPoller.isValid } + ) + } + .eraseToAnyPublisher() } - private static func pollForClosedGroupMessages() -> [Promise] { + private static func pollForClosedGroupMessages() -> [AnyPublisher] { // Fetch all closed groups (excluding any don't contain the current user as a // GroupMemeber as the user is no longer a member of those) return Storage.shared @@ -78,90 +102,13 @@ public final class BackgroundPoller { } .defaulting(to: []) .map { groupPublicKey in - ClosedGroupPoller.poll( - groupPublicKey, - on: DispatchQueue.main, - maxRetryCount: 0, - calledFromBackgroundPoller: true, - isBackgroundPollValid: { BackgroundPoller.isValid } - ) - } - } - - private static func getMessages(for publicKey: String) -> Promise { - return SnodeAPI.getSwarm(for: publicKey) - .then(on: DispatchQueue.main) { swarm -> Promise in - guard let snode = swarm.randomElement() else { throw SnodeAPIError.generic } - - return SnodeAPI.getMessages(from: snode, associatedWith: publicKey) - .then(on: DispatchQueue.main) { messages, lastHash -> Promise in - guard !messages.isEmpty, BackgroundPoller.isValid else { return Promise.value(()) } - - var jobsToRun: [Job] = [] - var messageCount: Int = 0 - var hadValidHashUpdate: Bool = false - - Storage.shared.write { db in - messages - .compactMap { message -> ProcessedMessage? in - do { - return try Message.processRawReceivedMessage(db, rawMessage: message) - } - catch { - switch error { - // Ignore duplicate & selfSend message errors (and don't bother - // logging them as there will be a lot since we each service node - // duplicates messages) - case DatabaseError.SQLITE_CONSTRAINT_UNIQUE, - MessageReceiverError.duplicateMessage, - MessageReceiverError.duplicateControlMessage, - MessageReceiverError.selfSend: - break - - case MessageReceiverError.duplicateMessageNewSnode: - hadValidHashUpdate = true - break - - // In the background ignore 'SQLITE_ABORT' (it generally means - // the BackgroundPoller has timed out - case DatabaseError.SQLITE_ABORT: break - - default: SNLog("Failed to deserialize envelope due to error: \(error).") - } - - return nil - } - } - .grouped { threadId, _, _ in (threadId ?? Message.nonThreadMessageId) } - .forEach { threadId, threadMessages in - messageCount += threadMessages.count - - let maybeJob: Job? = Job( - variant: .messageReceive, - behaviour: .runOnce, - threadId: threadId, - details: MessageReceiveJob.Details( - messages: threadMessages.map { $0.messageInfo }, - calledFromBackgroundPoller: true - ) - ) - - guard let job: Job = maybeJob else { return } - - // Add to the JobRunner so they are persistent and will retry on - // the next app run if they fail - JobRunner.add(db, job: job, canStartJob: false) - jobsToRun.append(job) - } - - if messageCount == 0 && !hadValidHashUpdate, let lastHash: String = lastHash { - // Update the cached validity of the messages - try SnodeReceivedMessageInfo.handlePotentialDeletedOrInvalidHash( - db, - potentiallyInvalidHashes: [lastHash], - otherKnownValidHashes: messages.map { $0.info.hash } - ) - } + SnodeAPI.getSwarm(for: groupPublicKey) + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .flatMap { swarm -> AnyPublisher in + guard let snode: Snode = swarm.randomElement() else { + return Fail(error: OnionRequestAPIError.insufficientSnodes) + .eraseToAnyPublisher() } let promises: [Promise] = jobsToRun.map { job -> Promise in @@ -181,6 +128,7 @@ public final class BackgroundPoller { return when(fulfilled: promises) } + .eraseToAnyPublisher() } } } diff --git a/Session/Utilities/CGRect+Utilities.swift b/Session/Utilities/CGRect+Utilities.swift index 68f8338da..8d2034a5c 100644 --- a/Session/Utilities/CGRect+Utilities.swift +++ b/Session/Utilities/CGRect+Utilities.swift @@ -1,6 +1,8 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation extension CGRect { - init(center: CGPoint, size: CGSize) { let originX = center.x - size.width / 2 let originY = center.y - size.height / 2 diff --git a/SessionMessagingKit/Database/Models/Attachment.swift b/SessionMessagingKit/Database/Models/Attachment.swift index a62849bd7..ce26bc32b 100644 --- a/SessionMessagingKit/Database/Models/Attachment.swift +++ b/SessionMessagingKit/Database/Models/Attachment.swift @@ -1,12 +1,12 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation -import GRDB -import PromiseKit -import SignalCoreKit -import SessionUtilitiesKit import AVFAudio import AVFoundation +import Combine +import GRDB +import SignalCoreKit +import SessionUtilitiesKit public struct Attachment: Codable, Identifiable, Equatable, Hashable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { public static var databaseTableName: String { "attachment" } @@ -975,7 +975,7 @@ extension Attachment { internal func upload( _ db: Database? = nil, queue: DispatchQueue, - using upload: (Database, Data) -> Promise, + using upload: @escaping (Database, Data) -> AnyPublisher, encrypt: Bool, success: ((String?) -> Void)?, failure: ((Error) -> Void)? @@ -1093,50 +1093,56 @@ extension Attachment { } // Perform the upload - let uploadPromise: Promise = { + let uploadPublisher: AnyPublisher = { guard let db: Database = db else { - return Storage.shared.read { db in upload(db, data) } + return Storage.shared.readPublisherFlatMap { db in upload(db, data) } } return upload(db, data) }() - uploadPromise - .done(on: queue) { fileId in - /// Save the final upload info - /// - /// **Note:** We **MUST** use the `.with` function here to ensure the `isValid` flag is - /// updated correctly - let uploadedAttachment: Attachment? = Storage.shared.write { db in - try updatedAttachment? - .with( - serverId: "\(fileId)", - state: .uploaded, - creationTimestamp: ( - updatedAttachment?.creationTimestamp ?? - Date().timeIntervalSince1970 - ), - downloadUrl: "\(FileServerAPI.server)/files/\(fileId)" - ) - .saved(db) - } - - guard uploadedAttachment != nil else { - SNLog("Couldn't update attachmentUpload job.") - failure?(StorageError.failedToSave) - return - } + uploadPublisher + .sinkUntilComplete( + receiveCompletion: { result in + switch result { + case .finished: break + case .failure(let error): + Storage.shared.write { db in + try Attachment + .filter(id: attachmentId) + .updateAll(db, Attachment.Columns.state.set(to: Attachment.State.failedUpload)) + } + + failure?(error) + } + }, + receiveValue: { fileId in + /// Save the final upload info + /// + /// **Note:** We **MUST** use the `.with` function here to ensure the `isValid` flag is + /// updated correctly + let uploadedAttachment: Attachment? = Storage.shared.write { db in + try updatedAttachment? + .with( + serverId: "\(fileId)", + state: .uploaded, + creationTimestamp: ( + updatedAttachment?.creationTimestamp ?? + Date().timeIntervalSince1970 + ), + downloadUrl: "\(FileServerAPI.server)/files/\(fileId)" + ) + .saved(db) + } - success?(fileId) - } - .catch(on: queue) { error in - Storage.shared.write { db in - try Attachment - .filter(id: attachmentId) - .updateAll(db, Attachment.Columns.state.set(to: Attachment.State.failedUpload)) + guard uploadedAttachment != nil else { + SNLog("Couldn't update attachmentUpload job.") + failure?(StorageError.failedToSave) + return + } + + success?(fileId) } - - failure?(error) - } + ) } } diff --git a/SessionMessagingKit/Database/Models/ClosedGroup.swift b/SessionMessagingKit/Database/Models/ClosedGroup.swift index 00210ff12..9016643c3 100644 --- a/SessionMessagingKit/Database/Models/ClosedGroup.swift +++ b/SessionMessagingKit/Database/Models/ClosedGroup.swift @@ -88,3 +88,76 @@ public extension ClosedGroup { .fetchOne(db) } } + +// MARK: - Convenience + +public extension ClosedGroup { + func asProfile() -> Profile { + return Profile( + id: threadId, + name: name, + profilePictureUrl: groupImageUrl, + profilePictureFileName: groupImageFileName, + profileEncryptionKey: groupImageEncryptionKey + ) + } + + static func removeKeysAndUnsubscribe( + _ db: Database? = nil, + threadId: String, + removeGroupData: Bool = false + ) throws { + try removeKeysAndUnsubscribe(db, threadIds: [threadId], removeGroupData: removeGroupData) + } + + static func removeKeysAndUnsubscribe( + _ db: Database? = nil, + threadIds: [String], + removeGroupData: Bool = false + ) throws { + guard let db: Database = db else { + Storage.shared.write { db in + try ClosedGroup.removeKeysAndUnsubscribe( + db, + threadIds: threadIds, + removeGroupData: removeGroupData) + } + return + } + + // Remove the group from the database and unsubscribe from PNs + let userPublicKey: String = getUserHexEncodedPublicKey(db) + + threadIds.forEach { threadId in + ClosedGroupPoller.shared.stopPolling(for: threadId) + + PushNotificationAPI + .performOperation( + .unsubscribe, + for: threadId, + publicKey: userPublicKey + ) + .sinkUntilComplete() + } + + // Remove the keys for the group + try ClosedGroupKeyPair + .filter(threadIds.contains(ClosedGroupKeyPair.Columns.threadId)) + .deleteAll(db) + + // Remove the remaining group data if desired + if removeGroupData { + try SessionThread + .filter(ids: threadIds) + .deleteAll(db) + + try ClosedGroup + .filter(ids: threadIds) + .deleteAll(db) + + try GroupMember + .filter(threadIds.contains(GroupMember.Columns.groupId)) + .deleteAll(db) + } + } +} diff --git a/SessionMessagingKit/File Server/FileServerAPI.swift b/SessionMessagingKit/File Server/FileServerAPI.swift index 694bf53be..8b77e485e 100644 --- a/SessionMessagingKit/File Server/FileServerAPI.swift +++ b/SessionMessagingKit/File Server/FileServerAPI.swift @@ -1,31 +1,33 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation -import PromiseKit +import Combine import SessionSnodeKit import SessionUtilitiesKit -@objc(SNFileServerAPI) -public final class FileServerAPI: NSObject { +public enum FileServerAPI { // MARK: - Settings - @objc public static let oldServer = "http://88.99.175.227" + public static let oldServer = "http://88.99.175.227" public static let oldServerPublicKey = "7cb31905b55cd5580c686911debf672577b3fb0bff81df4ce2d5c4cb3a7aaa69" - @objc public static let server = "http://filev2.getsession.org" + public static let server = "http://filev2.getsession.org" public static let serverPublicKey = "da21e1d886c6fbaea313f75298bd64aab03a97ce985b46bb2dad9f2089c8ee59" public static let maxFileSize = (10 * 1024 * 1024) // 10 MB - /// The file server has a file size limit of `maxFileSize`, which the Service Nodes try to enforce as well. However, the limit applied by the Service Nodes - /// is on the **HTTP request** and not the actual file size. Because the file server expects the file data to be base 64 encoded, the size of the HTTP - /// request for a given file will be at least `ceil(n / 3) * 4` bytes, where n is the file size in bytes. This is the minimum size because there might also - /// be other parameters in the request. On average the multiplier appears to be about 1.5, so when checking whether the file will exceed the file size limit when - /// uploading a file we just divide the size of the file by this number. The alternative would be to actually check the size of the HTTP request but that's only - /// possible after proof of work has been calculated and the onion request encryption has happened, which takes several seconds. + + /// The file server has a file size limit of `maxFileSize`, which the Service Nodes try to enforce as well. However, the limit + /// applied by the Service Nodes is on the **HTTP request** and not the actual file size. Because the file server expects the + /// file data to be base 64 encoded, the size of the HTTP request for a given file will be at least `ceil(n / 3) * 4` bytes, + /// where n is the file size in bytes. This is the minimum size because there might also be other parameters in the request. On + /// average the multiplier appears to be about 1.5, so when checking whether the file will exceed the file size limit when uploading + /// a file we just divide the size of the file by this number. The alternative would be to actually check the size of the HTTP request + /// but that's only possible after proof of work has been calculated and the onion request encryption has happened, which takes + /// several seconds. public static let fileSizeORMultiplier: Double = 2 // MARK: - File Storage - public static func upload(_ file: Data) -> Promise { + public static func upload(_ file: Data) -> AnyPublisher { let request = Request( method: .post, server: server, @@ -38,10 +40,10 @@ public final class FileServerAPI: NSObject { ) return send(request, serverPublicKey: serverPublicKey) - .decoded(as: FileUploadResponse.self, on: .global(qos: .userInitiated)) + .decoded(as: FileUploadResponse.self) } - public static func download(_ fileId: String, useOldServer: Bool) -> Promise { + public static func download(_ fileId: String, useOldServer: Bool) -> AnyPublisher { let serverPublicKey: String = (useOldServer ? oldServerPublicKey : serverPublicKey) let request = Request( server: (useOldServer ? oldServer : server), @@ -51,7 +53,7 @@ public final class FileServerAPI: NSObject { return send(request, serverPublicKey: serverPublicKey) } - public static func getVersion(_ platform: String) -> Promise { + public static func getVersion(_ platform: String) -> AnyPublisher { let request = Request( server: server, endpoint: .sessionVersion, @@ -61,27 +63,39 @@ public final class FileServerAPI: NSObject { ) return send(request, serverPublicKey: serverPublicKey) - .decoded(as: VersionResponse.self, on: .global(qos: .userInitiated)) + .decoded(as: VersionResponse.self) .map { response in response.version } + .eraseToAnyPublisher() } // MARK: - Convenience - private static func send(_ request: Request, serverPublicKey: String) -> Promise { + private static func send( + _ request: Request, + serverPublicKey: String + ) -> AnyPublisher { let urlRequest: URLRequest do { urlRequest = try request.generateUrlRequest() } catch { - return Promise(error: error) + return Fail(error: error) + .eraseToAnyPublisher() } return OnionRequestAPI.sendOnionRequest(urlRequest, to: request.server, with: serverPublicKey) - .map2 { _, response in - guard let response: Data = response else { throw HTTP.Error.parsingFailed } + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) + .flatMap { _, response -> AnyPublisher in + guard let response: Data = response else { + return Fail(error: HTTPError.parsingFailed) + .eraseToAnyPublisher() + } - return response + return Just(response) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } + .eraseToAnyPublisher() } } diff --git a/SessionMessagingKit/Jobs/Types/AttachmentDownloadJob.swift b/SessionMessagingKit/Jobs/Types/AttachmentDownloadJob.swift index 6a1d4fc16..a464ea744 100644 --- a/SessionMessagingKit/Jobs/Types/AttachmentDownloadJob.swift +++ b/SessionMessagingKit/Jobs/Types/AttachmentDownloadJob.swift @@ -1,7 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation -import PromiseKit +import Combine import SessionUtilitiesKit import SessionSnodeKit import SignalCoreKit @@ -84,119 +84,150 @@ public enum AttachmentDownloadJob: JobExecutor { let temporaryFileUrl: URL = URL( fileURLWithPath: OWSTemporaryDirectoryAccessibleAfterFirstAuth() + UUID().uuidString ) - let downloadPromise: Promise = { - guard - let downloadUrl: String = attachment.downloadUrl, - let fileId: String = Attachment.fileId(for: downloadUrl) - else { - return Promise(error: AttachmentDownloadError.invalidUrl) - } - - let maybeOpenGroupDownloadPromise: Promise? = Storage.shared.read({ db in - guard let openGroup: OpenGroup = try OpenGroup.fetchOne(db, id: threadId) else { - return nil // Not an open group so just use standard FileServer upload - } - - return OpenGroupAPI.downloadFile(db, fileId: fileId, from: openGroup.roomToken, on: openGroup.server) - .map { _, data in data } - }) - - return ( - maybeOpenGroupDownloadPromise ?? - FileServerAPI.download(fileId, useOldServer: downloadUrl.contains(FileServerAPI.oldServer)) - ) - }() - downloadPromise - .then(on: queue) { data -> Promise in - try data.write(to: temporaryFileUrl, options: .atomic) - - let plaintext: Data = try { - guard - let key: Data = attachment.encryptionKey, - let digest: Data = attachment.digest, - key.count > 0, - digest.count > 0 - else { return data } // Open group attachments are unencrypted - - return try Cryptography.decryptAttachment( - data, - withKey: key, - digest: digest, - unpaddedSize: UInt32(attachment.byteCount) - ) - }() - - guard try attachment.write(data: plaintext) else { - throw AttachmentDownloadError.failedToSaveFile + Just(attachment.downloadUrl) + .setFailureType(to: Error.self) + .flatMap { maybeDownloadUrl -> AnyPublisher in + guard + let downloadUrl: String = maybeDownloadUrl, + let fileId: String = Attachment.fileId(for: downloadUrl) + else { + return Fail(error: AttachmentDownloadError.invalidUrl) + .eraseToAnyPublisher() } - return Promise.value(()) - } - .done(on: queue) { - // Remove the temporary file - OWSFileSystem.deleteFile(temporaryFileUrl.path) - - /// Update the attachment state - /// - /// **Note:** We **MUST** use the `'with()` function here as it will update the - /// `isValid` and `duration` values based on the downloaded data and the state - Storage.shared.write { db in - _ = try attachment - .with( - state: .downloaded, - creationTimestamp: Date().timeIntervalSince1970, - localRelativeFilePath: ( - attachment.localRelativeFilePath ?? - Attachment.localRelativeFilePath(from: attachment.originalFilePath) - ) - ) - .saved(db) - } - - success(job, false) - } - .catch(on: queue) { error in - OWSFileSystem.deleteFile(temporaryFileUrl.path) - - let targetState: Attachment.State - let permanentFailure: Bool - - switch error { - /// If we get a 404 then we got a successful response from the server but the attachment doesn't - /// exist, in this case update the attachment to an "invalid" state so the user doesn't get stuck in - /// a retry download loop - case OnionRequestAPIError.httpRequestFailedAtDestination(let statusCode, _, _) where statusCode == 404: - targetState = .invalid - permanentFailure = true + return Storage.shared + .readPublisher { db in try OpenGroup.fetchOne(db, id: threadId) } + .flatMap { maybeOpenGroup -> AnyPublisher in + guard let openGroup: OpenGroup = maybeOpenGroup else { + return FileServerAPI + .download( + fileId, + useOldServer: downloadUrl.contains(FileServerAPI.oldServer) + ) + .eraseToAnyPublisher() + } - case OnionRequestAPIError.httpRequestFailedAtDestination(let statusCode, _, _) where statusCode == 400 || statusCode == 401: - /// If we got a 400 or a 401 then we want to fail the download in a way that has to be manually retried as it's - /// likely something else is going on that caused the failure - targetState = .failedDownload - permanentFailure = true + return Storage.shared + .readPublisherFlatMap { db in + OpenGroupAPI + .downloadFile( + db, + fileId: fileId, + from: openGroup.roomToken, + on: openGroup.server + ) + } + .map { _, data in data } + .eraseToAnyPublisher() + } + .eraseToAnyPublisher() + } + .flatMap { data -> AnyPublisher in + do { + // Store the encrypted data temporarily + try data.write(to: temporaryFileUrl, options: .atomic) - /// For any other error it's likely either the server is down or something weird just happened with the request - /// so we want to automatically retry - default: - targetState = .failedDownload - permanentFailure = false + // Decrypt the data + let plaintext: Data = try { + guard + let key: Data = attachment.encryptionKey, + let digest: Data = attachment.digest, + key.count > 0, + digest.count > 0 + else { return data } // Open group attachments are unencrypted + + return try Cryptography.decryptAttachment( + data, + withKey: key, + digest: digest, + unpaddedSize: UInt32(attachment.byteCount) + ) + }() + + // Write the data to disk + guard try attachment.write(data: plaintext) else { + throw AttachmentDownloadError.failedToSaveFile + } + + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } - - /// To prevent the attachment from showing a state of downloading forever, we need to update the attachment - /// state here based on the type of error that occurred - /// - /// **Note:** We **MUST** use the `'with()` function here as it will update the - /// `isValid` and `duration` values based on the downloaded data and the state - Storage.shared.write { db in - _ = try Attachment - .filter(id: attachment.id) - .updateAll(db, Attachment.Columns.state.set(to: targetState)) + catch { + return Fail(error: error) + .eraseToAnyPublisher() } - - /// Trigger the failure and provide the `permanentFailure` value defined above - failure(job, error, permanentFailure) } + .sinkUntilComplete( + receiveCompletion: { result in + switch result { + case .finished: + // Remove the temporary file + OWSFileSystem.deleteFile(temporaryFileUrl.path) + + /// Update the attachment state + /// + /// **Note:** We **MUST** use the `'with()` function here as it will update the + /// `isValid` and `duration` values based on the downloaded data and the state + Storage.shared.write { db in + _ = try attachment + .with( + state: .downloaded, + creationTimestamp: Date().timeIntervalSince1970, + localRelativeFilePath: ( + attachment.localRelativeFilePath ?? + Attachment.localRelativeFilePath(from: attachment.originalFilePath) + ) + ) + .saved(db) + } + + success(job, false) + + case .failure(let error): + OWSFileSystem.deleteFile(temporaryFileUrl.path) + + let targetState: Attachment.State + let permanentFailure: Bool + + switch error { + /// If we get a 404 then we got a successful response from the server but the attachment doesn't + /// exist, in this case update the attachment to an "invalid" state so the user doesn't get stuck in + /// a retry download loop + case OnionRequestAPIError.httpRequestFailedAtDestination(let statusCode, _, _) where statusCode == 404: + targetState = .invalid + permanentFailure = true + + case OnionRequestAPIError.httpRequestFailedAtDestination(let statusCode, _, _) where statusCode == 400 || statusCode == 401: + /// If we got a 400 or a 401 then we want to fail the download in a way that has to be manually retried as it's + /// likely something else is going on that caused the failure + targetState = .failedDownload + permanentFailure = true + + /// For any other error it's likely either the server is down or something weird just happened with the request + /// so we want to automatically retry + default: + targetState = .failedDownload + permanentFailure = false + } + + /// To prevent the attachment from showing a state of downloading forever, we need to update the attachment + /// state here based on the type of error that occurred + /// + /// **Note:** We **MUST** use the `'with()` function here as it will update the + /// `isValid` and `duration` values based on the downloaded data and the state + Storage.shared.write { db in + _ = try Attachment + .filter(id: attachment.id) + .updateAll(db, Attachment.Columns.state.set(to: targetState)) + } + + /// Trigger the failure and provide the `permanentFailure` value defined above + failure(job, error, permanentFailure) + } + } + ) } } diff --git a/SessionMessagingKit/Jobs/Types/AttachmentUploadJob.swift b/SessionMessagingKit/Jobs/Types/AttachmentUploadJob.swift index fac908cc0..f8c725f8e 100644 --- a/SessionMessagingKit/Jobs/Types/AttachmentUploadJob.swift +++ b/SessionMessagingKit/Jobs/Types/AttachmentUploadJob.swift @@ -64,10 +64,12 @@ public enum AttachmentUploadJob: JobExecutor { on: openGroup.server ) .map { _, response -> String in response.id } + .eraseToAnyPublisher() } return FileServerAPI.upload(data) .map { response -> String in response.id } + .eraseToAnyPublisher() }, encrypt: (openGroup == nil), success: { _ in success(job, false) }, diff --git a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift index d41b71b9d..cec000718 100644 --- a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift +++ b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift @@ -160,48 +160,58 @@ public enum MessageSendJob: JobExecutor { details.message.threadId = (details.message.threadId ?? job.threadId) // Perform the actual message sending - Storage.shared.writeAsync { db -> Promise in - try MessageSender.sendImmediate( - db, - message: details.message, - to: details.destination - .with(fileIds: messageFileIds), - interactionId: job.interactionId - ) - } - .done(on: queue) { _ in success(job, false) } - .catch(on: queue) { error in - SNLog("Couldn't send message due to error: \(error).") - - switch error { - case let senderError as MessageSenderError where !senderError.isRetryable: - failure(job, error, true) - - case OnionRequestAPIError.httpRequestFailedAtDestination(let statusCode, _, _) where statusCode == 429: // Rate limited - failure(job, error, true) - - case SnodeAPIError.clockOutOfSync: - SNLog("\(originalSentTimestamp != nil ? "Permanently Failing" : "Failing") to send \(type(of: details.message)) due to clock out of sync issue.") - failure(job, error, (originalSentTimestamp != nil)) - - default: - SNLog("Failed to send \(type(of: details.message)).") - - if details.message is VisibleMessage { - guard - let interactionId: Int64 = job.interactionId, - Storage.shared.read({ db in try Interaction.exists(db, id: interactionId) }) == true - else { - // The message has been deleted so permanently fail the job - failure(job, error, true) - return - } - } - - failure(job, error, false) + Storage.shared + .writePublisher { db in + // TODO: Will need to split the attachment upload from the message preparation logic + try MessageSender.preparedSendData( + db, + message: details.message, + to: details.destination + .with(fileIds: messageFileIds), // TODO: This??? + interactionId: job.interactionId + ) } - } - .retainUntilComplete() + .subscribe(on: queue) + // TODO: Is this needed? (should be caught before this??) +// .flatMap { MessageSender.performUploadsIfNeeded(preparedSendData: $0) } + .flatMap { MessageSender.sendImmediate(data: $0) } + .sinkUntilComplete( + receiveCompletion: { result in + switch result { + case .finished: success(job, false) + case .failure(let error): + SNLog("Couldn't send message due to error: \(error).") + + switch error { + case let senderError as MessageSenderError where !senderError.isRetryable: + failure(job, error, true) + + case OnionRequestAPIError.httpRequestFailedAtDestination(let statusCode, _, _) where statusCode == 429: // Rate limited + failure(job, error, true) + + case SnodeAPIError.clockOutOfSync: + SNLog("\(originalSentTimestamp != nil ? "Permanently Failing" : "Failing") to send \(type(of: details.message)) due to clock out of sync issue.") + failure(job, error, (originalSentTimestamp != nil)) + + default: + SNLog("Failed to send \(type(of: details.message)).") + + if details.message is VisibleMessage { + guard + let interactionId: Int64 = job.interactionId, + Storage.shared.read({ db in try Interaction.exists(db, id: interactionId) }) == true + else { + // The message has been deleted so permanently fail the job + failure(job, error, true) + return + } + } + + failure(job, error, false) + } + } + } + ) } } diff --git a/SessionMessagingKit/Jobs/Types/NotifyPushServerJob.swift b/SessionMessagingKit/Jobs/Types/NotifyPushServerJob.swift index 63885541a..87de7b174 100644 --- a/SessionMessagingKit/Jobs/Types/NotifyPushServerJob.swift +++ b/SessionMessagingKit/Jobs/Types/NotifyPushServerJob.swift @@ -1,7 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation -import PromiseKit +import Combine import SessionSnodeKit import SessionUtilitiesKit @@ -29,12 +29,18 @@ public enum NotifyPushServerJob: JobExecutor { .notify( recipient: details.message.recipient, with: details.message.data, - maxRetryCount: 4, - queue: queue + maxRetryCount: 4 + ) + .subscribe(on: queue) + .receive(on: queue) + .sinkUntilComplete( + receiveCompletion: { result in + switch result { + case .finished: success(job, false) + case .failure(let error): failure(job, error, false) + } + } ) - .done(on: queue) { _ in success(job, false) } - .catch(on: queue) { error in failure(job, error, false) } - .retainUntilComplete() } } diff --git a/SessionMessagingKit/Jobs/Types/RetrieveDefaultOpenGroupRoomsJob.swift b/SessionMessagingKit/Jobs/Types/RetrieveDefaultOpenGroupRoomsJob.swift index 5d33b0fed..ba029b0cb 100644 --- a/SessionMessagingKit/Jobs/Types/RetrieveDefaultOpenGroupRoomsJob.swift +++ b/SessionMessagingKit/Jobs/Types/RetrieveDefaultOpenGroupRoomsJob.swift @@ -42,8 +42,15 @@ public enum RetrieveDefaultOpenGroupRoomsJob: JobExecutor { } OpenGroupManager.getDefaultRoomsIfNeeded() - .done(on: queue) { _ in success(job, false) } - .catch(on: queue) { error in failure(job, error, false) } - .retainUntilComplete() + .subscribe(on: queue) + .receive(on: queue) + .sinkUntilComplete( + receiveCompletion: { result in + switch result { + case .finished: success(job, false) + case .failure(let error): failure(job, error, false) + } + } + ) } } diff --git a/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift b/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift index 33d369fc8..78f9fea9b 100644 --- a/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift +++ b/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift @@ -36,8 +36,8 @@ public enum SendReadReceiptsJob: JobExecutor { } Storage.shared - .writeAsync { db in - try MessageSender.sendImmediate( + .writePublisher { db in + try MessageSender.preparedSendData( db, message: ReadReceipt( timestamps: details.timestampMsValues.map { UInt64($0) } @@ -46,42 +46,49 @@ public enum SendReadReceiptsJob: JobExecutor { interactionId: nil ) } - .done(on: queue) { - // When we complete the 'SendReadReceiptsJob' we want to immediately schedule - // another one for the same thread but with a 'nextRunTimestamp' set to the - // 'minRunFrequency' value to throttle the read receipt requests - var shouldFinishCurrentJob: Bool = false - let nextRunTimestamp: TimeInterval = (Date().timeIntervalSince1970 + minRunFrequency) - - let updatedJob: Job? = Storage.shared.write { db in - // If another 'sendReadReceipts' job was scheduled then update that one - // to run at 'nextRunTimestamp' and make the current job stop - if - let existingJob: Job = try? Job - .filter(Job.Columns.id != job.id) - .filter(Job.Columns.variant == Job.Variant.sendReadReceipts) - .filter(Job.Columns.threadId == threadId) - .fetchOne(db), - !JobRunner.isCurrentlyRunning(existingJob) - { - _ = try existingJob - .with(nextRunTimestamp: nextRunTimestamp) - .saved(db) - shouldFinishCurrentJob = true - return job + .subscribe(on: queue) + .receive(on: queue) + .flatMap { MessageSender.sendImmediate(data: $0) } + .sinkUntilComplete( + receiveCompletion: { result in + switch result { + case .failure(let error): failure(job, error, false) + case .finished: + // When we complete the 'SendReadReceiptsJob' we want to immediately schedule + // another one for the same thread but with a 'nextRunTimestamp' set to the + // 'minRunFrequency' value to throttle the read receipt requests + var shouldFinishCurrentJob: Bool = false + let nextRunTimestamp: TimeInterval = (Date().timeIntervalSince1970 + minRunFrequency) + + let updatedJob: Job? = Storage.shared.write { db in + // If another 'sendReadReceipts' job was scheduled then update that one + // to run at 'nextRunTimestamp' and make the current job stop + if + let existingJob: Job = try? Job + .filter(Job.Columns.id != job.id) + .filter(Job.Columns.variant == Job.Variant.sendReadReceipts) + .filter(Job.Columns.threadId == threadId) + .fetchOne(db), + !JobRunner.isCurrentlyRunning(existingJob) + { + _ = try existingJob + .with(nextRunTimestamp: nextRunTimestamp) + .saved(db) + shouldFinishCurrentJob = true + return job + } + + return try job + .with(details: Details(destination: details.destination, timestampMsValues: [])) + .defaulting(to: job) + .with(nextRunTimestamp: nextRunTimestamp) + .saved(db) + } + + success(updatedJob ?? job, shouldFinishCurrentJob) } - - return try job - .with(details: Details(destination: details.destination, timestampMsValues: [])) - .defaulting(to: job) - .with(nextRunTimestamp: nextRunTimestamp) - .saved(db) } - - success(updatedJob ?? job, shouldFinishCurrentJob) - } - .catch(on: queue) { error in failure(job, error, false) } - .retainUntilComplete() + ) } } diff --git a/SessionMessagingKit/Jobs/Types/UpdateProfilePictureJob.swift b/SessionMessagingKit/Jobs/Types/UpdateProfilePictureJob.swift index d8dfc6b91..5050c035a 100644 --- a/SessionMessagingKit/Jobs/Types/UpdateProfilePictureJob.swift +++ b/SessionMessagingKit/Jobs/Types/UpdateProfilePictureJob.swift @@ -52,7 +52,7 @@ public enum UpdateProfilePictureJob: JobExecutor { image: nil, imageFilePath: profileFilePath, success: { db, _ in - try MessageSender.syncConfiguration(db, forceSyncNow: true).retainUntilComplete() + try MessageSender.syncConfiguration(db, forceSyncNow: true).sinkUntilComplete() // Need to call the 'success' closure asynchronously on the queue to prevent a reentrancy // issue as it will write to the database and this closure is already called within diff --git a/SessionMessagingKit/Messages/Message+Destination.swift b/SessionMessagingKit/Messages/Message+Destination.swift index 3a04f5ac3..9694b2670 100644 --- a/SessionMessagingKit/Messages/Message+Destination.swift +++ b/SessionMessagingKit/Messages/Message+Destination.swift @@ -24,7 +24,7 @@ public extension Message { ) case openGroupInbox(server: String, openGroupPublicKey: String, blindedPublicKey: String) - static func from( + public static func from( _ db: Database, thread: SessionThread, fileIds: [String]? = nil diff --git a/SessionMessagingKit/Open Groups/Models/SOGSBatchRequest.swift b/SessionMessagingKit/Open Groups/Models/SOGSBatchRequest.swift index bb38ed258..5d97b576f 100644 --- a/SessionMessagingKit/Open Groups/Models/SOGSBatchRequest.swift +++ b/SessionMessagingKit/Open Groups/Models/SOGSBatchRequest.swift @@ -1,7 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation -import PromiseKit +import Combine import SessionUtilitiesKit internal extension OpenGroupAPI { @@ -104,18 +104,20 @@ internal extension OpenGroupAPI { // MARK: - Convenience -internal extension Promise where T == HTTP.BatchResponse { +internal extension AnyPublisher where Output == HTTP.BatchResponse, Failure == Error { func map( requests: [OpenGroupAPI.BatchRequest.Info], toHashMapFor endpointType: E.Type - ) -> Promise<[E: (ResponseInfoType, Codable?)]> { - return self.map { result in - result.enumerated() - .reduce(into: [:]) { prev, next in - guard let endpoint: E = requests[next.offset].endpoint as? E else { return } - - prev[endpoint] = next.element - } - } + ) -> AnyPublisher<[E: (ResponseInfoType, Codable?)], Error> { + return self + .map { result in + result.enumerated() + .reduce(into: [:]) { prev, next in + guard let endpoint: E = requests[next.offset].endpoint as? E else { return } + + prev[endpoint] = next.element + } + } + .eraseToAnyPublisher() } } diff --git a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift index 051ba7cc2..d08ff23bf 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift @@ -1,8 +1,8 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import Combine import GRDB -import PromiseKit import Sodium import Curve25519Kit import SessionSnodeKit @@ -33,7 +33,7 @@ public enum OpenGroupAPI { hasPerformedInitialPoll: Bool, timeSinceLastPoll: TimeInterval, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise<[Endpoint: (OnionRequestResponseInfoType, Codable?)]> { + ) -> AnyPublisher<[Endpoint: (ResponseInfoType, Codable?)], Error> { let lastInboxMessageId: Int64 = (try? OpenGroup .select(.inboxLatestMessageId) .filter(OpenGroup.Columns.server == server) @@ -153,8 +153,7 @@ public enum OpenGroupAPI { server: String, requests: [BatchRequestInfoType], using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise<[Endpoint: (OnionRequestResponseInfoType, Codable?)]> { - let requestBody: BatchRequest = requests.map { $0.toSubRequest() } + ) -> AnyPublisher<[Endpoint: (ResponseInfoType, Codable?)], Error> { let responseTypes = requests.map { $0.responseType } return OpenGroupAPI @@ -168,13 +167,8 @@ public enum OpenGroupAPI { ), using: dependencies ) - .decoded(as: responseTypes, on: OpenGroupAPI.workQueue, using: dependencies) - .map { result in - result.enumerated() - .reduce(into: [:]) { prev, next in - prev[requests[next.offset].endpoint] = next.element - } - } + .decoded(as: responseTypes, using: dependencies) + .map(requests: requests, toHashMapFor: Endpoint.self) } /// This is like `/batch`, except that it guarantees to perform requests sequentially in the order provided and will stop processing requests if the previous request @@ -191,8 +185,7 @@ public enum OpenGroupAPI { server: String, requests: [BatchRequestInfoType], using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise<[Endpoint: (OnionRequestResponseInfoType, Codable?)]> { - let requestBody: BatchRequest = requests.map { $0.toSubRequest() } + ) -> AnyPublisher<[Endpoint: (ResponseInfoType, Codable?)], Error> { let responseTypes = requests.map { $0.responseType } return OpenGroupAPI @@ -206,13 +199,8 @@ public enum OpenGroupAPI { ), using: dependencies ) - .decoded(as: responseTypes, on: OpenGroupAPI.workQueue, using: dependencies) - .map { result in - result.enumerated() - .reduce(into: [:]) { prev, next in - prev[requests[next.offset].endpoint] = next.element - } - } + .decoded(as: responseTypes, using: dependencies) + .map(requests: requests, toHashMapFor: Endpoint.self) } // MARK: - Capabilities @@ -229,7 +217,7 @@ public enum OpenGroupAPI { server: String, forceBlinded: Bool = false, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise<(OnionRequestResponseInfoType, Capabilities)> { + ) -> AnyPublisher<(ResponseInfoType, Capabilities), Error> { return OpenGroupAPI .send( db, @@ -240,7 +228,7 @@ public enum OpenGroupAPI { forceBlinded: forceBlinded, using: dependencies ) - .decoded(as: Capabilities.self, on: OpenGroupAPI.workQueue, using: dependencies) + .decoded(as: Capabilities.self, using: dependencies) } // MARK: - Room @@ -252,7 +240,7 @@ public enum OpenGroupAPI { _ db: Database, server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise<(OnionRequestResponseInfoType, [Room])> { + ) -> AnyPublisher<(ResponseInfoType, [Room]), Error> { return OpenGroupAPI .send( db, @@ -262,7 +250,7 @@ public enum OpenGroupAPI { ), using: dependencies ) - .decoded(as: [Room].self, on: OpenGroupAPI.workQueue, using: dependencies) + .decoded(as: [Room].self, using: dependencies) } /// Returns the details of a single room @@ -276,7 +264,7 @@ public enum OpenGroupAPI { for roomToken: String, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise<(OnionRequestResponseInfoType, Room)> { + ) -> AnyPublisher<(ResponseInfoType, Room), Error> { return OpenGroupAPI .send( db, @@ -286,7 +274,7 @@ public enum OpenGroupAPI { ), using: dependencies ) - .decoded(as: Room.self, on: OpenGroupAPI.workQueue, using: dependencies) + .decoded(as: Room.self, using: dependencies) } /// Polls a room for metadata updates @@ -304,7 +292,7 @@ public enum OpenGroupAPI { for roomToken: String, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise<(OnionRequestResponseInfoType, RoomPollInfo)> { + ) -> AnyPublisher<(ResponseInfoType, RoomPollInfo), Error> { return OpenGroupAPI .send( db, @@ -314,7 +302,7 @@ public enum OpenGroupAPI { ), using: dependencies ) - .decoded(as: RoomPollInfo.self, on: OpenGroupAPI.workQueue, using: dependencies) + .decoded(as: RoomPollInfo.self, using: dependencies) } /// This is a convenience method which constructs a `/sequence` of the `capabilities` and `room` requests, refer to those @@ -324,8 +312,8 @@ public enum OpenGroupAPI { for roomToken: String, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise<(capabilities: (info: OnionRequestResponseInfoType, data: Capabilities), room: (info: OnionRequestResponseInfoType, data: Room))> { - let requestResponseType: [BatchRequestInfoType] = [ + ) -> AnyPublisher<(capabilities: (info: ResponseInfoType, data: Capabilities), room: (info: ResponseInfoType, data: Room)), Error> { + let requestResponseType: [BatchRequest.Info] = [ // Get the latest capabilities for the server (in case it's a new server or the cached ones are stale) BatchRequestInfo( request: Request( @@ -352,10 +340,10 @@ public enum OpenGroupAPI { requests: requestResponseType, using: dependencies ) - .map { (response: [Endpoint: (OnionRequestResponseInfoType, Codable?)]) -> (capabilities: (OnionRequestResponseInfoType, Capabilities), room: (OnionRequestResponseInfoType, Room)) in - let maybeCapabilities: (info: OnionRequestResponseInfoType, data: Capabilities?)? = response[.capabilities] - .map { info, data in (info, (data as? BatchSubResponse)?.body) } - let maybeRoomResponse: (OnionRequestResponseInfoType, Codable?)? = response + .flatMap { (response: [Endpoint: (ResponseInfoType, Codable?)]) -> AnyPublisher<(capabilities: (info: ResponseInfoType, data: Capabilities), room: (info: ResponseInfoType, data: Room)), Error> in + let maybeCapabilities: (info: ResponseInfoType, data: Capabilities?)? = response[.capabilities] + .map { info, data in (info, (data as? HTTP.BatchSubResponse)?.body) } + let maybeRoomResponse: (ResponseInfoType, Codable?)? = response .first(where: { key, _ in switch key { case .room: return true @@ -372,14 +360,18 @@ public enum OpenGroupAPI { let roomInfo: OnionRequestResponseInfoType = maybeRoom?.info, let room: Room = maybeRoom?.data else { - throw HTTP.Error.parsingFailed + return Fail(error: HTTPError.parsingFailed) + .eraseToAnyPublisher() } - return ( - (capabilitiesInfo, capabilities), - (roomInfo, room) - ) + return Just(( + capabilities: (info: capabilitiesInfo, data: capabilities), + room: (info: roomInfo, data: room) + )) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } + .eraseToAnyPublisher() } /// This is a convenience method which constructs a `/sequence` of the `capabilities` and `rooms` requests, refer to those @@ -388,8 +380,8 @@ public enum OpenGroupAPI { _ db: Database, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise<(capabilities: (info: OnionRequestResponseInfoType, data: Capabilities), rooms: (info: OnionRequestResponseInfoType, data: [Room]))> { - let requestResponseType: [BatchRequestInfoType] = [ + ) -> AnyPublisher<(capabilities: (info: ResponseInfoType, data: Capabilities), rooms: (info: ResponseInfoType, data: [Room])), Error> { + let requestResponseType: [BatchRequest.Info] = [ // Get the latest capabilities for the server (in case it's a new server or the cached ones are stale) BatchRequestInfo( request: Request( @@ -416,10 +408,10 @@ public enum OpenGroupAPI { requests: requestResponseType, using: dependencies ) - .map { (response: [Endpoint: (OnionRequestResponseInfoType, Codable?)]) -> (capabilities: (OnionRequestResponseInfoType, Capabilities), rooms: (OnionRequestResponseInfoType, [Room])) in - let maybeCapabilities: (info: OnionRequestResponseInfoType, data: Capabilities?)? = response[.capabilities] - .map { info, data in (info, (data as? BatchSubResponse)?.body) } - let maybeRoomResponse: (OnionRequestResponseInfoType, Codable?)? = response + .flatMap { (response: [Endpoint: (ResponseInfoType, Codable?)]) -> AnyPublisher<(capabilities: (info: ResponseInfoType, data: Capabilities), rooms: (info: ResponseInfoType, data: [Room])), Error> in + let maybeCapabilities: (info: ResponseInfoType, data: Capabilities?)? = response[.capabilities] + .map { info, data in (info, (data as? HTTP.BatchSubResponse)?.body) } + let maybeRoomResponse: (ResponseInfoType, Codable?)? = response .first(where: { key, _ in switch key { case .rooms: return true @@ -436,14 +428,18 @@ public enum OpenGroupAPI { let roomsInfo: OnionRequestResponseInfoType = maybeRooms?.info, let rooms: [Room] = maybeRooms?.data else { - throw HTTP.Error.parsingFailed + return Fail(error: HTTPError.parsingFailed) + .eraseToAnyPublisher() } - return ( - (capabilitiesInfo, capabilities), - (roomsInfo, rooms) - ) + return Just(( + capabilities: (info: capabilitiesInfo, data: capabilities), + rooms: (info: roomsInfo, data: rooms) + )) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } + .eraseToAnyPublisher() } // MARK: - Messages @@ -458,9 +454,10 @@ public enum OpenGroupAPI { whisperMods: Bool, fileIds: [String]?, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise<(OnionRequestResponseInfoType, Message)> { + ) -> AnyPublisher<(ResponseInfoType, Message), Error> { guard let signResult: (publicKey: String, signature: Bytes) = sign(db, messageBytes: plaintext.bytes, for: server, fallbackSigningType: .standard, using: dependencies) else { - return Promise(error: OpenGroupAPIError.signingFailed) + return Fail(error: OpenGroupAPIError.signingFailed) + .eraseToAnyPublisher() } return OpenGroupAPI @@ -480,7 +477,7 @@ public enum OpenGroupAPI { ), using: dependencies ) - .decoded(as: Message.self, on: OpenGroupAPI.workQueue, using: dependencies) + .decoded(as: Message.self, using: dependencies) } /// Returns a single message by ID @@ -490,7 +487,7 @@ public enum OpenGroupAPI { in roomToken: String, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise<(OnionRequestResponseInfoType, Message)> { + ) -> AnyPublisher<(ResponseInfoType, Message), Error> { return OpenGroupAPI .send( db, @@ -500,7 +497,7 @@ public enum OpenGroupAPI { ), using: dependencies ) - .decoded(as: Message.self, on: OpenGroupAPI.workQueue, using: dependencies) + .decoded(as: Message.self, using: dependencies) } /// Edits a message, replacing its existing content with new content and a new signature @@ -514,9 +511,10 @@ public enum OpenGroupAPI { in roomToken: String, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise<(OnionRequestResponseInfoType, Data?)> { + ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { guard let signResult: (publicKey: String, signature: Bytes) = sign(db, messageBytes: plaintext.bytes, for: server, fallbackSigningType: .standard, using: dependencies) else { - return Promise(error: OpenGroupAPIError.signingFailed) + return Fail(error: OpenGroupAPIError.signingFailed) + .eraseToAnyPublisher() } return OpenGroupAPI @@ -542,7 +540,7 @@ public enum OpenGroupAPI { in roomToken: String, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise<(OnionRequestResponseInfoType, Data?)> { + ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { return OpenGroupAPI .send( db, @@ -564,7 +562,7 @@ public enum OpenGroupAPI { in roomToken: String, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise<(OnionRequestResponseInfoType, [Message])> { + ) -> AnyPublisher<(ResponseInfoType, [Message]), Error> { return OpenGroupAPI .send( db, @@ -574,7 +572,7 @@ public enum OpenGroupAPI { ), using: dependencies ) - .decoded(as: [Message].self, on: OpenGroupAPI.workQueue, using: dependencies) + .decoded(as: [Message].self, using: dependencies) } /// **Note:** This is the direct request to retrieve recent messages before a given message and is currently unused, in order to call this directly @@ -587,7 +585,7 @@ public enum OpenGroupAPI { in roomToken: String, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise<(OnionRequestResponseInfoType, [Message])> { + ) -> AnyPublisher<(ResponseInfoType, [Message]), Error> { return OpenGroupAPI .send( db, @@ -597,7 +595,7 @@ public enum OpenGroupAPI { ), using: dependencies ) - .decoded(as: [Message].self, on: OpenGroupAPI.workQueue, using: dependencies) + .decoded(as: [Message].self, using: dependencies) } /// **Note:** This is the direct request to retrieve messages since a given message `seqNo` so should be retrieved automatically from the @@ -610,7 +608,7 @@ public enum OpenGroupAPI { in roomToken: String, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise<(OnionRequestResponseInfoType, [Message])> { + ) -> AnyPublisher<(ResponseInfoType, [Message]), Error> { return OpenGroupAPI .send( db, @@ -624,7 +622,7 @@ public enum OpenGroupAPI { ), using: dependencies ) - .decoded(as: [Message].self, on: OpenGroupAPI.workQueue, using: dependencies) + .decoded(as: [Message].self, using: dependencies) } /// Deletes all messages from a given sessionId within the provided rooms (or globally) on a server @@ -646,7 +644,7 @@ public enum OpenGroupAPI { in roomToken: String, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise<(OnionRequestResponseInfoType, Data?)> { + ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { return OpenGroupAPI .send( db, @@ -668,11 +666,12 @@ public enum OpenGroupAPI { in roomToken: String, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise { + ) -> AnyPublisher { /// URL(String:) won't convert raw emojis, so need to do a little encoding here. /// The raw emoji will come back when calling url.path guard let encodedEmoji: String = emoji.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else { - return Promise(error: OpenGroupAPIError.invalidEmoji) + return Fail(error: OpenGroupAPIError.invalidEmoji) + .eraseToAnyPublisher() } return OpenGroupAPI @@ -686,6 +685,7 @@ public enum OpenGroupAPI { using: dependencies ) .map { responseInfo, _ in responseInfo } + .eraseToAnyPublisher() } public static func reactionAdd( @@ -695,11 +695,12 @@ public enum OpenGroupAPI { in roomToken: String, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise<(OnionRequestResponseInfoType, ReactionAddResponse)> { + ) -> AnyPublisher<(ResponseInfoType, ReactionAddResponse), Error> { /// URL(String:) won't convert raw emojis, so need to do a little encoding here. /// The raw emoji will come back when calling url.path guard let encodedEmoji: String = emoji.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else { - return Promise(error: OpenGroupAPIError.invalidEmoji) + return Fail(error: OpenGroupAPIError.invalidEmoji) + .eraseToAnyPublisher() } return OpenGroupAPI @@ -712,7 +713,7 @@ public enum OpenGroupAPI { ), using: dependencies ) - .decoded(as: ReactionAddResponse.self, on: OpenGroupAPI.workQueue, using: dependencies) + .decoded(as: ReactionAddResponse.self, using: dependencies) } public static func reactionDelete( @@ -722,11 +723,12 @@ public enum OpenGroupAPI { in roomToken: String, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise<(OnionRequestResponseInfoType, ReactionRemoveResponse)> { + ) -> AnyPublisher<(ResponseInfoType, ReactionRemoveResponse), Error> { /// URL(String:) won't convert raw emojis, so need to do a little encoding here. /// The raw emoji will come back when calling url.path guard let encodedEmoji: String = emoji.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else { - return Promise(error: OpenGroupAPIError.invalidEmoji) + return Fail(error: OpenGroupAPIError.invalidEmoji) + .eraseToAnyPublisher() } return OpenGroupAPI @@ -739,7 +741,7 @@ public enum OpenGroupAPI { ), using: dependencies ) - .decoded(as: ReactionRemoveResponse.self, on: OpenGroupAPI.workQueue, using: dependencies) + .decoded(as: ReactionRemoveResponse.self, using: dependencies) } public static func reactionDeleteAll( @@ -749,11 +751,12 @@ public enum OpenGroupAPI { in roomToken: String, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise<(OnionRequestResponseInfoType, ReactionRemoveAllResponse)> { + ) -> AnyPublisher<(ResponseInfoType, ReactionRemoveAllResponse), Error> { /// URL(String:) won't convert raw emojis, so need to do a little encoding here. /// The raw emoji will come back when calling url.path guard let encodedEmoji: String = emoji.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else { - return Promise(error: OpenGroupAPIError.invalidEmoji) + return Fail(error: OpenGroupAPIError.invalidEmoji) + .eraseToAnyPublisher() } return OpenGroupAPI @@ -766,7 +769,7 @@ public enum OpenGroupAPI { ), using: dependencies ) - .decoded(as: ReactionRemoveAllResponse.self, on: OpenGroupAPI.workQueue, using: dependencies) + .decoded(as: ReactionRemoveAllResponse.self, using: dependencies) } // MARK: - Pinning @@ -787,7 +790,7 @@ public enum OpenGroupAPI { in roomToken: String, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise { + ) -> AnyPublisher { return OpenGroupAPI .send( db, @@ -799,6 +802,7 @@ public enum OpenGroupAPI { using: dependencies ) .map { responseInfo, _ in responseInfo } + .eraseToAnyPublisher() } /// Remove a message from this room's pinned message list @@ -810,7 +814,7 @@ public enum OpenGroupAPI { in roomToken: String, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise { + ) -> AnyPublisher { return OpenGroupAPI .send( db, @@ -822,6 +826,7 @@ public enum OpenGroupAPI { using: dependencies ) .map { responseInfo, _ in responseInfo } + .eraseToAnyPublisher() } /// Removes _all_ pinned messages from this room @@ -832,7 +837,7 @@ public enum OpenGroupAPI { in roomToken: String, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise { + ) -> AnyPublisher { return OpenGroupAPI .send( db, @@ -844,6 +849,7 @@ public enum OpenGroupAPI { using: dependencies ) .map { responseInfo, _ in responseInfo } + .eraseToAnyPublisher() } // MARK: - Files @@ -855,7 +861,7 @@ public enum OpenGroupAPI { to roomToken: String, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise<(OnionRequestResponseInfoType, FileUploadResponse)> { + ) -> AnyPublisher<(ResponseInfoType, FileUploadResponse), Error> { return OpenGroupAPI .send( db, @@ -873,7 +879,7 @@ public enum OpenGroupAPI { ), using: dependencies ) - .decoded(as: FileUploadResponse.self, on: OpenGroupAPI.workQueue, using: dependencies) + .decoded(as: FileUploadResponse.self, using: dependencies) } public static func downloadFile( @@ -882,7 +888,7 @@ public enum OpenGroupAPI { from roomToken: String, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise<(OnionRequestResponseInfoType, Data)> { + ) -> AnyPublisher<(ResponseInfoType, Data), Error> { return OpenGroupAPI .send( db, @@ -892,11 +898,17 @@ public enum OpenGroupAPI { ), using: dependencies ) - .map { responseInfo, maybeData in - guard let data: Data = maybeData else { throw HTTP.Error.parsingFailed } + .flatMap { responseInfo, maybeData -> AnyPublisher<(ResponseInfoType, Data), Error> in + guard let data: Data = maybeData else { + return Fail(error: HTTPError.parsingFailed) + .eraseToAnyPublisher() + } - return (responseInfo, data) + return Just((responseInfo, data)) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } + .eraseToAnyPublisher() } // MARK: - Inbox/Outbox (Message Requests) @@ -911,7 +923,7 @@ public enum OpenGroupAPI { _ db: Database, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise<(OnionRequestResponseInfoType, [DirectMessage]?)> { + ) -> AnyPublisher<(ResponseInfoType, [DirectMessage]?), Error> { return OpenGroupAPI .send( db, @@ -921,7 +933,7 @@ public enum OpenGroupAPI { ), using: dependencies ) - .decoded(as: [DirectMessage]?.self, on: OpenGroupAPI.workQueue, using: dependencies) + .decoded(as: [DirectMessage]?.self, using: dependencies) } /// Polls for any DMs received since the given id, this method will return a `304` with an empty response if there are no messages @@ -935,7 +947,7 @@ public enum OpenGroupAPI { id: Int64, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise<(OnionRequestResponseInfoType, [DirectMessage]?)> { + ) -> AnyPublisher<(ResponseInfoType, [DirectMessage]?), Error> { return OpenGroupAPI .send( db, @@ -945,7 +957,7 @@ public enum OpenGroupAPI { ), using: dependencies ) - .decoded(as: [DirectMessage]?.self, on: OpenGroupAPI.workQueue, using: dependencies) + .decoded(as: [DirectMessage]?.self, using: dependencies) } /// Delivers a direct message to a user via their blinded Session ID @@ -957,7 +969,7 @@ public enum OpenGroupAPI { toInboxFor blindedSessionId: String, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise<(OnionRequestResponseInfoType, SendDirectMessageResponse)> { + ) -> AnyPublisher<(ResponseInfoType, SendDirectMessageResponse), Error> { return OpenGroupAPI .send( db, @@ -971,7 +983,7 @@ public enum OpenGroupAPI { ), using: dependencies ) - .decoded(as: SendDirectMessageResponse.self, on: OpenGroupAPI.workQueue, using: dependencies) + .decoded(as: SendDirectMessageResponse.self, using: dependencies) } /// Retrieves all of the user's sent DMs (up to limit) @@ -984,7 +996,7 @@ public enum OpenGroupAPI { _ db: Database, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise<(OnionRequestResponseInfoType, [DirectMessage]?)> { + ) -> AnyPublisher<(ResponseInfoType, [DirectMessage]?), Error> { return OpenGroupAPI .send( db, @@ -994,7 +1006,7 @@ public enum OpenGroupAPI { ), using: dependencies ) - .decoded(as: [DirectMessage]?.self, on: OpenGroupAPI.workQueue, using: dependencies) + .decoded(as: [DirectMessage]?.self, using: dependencies) } /// Polls for any DMs sent since the given id, this method will return a `304` with an empty response if there are no messages @@ -1008,7 +1020,7 @@ public enum OpenGroupAPI { id: Int64, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise<(OnionRequestResponseInfoType, [DirectMessage]?)> { + ) -> AnyPublisher<(ResponseInfoType, [DirectMessage]?), Error> { return OpenGroupAPI .send( db, @@ -1018,7 +1030,7 @@ public enum OpenGroupAPI { ), using: dependencies ) - .decoded(as: [DirectMessage]?.self, on: OpenGroupAPI.workQueue, using: dependencies) + .decoded(as: [DirectMessage]?.self, using: dependencies) } // MARK: - Users @@ -1061,7 +1073,7 @@ public enum OpenGroupAPI { from roomTokens: [String]? = nil, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise<(OnionRequestResponseInfoType, Data?)> { + ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { return OpenGroupAPI .send( db, @@ -1109,7 +1121,7 @@ public enum OpenGroupAPI { from roomTokens: [String]?, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise<(OnionRequestResponseInfoType, Data?)> { + ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { return OpenGroupAPI .send( db, @@ -1186,9 +1198,10 @@ public enum OpenGroupAPI { for roomTokens: [String]?, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise<(OnionRequestResponseInfoType, Data?)> { + ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { guard (moderator != nil && admin == nil) || (moderator == nil && admin != nil) else { - return Promise(error: HTTP.Error.generic) + return Fail(error: HTTPError.generic) + .eraseToAnyPublisher() } return OpenGroupAPI @@ -1218,7 +1231,7 @@ public enum OpenGroupAPI { in roomToken: String, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise<[OnionRequestResponseInfoType]> { + ) -> AnyPublisher<[ResponseInfoType], Error> { let banRequestBody: UserBanRequest = UserBanRequest( rooms: [roomToken], global: nil, @@ -1252,6 +1265,7 @@ public enum OpenGroupAPI { using: dependencies ) .map { $0.values.map { responseInfo, _ in responseInfo } } + .eraseToAnyPublisher() } // MARK: - Authentication @@ -1392,14 +1406,15 @@ public enum OpenGroupAPI { request: Request, forceBlinded: Bool = false, using dependencies: SMKDependencies = SMKDependencies() - ) -> Promise<(OnionRequestResponseInfoType, Data?)> { + ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { let urlRequest: URLRequest do { urlRequest = try request.generateUrlRequest() } catch { - return Promise(error: error) + return Fail(error: error) + .eraseToAnyPublisher() } let maybePublicKey: String? = try? OpenGroup @@ -1408,13 +1423,20 @@ public enum OpenGroupAPI { .asRequest(of: String.self) .fetchOne(db) - guard let publicKey: String = maybePublicKey else { return Promise(error: OpenGroupAPIError.noPublicKey) } + guard let publicKey: String = maybePublicKey else { + return Fail(error: OpenGroupAPIError.noPublicKey) + .eraseToAnyPublisher() + } // Attempt to sign the request with the new auth guard let signedRequest: URLRequest = sign(db, request: urlRequest, for: request.server, with: publicKey, forceBlinded: forceBlinded, using: dependencies) else { - return Promise(error: OpenGroupAPIError.signingFailed) + return Fail(error: OpenGroupAPIError.signingFailed) + .eraseToAnyPublisher() } - return dependencies.onionApi.sendOnionRequest(signedRequest, to: request.server, with: publicKey) + return dependencies.onionApi + .sendOnionRequest(signedRequest, to: request.server, with: publicKey) + .subscribe(on: OpenGroupAPI.workQueue) + .eraseToAnyPublisher() } } diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index d5baf0d36..abbfbdde3 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -1,8 +1,8 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import Combine import GRDB -import PromiseKit import Sodium import SessionUtilitiesKit import SessionSnodeKit @@ -10,8 +10,8 @@ import SessionSnodeKit // MARK: - OGMCacheType public protocol OGMCacheType { - var defaultRoomsPromise: Promise<[OpenGroupAPI.Room]>? { get set } - var groupImagePromises: [String: Promise] { get set } + var defaultRoomsPublisher: AnyPublisher<[OpenGroupAPI.Room], Error>? { get set } + var groupImagePublishers: [String: AnyPublisher] { get set } var pollers: [String: OpenGroupAPI.Poller] { get set } var isPolling: Bool { get set } @@ -31,8 +31,8 @@ public final class OpenGroupManager: NSObject { // MARK: - Cache public class Cache: OGMCacheType { - public var defaultRoomsPromise: Promise<[OpenGroupAPI.Room]>? - public var groupImagePromises: [String: Promise] = [:] + public var defaultRoomsPublisher: AnyPublisher<[OpenGroupAPI.Room], Error>? + public var groupImagePublishers: [String: AnyPublisher] = [:] public var pollers: [String: OpenGroupAPI.Poller] = [:] // One for each server public var isPolling: Bool = false @@ -199,11 +199,20 @@ public final class OpenGroupManager: NSObject { return hasExistingThread } - public func add(_ db: Database, roomToken: String, server: String, publicKey: String, isConfigMessage: Bool, dependencies: OGMDependencies = OGMDependencies()) -> Promise { + public func add( + _ db: Database, + roomToken: String, + server: String, + publicKey: String, + isConfigMessage: Bool, + dependencies: OGMDependencies = OGMDependencies() + ) -> AnyPublisher { // If we are currently polling for this server and already have a TSGroupThread for this room the do nothing if hasExistingOpenGroup(db, roomToken: roomToken, server: server, publicKey: publicKey, dependencies: dependencies) { SNLog("Ignoring join open group attempt (already joined), user initiated: \(!isConfigMessage)") - return Promise.value(()) + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } // Store the open group information @@ -237,25 +246,29 @@ public final class OpenGroupManager: NSObject { OpenGroup.Columns.sequenceNumber.set(to: 0) ) - let (promise, seal) = Promise.pending() - // Note: We don't do this after the db commit as it can fail (resulting in endless loading) - OpenGroupAPI.workQueue.async { - dependencies.storage - .writeAsync { db in - // Note: The initial request for room info and it's capabilities should NOT be - // authenticated (this is because if the server requires blinding and the auth - // headers aren't blinded it will error - these endpoints do support unauthenticated - // retrieval so doing so prevents the error) - OpenGroupAPI - .capabilitiesAndRoom( - db, - for: roomToken, - on: targetServer, - using: dependencies - ) - } - .done(on: OpenGroupAPI.workQueue) { response in + return Future { resolver in + OpenGroupAPI.workQueue.async { resolver(Result.success(())) } + } + .subscribe(on: OpenGroupAPI.workQueue) + .flatMap { _ in + dependencies.storage + .readPublisherFlatMap { db in + // Note: The initial request for room info and it's capabilities should NOT be + // authenticated (this is because if the server requires blinding and the auth + // headers aren't blinded it will error - these endpoints do support unauthenticated + // retrieval so doing so prevents the error) + OpenGroupAPI + .capabilitiesAndRoom( + db, + for: roomToken, + on: targetServer, + using: dependencies + ) + } + } + .flatMap { response -> Future in + Future { resolver in dependencies.storage.write { db in // Store the capabilities first OpenGroupManager.handleCapabilities( @@ -273,18 +286,21 @@ public final class OpenGroupManager: NSObject { on: targetServer, dependencies: dependencies ) { - seal.fulfill(()) + // TODO: Remove the 'Swift.' + resolver(Swift.Result.success(())) } } } - .catch(on: DispatchQueue.global(qos: .userInitiated)) { error in - SNLog("Failed to join open group.") - seal.reject(error) + } + .handleEvents( + receiveCompletion: { result in + switch result { + case .finished: break + case .failure: SNLog("Failed to join open group.") + } } - .retainUntilComplete() - } - - return promise + ) + .eraseToAnyPublisher() } public func delete(_ db: Database, openGroupId: String, dependencies: OGMDependencies = OGMDependencies()) { @@ -485,24 +501,28 @@ public final class OpenGroupManager: NSObject { openGroup.imageId != imageId ) { - OpenGroupManager.roomImage(db, fileId: imageId, for: roomToken, on: server, using: dependencies) - .done { data in - dependencies.storage.write { db in - _ = try OpenGroup - .filter(id: threadId) - .updateAll(db, OpenGroup.Columns.imageData.set(to: data)) - + OpenGroupManager + .roomImage( + db, + fileId: imageId, + for: roomToken, + on: server, + using: dependencies + ) + .sinkUntilComplete( + receiveCompletion: { _ in if waitForImageToComplete { completion?() } + }, + receiveValue: { data in + dependencies.storage.write { db in + _ = try OpenGroup + .filter(id: threadId) + .updateAll(db, OpenGroup.Columns.imageData.set(to: data)) + } } - } - .catch { _ in - if waitForImageToComplete { - completion?() - } - } - .retainUntilComplete() + ) } else if waitForImageToComplete { completion?() @@ -920,90 +940,103 @@ public final class OpenGroupManager: NSObject { .defaulting(to: false) } - @discardableResult public static func getDefaultRoomsIfNeeded(using dependencies: OGMDependencies = OGMDependencies()) -> Promise<[OpenGroupAPI.Room]> { + @discardableResult public static func getDefaultRoomsIfNeeded( + using dependencies: OGMDependencies = OGMDependencies() + ) -> AnyPublisher<[OpenGroupAPI.Room], Error> { + return Just([]) // TODO: Remove this + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + // Note: If we already have a 'defaultRoomsPromise' then there is no need to get it again - if let existingPromise: Promise<[OpenGroupAPI.Room]> = dependencies.cache.defaultRoomsPromise { - return existingPromise + if let existingPublisher: AnyPublisher<[OpenGroupAPI.Room], Error> = dependencies.cache.defaultRoomsPublisher { + return existingPublisher } - let (promise, seal) = Promise<[OpenGroupAPI.Room]>.pending() - // Try to retrieve the default rooms 8 times - attempt(maxRetryCount: 8, recoveringOn: OpenGroupAPI.workQueue) { - dependencies.storage.read { db in + let publisher: AnyPublisher<[OpenGroupAPI.Room], Error> = dependencies.storage + .readPublisherFlatMap { db in OpenGroupAPI.capabilitiesAndRooms( db, on: OpenGroupAPI.defaultServer, using: dependencies ) } - } - .done(on: OpenGroupAPI.workQueue) { response in - dependencies.storage.writeAsync { db in - // Store the capabilities first - OpenGroupManager.handleCapabilities( - db, - capabilities: response.capabilities.data, - on: OpenGroupAPI.defaultServer - ) - - // Then the rooms - response.rooms.data - .compactMap { room -> (String, String)? in - // Try to insert an inactive version of the OpenGroup (use 'insert' rather than 'save' - // as we want it to fail if the room already exists) - do { - _ = try OpenGroup( - server: OpenGroupAPI.defaultServer, - roomToken: room.token, - publicKey: OpenGroupAPI.defaultServerPublicKey, - isActive: false, - name: room.name, - roomDescription: room.roomDescription, - imageId: room.imageId, - imageData: nil, - userCount: room.activeUsers, - infoUpdates: room.infoUpdates, - sequenceNumber: 0, - inboxLatestMessageId: 0, - outboxLatestMessageId: 0 - ) - .inserted(db) + .subscribe(on: OpenGroupAPI.workQueue) + .retry(8) + .map { response in + dependencies.storage.writeAsync { db in + // Store the capabilities first + OpenGroupManager.handleCapabilities( + db, + capabilities: response.capabilities.data, + on: OpenGroupAPI.defaultServer + ) + + // Then the rooms + response.rooms.data + .compactMap { room -> (String, String)? in + // Try to insert an inactive version of the OpenGroup (use 'insert' + // rather than 'save' as we want it to fail if the room already exists) + do { + _ = try OpenGroup( + server: OpenGroupAPI.defaultServer, + roomToken: room.token, + publicKey: OpenGroupAPI.defaultServerPublicKey, + isActive: false, + name: room.name, + roomDescription: room.roomDescription, + imageId: room.imageId, + imageData: nil, + userCount: room.activeUsers, + infoUpdates: room.infoUpdates, + sequenceNumber: 0, + inboxLatestMessageId: 0, + outboxLatestMessageId: 0 + ) + .inserted(db) + } + catch {} + + guard let imageId: String = room.imageId else { return nil } + + return (imageId, room.token) } - catch {} - - guard let imageId: String = room.imageId else { return nil } - - return (imageId, room.token) - } - .forEach { imageId, roomToken in - roomImage( - db, - fileId: imageId, - for: roomToken, - on: OpenGroupAPI.defaultServer, - using: dependencies - ) - .retainUntilComplete() - } + .forEach { imageId, roomToken in + roomImage( + db, + fileId: imageId, + for: roomToken, + on: OpenGroupAPI.defaultServer, + using: dependencies + ) + .sinkUntilComplete() + } + } + + return response.rooms.data } - - seal.fulfill(response.rooms.data) - } - .catch(on: OpenGroupAPI.workQueue) { error in - dependencies.mutableCache.mutate { cache in - cache.defaultRoomsPromise = nil - } - - seal.reject(error) - } - .retainUntilComplete() + .handleEvents( + receiveCompletion: { result in + switch result { + case .finished: break + case .failure: + dependencies.mutableCache.mutate { cache in + cache.defaultRoomsPublisher = nil + } + } + } + ) + .shareReplay(1) + .eraseToAnyPublisher() dependencies.mutableCache.mutate { cache in - cache.defaultRoomsPromise = promise + cache.defaultRoomsPublisher = publisher } - return promise + // Hold on to the publisher until it has completed at least once + publisher.sinkUntilComplete() + + return publisher } public static func roomImage( @@ -1012,7 +1045,7 @@ public final class OpenGroupManager: NSObject { for roomToken: String, on server: String, using dependencies: OGMDependencies = OGMDependencies() - ) -> Promise { + ) -> AnyPublisher { // Normally the image for a given group is stored with the group thread, so it's only // fetched once. However, on the join open group screen we show images for groups the // user * hasn't * joined yet. We don't want to re-fetch these images every time the @@ -1036,48 +1069,52 @@ public final class OpenGroupManager: NSObject { .filter(id: threadId) .asRequest(of: Data.self) .fetchOne(db) - { return Promise.value(data) } - - if let promise = dependencies.cache.groupImagePromises[threadId] { - return promise + { + return Just(data) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } - let (promise, seal) = Promise.pending() + if let publisher: AnyPublisher = dependencies.cache.groupImagePublishers[threadId] { + return publisher + } // Trigger the download on a background queue - DispatchQueue.global(qos: .background).async { - dependencies.storage - .read { db in - OpenGroupAPI - .downloadFile( - db, - fileId: fileId, - from: roomToken, - on: server, - using: dependencies - ) - } - .done { _, imageData in - if server.lowercased() == OpenGroupAPI.defaultServer { - dependencies.storage.write { db in - _ = try OpenGroup - .filter(id: threadId) - .updateAll(db, OpenGroup.Columns.imageData.set(to: imageData)) - } - dependencies.standardUserDefaults[.lastOpenGroupImageUpdate] = now + let publisher: AnyPublisher = dependencies.storage + .readPublisherFlatMap { db in + OpenGroupAPI + .downloadFile( + db, + fileId: fileId, + from: roomToken, + on: server, + using: dependencies + ) + } + .subscribe(on: DispatchQueue.global(qos: .background)) + .map { _, imageData in + if server.lowercased() == OpenGroupAPI.defaultServer { + dependencies.storage.write { db in + _ = try OpenGroup + .filter(id: threadId) + .updateAll(db, OpenGroup.Columns.imageData.set(to: imageData)) } - - seal.fulfill(imageData) + dependencies.standardUserDefaults[.lastOpenGroupImageUpdate] = now } - .catch { seal.reject($0) } - .retainUntilComplete() - } + + return imageData + } + .shareReplay(1) + .eraseToAnyPublisher() dependencies.mutableCache.mutate { cache in - cache.groupImagePromises[threadId] = promise + cache.groupImagePublishers[threadId] = publisher } - return promise + // Hold on to the publisher until it has completed at least once + publisher.sinkUntilComplete() + + return publisher } public static func parseOpenGroup(from string: String) -> (room: String, server: String, publicKey: String)? { diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift index 5c5620a81..a1fcb82a8 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import Combine import GRDB import Sodium import SessionUtilitiesKit diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift index 71a56d0f3..9fe36f054 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift @@ -166,7 +166,7 @@ extension MessageReceiver { if let (room, server, publicKey) = OpenGroupManager.parseOpenGroup(from: openGroupURL) { OpenGroupManager.shared .add(db, roomToken: room, server: server, publicKey: publicKey, isConfigMessage: true) - .retainUntilComplete() + .sinkUntilComplete() } } } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift index d930b4239..179d6a5b3 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import Combine import GRDB import SessionUtilitiesKit @@ -167,6 +168,6 @@ extension MessageReceiver { // Force a config sync to ensure all devices know the contact approval state if desired guard forceConfigSync else { return } - try MessageSender.syncConfiguration(db, forceSyncNow: true).retainUntilComplete() + try MessageSender.syncConfiguration(db, forceSyncNow: true).sinkUntilComplete() } } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+UnsendRequests.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+UnsendRequests.swift index 28d9517d7..277f26000 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+UnsendRequests.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+UnsendRequests.swift @@ -37,7 +37,12 @@ extension MessageReceiver { } if author == message.sender, let serverHash: String = interaction.serverHash { - SnodeAPI.deleteMessage(publicKey: author, serverHashes: [serverHash]).retainUntilComplete() + SnodeAPI + .deleteMessages( + publicKey: author, + serverHashes: [serverHash] + ) + .sinkUntilComplete() } switch (interaction.variant, (author == message.sender)) { diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift index bc8d52a0b..657073676 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift @@ -1,16 +1,20 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import Combine import GRDB import Sodium import Curve25519Kit -import PromiseKit import SessionUtilitiesKit extension MessageSender { public static var distributingKeyPairs: Atomic<[String: [ClosedGroupKeyPair]]> = Atomic([:]) - public static func createClosedGroup(_ db: Database, name: String, members: Set) throws -> Promise { + public static func createClosedGroup( + _ db: Database, + name: String, + members: Set + ) -> AnyPublisher { let userPublicKey: String = getUserHexEncodedPublicKey(db) var members: Set = members @@ -25,98 +29,114 @@ extension MessageSender { let admins = [ userPublicKey ] let adminsAsData = admins.map { Data(hex: $0) } let formationTimestamp: TimeInterval = Date().timeIntervalSince1970 - let thread: SessionThread = try SessionThread - .fetchOrCreate(db, id: groupPublicKey, variant: .closedGroup) - try ClosedGroup( - threadId: groupPublicKey, - name: name, - formationTimestamp: formationTimestamp - ).insert(db) + let thread: SessionThread + let memberSendData: [MessageSender.PreparedSendData] - try admins.forEach { adminId in - try GroupMember( - groupId: groupPublicKey, - profileId: adminId, - role: .admin, - isHidden: false + do { + // Create the relevant objects in the database + thread = try SessionThread + .fetchOrCreate(db, id: groupPublicKey, variant: .closedGroup) + try ClosedGroup( + threadId: groupPublicKey, + name: name, + formationTimestamp: formationTimestamp ).insert(db) - } - - // Send a closed group update message to all members individually - var promises: [Promise] = [] - - try members.forEach { memberId in - try GroupMember( - groupId: groupPublicKey, - profileId: memberId, - role: .standard, - isHidden: false - ).insert(db) - } - - try members.forEach { memberId in - let contactThread: SessionThread = try SessionThread - .fetchOrCreate(db, id: memberId, variant: .contact) - // Sending this non-durably is okay because we show a loader to the user. If they - // close the app while the loader is still showing, it's within expectation that - // the group creation might be incomplete. - promises.append( - try MessageSender.sendNonDurably( - db, - message: ClosedGroupControlMessage( - kind: .new( - publicKey: Data(hex: groupPublicKey), - name: name, - encryptionKeyPair: Box.KeyPair( - publicKey: encryptionKeyPair.publicKey.bytes, - secretKey: encryptionKeyPair.privateKey.bytes + // Store the key pair + try ClosedGroupKeyPair( + threadId: groupPublicKey, + publicKey: encryptionKeyPair.publicKey, + secretKey: encryptionKeyPair.privateKey, + receivedTimestamp: Date().timeIntervalSince1970 + ).insert(db) + + // Create the member objects + try admins.forEach { adminId in + try GroupMember( + groupId: groupPublicKey, + profileId: adminId, + role: .admin, + isHidden: false + ).insert(db) + } + + try members.forEach { memberId in + try GroupMember( + groupId: groupPublicKey, + profileId: memberId, + role: .standard, + isHidden: false + ).insert(db) + } + + // Notify the user + // + // Note: Intentionally don't want a 'serverHash' for closed group creation + _ = try Interaction( + threadId: thread.id, + authorId: userPublicKey, + variant: .infoClosedGroupCreated, + timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)) + ).inserted(db) + + memberSendData = try members + .map { memberId -> MessageSender.PreparedSendData in + try MessageSender.preparedSendData( + db, + message: LegacyClosedGroupControlMessage( + kind: .new( + publicKey: Data(hex: groupPublicKey), + name: name, + encryptionKeyPair: Box.KeyPair( + publicKey: encryptionKeyPair.publicKey.bytes, + secretKey: encryptionKeyPair.privateKey.bytes + ), + members: membersAsData, + admins: adminsAsData, + expirationTimer: 0 ), - members: membersAsData, - admins: adminsAsData, - expirationTimer: 0 + // Note: We set this here to ensure the value matches + // the 'ClosedGroup' object we created + sentTimestampMs: UInt64(floor(formationTimestamp * 1000)) ), - // Note: We set this here to ensure the value matches the 'ClosedGroup' - // object we created - sentTimestampMs: UInt64(floor(formationTimestamp * 1000)) - ), - interactionId: nil, - in: contactThread - ) - ) + to: try Message.Destination.from(db, thread: thread), + interactionId: nil + ) + } + } + catch { + return Fail(error: error) + .eraseToAnyPublisher() } - // Store the key pair - try ClosedGroupKeyPair( - threadId: groupPublicKey, - publicKey: encryptionKeyPair.publicKey, - secretKey: encryptionKeyPair.privateKey, - receivedTimestamp: Date().timeIntervalSince1970 - ).insert(db) - - // Notify the PN server - promises.append( - PushNotificationAPI.performOperation( - .subscribe, - for: groupPublicKey, - publicKey: userPublicKey + return Publishers + .MergeMany( + // Send a closed group update message to all members individually + memberSendData + .map { MessageSender.sendImmediate(data: $0) } + .appending( + // Notify the PN server + PushNotificationAPI.performOperation( + .subscribe, + for: groupPublicKey, + publicKey: userPublicKey + ) + ) ) - ) - - // Notify the user - // - // Note: Intentionally don't want a 'serverHash' for closed group creation - _ = try Interaction( - threadId: thread.id, - authorId: userPublicKey, - variant: .infoClosedGroupCreated, - timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)) - ).inserted(db) - - // Start polling - ClosedGroupPoller.shared.startPolling(for: groupPublicKey) - - return when(fulfilled: promises).map2 { thread } + .collect() + .map { _ in thread } + .eraseToAnyPublisher() + .handleEvents( + receiveCompletion: { result in + switch result { + case .failure: break + case .finished: + // Start polling + ClosedGroupPoller.shared.startIfNeeded(for: groupPublicKey) + } + } + ) + .eraseToAnyPublisher() } /// Generates and distributes a new encryption key pair for the group with the given closed group. This sends an @@ -132,34 +152,39 @@ extension MessageSender { allGroupMembers: [GroupMember], closedGroup: ClosedGroup, thread: SessionThread - ) throws -> Promise { + ) -> AnyPublisher { guard allGroupMembers.contains(where: { $0.role == .admin && $0.profileId == userPublicKey }) else { - return Promise(error: MessageSenderError.invalidClosedGroupUpdate) + return Fail(error: MessageSenderError.invalidClosedGroupUpdate) + .eraseToAnyPublisher() } - // Generate the new encryption key pair - let legacyNewKeyPair: ECKeyPair = Curve25519.generateKeyPair() - let newKeyPair: ClosedGroupKeyPair = ClosedGroupKeyPair( - threadId: closedGroup.threadId, - publicKey: legacyNewKeyPair.publicKey, - secretKey: legacyNewKeyPair.privateKey, - receivedTimestamp: Date().timeIntervalSince1970 - ) - // Distribute it - let proto = try SNProtoKeyPair.builder( - publicKey: newKeyPair.publicKey, - privateKey: newKeyPair.secretKey - ).build() - let plaintext = try proto.serializedData() - - distributingKeyPairs.mutate { - $0[closedGroup.id] = ($0[closedGroup.id] ?? []) - .appending(newKeyPair) - } + let newKeyPair: ClosedGroupKeyPair + let sendData: MessageSender.PreparedSendData do { - return try MessageSender - .sendNonDurably( + // Generate the new encryption key pair + let legacyNewKeyPair: ECKeyPair = Curve25519.generateKeyPair() + newKeyPair = ClosedGroupKeyPair( + threadId: closedGroup.threadId, + publicKey: legacyNewKeyPair.publicKey, + secretKey: legacyNewKeyPair.privateKey, + receivedTimestamp: Date().timeIntervalSince1970 + ) + + // Distribute it + let proto = try SNProtoKeyPair.builder( + publicKey: newKeyPair.publicKey, + privateKey: newKeyPair.secretKey + ).build() + let plaintext = try proto.serializedData() + + distributingKeyPairs.mutate { + $0[closedGroup.id] = ($0[closedGroup.id] ?? []) + .appending(newKeyPair) + } + + sendData = try MessageSender + .preparedSendData( db, message: ClosedGroupControlMessage( kind: .encryptionKeyPair( @@ -175,27 +200,35 @@ extension MessageSender { } ) ), - interactionId: nil, - in: thread + to: try Message.Destination.from(db, thread: thread), + interactionId: nil ) - .done { + } + catch { + return Fail(error: error) + .eraseToAnyPublisher() + } + + return MessageSender.sendImmediate(data: sendData) + .map { _ in newKeyPair } + .eraseToAnyPublisher() + .handleEvents( + receiveOutput: { newKeyPair in /// Store it **after** having sent out the message to the group Storage.shared.write { db in try newKeyPair.insert(db) - - distributingKeyPairs.mutate { - if let index = ($0[closedGroup.id] ?? []).firstIndex(of: newKeyPair) { - $0[closedGroup.id] = ($0[closedGroup.id] ?? []) - .removing(index: index) - } + } + + distributingKeyPairs.mutate { + if let index = ($0[closedGroup.id] ?? []).firstIndex(of: newKeyPair) { + $0[closedGroup.id] = ($0[closedGroup.id] ?? []) + .removing(index: index) } } } - .map { _ in } - } - catch { - return Promise(error: MessageSenderError.invalidClosedGroupUpdate) - } + ) + .map { _ in () } + .eraseToAnyPublisher() } public static func update( @@ -203,51 +236,59 @@ extension MessageSender { groupPublicKey: String, with members: Set, name: String - ) throws -> Promise { + ) -> AnyPublisher { // Get the group, check preconditions & prepare guard let thread: SessionThread = try? SessionThread.fetchOne(db, id: groupPublicKey) else { SNLog("Can't update nonexistent closed group.") - return Promise(error: MessageSenderError.noThread) + return Fail(error: MessageSenderError.noThread) + .eraseToAnyPublisher() } guard let closedGroup: ClosedGroup = try? thread.closedGroup.fetchOne(db) else { - return Promise(error: MessageSenderError.invalidClosedGroupUpdate) + return Fail(error: MessageSenderError.invalidClosedGroupUpdate) + .eraseToAnyPublisher() } let userPublicKey: String = getUserHexEncodedPublicKey(db) - // Update name if needed - if name != closedGroup.name { - // Update the group - _ = try ClosedGroup - .filter(id: closedGroup.id) - .updateAll(db, ClosedGroup.Columns.name.set(to: name)) - - // Notify the user - let interaction: Interaction = try Interaction( - threadId: thread.id, - authorId: userPublicKey, - variant: .infoClosedGroupUpdated, - body: ClosedGroupControlMessage.Kind - .nameChange(name: name) - .infoMessage(db, sender: userPublicKey), - timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)) - ).inserted(db) - - guard let interactionId: Int64 = interaction.id else { throw StorageError.objectNotSaved } - - // Send the update to the group - let closedGroupControlMessage = ClosedGroupControlMessage(kind: .nameChange(name: name)) - try MessageSender.send( - db, - message: closedGroupControlMessage, - interactionId: interactionId, - in: thread - ) + do { + // Update name if needed + if name != closedGroup.name { + // Update the group + _ = try ClosedGroup + .filter(id: closedGroup.id) + .updateAll(db, ClosedGroup.Columns.name.set(to: name)) + + // Notify the user + let interaction: Interaction = try Interaction( + threadId: thread.id, + authorId: userPublicKey, + variant: .infoClosedGroupUpdated, + body: LegacyClosedGroupControlMessage.Kind + .nameChange(name: name) + .infoMessage(db, sender: userPublicKey), + timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)) + ).inserted(db) + + guard let interactionId: Int64 = interaction.id else { throw StorageError.objectNotSaved } + + // Send the update to the group + try MessageSender.send( + db, + message: LegacyClosedGroupControlMessage(kind: .nameChange(name: name)), + interactionId: interactionId, + in: thread + ) + } + } + catch { + return Fail(error: error) + .eraseToAnyPublisher() } // Retrieve member info guard let allGroupMembers: [GroupMember] = try? closedGroup.allMembers.fetchAll(db) else { - return Promise(error: MessageSenderError.invalidClosedGroupUpdate) + return Fail(error: MessageSenderError.invalidClosedGroupUpdate) + .eraseToAnyPublisher() } let standardAndZombieMemberIds: [String] = allGroupMembers @@ -268,7 +309,8 @@ extension MessageSender { ) } catch { - return Promise(error: MessageSenderError.invalidClosedGroupUpdate) + return Fail(error: MessageSenderError.invalidClosedGroupUpdate) + .eraseToAnyPublisher() } } @@ -287,11 +329,14 @@ extension MessageSender { ) } catch { - return Promise(error: MessageSenderError.invalidClosedGroupUpdate) + return Fail(error: MessageSenderError.invalidClosedGroupUpdate) + .eraseToAnyPublisher() } } - return Promise.value(()) + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } @@ -395,7 +440,7 @@ extension MessageSender { allGroupMembers: [GroupMember], closedGroup: ClosedGroup, thread: SessionThread - ) throws -> Promise { + ) throws -> AnyPublisher { guard !removedMembers.contains(userPublicKey) else { SNLog("Invalid closed group update.") throw MessageSenderError.invalidClosedGroupUpdate @@ -443,19 +488,22 @@ extension MessageSender { } // Send the update to the group and generate + distribute a new encryption key pair - let promise = try MessageSender - .sendNonDurably( - db, - message: ClosedGroupControlMessage( - kind: .membersRemoved( - members: removedMembers.map { Data(hex: $0) } + return MessageSender + .sendImmediate( + data: try MessageSender + .preparedSendData( + db, + message: LegacyClosedGroupControlMessage( + kind: .membersRemoved( + members: removedMembers.map { Data(hex: $0) } + ) + ), + to: try Message.Destination.from(db, thread: thread), + interactionId: interactionId ) - ), - interactionId: interactionId, - in: thread ) - .map { _ in - try generateAndSendNewEncryptionKeyPair( + .flatMap { _ -> AnyPublisher in + generateAndSendNewEncryptionKeyPair( db, targetMembers: members, userPublicKey: userPublicKey, @@ -464,9 +512,7 @@ extension MessageSender { thread: thread ) } - .map { _ in } - - return promise + .eraseToAnyPublisher() } /// Leave the group with the given `groupPublicKey`. If the current user is the admin, the group is disbanded entirely. If the @@ -477,104 +523,93 @@ extension MessageSender { /// unregisters from push notifications. /// /// The returned promise is fulfilled when the `MEMBER_LEFT` message has been sent to the group. - public static func leave(_ db: Database, groupPublicKey: String) throws -> Promise { + public static func leave( + _ db: Database, + groupPublicKey: String + ) -> AnyPublisher { guard let thread: SessionThread = try? SessionThread.fetchOne(db, id: groupPublicKey) else { SNLog("Can't leave nonexistent closed group.") - return Promise(error: MessageSenderError.noThread) + return Fail(error: MessageSenderError.noThread) + .eraseToAnyPublisher() } - guard let closedGroup: ClosedGroup = try? thread.closedGroup.fetchOne(db) else { - return Promise(error: MessageSenderError.invalidClosedGroupUpdate) + guard thread.closedGroup.isNotEmpty(db) else { + return Fail(error: MessageSenderError.invalidClosedGroupUpdate) + .eraseToAnyPublisher() } let userPublicKey: String = getUserHexEncodedPublicKey(db) + let sendData: MessageSender.PreparedSendData - // Notify the user - let interaction: Interaction = try Interaction( - threadId: thread.id, - authorId: userPublicKey, - variant: .infoClosedGroupCurrentUserLeft, - body: ClosedGroupControlMessage.Kind - .memberLeft - .infoMessage(db, sender: userPublicKey), - timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)) - ).inserted(db) - - guard let interactionId: Int64 = interaction.id else { - throw StorageError.objectNotSaved - } - - // Send the update to the group - let promise = try MessageSender - .sendNonDurably( - db, - message: ClosedGroupControlMessage( - kind: .memberLeft - ), - interactionId: interactionId, - in: thread - ) - .done { - // Remove the group from the database and unsubscribe from PNs - ClosedGroupPoller.shared.stopPolling(for: groupPublicKey) - - Storage.shared.write { db in - try closedGroup - .keyPairs - .deleteAll(db) - - let _ = PushNotificationAPI.performOperation( - .unsubscribe, - for: groupPublicKey, - publicKey: userPublicKey - ) - } + do { + // Notify the user + let interaction: Interaction = try Interaction( + threadId: thread.id, + authorId: userPublicKey, + variant: .infoClosedGroupCurrentUserLeft, + body: LegacyClosedGroupControlMessage.Kind + .memberLeft + .infoMessage(db, sender: userPublicKey), + timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)) + ).inserted(db) + + guard let interactionId: Int64 = interaction.id else { + return Fail(error: StorageError.objectNotSaved) + .eraseToAnyPublisher() } - .map { _ in } - - // Update the group (if the admin leaves the group is disbanded) - let wasAdminUser: Bool = try GroupMember - .filter(GroupMember.Columns.groupId == thread.id) - .filter(GroupMember.Columns.profileId == userPublicKey) - .filter(GroupMember.Columns.role == GroupMember.Role.admin) - .isNotEmpty(db) - - if wasAdminUser { - try GroupMember - .filter(GroupMember.Columns.groupId == thread.id) - .deleteAll(db) - } - else { - try GroupMember + + // Send the update to the group + sendData = try MessageSender + .preparedSendData( + db, + message: LegacyClosedGroupControlMessage( + kind: .memberLeft + ), + to: try Message.Destination.from(db, thread: thread), + interactionId: interactionId + ) + + // Update the group (if the admin leaves the group is disbanded) + let wasAdminUser: Bool = GroupMember .filter(GroupMember.Columns.groupId == thread.id) .filter(GroupMember.Columns.profileId == userPublicKey) - .deleteAll(db) + .filter(GroupMember.Columns.role == GroupMember.Role.admin) + .isNotEmpty(db) + + if wasAdminUser { + try GroupMember + .filter(GroupMember.Columns.groupId == thread.id) + .deleteAll(db) + } + else { + try GroupMember + .filter(GroupMember.Columns.groupId == thread.id) + .filter(GroupMember.Columns.profileId == userPublicKey) + .deleteAll(db) + } + } + catch { + return Fail(error: error) + .eraseToAnyPublisher() } - // Return - return promise + return MessageSender + .sendImmediate(data: sendData) + .handleEvents( + receiveCompletion: { result in + switch result { + case .failure: break + case .finished: try? ClosedGroup.removeKeysAndUnsubscribe(threadId: groupPublicKey) + } + } + ) + .eraseToAnyPublisher() } - /* - public static func requestEncryptionKeyPair(for groupPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) throws { - #if DEBUG - preconditionFailure("Shouldn't currently be in use.") - #endif - // Get the group, check preconditions & prepare - let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey) - let threadID = TSGroupThread.threadId(fromGroupId: groupID) - guard let thread = TSGroupThread.fetch(uniqueId: threadID, transaction: transaction) else { - SNLog("Can't request encryption key pair for nonexistent closed group.") - throw Error.noThread - } - let group = thread.groupModel - guard group.groupMemberIds.contains(getUserHexEncodedPublicKey()) else { return } - // Send the request to the group - let closedGroupControlMessage = ClosedGroupControlMessage(kind: .encryptionKeyPairRequest) - MessageSender.send(closedGroupControlMessage, in: thread, using: transaction) - } - */ - - public static func sendLatestEncryptionKeyPair(_ db: Database, to publicKey: String, for groupPublicKey: String) { + public static func sendLatestEncryptionKeyPair( + _ db: Database, + to publicKey: String, + for groupPublicKey: String + ) { guard let thread: SessionThread = try? SessionThread.fetchOne(db, id: groupPublicKey) else { return SNLog("Couldn't send key pair for nonexistent closed group.") } diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift b/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift index ac1160084..f1099d816 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import Combine import GRDB import PromiseKit import SessionUtilitiesKit @@ -90,6 +91,23 @@ extension MessageSender { ) } + public static func preparedSendData( + _ db: Database, + interaction: Interaction, + in thread: SessionThread + ) throws -> PreparedSendData { + // Only 'VisibleMessage' types can be sent via this method + guard interaction.variant == .standardOutgoing else { throw MessageSenderError.invalidMessage } + guard let interactionId: Int64 = interaction.id else { throw StorageError.objectNotSaved } + + return try MessageSender.preparedSendData( + db, + message: VisibleMessage.from(db, interaction: interaction), + to: try Message.Destination.from(db, thread: thread), + interactionId: interactionId + ) + } + public static func sendNonDurably(_ db: Database, message: Message, interactionId: Int64?, in thread: SessionThread) throws -> Promise { return sendNonDurably( db, @@ -140,10 +158,12 @@ extension MessageSender { on: openGroup.server ) .map { _, response -> String in response.id } + .eraseToAnyPublisher() } return FileServerAPI.upload(data) .map { response -> String in response.id } + .eraseToAnyPublisher() }, encrypt: (openGroup == nil), success: { fileId in seal.fulfill(fileId) }, @@ -155,46 +175,146 @@ extension MessageSender { } // Once the attachments are processed then send the message - return when(resolved: attachmentUploadPromises) - .then { results -> Promise in - let errors: [Error] = results - .compactMap { result -> Error? in - if case .rejected(let error) = result { return error } - - return nil - } - - if let error: Error = errors.first { return Promise(error: error) } - - return Storage.shared.writeAsync { db in - let fileIds: [String] = results - .compactMap { result -> String? in - if case .fulfilled(let value) = result { return value } - - return nil - } + // TODO: Need to update all usages of this method + preconditionFailure() +// return when(resolved: attachmentUploadPromises) +// .then { results -> Promise in +// let errors: [Error] = results +// .compactMap { result -> Error? in +// if case .rejected(let error) = result { return error } +// +// return nil +// } +// +// if let error: Error = errors.first { return Promise(error: error) } +// +// return Storage.shared.writeAsync { db in +// let fileIds: [String] = results +// .compactMap { result -> String? in +// if case .fulfilled(let value) = result { return value } +// +// return nil +// } +// +// return try MessageSender.sendImmediate( +// db, +// message: message, +// to: destination +// .with(fileIds: fileIds), +// interactionId: interactionId +// ) +// } +// } + } + + public static func performUploadsIfNeeded( + preparedSendData: PreparedSendData + ) -> AnyPublisher { + // We need an interactionId in order for a message to have uploads + guard let interactionId: Int64 = preparedSendData.interactionId else { + return Just(preparedSendData) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + + // Ensure we have the rest of the required data + guard let destination: Message.Destination = preparedSendData.destination else { + return Fail(error: MessageSenderError.invalidMessage) + .eraseToAnyPublisher() + } + + let threadId: String = { + switch destination { + case .contact(let publicKey): return publicKey + case .closedGroup(let groupPublicKey): return groupPublicKey + case .openGroup(let roomToken, let server, _, _, _): + return OpenGroup.idFor(roomToken: roomToken, server: server) - return try MessageSender.sendImmediate( - db, - message: message, - to: destination - .with(fileIds: fileIds), - interactionId: interactionId - ) - } + case .openGroupInbox(_, _, let blindedPublicKey): return blindedPublicKey } + }() + let fileIdPublisher: AnyPublisher<[String?], Error> = Storage.shared + .write { db -> AnyPublisher<[String?], Error>? in + let attachmentStateInfo: [Attachment.StateInfo] = (try? Attachment + .stateInfo(interactionId: interactionId, state: .uploading) + .fetchAll(db)) + .defaulting(to: []) + + // If there is no attachment data then just return early + guard !attachmentStateInfo.isEmpty else { return nil } + + // Otherwise we need to generate the upload requests + let openGroup: OpenGroup? = try? OpenGroup.fetchOne(db, id: threadId) + + return Publishers + .MergeMany( + (try? Attachment + .filter(ids: attachmentStateInfo.map { $0.attachmentId }) + .fetchAll(db)) + .defaulting(to: []) + .map { attachment -> AnyPublisher in + Future { resolver in + attachment.upload( + db, + queue: DispatchQueue.global(qos: .userInitiated), + using: { db, data in + if let openGroup: OpenGroup = openGroup { + return OpenGroupAPI + .uploadFile( + db, + bytes: data.bytes, + to: openGroup.roomToken, + on: openGroup.server + ) + .map { _, response -> String in response.id } + .eraseToAnyPublisher() + } + + return FileServerAPI.upload(data) + .map { response -> String in response.id } + .eraseToAnyPublisher() + }, + encrypt: (openGroup == nil), + success: { fileId in resolver(Swift.Result.success(fileId)) }, + failure: { resolver(Swift.Result.failure($0)) } + ) + } + .eraseToAnyPublisher() + } + ) + .collect() + .eraseToAnyPublisher() + } + .defaulting( + to: Just<[String?]>([]) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + ) + + return fileIdPublisher + .map { results in + // Once the attachments are processed then update the PreparedSendData with + // the fileIds associated to the message + let fileIds: [String] = results.compactMap { result -> String? in result } + + return preparedSendData.with(fileIds: fileIds) + } + .eraseToAnyPublisher() } /// This method requires the `db` value to be passed in because if it's called within a `writeAsync` completion block /// it will throw a "re-entrant" fatal error when attempting to write again - public static func syncConfiguration(_ db: Database, forceSyncNow: Bool = true) throws -> Promise { + public static func syncConfiguration( + _ db: Database, + forceSyncNow: Bool = true + ) throws -> AnyPublisher { // If we don't have a userKeyPair yet then there is no need to sync the configuration // as the user doesn't exist yet (this will get triggered on the first launch of a // fresh install due to the migrations getting run) - guard - Identity.userExists(db), - let ed25519SecretKey: [UInt8] = Identity.fetchUserEd25519KeyPair(db)?.secretKey - else { return Promise(error: StorageError.generic) } + guard Identity.userExists(db) else { + return Fail(error: StorageError.generic) + .eraseToAnyPublisher() + } let publicKey: String = getUserHexEncodedPublicKey(db) let legacyDestination: Message.Destination = Message.Destination.contact( @@ -202,8 +322,6 @@ extension MessageSender { namespace: .default ) let legacyConfigurationMessage = try ConfigurationMessage.getCurrent(db) - let (promise, seal) = Promise.pending() - let userConfigMessageChanges: [SharedConfigMessage] = SessionUtil.getChanges( ed25519SecretKey: ed25519SecretKey ) @@ -212,42 +330,7 @@ extension MessageSender { namespace: .userProfileConfig ) - if forceSyncNow { - try MessageSender - .sendImmediate(db, message: legacyConfigurationMessage, to: legacyDestination, interactionId: nil) - .done { seal.fulfill(()) } - .catch { _ in seal.reject(StorageError.generic) } - .retainUntilComplete() - when( - resolved: try userConfigMessageChanges.map { message in - try MessageSender - .sendImmediate( - db, - message: message, - to: destination, - interactionId: nil - ) - } - ) - .done { results in - let hadError: Bool = results.contains { result in - switch result { - case .fulfilled: return false - case .rejected: return true - } - } - - guard !hadError else { - seal.reject(StorageError.generic) - return - } - - seal.fulfill(()) - } - .catch { _ in seal.reject(StorageError.generic) } - .retainUntilComplete() - } - else { + guard forceSyncNow else { JobRunner.add( db, job: Job( @@ -259,9 +342,66 @@ extension MessageSender { ) ) ) - seal.fulfill(()) + + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } - return promise + let sendData: PreparedSendData = try MessageSender.preparedSendData( + db, + message: legacyConfigurationMessage, + to: legacyDestination, + interactionId: nil + ) + + when( + resolved: try userConfigMessageChanges.map { message in + try MessageSender + .sendImmediate( + db, + message: message, + to: destination, + interactionId: nil + ) + } + ) + .done { results in + let hadError: Bool = results.contains { result in + switch result { + case .fulfilled: return false + case .rejected: return true + } + } + + guard !hadError else { + seal.reject(StorageError.generic) + return + } + + seal.fulfill(()) + } + .catch { _ in seal.reject(StorageError.generic) } + .retainUntilComplete() + + // TODO: Test this (does it break anything? want to stop the db write asap) + return Future { resolver in + db.afterNextTransaction { _ in + // TODO: Remove the 'Swift.' + resolver(Swift.Result.success(())) + } + } + .flatMap { _ in MessageSender.sendImmediate(data: sendData) } + .eraseToAnyPublisher() + +// return MessageSender +// .sendImmediate( +// data: try MessageSender.preparedSendData( +// db, +// message: configurationMessage, +// to: destination, +// interactionId: nil +// ) +// ) } } diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index 6a05a0e38..469c440c1 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -1,8 +1,8 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import Combine import GRDB -import PromiseKit import SessionSnodeKit import SessionUtilitiesKit import Sodium @@ -42,38 +42,225 @@ public final class MessageSender { // MARK: - Convenience - public static func sendImmediate(_ db: Database, message: Message, to destination: Message.Destination, interactionId: Int64?) throws -> Promise { - switch destination { - case .contact, .closedGroup: - return try sendToSnodeDestination(db, message: message, to: destination, interactionId: interactionId) - - case .openGroup: - return sendToOpenGroupDestination(db, message: message, to: destination, interactionId: interactionId) - - case .openGroupInbox: - return sendToOpenGroupInboxDestination(db, message: message, to: destination, interactionId: interactionId) +// public static func sendImmediate(_ db: Database, message: Message, to destination: Message.Destination, interactionId: Int64?) throws -> Promise { +// switch destination { +// case .contact, .closedGroup: +// return try sendToSnodeDestination(db, message: message, to: destination, interactionId: interactionId) +// +// case .openGroup: +// return sendToOpenGroupDestination(db, message: message, to: destination, interactionId: interactionId) +// +// case .openGroupInbox: +// return sendToOpenGroupInboxDestination(db, message: message, to: destination, interactionId: interactionId) +// } +// } + // MARK: - Message Preparation + + public struct PreparedSendData { + let shouldSend: Bool + + let message: Message? + let destination: Message.Destination? + let interactionId: Int64? + let isSyncMessage: Bool? + + let snodeMessage: SnodeMessage? + let plaintext: Data? + let ciphertext: Data? + + // TODO: Replace these with the target namespaces + let isClosedGroupMessage: Bool + let isConfigMessage: Bool + + private init( + shouldSend: Bool, + message: Message?, + destination: Message.Destination?, + interactionId: Int64?, + isSyncMessage: Bool?, + snodeMessage: SnodeMessage?, + plaintext: Data?, + ciphertext: Data?, + isClosedGroupMessage: Bool, + isConfigMessage: Bool + ) { + self.shouldSend = shouldSend + + self.message = message + self.destination = destination + self.interactionId = interactionId + self.isSyncMessage = isSyncMessage + + self.snodeMessage = snodeMessage + self.plaintext = plaintext + self.ciphertext = ciphertext + self.isClosedGroupMessage = isClosedGroupMessage + self.isConfigMessage = isConfigMessage + } + + // The default constructor creats an instance that doesn't actually send a message + fileprivate init() { + self.shouldSend = false + + self.message = nil + self.destination = nil + self.interactionId = nil + self.isSyncMessage = nil + + self.snodeMessage = nil + self.plaintext = nil + self.ciphertext = nil + self.isClosedGroupMessage = false + self.isConfigMessage = false + } + + /// This should be used to send a message to one-to-one or closed group conversations + fileprivate init( + message: Message, + destination: Message.Destination, + interactionId: Int64?, + isSyncMessage: Bool?, + snodeMessage: SnodeMessage, + isClosedGroupMessage: Bool, + isConfigMessage: Bool + ) { + self.shouldSend = true + + self.message = message + self.destination = destination + self.interactionId = interactionId + self.isSyncMessage = isSyncMessage + + self.snodeMessage = snodeMessage + self.plaintext = nil + self.ciphertext = nil + self.isClosedGroupMessage = isClosedGroupMessage + self.isConfigMessage = isConfigMessage + } + + /// This should be used to send a message to open group conversations + fileprivate init( + message: Message, + destination: Message.Destination, + interactionId: Int64?, + plaintext: Data + ) { + self.shouldSend = true + + self.message = message + self.destination = destination + self.interactionId = interactionId + self.isSyncMessage = false + + self.snodeMessage = nil + self.plaintext = plaintext + self.ciphertext = nil + self.isClosedGroupMessage = false + self.isConfigMessage = false + } + + /// This should be used to send a message to an open group inbox + fileprivate init( + message: Message, + destination: Message.Destination, + interactionId: Int64?, + ciphertext: Data + ) { + self.shouldSend = true + + self.message = message + self.destination = destination + self.interactionId = interactionId + self.isSyncMessage = false + + self.snodeMessage = nil + self.plaintext = nil + self.ciphertext = ciphertext + self.isClosedGroupMessage = false + self.isConfigMessage = false + } + + // MARK: - Mutation + + internal func with(fileIds: [String]) -> PreparedSendData { + return PreparedSendData( + shouldSend: shouldSend, + message: message, + destination: destination?.with(fileIds: fileIds), + interactionId: interactionId, + isSyncMessage: isSyncMessage, + snodeMessage: snodeMessage, + plaintext: plaintext, + ciphertext: ciphertext, + isClosedGroupMessage: isClosedGroupMessage, + isConfigMessage: isConfigMessage + ) } } - - // MARK: One-on-One Chats & Closed Groups - internal static func sendToSnodeDestination( + public static func preparedSendData( _ db: Database, message: Message, to destination: Message.Destination, interactionId: Int64?, - isSyncMessage: Bool = false - ) throws -> Promise { - let (promise, seal) = Promise.pending() - let userPublicKey: String = getUserHexEncodedPublicKey(db) - let isMainAppActive: Bool = (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) + using dependencies: SMKDependencies = SMKDependencies() + ) throws -> PreparedSendData { + // Common logic for all destinations + let userPublicKey: String = getUserHexEncodedPublicKey(db, dependencies: dependencies) let messageSendTimestamp: Int64 = Int64(floor(Date().timeIntervalSince1970 * 1000)) + let updatedMessage: Message = message - // Set the timestamp, sender and recipient - message.sentTimestamp = ( - message.sentTimestamp ?? // Visible messages will already have their sent timestamp set + // Set the message 'sentTimestamp' (Visible messages will already have their sent timestamp set) + updatedMessage.sentTimestamp = ( + updatedMessage.sentTimestamp ?? UInt64(messageSendTimestamp) ) + + switch destination { + case .contact, .closedGroup: + return try prepareSendToSnodeDestination( + db, + message: updatedMessage, + to: destination, + interactionId: interactionId, + userPublicKey: userPublicKey, + messageSendTimestamp: messageSendTimestamp, + using: dependencies + ) + + case .openGroup: + return try prepareSendToOpenGroupDestination( + db, + message: updatedMessage, + to: destination, + interactionId: interactionId, + messageSendTimestamp: messageSendTimestamp, + using: dependencies + ) + + case .openGroupInbox: + return try prepareSendToOpenGroupInboxDestination( + db, + message: message, + to: destination, + interactionId: interactionId, + userPublicKey: userPublicKey, + messageSendTimestamp: messageSendTimestamp, + using: dependencies + ) + } + } + + internal static func prepareSendToSnodeDestination( + _ db: Database, + message: Message, + to destination: Message.Destination, + interactionId: Int64?, + userPublicKey: String, + messageSendTimestamp: Int64, + isSyncMessage: Bool = false, + using dependencies: SMKDependencies = SMKDependencies() + ) throws -> PreparedSendData { message.sender = userPublicKey message.recipient = { switch destination { @@ -83,29 +270,27 @@ public final class MessageSender { } }() - // Set the failure handler (need it here already for precondition failure handling) - func handleFailure(_ db: Database, with error: MessageSenderError) { - MessageSender.handleFailedMessageSend(db, message: message, with: error, interactionId: interactionId) - seal.reject(error) - } - // Validate the message guard message.isValid else { - handleFailure(db, with: .invalidMessage) - return promise + throw MessageSender.handleFailedMessageSend( + db, + message: message, + with: .invalidMessage, + interactionId: interactionId, + using: dependencies + ) } // Stop here if this is a self-send, unless we should sync the message let isSelfSend: Bool = (message.recipient == userPublicKey) - + guard !isSelfSend || isSyncMessage || Message.shouldSync(message: message) else { - try MessageSender.handleSuccessfulMessageSend(db, message: message, to: destination, interactionId: interactionId) - seal.fulfill(()) - return promise + try MessageSender.handleSuccessfulMessageSend(db, message: message, to: destination, interactionId: interactionId, using: dependencies) + return PreparedSendData() } // Attach the user's profile if needed @@ -126,8 +311,13 @@ public final class MessageSender { // Convert it to protobuf guard let proto = message.toProto(db) else { - handleFailure(db, with: .protoConversionFailed) - return promise + throw MessageSender.handleFailedMessageSend( + db, + message: message, + with: .protoConversionFailed, + interactionId: interactionId, + using: dependencies + ) } // Serialize the protobuf @@ -138,8 +328,13 @@ public final class MessageSender { } catch { SNLog("Couldn't serialize proto due to error: \(error).") - handleFailure(db, with: .other(error)) - return promise + throw MessageSender.handleFailedMessageSend( + db, + message: message, + with: .other(error), + interactionId: interactionId, + using: dependencies + ) } // Encrypt the serialized protobuf @@ -164,8 +359,13 @@ public final class MessageSender { } catch { SNLog("Couldn't encrypt message for destination: \(destination) due to error: \(error).") - handleFailure(db, with: .other(error)) - return promise + throw MessageSender.handleFailedMessageSend( + db, + message: message, + with: .other(error), + interactionId: interactionId, + using: dependencies + ) } // Wrap the result @@ -189,18 +389,27 @@ public final class MessageSender { let wrappedMessage: Data do { - wrappedMessage = try MessageWrapper.wrap(type: kind, timestamp: message.sentTimestamp!, - senderPublicKey: senderPublicKey, base64EncodedContent: ciphertext.base64EncodedString()) + wrappedMessage = try MessageWrapper.wrap( + type: kind, + timestamp: message.sentTimestamp!, + senderPublicKey: senderPublicKey, + base64EncodedContent: ciphertext.base64EncodedString() + ) } catch { SNLog("Couldn't wrap message due to error: \(error).") - handleFailure(db, with: .other(error)) - return promise + throw MessageSender.handleFailedMessageSend( + db, + message: message, + with: .other(error), + interactionId: interactionId, + using: dependencies + ) } // Send the result let base64EncodedData = wrappedMessage.base64EncodedString() - + let snodeMessage = SnodeMessage( recipient: message.recipient!, data: base64EncodedData, @@ -208,125 +417,27 @@ public final class MessageSender { timestampMs: UInt64(messageSendTimestamp + SnodeAPI.clockOffset.wrappedValue) ) - SnodeAPI - .sendMessage( - snodeMessage, - in: namespace - ) - .done(on: DispatchQueue.global(qos: .default)) { promises in - let promiseCount = promises.count - var isSuccess = false - var errorCount = 0 - - promises.forEach { - let _ = $0.done(on: DispatchQueue.global(qos: .default)) { responseData in - guard !isSuccess else { return } // Succeed as soon as the first promise succeeds - isSuccess = true - - Storage.shared.write { db in - let responseJson: JSON? = try? JSONSerialization.jsonObject(with: responseData, options: [ .fragmentsAllowed ]) as? JSON - message.serverHash = (responseJson?["hash"] as? String) - - try MessageSender.handleSuccessfulMessageSend( - db, - message: message, - to: destination, - interactionId: interactionId, - isSyncMessage: isSyncMessage - ) - - let shouldNotify: Bool = { - switch message { - case is VisibleMessage, is UnsendRequest: return !isSyncMessage - case let callMessage as CallMessage: - switch callMessage.kind { - case .preOffer: return true - default: return false - } - - default: return false - } - }() - - /* - if let closedGroupControlMessage = message as? ClosedGroupControlMessage, case .new = closedGroupControlMessage.kind { - shouldNotify = true - } - */ - guard shouldNotify else { - seal.fulfill(()) - return - } - - let job: Job? = Job( - variant: .notifyPushServer, - behaviour: .runOnce, - details: NotifyPushServerJob.Details(message: snodeMessage) - ) - - if isMainAppActive { - JobRunner.add(db, job: job) - seal.fulfill(()) - } - else if let job: Job = job { - NotifyPushServerJob.run( - job, - queue: DispatchQueue.global(qos: .default), - success: { _, _ in seal.fulfill(()) }, - failure: { _, _, _ in - // Always fulfill because the notify PN server job isn't critical. - seal.fulfill(()) - }, - deferred: { _ in - // Always fulfill because the notify PN server job isn't critical. - seal.fulfill(()) - } - ) - } - else { - // Always fulfill because the notify PN server job isn't critical. - seal.fulfill(()) - } - } - } - $0.catch(on: DispatchQueue.global(qos: .default)) { error in - errorCount += 1 - guard errorCount == promiseCount else { return } // Only error out if all promises failed - - Storage.shared.read { db in - handleFailure(db, with: .other(error)) - } - } - } - } - .catch(on: DispatchQueue.global(qos: .default)) { error in - SNLog("Couldn't send message due to error: \(error).") - - Storage.shared.read { db in - handleFailure(db, with: .other(error)) - } - } - - return promise + return PreparedSendData( + message: message, + destination: destination, + interactionId: interactionId, + isSyncMessage: isSyncMessage, + snodeMessage: snodeMessage, + isClosedGroupMessage: (kind == .closedGroupMessage), + isConfigMessage: (message is ConfigurationMessage) + ) } - - // MARK: Open Groups - internal static func sendToOpenGroupDestination( + internal static func prepareSendToOpenGroupDestination( _ db: Database, message: Message, to destination: Message.Destination, interactionId: Int64?, - dependencies: SMKDependencies = SMKDependencies() - ) -> Promise { - let (promise, seal) = Promise.pending() + messageSendTimestamp: Int64, + using dependencies: SMKDependencies = SMKDependencies() + ) throws -> PreparedSendData { let threadId: String - // Set the timestamp, sender and recipient - if message.sentTimestamp == nil { // Visible messages will already have their sent timestamp set - message.sentTimestamp = UInt64(floor(Date().timeIntervalSince1970 * 1000)) - } - switch destination { case .contact, .closedGroup, .openGroupInbox: preconditionFailure() case .openGroup(let roomToken, let server, let whisperTo, let whisperMods, _): @@ -347,10 +458,9 @@ public final class MessageSender { guard let openGroup: OpenGroup = try? OpenGroup.fetchOne(db, id: threadId), let userEdKeyPair: Box.KeyPair = Identity.fetchUserEd25519KeyPair(db), - case .openGroup(let roomToken, let server, let whisperTo, let whisperMods, let fileIds) = destination + case .openGroup(_, let server, _, _, _) = destination else { - seal.reject(MessageSenderError.invalidMessage) - return promise + throw MessageSenderError.invalidMessage } message.sender = { @@ -373,24 +483,21 @@ public final class MessageSender { return SessionId(.blinded, publicKey: blindedKeyPair.publicKey).hexString }() - // Set the failure handler (need it here already for precondition failure handling) - func handleFailure(_ db: Database, with error: MessageSenderError) { - MessageSender.handleFailedMessageSend(db, message: message, with: error, interactionId: interactionId) - seal.reject(error) - } - // Validate the message - guard let message = message as? VisibleMessage else { + guard + let message = message as? VisibleMessage, + message.isValid + else { #if DEBUG - preconditionFailure() - #else - handleFailure(db, with: MessageSenderError.invalidMessage) - return promise + if (message as? VisibleMessage) == nil { preconditionFailure() } #endif - } - guard message.isValid else { - handleFailure(db, with: .invalidMessage) - return promise + throw MessageSender.handleFailedMessageSend( + db, + message: message, + with: .invalidMessage, + interactionId: interactionId, + using: dependencies + ) } // Attach the user's profile @@ -399,14 +506,24 @@ public final class MessageSender { ) if (message.profile?.displayName ?? "").isEmpty { - handleFailure(db, with: .noUsername) - return promise + throw MessageSender.handleFailedMessageSend( + db, + message: message, + with: .noUsername, + interactionId: interactionId, + using: dependencies + ) } // Convert it to protobuf guard let proto = message.toProto(db) else { - handleFailure(db, with: .protoConversionFailed) - return promise + throw MessageSender.handleFailedMessageSend( + db, + message: message, + with: .protoConversionFailed, + interactionId: interactionId, + using: dependencies + ) } // Serialize the protobuf @@ -417,75 +534,39 @@ public final class MessageSender { } catch { SNLog("Couldn't serialize proto due to error: \(error).") - handleFailure(db, with: .other(error)) - return promise - } - - // Send the result - OpenGroupAPI - .send( + throw MessageSender.handleFailedMessageSend( db, - plaintext: plaintext, - to: roomToken, - on: server, - whisperTo: whisperTo, - whisperMods: whisperMods, - fileIds: fileIds, + message: message, + with: .other(error), + interactionId: interactionId, using: dependencies ) - .done(on: DispatchQueue.global(qos: .default)) { responseInfo, data in - message.openGroupServerMessageId = UInt64(data.id) - let serverTimestampMs: UInt64? = data.posted.map { UInt64(floor($0 * 1000)) } - - dependencies.storage.write { db in - // The `posted` value is in seconds but we sent it in ms so need that for de-duping - try MessageSender.handleSuccessfulMessageSend( - db, - message: message, - to: destination, - interactionId: interactionId, - serverTimestampMs: serverTimestampMs - ) - seal.fulfill(()) - } - } - .catch(on: DispatchQueue.global(qos: .default)) { error in - dependencies.storage.read { db in - handleFailure(db, with: .other(error)) - } - } + } - return promise + return PreparedSendData( + message: message, + destination: destination, + interactionId: interactionId, + plaintext: plaintext + ) } - - internal static func sendToOpenGroupInboxDestination( + + internal static func prepareSendToOpenGroupInboxDestination( _ db: Database, message: Message, to destination: Message.Destination, interactionId: Int64?, - dependencies: SMKDependencies = SMKDependencies() - ) -> Promise { - let (promise, seal) = Promise.pending() - let userPublicKey: String = getUserHexEncodedPublicKey(db, dependencies: dependencies) - - guard case .openGroupInbox(let server, let openGroupPublicKey, let recipientBlindedPublicKey) = destination else { - preconditionFailure() - } - - // Set the timestamp, sender and recipient - if message.sentTimestamp == nil { // Visible messages will already have their sent timestamp set - message.sentTimestamp = UInt64(floor(Date().timeIntervalSince1970 * 1000)) + userPublicKey: String, + messageSendTimestamp: Int64, + using dependencies: SMKDependencies = SMKDependencies() + ) throws -> PreparedSendData { + guard case .openGroupInbox(_, let openGroupPublicKey, let recipientBlindedPublicKey) = destination else { + throw MessageSenderError.invalidMessage } message.sender = userPublicKey message.recipient = recipientBlindedPublicKey - // Set the failure handler (need it here already for precondition failure handling) - func handleFailure(_ db: Database, with error: MessageSenderError) { - MessageSender.handleFailedMessageSend(db, message: message, with: error, interactionId: interactionId) - seal.reject(error) - } - // Attach the user's profile if needed if let message: VisibleMessage = message as? VisibleMessage { let profile: Profile = Profile.fetchOrCreateCurrentUser(db) @@ -504,8 +585,13 @@ public final class MessageSender { // Convert it to protobuf guard let proto = message.toProto(db) else { - handleFailure(db, with: .protoConversionFailed) - return promise + throw MessageSender.handleFailedMessageSend( + db, + message: message, + with: .protoConversionFailed, + interactionId: interactionId, + using: dependencies + ) } // Serialize the protobuf @@ -516,8 +602,13 @@ public final class MessageSender { } catch { SNLog("Couldn't serialize proto due to error: \(error).") - handleFailure(db, with: .other(error)) - return promise + throw MessageSender.handleFailedMessageSend( + db, + message: message, + with: .other(error), + interactionId: interactionId, + using: dependencies + ) } // Encrypt the serialized protobuf @@ -533,39 +624,326 @@ public final class MessageSender { } catch { SNLog("Couldn't encrypt message for destination: \(destination) due to error: \(error).") - handleFailure(db, with: .other(error)) - return promise + throw MessageSender.handleFailedMessageSend( + db, + message: message, + with: .other(error), + interactionId: interactionId, + using: dependencies + ) + } + + return PreparedSendData( + message: message, + destination: destination, + interactionId: interactionId, + ciphertext: ciphertext + ) + } + + // MARK: - Sending + + public static func sendImmediate( + data: PreparedSendData, + using dependencies: SMKDependencies = SMKDependencies() + ) -> AnyPublisher { + guard data.shouldSend else { + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + + switch data.destination { + case .contact, .closedGroup: return sendToSnodeDestination(data: data, using: dependencies) + case .openGroup: return sendToOpenGroupDestination(data: data, using: dependencies) + case .openGroupInbox: return sendToOpenGroupInbox(data: data, using: dependencies) + case .none: + return Fail(error: MessageSenderError.invalidMessage) + .eraseToAnyPublisher() + } + } + + // MARK: - One-to-One + + private static func sendToSnodeDestination( + data: PreparedSendData, + using dependencies: SMKDependencies = SMKDependencies() + ) -> AnyPublisher { + guard + let message: Message = data.message, + let destination: Message.Destination = data.destination, + let isSyncMessage: Bool = data.isSyncMessage, + let snodeMessage: SnodeMessage = data.snodeMessage + else { + return Fail(error: MessageSenderError.invalidMessage) + .eraseToAnyPublisher() + } + + let isMainAppActive: Bool = (UserDefaults.sharedLokiProject?[.isMainAppActive]) + .defaulting(to: false) + var isSuccess = false + var errorCount = 0 + + return SnodeAPI + .sendMessage( + snodeMessage, + in: (data.isClosedGroupMessage ? + .legacyClosedGroup : + .default + ) + ) + .subscribe(on: DispatchQueue.global(qos: .default)) + .flatMap { result, totalCount -> AnyPublisher in + switch result { + case .success(let response): + // Don't emit if we've already succeeded + guard !isSuccess else { + return Just(false) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + isSuccess = true + + let updatedMessage: Message = message + updatedMessage.serverHash = response.1.hash + + let job: Job? = Job( + variant: .notifyPushServer, + behaviour: .runOnce, + details: NotifyPushServerJob.Details(message: snodeMessage) + ) + let shouldNotify: Bool = { + switch updatedMessage { + case is VisibleMessage, is UnsendRequest: return !isSyncMessage + case let callMessage as CallMessage: + switch callMessage.kind { + case .preOffer: return true + default: return false + } + + default: return false + } + }() + + return dependencies.storage + .writePublisher { db -> Void in + try MessageSender.handleSuccessfulMessageSend( + db, + message: updatedMessage, + to: destination, + interactionId: data.interactionId, + isSyncMessage: isSyncMessage, + using: dependencies + ) + + guard shouldNotify && isMainAppActive else { return () } + + JobRunner.add(db, job: job) + return () + } + .flatMap { _ -> AnyPublisher in + guard shouldNotify && !isMainAppActive else { + return Just(true) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + guard let job: Job = job else { + return Just(true) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + + return Future { resolver in + NotifyPushServerJob.run( + job, + queue: DispatchQueue.global(qos: .default), + success: { _, _ in resolver(Result.success(true)) }, + failure: { _, _, _ in + // Always fulfill because the notify PN server job isn't critical. + resolver(Result.success(true)) + }, + deferred: { _ in + // Always fulfill because the notify PN server job isn't critical. + resolver(Result.success(true)) + } + ) + } + .eraseToAnyPublisher() + } + .eraseToAnyPublisher() + + case .failure(let error): + errorCount += 1 + + // Only process the error if all promises failed + guard errorCount == totalCount else { + return Just(false) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + + return Fail(error: error) + .eraseToAnyPublisher() + } + } + .filter { $0 } + .handleEvents( + receiveCompletion: { result in + switch result { + case .finished: break + case .failure(let error): + SNLog("Couldn't send message due to error: \(error).") + + dependencies.storage.read { db in + MessageSender.handleFailedMessageSend( + db, + message: message, + with: .other(error), + interactionId: data.interactionId, + using: dependencies + ) + } + } + } + ) + .map { _ in () } + .eraseToAnyPublisher() + } + + // MARK: - Open Groups + + private static func sendToOpenGroupDestination( + data: PreparedSendData, + using dependencies: SMKDependencies = SMKDependencies() + ) -> AnyPublisher { + guard + let message: Message = data.message, + let destination: Message.Destination = data.destination, + case .openGroup(let roomToken, let server, let whisperTo, let whisperMods, let fileIds) = destination, + let plaintext: Data = data.plaintext + else { + return Fail(error: MessageSenderError.invalidMessage) + .eraseToAnyPublisher() } // Send the result - OpenGroupAPI - .send( - db, - ciphertext: ciphertext, - toInboxFor: recipientBlindedPublicKey, - on: server, - using: dependencies - ) - .done(on: DispatchQueue.global(qos: .default)) { responseInfo, data in - message.openGroupServerMessageId = UInt64(data.id) + return dependencies.storage + .readPublisherFlatMap { db in + OpenGroupAPI + .send( + db, + plaintext: plaintext, + to: roomToken, + on: server, + whisperTo: whisperTo, + whisperMods: whisperMods, + fileIds: fileIds, + using: dependencies + ) + } + .subscribe(on: DispatchQueue.global(qos: .default)) + .flatMap { (responseInfo, responseData) -> AnyPublisher in + let serverTimestampMs: UInt64? = responseData.posted.map { UInt64(floor($0 * 1000)) } + let updatedMessage: Message = message + updatedMessage.openGroupServerMessageId = UInt64(responseData.id) - dependencies.storage.write { transaction in + return dependencies.storage.writePublisher { db in + // The `posted` value is in seconds but we sent it in ms so need that for de-duping try MessageSender.handleSuccessfulMessageSend( db, - message: message, + message: updatedMessage, to: destination, - interactionId: interactionId + interactionId: data.interactionId, + serverTimestampMs: serverTimestampMs, + using: dependencies ) - seal.fulfill(()) + + return () } } - .catch(on: DispatchQueue.global(qos: .default)) { error in - dependencies.storage.read { db in - handleFailure(db, with: .other(error)) + .handleEvents( + receiveCompletion: { result in + switch result { + case .finished: break + case .failure(let error): + dependencies.storage.read { db in + MessageSender.handleFailedMessageSend( + db, + message: message, + with: .other(error), + interactionId: data.interactionId, + using: dependencies + ) + } + } } - } + ) + .eraseToAnyPublisher() + } + + private static func sendToOpenGroupInbox( + data: PreparedSendData, + using dependencies: SMKDependencies = SMKDependencies() + ) -> AnyPublisher { + guard + let message: Message = data.message, + let destination: Message.Destination = data.destination, + case .openGroupInbox(let server, _, let recipientBlindedPublicKey) = destination, + let ciphertext: Data = data.ciphertext + else { + return Fail(error: MessageSenderError.invalidMessage) + .eraseToAnyPublisher() + } - return promise + // Send the result + return dependencies.storage + .readPublisherFlatMap { db in + return OpenGroupAPI + .send( + db, + ciphertext: ciphertext, + toInboxFor: recipientBlindedPublicKey, + on: server, + using: dependencies + ) + } + .subscribe(on: DispatchQueue.global(qos: .default)) + .flatMap { (responseInfo, responseData) -> AnyPublisher in + let updatedMessage: Message = message + updatedMessage.openGroupServerMessageId = UInt64(responseData.id) + + return dependencies.storage.writePublisher { db in + // The `posted` value is in seconds but we sent it in ms so need that for de-duping + try MessageSender.handleSuccessfulMessageSend( + db, + message: updatedMessage, + to: destination, + interactionId: data.interactionId, + serverTimestampMs: UInt64(floor(responseData.posted * 1000)), + using: dependencies + ) + + return () + } + } + .handleEvents( + receiveCompletion: { result in + switch result { + case .finished: break + case .failure(let error): + dependencies.storage.read { db in + MessageSender.handleFailedMessageSend( + db, + message: message, + with: .other(error), + interactionId: data.interactionId, + using: dependencies + ) + } + } + } + ) + .eraseToAnyPublisher() } // MARK: Success & Failure Handling @@ -576,7 +954,8 @@ public final class MessageSender { to destination: Message.Destination, interactionId: Int64?, serverTimestampMs: UInt64? = nil, - isSyncMessage: Bool = false + isSyncMessage: Bool = false, + using dependencies: SMKDependencies = SMKDependencies() ) throws { // If the message was a reaction then we want to update the reaction instead of the original // interaciton (which the 'interactionId' is pointing to @@ -650,27 +1029,34 @@ public final class MessageSender { if let message = message as? VisibleMessage { message.syncTarget = publicKey } if let message = message as? ExpirationTimerUpdate { message.syncTarget = publicKey } - // FIXME: Make this a job - try sendToSnodeDestination( - db, - message: message, - to: .contact(publicKey: userPublicKey, namespace: namespace), - interactionId: interactionId, - isSyncMessage: true - ).retainUntilComplete() + MessageSender + .sendToSnodeDestination( + data: try prepareSendToSnodeDestination( + db, + message: message, + to: .contact(publicKey: userPublicKey), + interactionId: interactionId, + userPublicKey: userPublicKey, + messageSendTimestamp: Int64(floor(Date().timeIntervalSince1970 * 1000)), + isSyncMessage: true + ), + using: dependencies + ) + .sinkUntilComplete() } } - public static func handleFailedMessageSend( + @discardableResult private static func handleFailedMessageSend( _ db: Database, message: Message, with error: MessageSenderError, - interactionId: Int64? - ) { + interactionId: Int64?, + using dependencies: SMKDependencies = SMKDependencies() + ) -> Error { // TODO: Revert the local database change // If the message was a reaction then we don't want to do anything to the original // interaciton (which the 'interactionId' is pointing to - guard (message as? VisibleMessage)?.reaction == nil else { return } + guard (message as? VisibleMessage)?.reaction == nil else { return error } // Check if we need to mark any "sending" recipients as "failed" // @@ -685,12 +1071,12 @@ public final class MessageSender { .fetchAll(db)) .defaulting(to: []) - guard !rowIds.isEmpty else { return } + guard !rowIds.isEmpty else { return error } // Need to dispatch to a different thread to prevent a potential db re-entrancy // issue from occuring in some cases DispatchQueue.global(qos: .background).async { - Storage.shared.write { db in + dependencies.storage.write { db in try RecipientState .filter(rowIds.contains(Column.rowID)) .updateAll( @@ -700,6 +1086,8 @@ public final class MessageSender { ) } } + + return error } // MARK: - Convenience @@ -718,26 +1106,3 @@ public final class MessageSender { return nil } } - -// MARK: - Objective-C Support - -// FIXME: Remove when possible - -@objc(SMKMessageSender) -public class SMKMessageSender: NSObject { - @objc(leaveClosedGroupWithPublicKey:) - public static func objc_leave(_ groupPublicKey: String) -> AnyPromise { - let promise = Storage.shared.writeAsync { db in - try MessageSender.leave(db, groupPublicKey: groupPublicKey) - } - - return AnyPromise.from(promise) - } - - @objc(forceSyncConfigurationNow) - public static func objc_forceSyncConfigurationNow() { - Storage.shared.write { db in - try MessageSender.syncConfiguration(db, forceSyncNow: true).retainUntilComplete() - } - } -} diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift index 11499c28f..53453fe80 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift +++ b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift @@ -1,12 +1,12 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +import Foundation +import Combine import GRDB -import PromiseKit import SessionSnodeKit import SessionUtilitiesKit -@objc(LKPushNotificationAPI) -public final class PushNotificationAPI : NSObject { +public enum PushNotificationAPI { struct RegistrationRequestBody: Codable { let token: String let pubKey: String? @@ -28,13 +28,14 @@ public final class PushNotificationAPI : NSObject { } // MARK: - Settings + public static let server = "https://live.apns.getsession.org" public static let serverPublicKey = "642a6585919742e5a2d4dc51244964fbcd8bcab2b75612407de58b810740d049" - private static let maxRetryCount: UInt = 4 + private static let maxRetryCount: Int = 4 private static let tokenExpirationInterval: TimeInterval = 12 * 60 * 60 - @objc public enum ClosedGroupOperation : Int { + public enum ClosedGroupOperation: Int { case subscribe, unsubscribe public var endpoint: String { @@ -44,77 +45,100 @@ public final class PushNotificationAPI : NSObject { } } } - - // MARK: - Initialization - private override init() { } - // MARK: - Registration - public static func unregister(_ token: Data) -> Promise { + public static func unregister(_ token: Data) -> AnyPublisher { let requestBody: RegistrationRequestBody = RegistrationRequestBody(token: token.toHexString(), pubKey: nil) guard let body: Data = try? JSONEncoder().encode(requestBody) else { - return Promise(error: HTTP.Error.invalidJSON) + return Fail(error: HTTPError.invalidJSON) + .eraseToAnyPublisher() } + // Unsubscribe from all closed groups (including ones the user is no longer a member of, + // just in case) + Storage.shared + .readPublisher { db -> (String, Set) in + ( + getUserHexEncodedPublicKey(db), + try ClosedGroup + .select(.threadId) + .asRequest(of: String.self) + .fetchSet(db) + ) + } + .flatMap { userPublicKey, closedGroupPublicKeys in + Publishers + .MergeMany( + closedGroupPublicKeys + .map { closedGroupPublicKey -> AnyPublisher in + PushNotificationAPI + .performOperation( + .unsubscribe, + for: closedGroupPublicKey, + publicKey: userPublicKey + ) + } + ) + .collect() + .eraseToAnyPublisher() + } + .sinkUntilComplete() + + // Unregister for normal push notifications let url = URL(string: "\(server)/unregister")! var request: URLRequest = URLRequest(url: url) request.httpMethod = "POST" request.allHTTPHeaderFields = [ Header.contentType.rawValue: "application/json" ] request.httpBody = body - let promise: Promise = attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global()) { - OnionRequestAPI.sendOnionRequest(request, to: server, with: serverPublicKey) - .map2 { _, data in - guard let response: PushServerResponse = try? data?.decoded(as: PushServerResponse.self) else { - return SNLog("Couldn't unregister from push notifications.") - } - guard response.code != 0 else { - return SNLog("Couldn't unregister from push notifications due to error: \(response.message ?? "nil").") + return OnionRequestAPI + .sendOnionRequest(request, to: server, with: serverPublicKey) + .map { _, data -> Void in + guard let response: PushServerResponse = try? data?.decoded(as: PushServerResponse.self) else { + return SNLog("Couldn't unregister from push notifications.") + } + guard response.code != 0 else { + return SNLog("Couldn't unregister from push notifications due to error: \(response.message ?? "nil").") + } + + return () + } + .retry(maxRetryCount) + .handleEvents( + receiveCompletion: { result in + switch result { + case .finished: break + case .failure: SNLog("Couldn't unregister from push notifications.") } } - } - promise.catch2 { error in - SNLog("Couldn't unregister from push notifications.") - } - - // Unsubscribe from all closed groups (including ones the user is no longer a member of, just in case) - Storage.shared.read { db in - let userPublicKey: String = getUserHexEncodedPublicKey(db) - - try ClosedGroup - .select(.threadId) - .asRequest(of: String.self) - .fetchAll(db) - .forEach { closedGroupPublicKey in - performOperation(.unsubscribe, for: closedGroupPublicKey, publicKey: userPublicKey) - } - } - - return promise + ) + .eraseToAnyPublisher() } - - @objc(unregisterToken:) - public static func objc_unregister(token: Data) -> AnyPromise { - return AnyPromise.from(unregister(token)) - } - - public static func register(with token: Data, publicKey: String, isForcedUpdate: Bool) -> Promise { + + public static func register( + with token: Data, + publicKey: String, + isForcedUpdate: Bool + ) -> AnyPublisher { let hexEncodedToken: String = token.toHexString() let requestBody: RegistrationRequestBody = RegistrationRequestBody(token: hexEncodedToken, pubKey: publicKey) guard let body: Data = try? JSONEncoder().encode(requestBody) else { - return Promise(error: HTTP.Error.invalidJSON) + return Fail(error: HTTPError.invalidJSON) + .eraseToAnyPublisher() } - let userDefaults = UserDefaults.standard - let oldToken = userDefaults[.deviceToken] - let lastUploadTime = userDefaults[.lastDeviceTokenUpload] - let now = Date().timeIntervalSince1970 + let oldToken: String? = UserDefaults.standard[.deviceToken] + let lastUploadTime: Double = UserDefaults.standard[.lastDeviceTokenUpload] + let now: TimeInterval = Date().timeIntervalSince1970 + guard isForcedUpdate || hexEncodedToken != oldToken || now - lastUploadTime > tokenExpirationInterval else { SNLog("Device token hasn't changed or expired; no need to re-upload.") - return Promise { $0.fulfill(()) } + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } let url = URL(string: "\(server)/register")! @@ -123,67 +147,81 @@ public final class PushNotificationAPI : NSObject { request.allHTTPHeaderFields = [ Header.contentType.rawValue: "application/json" ] request.httpBody = body - var promises: [Promise] = [] - - promises.append( - attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global()) { - OnionRequestAPI.sendOnionRequest(request, to: server, with: serverPublicKey) - .map2 { _, data -> Void in - guard let response: PushServerResponse = try? data?.decoded(as: PushServerResponse.self) else { - return SNLog("Couldn't register device token.") + return Publishers + .MergeMany( + [ + OnionRequestAPI + .sendOnionRequest(request, to: server, with: serverPublicKey) + .map { _, data -> Void in + guard let response: PushServerResponse = try? data?.decoded(as: PushServerResponse.self) else { + return SNLog("Couldn't register device token.") + } + guard response.code != 0 else { + return SNLog("Couldn't register device token due to error: \(response.message ?? "nil").") + } + + UserDefaults.standard[.deviceToken] = hexEncodedToken + UserDefaults.standard[.lastDeviceTokenUpload] = now + UserDefaults.standard[.isUsingFullAPNs] = true + return () } - guard response.code != 0 else { - return SNLog("Couldn't register device token due to error: \(response.message ?? "nil").") - } - - userDefaults[.deviceToken] = hexEncodedToken - userDefaults[.lastDeviceTokenUpload] = now - userDefaults[.isUsingFullAPNs] = true - } - } - ) - promises.first?.catch2 { error in - SNLog("Couldn't register device token.") - } - - // Subscribe to all closed groups - promises.append( - contentsOf: Storage.shared - .read { db -> [String] in - try ClosedGroup - .select(.threadId) - .joining( - required: ClosedGroup.members - .filter(GroupMember.Columns.profileId == getUserHexEncodedPublicKey(db)) + .retry(maxRetryCount) + .handleEvents( + receiveCompletion: { result in + switch result { + case .finished: break + case .failure: SNLog("Couldn't register device token.") + } + } ) - .asRequest(of: String.self) - .fetchAll(db) - } - .defaulting(to: []) - .map { closedGroupPublicKey -> Promise in - performOperation(.subscribe, for: closedGroupPublicKey, publicKey: publicKey) - } - ) - - return when(fulfilled: promises) + .eraseToAnyPublisher() + ].appending( + contentsOf: Storage.shared + .read { db -> [String] in + try ClosedGroup + .select(.threadId) + .joining( + required: ClosedGroup.members + .filter(GroupMember.Columns.profileId == getUserHexEncodedPublicKey(db)) + ) + .asRequest(of: String.self) + .fetchAll(db) + } + .defaulting(to: []) + .map { closedGroupPublicKey -> AnyPublisher in + PushNotificationAPI + .performOperation( + .subscribe, + for: closedGroupPublicKey, + publicKey: publicKey + ) + } + ) + ) + .collect() + .map { _ in () } + .eraseToAnyPublisher() } - @objc(registerWithToken:hexEncodedPublicKey:isForcedUpdate:) - public static func objc_register(with token: Data, publicKey: String, isForcedUpdate: Bool) -> AnyPromise { - return AnyPromise.from(register(with: token, publicKey: publicKey, isForcedUpdate: isForcedUpdate)) - } - - @discardableResult - public static func performOperation(_ operation: ClosedGroupOperation, for closedGroupPublicKey: String, publicKey: String) -> Promise { + public static func performOperation( + _ operation: ClosedGroupOperation, + for closedGroupPublicKey: String, + publicKey: String + ) -> AnyPublisher { let isUsingFullAPNs = UserDefaults.standard[.isUsingFullAPNs] let requestBody: ClosedGroupRequestBody = ClosedGroupRequestBody( closedGroupPublicKey: closedGroupPublicKey, pubKey: publicKey ) - guard isUsingFullAPNs else { return Promise { $0.fulfill(()) } } + guard isUsingFullAPNs else { + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } guard let body: Data = try? JSONEncoder().encode(requestBody) else { - return Promise(error: HTTP.Error.invalidJSON) + return Fail(error: HTTPError.invalidJSON) + .eraseToAnyPublisher() } let url = URL(string: "\(server)/\(operation.endpoint)")! @@ -192,26 +230,29 @@ public final class PushNotificationAPI : NSObject { request.allHTTPHeaderFields = [ Header.contentType.rawValue: "application/json" ] request.httpBody = body - let promise: Promise = attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global()) { - OnionRequestAPI.sendOnionRequest(request, to: server, with: serverPublicKey) - .map2 { _, data in - guard let response: PushServerResponse = try? data?.decoded(as: PushServerResponse.self) else { - return SNLog("Couldn't subscribe/unsubscribe for closed group: \(closedGroupPublicKey).") - } - guard response.code != 0 else { - return SNLog("Couldn't subscribe/unsubscribe for closed group: \(closedGroupPublicKey) due to error: \(response.message ?? "nil").") + return OnionRequestAPI + .sendOnionRequest(request, to: server, with: serverPublicKey) + .map { _, data in + guard let response: PushServerResponse = try? data?.decoded(as: PushServerResponse.self) else { + return SNLog("Couldn't subscribe/unsubscribe for closed group: \(closedGroupPublicKey).") + } + guard response.code != 0 else { + return SNLog("Couldn't subscribe/unsubscribe for closed group: \(closedGroupPublicKey) due to error: \(response.message ?? "nil").") + } + + return () + } + .retry(maxRetryCount) + .handleEvents( + receiveCompletion: { result in + switch result { + case .finished: break + case .failure: + SNLog("Couldn't subscribe/unsubscribe for closed group: \(closedGroupPublicKey).") } } - } - promise.catch2 { error in - SNLog("Couldn't subscribe/unsubscribe for closed group: \(closedGroupPublicKey).") - } - return promise - } - - @objc(performOperation:forClosedGroupWithPublicKey:userPublicKey:) - public static func objc_performOperation(_ operation: ClosedGroupOperation, for closedGroupPublicKey: String, publicKey: String) -> AnyPromise { - return AnyPromise.from(performOperation(operation, for: closedGroupPublicKey, publicKey: publicKey)) + ) + .eraseToAnyPublisher() } // MARK: - Notify @@ -219,13 +260,13 @@ public final class PushNotificationAPI : NSObject { public static func notify( recipient: String, with message: String, - maxRetryCount: UInt? = nil, - queue: DispatchQueue = DispatchQueue.global() - ) -> Promise { + maxRetryCount: Int? = nil + ) -> AnyPublisher { let requestBody: NotifyRequestBody = NotifyRequestBody(data: message, sendTo: recipient) guard let body: Data = try? JSONEncoder().encode(requestBody) else { - return Promise(error: HTTP.Error.invalidJSON) + return Fail(error: HTTPError.invalidJSON) + .eraseToAnyPublisher() } let url = URL(string: "\(server)/notify")! @@ -234,19 +275,19 @@ public final class PushNotificationAPI : NSObject { request.allHTTPHeaderFields = [ Header.contentType.rawValue: "application/json" ] request.httpBody = body - let retryCount: UInt = (maxRetryCount ?? PushNotificationAPI.maxRetryCount) - let promise: Promise = attempt(maxRetryCount: retryCount, recoveringOn: queue) { - OnionRequestAPI.sendOnionRequest(request, to: server, with: serverPublicKey) - .map2 { _, data in - guard let response: PushServerResponse = try? data?.decoded(as: PushServerResponse.self) else { - return SNLog("Couldn't send push notification.") - } - guard response.code != 0 else { - return SNLog("Couldn't send push notification due to error: \(response.message ?? "nil").") - } + return OnionRequestAPI + .sendOnionRequest(request, to: server, with: serverPublicKey) + .map { _, data -> Void in + guard let response: PushServerResponse = try? data?.decoded(as: PushServerResponse.self) else { + return SNLog("Couldn't send push notification.") } - } - - return promise + guard response.code != 0 else { + return SNLog("Couldn't send push notification due to error: \(response.message ?? "nil").") + } + + return () + } + .retry(maxRetryCount ?? PushNotificationAPI.maxRetryCount) + .eraseToAnyPublisher() } } diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift index 369959742..1878f6a60 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift @@ -1,41 +1,29 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import Combine import GRDB -import PromiseKit import SessionSnodeKit import SessionUtilitiesKit -public final class ClosedGroupPoller { - private var isPolling: Atomic<[String: Bool]> = Atomic([:]) - private var timers: [String: Timer] = [:] +public final class ClosedGroupPoller: Poller { + public static var namespaces: [SnodeAPI.Namespace] = [.legacyClosedGroup] // MARK: - Settings + override var namespaces: [SnodeAPI.Namespace] { ClosedGroupPoller.namespaces } + override var maxNodePollCount: UInt { 0 } + private static let minPollInterval: Double = 2 private static let maxPollInterval: Double = 30 - // MARK: - Error - - private enum Error: LocalizedError { - case insufficientSnodes - case pollingCanceled - - internal var errorDescription: String? { - switch self { - case .insufficientSnodes: return "No snodes left to poll." - case .pollingCanceled: return "Polling canceled." - } - } - } - // MARK: - Initialization - public static let shared = ClosedGroupPoller() + public static let shared: ClosedGroupPoller = ClosedGroupPoller() // MARK: - Public API - @objc public func start() { + public func start() { // Fetch all closed groups (excluding any don't contain the current user as a // GroupMemeber as the user is no longer a member of those) Storage.shared @@ -50,62 +38,24 @@ public final class ClosedGroupPoller { .fetchAll(db) } .defaulting(to: []) - .forEach { [weak self] groupPublicKey in - self?.startPolling(for: groupPublicKey) + .forEach { [weak self] publicKey in + self?.startIfNeeded(for: publicKey) } } - public func startPolling(for groupPublicKey: String) { - guard isPolling.wrappedValue[groupPublicKey] != true else { return } - - // Might be a race condition that the setUpPolling finishes too soon, - // and the timer is not created, if we mark the group as is polling - // after setUpPolling. So the poller may not work, thus misses messages. - isPolling.mutate { $0[groupPublicKey] = true } - setUpPolling(for: groupPublicKey) - } - - public func stopAllPollers() { - let pollers: [String] = Array(isPolling.wrappedValue.keys) - - pollers.forEach { groupPublicKey in - self.stopPolling(for: groupPublicKey) - } - } - - public func stopPolling(for groupPublicKey: String) { - isPolling.mutate { $0[groupPublicKey] = false } - timers[groupPublicKey]?.invalidate() - } - - // MARK: - Private API + // MARK: - Abstract Methods - private func setUpPolling(for groupPublicKey: String) { - Threading.pollerQueue.async { - ClosedGroupPoller.poll(groupPublicKey, poller: self) - .done(on: Threading.pollerQueue) { [weak self] _ in - self?.pollRecursively(groupPublicKey) - } - .catch(on: Threading.pollerQueue) { [weak self] error in - // The error is logged in poll(_:) - self?.pollRecursively(groupPublicKey) - } - } + override func pollerName(for publicKey: String) -> String { + return "closed group with public key: \(publicKey)" } - private func pollRecursively(_ groupPublicKey: String) { - guard - isPolling.wrappedValue[groupPublicKey] == true, - let thread: SessionThread = Storage.shared.read({ db in try SessionThread.fetchOne(db, id: groupPublicKey) }) - else { return } - - // Get the received date of the last message in the thread. If we don't have any messages yet, pick some - // reasonable fake time interval to use instead - + override func nextPollDelay(for publicKey: String) -> TimeInterval { + // Get the received date of the last message in the thread. If we don't have + // any messages yet, pick some reasonable fake time interval to use instead let lastMessageDate: Date = Storage.shared .read { db in - try thread - .interactions + try Interaction + .filter(Interaction.Columns.threadId == publicKey) .select(.receivedAtTimestampMs) .order(Interaction.Columns.timestampMs.desc) .asRequest(of: Int64.self) @@ -121,193 +71,36 @@ public final class ClosedGroupPoller { let timeSinceLastMessage: TimeInterval = Date().timeIntervalSince(lastMessageDate) let minPollInterval: Double = ClosedGroupPoller.minPollInterval let limit: Double = (12 * 60 * 60) - let a = (ClosedGroupPoller.maxPollInterval - minPollInterval) / limit - let nextPollInterval = a * min(timeSinceLastMessage, limit) + minPollInterval - SNLog("Next poll interval for closed group with public key: \(groupPublicKey) is \(nextPollInterval) s.") + let a: TimeInterval = ((ClosedGroupPoller.maxPollInterval - minPollInterval) / limit) + let nextPollInterval: TimeInterval = a * min(timeSinceLastMessage, limit) + minPollInterval + SNLog("Next poll interval for closed group with public key: \(publicKey) is \(nextPollInterval) s.") - timers[groupPublicKey] = Timer.scheduledTimerOnMainThread(withTimeInterval: nextPollInterval, repeats: false) { [weak self] timer in - timer.invalidate() - - Threading.pollerQueue.async { - ClosedGroupPoller.poll(groupPublicKey, poller: self) - .done(on: Threading.pollerQueue) { _ in - self?.pollRecursively(groupPublicKey) - } - .catch(on: Threading.pollerQueue) { error in - // The error is logged in poll(_:) - self?.pollRecursively(groupPublicKey) - } - } - } + return nextPollInterval } - public static func poll( - _ groupPublicKey: String, - on queue: DispatchQueue = SessionSnodeKit.Threading.workQueue, - maxRetryCount: UInt = 0, - calledFromBackgroundPoller: Bool = false, - isBackgroundPollValid: @escaping (() -> Bool) = { true }, - poller: ClosedGroupPoller? = nil - ) -> Promise { - let promise: Promise = SnodeAPI.getSwarm(for: groupPublicKey) - .then(on: queue) { swarm -> Promise in - // randomElement() uses the system's default random generator, which is cryptographically secure - guard let snode = swarm.randomElement() else { return Promise(error: Error.insufficientSnodes) } - - return attempt(maxRetryCount: maxRetryCount, recoveringOn: queue) { - guard - (calledFromBackgroundPoller && isBackgroundPollValid()) || - poller?.isPolling.wrappedValue[groupPublicKey] == true - else { return Promise(error: Error.pollingCanceled) } - - let promises: [Promise<([SnodeReceivedMessage], String?)>] = { - if SnodeAPI.hardfork >= 19 && SnodeAPI.softfork >= 1 { - return [ SnodeAPI.getMessages(from: snode, associatedWith: groupPublicKey, authenticated: false) ] - } - - if SnodeAPI.hardfork >= 19 { - return [ - SnodeAPI.getClosedGroupMessagesFromDefaultNamespace(from: snode, associatedWith: groupPublicKey), - SnodeAPI.getMessages(from: snode, associatedWith: groupPublicKey, authenticated: false) - ] - } - - return [ SnodeAPI.getClosedGroupMessagesFromDefaultNamespace(from: snode, associatedWith: groupPublicKey) ] - }() - - return when(resolved: promises) - .then(on: queue) { messageResults -> Promise in - guard - (calledFromBackgroundPoller && isBackgroundPollValid()) || - poller?.isPolling.wrappedValue[groupPublicKey] == true - else { return Promise.value(()) } - - var promises: [Promise] = [] - var jobToRun: Job? = nil - let allMessages: [SnodeReceivedMessage] = messageResults - .reduce([]) { result, next in - switch next { - case .fulfilled(let data): return result.appending(contentsOf: data.0) - default: return result - } - } - let allHashes: [String] = messageResults - .reduce([]) { result, next in - switch next { - case .fulfilled(let data): return result.appending(data.1) - default: return result - } - } - .compactMap { $0 } - var messageCount: Int = 0 - var hadValidHashUpdate: Bool = false - - // No need to do anything if there are no messages - guard !allMessages.isEmpty else { - if !calledFromBackgroundPoller { - SNLog("Received no new messages in closed group with public key: \(groupPublicKey)") - } - return Promise.value(()) - } - - // Otherwise process the messages and add them to the queue for handling - Storage.shared.write { db in - let processedMessages: [ProcessedMessage] = allMessages - .compactMap { message -> ProcessedMessage? in - do { - return try Message.processRawReceivedMessage(db, rawMessage: message) - } - catch { - switch error { - // Ignore duplicate & selfSend message errors (and don't bother logging - // them as there will be a lot since we each service node duplicates messages) - case DatabaseError.SQLITE_CONSTRAINT_UNIQUE, - MessageReceiverError.duplicateMessage, - MessageReceiverError.duplicateControlMessage, - MessageReceiverError.selfSend: - break - - case MessageReceiverError.duplicateMessageNewSnode: - hadValidHashUpdate = true - break - - // In the background ignore 'SQLITE_ABORT' (it generally means - // the BackgroundPoller has timed out - case DatabaseError.SQLITE_ABORT: - guard !calledFromBackgroundPoller else { break } - - SNLog("Failed to the database being suspended (running in background with no background task).") - break - - default: SNLog("Failed to deserialize envelope due to error: \(error).") - } - - return nil - } - } - - messageCount = processedMessages.count - - jobToRun = Job( - variant: .messageReceive, - behaviour: .runOnce, - threadId: groupPublicKey, - details: MessageReceiveJob.Details( - messages: processedMessages.map { $0.messageInfo }, - calledFromBackgroundPoller: calledFromBackgroundPoller - ) - ) - - // If we are force-polling then add to the JobRunner so they are persistent and will retry on - // the next app run if they fail but don't let them auto-start - JobRunner.add(db, job: jobToRun, canStartJob: !calledFromBackgroundPoller) - - if messageCount == 0 && !hadValidHashUpdate, !allHashes.isEmpty { - SNLog("Received \(allMessages.count) new message\(messageCount == 1 ? "" : "s") in closed group with public key: \(groupPublicKey), all duplicates - marking the hashes we polled with as invalid") - - // Update the cached validity of the messages - try SnodeReceivedMessageInfo.handlePotentialDeletedOrInvalidHash( - db, - potentiallyInvalidHashes: allHashes, - otherKnownValidHashes: allMessages.map { $0.info.hash } - ) - } - } - - if calledFromBackgroundPoller { - // We want to try to handle the receive jobs immediately in the background - promises = promises.appending( - jobToRun.map { job -> Promise in - let (promise, seal) = Promise.pending() - - // Note: In the background we just want jobs to fail silently - MessageReceiveJob.run( - job, - queue: queue, - success: { _, _ in seal.fulfill(()) }, - failure: { _, _, _ in seal.fulfill(()) }, - deferred: { _ in seal.fulfill(()) } - ) - - return promise - } - ) - } - else if messageCount > 0 || hadValidHashUpdate { - SNLog("Received \(messageCount) new message\(messageCount == 1 ? "" : "s") in closed group with public key: \(groupPublicKey) (duplicates: \(allMessages.count - messageCount))") - } - - return when(fulfilled: promises) - } + override func getSnodeForPolling( + for publicKey: String + ) -> AnyPublisher { + return SnodeAPI.getSwarm(for: publicKey) + .flatMap { swarm -> AnyPublisher in + guard let snode: Snode = swarm.randomElement() else { + return Fail(error: OnionRequestAPIError.insufficientSnodes) + .eraseToAnyPublisher() } + + return Just(snode) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } + .eraseToAnyPublisher() + } + + override func handlePollError(_ error: Error, for publicKey: String) { + SNLog("Polling failed for closed group with public key: \(publicKey) due to error: \(error).") - if !calledFromBackgroundPoller { - promise.catch2 { error in - SNLog("Polling failed for closed group with public key: \(groupPublicKey) due to error: \(error).") - } + // Try to restart the poller from scratch + Threading.pollerQueue.async { [weak self] in + self?.setUpPolling(for: publicKey) } - - return promise } } diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift index 378d6fcc2..ebaaba9e1 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift @@ -1,8 +1,9 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import Combine import GRDB -import PromiseKit +import Sodium import SessionSnodeKit import SessionUtilitiesKit @@ -61,11 +62,12 @@ public final class CurrentUserPoller: Poller { } override func getSnodeForPolling( - for publicKey: String, - on queue: DispatchQueue - ) -> Promise { + for publicKey: String + ) -> AnyPublisher { if let targetSnode: Snode = self.targetSnode.wrappedValue { - return Promise.value(targetSnode) + return Just(targetSnode) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } // Used the cached swarm for the given key and update the list of unusedSnodes @@ -77,20 +79,26 @@ public final class CurrentUserPoller: Poller { self.targetSnode.mutate { $0 = nextSnode } self.usedSnodes.mutate { $0.insert(nextSnode) } - return Promise.value(nextSnode) + return Just(nextSnode) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } // If we haven't retrieved a target snode at this point then either the cache // is empty or we have used all of the snodes and need to start from scratch return SnodeAPI.getSwarm(for: publicKey) - .then(on: queue) { [weak self] _ -> Promise in - guard let strongSelf = self else { return Promise(error: SnodeAPIError.generic) } + .flatMap { [weak self] _ -> AnyPublisher in + guard let strongSelf = self else { + return Fail(error: SnodeAPIError.generic) + .eraseToAnyPublisher() + } self?.targetSnode.mutate { $0 = nil } self?.usedSnodes.mutate { $0.removeAll() } - return strongSelf.getSnodeForPolling(for: publicKey, on: queue) + return strongSelf.getSnodeForPolling(for: publicKey) } + .eraseToAnyPublisher() } override func handlePollError(_ error: Error, for publicKey: String) { diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift index 48b8c0dff..bc779d665 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift @@ -1,8 +1,8 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import Combine import GRDB -import PromiseKit import SessionSnodeKit import SessionUtilitiesKit @@ -12,8 +12,8 @@ extension OpenGroupAPI { private let server: String private var timer: Timer? = nil - private var hasStarted = false - private var isPolling = false + private var hasStarted: Bool = false + private var isPolling: Bool = false // MARK: - Settings @@ -28,6 +28,7 @@ extension OpenGroupAPI { } public func startIfNeeded(using dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies()) { + return// TODO: Remove this (reentrancy issues - looks like it could be resolved by splitting out the OpenGroupAPI request signing into it's own step) guard !hasStarted else { return } hasStarted = true @@ -55,7 +56,7 @@ extension OpenGroupAPI { .defaulting(to: 0) let nextPollInterval: TimeInterval = getInterval(for: minPollFailureCount, minInterval: Poller.minPollInterval, maxInterval: Poller.maxPollInterval) - poll(using: dependencies).retainUntilComplete() + poll(using: dependencies).sinkUntilComplete() timer = Timer.scheduledTimerOnMainThread(withTimeInterval: nextPollInterval, repeats: false) { [weak self] timer in timer.invalidate() @@ -65,131 +66,144 @@ extension OpenGroupAPI { } } - @discardableResult - public func poll(using dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies()) -> Promise { - return poll(calledFromBackgroundPoller: false, isPostCapabilitiesRetry: false, using: dependencies) + public func poll( + using dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies() + ) -> AnyPublisher { + return poll( + calledFromBackgroundPoller: false, + isPostCapabilitiesRetry: false, + using: dependencies + ) } - @discardableResult public func poll( calledFromBackgroundPoller: Bool, isBackgroundPollerValid: @escaping (() -> Bool) = { true }, isPostCapabilitiesRetry: Bool, using dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies() - ) -> Promise { - guard !self.isPolling else { return Promise.value(()) } + ) -> AnyPublisher { + guard !self.isPolling else { + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } self.isPolling = true let server: String = self.server - let (promise, seal) = Promise.pending() - promise.retainUntilComplete() - let pollingLogic: () -> Void = { - dependencies.storage - .read { db -> Promise<(Int64, PollResponse)> in - let failureCount: Int64 = (try? OpenGroup - .select(max(OpenGroup.Columns.pollFailureCount)) - .asRequest(of: Int64.self) - .fetchOne(db)) - .defaulting(to: 0) - - return OpenGroupAPI - .poll( - db, - server: server, - hasPerformedInitialPoll: dependencies.cache.hasPerformedInitialPoll[server] == true, - timeSinceLastPoll: ( - dependencies.cache.timeSinceLastPoll[server] ?? - dependencies.cache.getTimeSinceLastOpen(using: dependencies) - ), - using: dependencies - ) - .map(on: OpenGroupAPI.workQueue) { (failureCount, $0) } - } - .done(on: OpenGroupAPI.workQueue) { [weak self] failureCount, response in + return dependencies.storage + .readPublisherFlatMap { db -> AnyPublisher<(Int64, PollResponse), Error> in + let failureCount: Int64 = (try? OpenGroup + .select(max(OpenGroup.Columns.pollFailureCount)) + .asRequest(of: Int64.self) + .fetchOne(db)) + .defaulting(to: 0) + + return OpenGroupAPI + .poll( + db, + server: server, + hasPerformedInitialPoll: dependencies.cache.hasPerformedInitialPoll[server] == true, + timeSinceLastPoll: ( + dependencies.cache.timeSinceLastPoll[server] ?? + dependencies.cache.getTimeSinceLastOpen(using: dependencies) + ), + using: dependencies + ) + .map { response in (failureCount, response) } + .eraseToAnyPublisher() + } + .subscribe( + // If this was run via the background poller then don't run on the pollerQueue + // TODO: Need to test if this dispatches to the next run loop or blocks (want it to block) + on: (calledFromBackgroundPoller ? + DispatchQueue.main : + Threading.pollerQueue + ) + ) + .handleEvents( + receiveOutput: { [weak self] failureCount, response in guard !calledFromBackgroundPoller || isBackgroundPollerValid() else { // If this was a background poll and the background poll is no longer valid // then just stop self?.isPolling = false - seal.fulfill(()) return } - + self?.isPolling = false self?.handlePollResponse( response, failureCount: failureCount, using: dependencies ) - + dependencies.mutableCache.mutate { cache in cache.hasPerformedInitialPoll[server] = true cache.timeSinceLastPoll[server] = Date().timeIntervalSince1970 UserDefaults.standard[.lastOpen] = Date() } - + SNLog("Open group polling finished for \(server).") - seal.fulfill(()) } - .catch(on: OpenGroupAPI.workQueue) { [weak self] error in - guard !calledFromBackgroundPoller || isBackgroundPollerValid() else { - // If this was a background poll and the background poll is no longer valid - // then just stop - self?.isPolling = false - seal.fulfill(()) - return - } - - // If we are retrying then the error is being handled so no need to continue (this - // method will always resolve) - self?.updateCapabilitiesAndRetryIfNeeded( + ) + .map { _ in () } + .catch { [weak self] error -> AnyPublisher in + guard + let strongSelf = self, + (!calledFromBackgroundPoller || isBackgroundPollerValid()) + else { + // If this was a background poll and the background poll is no longer valid + // then just stop + self?.isPolling = false + + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + + // If we are retrying then the error is being handled so no need to continue (this + // method will always resolve) + return strongSelf + .updateCapabilitiesAndRetryIfNeeded( server: server, calledFromBackgroundPoller: calledFromBackgroundPoller, isBackgroundPollerValid: isBackgroundPollerValid, isPostCapabilitiesRetry: isPostCapabilitiesRetry, error: error ) - .done(on: OpenGroupAPI.workQueue) { [weak self] didHandleError in - if !didHandleError && isBackgroundPollerValid() { - // Increase the failure count - let pollFailureCount: Int64 = Storage.shared - .read { db in + .handleEvents( + receiveOutput: { [weak self] didHandleError in + if !didHandleError && isBackgroundPollerValid() { + // Increase the failure count + let pollFailureCount: Int64 = Storage.shared + .read { db in + try OpenGroup + .filter(OpenGroup.Columns.server == server) + .select(max(OpenGroup.Columns.pollFailureCount)) + .asRequest(of: Int64.self) + .fetchOne(db) + } + .defaulting(to: 0) + + Storage.shared.writeAsync { db in try OpenGroup .filter(OpenGroup.Columns.server == server) - .select(max(OpenGroup.Columns.pollFailureCount)) - .asRequest(of: Int64.self) - .fetchOne(db) + .updateAll( + db, + OpenGroup.Columns.pollFailureCount.set(to: (pollFailureCount + 1)) + ) } - .defaulting(to: 0) - - Storage.shared.writeAsync { db in - try OpenGroup - .filter(OpenGroup.Columns.server == server) - .updateAll( - db, - OpenGroup.Columns.pollFailureCount.set(to: (pollFailureCount + 1)) - ) + + SNLog("Open group polling failed due to error: \(error). Setting failure count to \(pollFailureCount).") } - - SNLog("Open group polling failed due to error: \(error). Setting failure count to \(pollFailureCount).") + + self?.isPolling = false } - - self?.isPolling = false - seal.fulfill(()) // The promise is just used to keep track of when we're done - } - .retainUntilComplete() - } - } - - // If this was run via the background poller then don't run on the pollerQueue - if calledFromBackgroundPoller { - pollingLogic() - } - else { - Threading.pollerQueue.async { pollingLogic() } - } - - return promise + ) + .map { _ in () } + .eraseToAnyPublisher() + } + .eraseToAnyPublisher() } private func updateCapabilitiesAndRetryIfNeeded( @@ -199,7 +213,7 @@ extension OpenGroupAPI { isPostCapabilitiesRetry: Bool, error: Error, using dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies() - ) -> Promise { + ) -> AnyPublisher { /// We want to custom handle a '400' error code due to not having blinded auth as it likely means that we join the /// OpenGroup before blinding was enabled and need to update it's capabilities /// @@ -212,12 +226,14 @@ extension OpenGroupAPI { statusCode == 400, let dataString: String = String(data: data, encoding: .utf8), dataString.contains("Invalid authentication: this server requires the use of blinded ids") - else { return Promise.value(false) } + else { + return Just(false) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } - let (promise, seal) = Promise.pending() - - dependencies.storage - .read { db in + return dependencies.storage + .readPublisherFlatMap { db in OpenGroupAPI.capabilities( db, server: server, @@ -225,8 +241,13 @@ extension OpenGroupAPI { using: dependencies ) } - .then(on: OpenGroupAPI.workQueue) { [weak self] _, responseBody -> Promise in - guard let strongSelf = self, isBackgroundPollerValid() else { return Promise.value(()) } + .subscribe(on: OpenGroupAPI.workQueue) + .flatMap { [weak self] _, responseBody -> AnyPublisher in + guard let strongSelf = self, isBackgroundPollerValid() else { + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } // Handle the updated capabilities and re-trigger the poll strongSelf.isPolling = false @@ -247,15 +268,17 @@ extension OpenGroupAPI { isPostCapabilitiesRetry: true, using: dependencies ) - .ensure { seal.fulfill(true) } + .map { _ in () } + .eraseToAnyPublisher() } - .catch(on: OpenGroupAPI.workQueue) { error in + .map { _ in true } + .catch { error -> AnyPublisher in SNLog("Open group updating capabilities failed due to error: \(error).") - seal.fulfill(true) + return Just(true) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } - .retainUntilComplete() - - return promise + .eraseToAnyPublisher() } private func handlePollResponse( diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift index 7fc7d7b3f..03cf63502 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift @@ -1,142 +1,238 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import Combine import GRDB -import PromiseKit +import Sodium import SessionSnodeKit import SessionUtilitiesKit -public final class Poller { - private var isPolling: Atomic = Atomic(false) - private var usedSnodes = Set() - private var pollCount = 0 - +public class Poller { + private var timers: Atomic<[String: Timer]> = Atomic([:]) + internal var isPolling: Atomic<[String: Bool]> = Atomic([:]) + internal var pollCount: Atomic<[String: Int]> = Atomic([:]) + internal var failureCount: Atomic<[String: Int]> = Atomic([:]) + // MARK: - Settings - private static let pollInterval: TimeInterval = 1.5 - private static let retryInterval: TimeInterval = 0.25 - private static let maxRetryInterval: TimeInterval = 15 + /// The namespaces which this poller queries + internal var namespaces: [SnodeAPI.Namespace] { + preconditionFailure("abstract class - override in subclass") + } - /// After polling a given snode this many times we always switch to a new one. - /// - /// The reason for doing this is that sometimes a snode will be giving us successful responses while - /// it isn't actually getting messages from other snodes. - private static let maxPollCount: UInt = 6 - - // MARK: - Error - - private enum Error: LocalizedError { - case pollLimitReached - - var localizedDescription: String { - switch self { - case .pollLimitReached: return "Poll limit reached for current snode." - } - } + /// The number of times the poller can poll before swapping to a new snode + internal var maxNodePollCount: UInt { + preconditionFailure("abstract class - override in subclass") } // MARK: - Public API public init() {} - public func startIfNeeded() { - guard !isPolling.wrappedValue else { return } + public func stopAllPollers() { + let pollers: [String] = Array(isPolling.wrappedValue.keys) - SNLog("Started polling.") - isPolling.mutate { $0 = true } - setUpPolling() + pollers.forEach { groupPublicKey in + self.stopPolling(for: groupPublicKey) + } } - - public func stop() { - SNLog("Stopped polling.") - isPolling.mutate { $0 = false } - usedSnodes.removeAll() + + public func stopPolling(for publicKey: String) { + isPolling.mutate { $0[publicKey] = false } + timers.mutate { $0[publicKey]?.invalidate() } + } + + // MARK: - Abstract Methods + + /// The name for this poller to appear in the logs + internal func pollerName(for publicKey: String) -> String { + preconditionFailure("abstract class - override in subclass") + } + + internal func nextPollDelay(for publicKey: String) -> TimeInterval { + preconditionFailure("abstract class - override in subclass") + } + + internal func getSnodeForPolling( + for publicKey: String + ) -> AnyPublisher { + preconditionFailure("abstract class - override in subclass") + } + + internal func handlePollError(_ error: Error, for publicKey: String) { + preconditionFailure("abstract class - override in subclass") } // MARK: - Private API - private func setUpPolling(delay: TimeInterval = Poller.retryInterval) { - guard isPolling.wrappedValue else { return } + internal func startIfNeeded(for publicKey: String) { + guard isPolling.wrappedValue[publicKey] != true else { return } - Threading.pollerQueue.async { - let _ = SnodeAPI.getSwarm(for: getUserHexEncodedPublicKey()) - .then(on: Threading.pollerQueue) { [weak self] _ -> Promise in - let (promise, seal) = Promise.pending() - - self?.usedSnodes.removeAll() - self?.pollNextSnode(seal: seal) - - return promise - } - .done(on: Threading.pollerQueue) { [weak self] in - guard self?.isPolling.wrappedValue == true else { return } - - Timer.scheduledTimerOnMainThread(withTimeInterval: Poller.retryInterval, repeats: false) { _ in - self?.setUpPolling() + // Might be a race condition that the setUpPolling finishes too soon, + // and the timer is not created, if we mark the group as is polling + // after setUpPolling. So the poller may not work, thus misses messages + isPolling.mutate { $0[publicKey] = true } + setUpPolling(for: publicKey) + } + + /// We want to initially trigger a poll against the target service node and then run the recursive polling, + /// if an error is thrown during the poll then this should automatically restart the polling + internal func setUpPolling(for publicKey: String) { + guard isPolling.wrappedValue[publicKey] == true else { return } + + let namespaces: [SnodeAPI.Namespace] = self.namespaces + + getSnodeForPolling(for: publicKey) + .subscribe(on: Threading.pollerQueue) + .flatMap { snode -> AnyPublisher in + Poller.poll( + namespaces: namespaces, + from: snode, + for: publicKey, + on: Threading.pollerQueue, + poller: self + ) + } + .receive(on: Threading.pollerQueue) + .sinkUntilComplete( + receiveCompletion: { [weak self] result in + switch result { + case .finished: self?.pollRecursively(for: publicKey) + case .failure(let error): + guard self?.isPolling.wrappedValue[publicKey] == true else { return } + + self?.handlePollError(error, for: publicKey) } } - .catch(on: Threading.pollerQueue) { [weak self] _ in - guard self?.isPolling.wrappedValue == true else { return } - - let nextDelay: TimeInterval = min(Poller.maxRetryInterval, (delay * 1.2)) - Timer.scheduledTimerOnMainThread(withTimeInterval: nextDelay, repeats: false) { _ in - self?.setUpPolling() - } - } - } + ) } - private func pollNextSnode(seal: Resolver) { - let userPublicKey = getUserHexEncodedPublicKey() - let swarm = SnodeAPI.swarmCache.wrappedValue[userPublicKey] ?? [] - let unusedSnodes = swarm.subtracting(usedSnodes) + private func pollRecursively(for publicKey: String) { + guard isPolling.wrappedValue[publicKey] == true else { return } - guard !unusedSnodes.isEmpty else { - seal.fulfill(()) - return - } + let namespaces: [SnodeAPI.Namespace] = self.namespaces + let nextPollInterval: TimeInterval = nextPollDelay(for: publicKey) - // randomElement() uses the system's default random generator, which is cryptographically secure - let nextSnode = unusedSnodes.randomElement()! - usedSnodes.insert(nextSnode) - - poll(nextSnode, seal: seal) - .done2 { - seal.fulfill(()) + timers.mutate { + $0[publicKey] = Timer.scheduledTimerOnMainThread( + withTimeInterval: nextPollInterval, + repeats: false + ) { [weak self] timer in + timer.invalidate() + + self?.getSnodeForPolling(for: publicKey) + .subscribe(on: Threading.pollerQueue) + .flatMap { snode -> AnyPublisher in + Poller.poll( + namespaces: namespaces, + from: snode, + for: publicKey, + on: Threading.pollerQueue, + poller: self + ) + } + .receive(on: Threading.pollerQueue) + .sinkUntilComplete( + receiveCompletion: { result in + switch result { + case .failure(let error): self?.handlePollError(error, for: publicKey) + case .finished: + let maxNodePollCount: UInt = (self?.maxNodePollCount ?? 0) + + // If we have polled this service node more than the + // maximum allowed then throw an error so the parent + // loop can restart the polling + if maxNodePollCount > 0 { + let pollCount: Int = (self?.pollCount.wrappedValue[publicKey] ?? 0) + self?.pollCount.mutate { $0[publicKey] = (pollCount + 1) } + + guard pollCount < maxNodePollCount else { + let newSnodeNextPollInterval: TimeInterval = (self?.nextPollDelay(for: publicKey) ?? nextPollInterval) + + self?.timers.mutate { + $0[publicKey] = Timer.scheduledTimerOnMainThread( + withTimeInterval: newSnodeNextPollInterval, + repeats: false + ) { [weak self] timer in + timer.invalidate() + + self?.pollCount.mutate { $0[publicKey] = 0 } + self?.setUpPolling(for: publicKey) + } + } + return + } + } + + // Otherwise just loop + self?.pollRecursively(for: publicKey) + } + } + ) } - .catch2 { [weak self] error in - if let error = error as? Error, error == .pollLimitReached { - self?.pollCount = 0 - } - else if UserDefaults.sharedLokiProject?[.isMainAppActive] != true { - // Do nothing when an error gets throws right after returning from the background (happens frequently) - } + } + } + + public static func poll( + namespaces: [SnodeAPI.Namespace], + from snode: Snode, + for publicKey: String, + on queue: DispatchQueue, + calledFromBackgroundPoller: Bool = false, + isBackgroundPollValid: @escaping (() -> Bool) = { true }, + poller: Poller? = nil + ) -> AnyPublisher { + // If the polling has been cancelled then don't continue + guard + (calledFromBackgroundPoller && isBackgroundPollValid()) || + poller?.isPolling.wrappedValue[publicKey] == true + else { + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + + let pollerName: String = ( + poller?.pollerName(for: publicKey) ?? + "poller with public key \(publicKey)" + ) + + // Fetch the messages + return SnodeAPI.getMessages(in: namespaces, from: snode, associatedWith: publicKey) + .flatMap { namespacedResults -> AnyPublisher in + guard + (calledFromBackgroundPoller && isBackgroundPollValid()) || + poller?.isPolling.wrappedValue[publicKey] == true else { - SNLog("Polling \(nextSnode) failed; dropping it and switching to next snode.") - SnodeAPI.dropSnodeFromSwarmIfNeeded(nextSnode, publicKey: userPublicKey) + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } - Threading.pollerQueue.async { - self?.pollNextSnode(seal: seal) - } - } - } - - private func poll(_ snode: Snode, seal longTermSeal: Resolver) -> Promise { - guard isPolling.wrappedValue else { return Promise { $0.fulfill(()) } } - - let userPublicKey: String = getUserHexEncodedPublicKey() - - return SnodeAPI.getMessages(from: snode, associatedWith: userPublicKey) - .then(on: Threading.pollerQueue) { [weak self] messages, lastHash -> Promise in - guard self?.isPolling.wrappedValue == true else { return Promise { $0.fulfill(()) } } + let allMessagesCount: Int = namespacedResults + .map { $0.value.data?.messages.count ?? 0 } + .reduce(0, +) - if !messages.isEmpty { - var messageCount: Int = 0 - var hadValidHashUpdate: Bool = false - - Storage.shared.write { db in - messages + // No need to do anything if there are no messages + guard allMessagesCount > 0 else { + if !calledFromBackgroundPoller { + SNLog("Received no new messages in \(pollerName)") + } + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + + // Otherwise process the messages and add them to the queue for handling + let lastHashes: [String] = namespacedResults + .compactMap { $0.value.data?.lastHash } + var messageCount: Int = 0 + var hadValidHashUpdate: Bool = false + var jobsToRun: [Job] = [] + + Storage.shared.write { db in + namespacedResults.forEach { namespace, result in + result.data?.messages .compactMap { message -> ProcessedMessage? in do { return try Message.processRawReceivedMessage(db, rawMessage: message) @@ -198,23 +294,35 @@ public final class Poller { } } } - else { - SNLog("Received no new messages") + + // If we aren't runing in a background poller then just finish immediately + guard calledFromBackgroundPoller else { + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } - self?.pollCount += 1 - - guard (self?.pollCount ?? 0) < Poller.maxPollCount else { - throw Error.pollLimitReached - } - - return withDelay(Poller.pollInterval, completionQueue: Threading.pollerQueue) { - guard let strongSelf = self, strongSelf.isPolling.wrappedValue else { - return Promise { $0.fulfill(()) } - } - - return strongSelf.poll(snode, seal: longTermSeal) - } + // We want to try to handle the receive jobs immediately in the background + return Publishers + .MergeMany( + jobsToRun.map { job -> AnyPublisher in + Future { resolver in + // Note: In the background we just want jobs to fail silently + MessageReceiveJob.run( + job, + queue: queue, + success: { _, _ in resolver(Result.success(())) }, + failure: { _, _, _ in resolver(Result.success(())) }, + deferred: { _ in resolver(Result.success(())) } + ) + } + .eraseToAnyPublisher() + } + ) + .collect() + .map { _ in () } + .eraseToAnyPublisher() } + .eraseToAnyPublisher() } } diff --git a/SessionMessagingKit/Utilities/ProfileManager.swift b/SessionMessagingKit/Utilities/ProfileManager.swift index 79c3435ae..f44ebc548 100644 --- a/SessionMessagingKit/Utilities/ProfileManager.swift +++ b/SessionMessagingKit/Utilities/ProfileManager.swift @@ -2,8 +2,8 @@ import UIKit import CryptoKit +import Combine import GRDB -import PromiseKit import SignalCoreKit import SessionUtilitiesKit @@ -185,22 +185,27 @@ public struct ProfileManager { return } - let queue: DispatchQueue = DispatchQueue.global(qos: .default) let fileName: String = UUID().uuidString.appendingFileExtension("jpg") let filePath: String = ProfileManager.profileAvatarFilepath(filename: fileName) var backgroundTask: OWSBackgroundTask? = OWSBackgroundTask(label: funcName) - queue.async { - OWSLogger.verbose("downloading profile avatar: \(profile.id)") - currentAvatarDownloads.mutate { $0.insert(profile.id) } - - let useOldServer: Bool = (profileUrlStringAtStart.contains(FileServerAPI.oldServer)) - - FileServerAPI - .download(fileId, useOldServer: useOldServer) - .done(on: queue) { data in + OWSLogger.verbose("downloading profile avatar: \(profile.id)") + currentAvatarDownloads.mutate { $0.insert(profile.id) } + + let useOldServer: Bool = (profileUrlStringAtStart.contains(FileServerAPI.oldServer)) + + FileServerAPI + .download(fileId, useOldServer: useOldServer) + .receive(on: DispatchQueue.global(qos: .default)) + .sinkUntilComplete( + receiveCompletion: { _ in currentAvatarDownloads.mutate { $0.remove(profile.id) } + // Redundant but without reading 'backgroundTask' it will warn that the variable + // isn't used + if backgroundTask != nil { backgroundTask = nil } + }, + receiveValue: { data in guard let latestProfile: Profile = Storage.shared.read({ db in try Profile.fetchOne(db, id: profile.id) }) else { return } @@ -242,20 +247,8 @@ public struct ProfileManager { .updateAll(db, Profile.Columns.profilePictureFileName.set(to: fileName)) profileAvatarCache.mutate { $0[fileName] = decryptedData } } - - // Redundant but without reading 'backgroundTask' it will warn that the variable - // isn't used - if backgroundTask != nil { backgroundTask = nil } } - .catch(on: queue) { _ in - currentAvatarDownloads.mutate { $0.remove(profile.id) } - - // Redundant but without reading 'backgroundTask' it will warn that the variable - // isn't used - if backgroundTask != nil { backgroundTask = nil } - } - .retainUntilComplete() - } + ) } // MARK: - Current User Profile @@ -463,38 +456,31 @@ public struct ProfileManager { // Upload the avatar to the FileServer FileServerAPI .upload(encryptedAvatarData) - .done(on: queue) { fileUploadResponse in - let downloadUrl: String = "\(FileServerAPI.server)/files/\(fileUploadResponse.id)" - UserDefaults.standard[.lastProfilePictureUpload] = Date() - - Storage.shared.writeAsync { db in - let profile: Profile = try Profile - .fetchOrCreateCurrentUser(db) - .with( - name: profileName, - profilePictureUrl: .update(downloadUrl), - profilePictureFileName: .update(fileName), - profileEncryptionKey: .update(newProfileKey) - ) - .saved(db) + .receive(on: queue) + .sinkUntilComplete( + receiveCompletion: { result in + switch result { + case .finished: break + case .failure(let error): + SNLog("Updating service with profile failed.") + + let isMaxFileSizeExceeded: Bool = ((error as? HTTPError) == .maxFileSizeExceeded) + failure?(isMaxFileSizeExceeded ? + .avatarUploadMaxFileSizeExceeded : + .avatarUploadFailed + ) + } + }, + receiveValue: { fileUploadResponse in + let downloadUrl: String = "\(FileServerAPI.server)/files/\(fileUploadResponse.id)" // Update the cached avatar image value profileAvatarCache.mutate { $0[fileName] = data } - SNLog("Successfully updated service with profile.") - try success?(db, profile) + SNLog("Successfully uploaded avatar image.") + success((downloadUrl, fileName), newProfileKey) } - } - .recover(on: queue) { error in - SNLog("Updating service with profile failed.") - - let isMaxFileSizeExceeded: Bool = ((error as? HTTP.Error) == HTTP.Error.maxFileSizeExceeded) - failure?(isMaxFileSizeExceeded ? - .avatarUploadMaxFileSizeExceeded : - .avatarUploadFailed - ) - } - .retainUntilComplete() + ) } } } diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index 5ff7d12f9..3ae2f1921 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -1,11 +1,11 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import Combine import GRDB import CallKit import UserNotifications import BackgroundTasks -import PromiseKit import SessionMessagingKit import SignalUtilitiesKit import SignalCoreKit @@ -46,11 +46,16 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension // Handle the push notification AppReadiness.runNowOrWhenAppDidBecomeReady { - let openGroupPollingPromises = self.pollForOpenGroups() + let openGroupPollingPublishers: [AnyPublisher] = self.pollForOpenGroups() defer { - when(resolved: openGroupPollingPromises).done { _ in - self.completeSilenty() - } + // TODO: Test this + Publishers + .MergeMany(openGroupPollingPublishers) + .sinkUntilComplete( + receiveCompletion: { _ in + self.completeSilenty() + } + ) } guard @@ -211,7 +216,7 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension // If we need a config sync then trigger it now if needsConfigSync { Storage.shared.write { db in - try MessageSender.syncConfiguration(db, forceSyncNow: true).retainUntilComplete() + try MessageSender.syncConfiguration(db, forceSyncNow: true).sinkUntilComplete() } } @@ -322,8 +327,8 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension // MARK: - Poll for open groups - private func pollForOpenGroups() -> [Promise] { - let promises: [Promise] = Storage.shared + private func pollForOpenGroups() -> [AnyPublisher] { + return Storage.shared .read { db in // The default room promise creates an OpenGroup with an empty `roomToken` value, // we don't want to start a poller for this as the user hasn't actually joined a room @@ -336,16 +341,16 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension .fetchSet(db) } .defaulting(to: []) - .map { server in + .map { server -> AnyPublisher in OpenGroupAPI.Poller(for: server) .poll(calledFromBackgroundPoller: true, isPostCapabilitiesRetry: false) .timeout( - seconds: 20, - timeoutError: NotificationServiceError.timeout + .seconds(20), + scheduler: DispatchQueue.global(qos: .default), + customError: { NotificationServiceError.timeout } ) + .eraseToAnyPublisher() } - - return promises } private enum NotificationServiceError: Error { diff --git a/SessionShareExtension/ShareVC.swift b/SessionShareExtension/ShareVC.swift index 47d431c1e..b471dfaec 100644 --- a/SessionShareExtension/ShareVC.swift +++ b/SessionShareExtension/ShareVC.swift @@ -93,7 +93,7 @@ final class ShareVC: UINavigationController, ShareViewDelegate { // If we need a config sync then trigger it now if needsConfigSync { Storage.shared.write { db in - try? MessageSender.syncConfiguration(db, forceSyncNow: true).retainUntilComplete() + try? MessageSender.syncConfiguration(db, forceSyncNow: true).sinkUntilComplete() } } diff --git a/SessionSnodeKit/GetSnodePoolJob.swift b/SessionSnodeKit/GetSnodePoolJob.swift index 0d792437f..4739bc411 100644 --- a/SessionSnodeKit/GetSnodePoolJob.swift +++ b/SessionSnodeKit/GetSnodePoolJob.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import Combine import GRDB import SessionUtilitiesKit @@ -28,15 +29,22 @@ public enum GetSnodePoolJob: JobExecutor { // to block if we have no Snode pool and prevent other jobs from failing but avoids having to // wait if we already have a potentially valid snode pool guard !SnodeAPI.hasCachedSnodesInclusingExpired() else { - SnodeAPI.getSnodePool().retainUntilComplete() + SnodeAPI.getSnodePool().sinkUntilComplete() success(job, false) return } SnodeAPI.getSnodePool() - .done(on: queue) { _ in success(job, false) } - .catch(on: queue) { error in failure(job, error, false) } - .retainUntilComplete() + .subscribe(on: queue) + .receive(on: queue) + .sinkUntilComplete( + receiveCompletion: { result in + switch result { + case .finished: success(job, false) + case .failure(let error): failure(job, error, false) + } + } + ) } public static func run() { diff --git a/SessionSnodeKit/Models/SnodeAPIError.swift b/SessionSnodeKit/Models/SnodeAPIError.swift index 60403b0ec..07e3c9366 100644 --- a/SessionSnodeKit/Models/SnodeAPIError.swift +++ b/SessionSnodeKit/Models/SnodeAPIError.swift @@ -11,6 +11,7 @@ public enum SnodeAPIError: LocalizedError { case signingFailed case signatureVerificationFailed case invalidIP + case emptySnodePool // ONS case decryptionFailed @@ -27,6 +28,7 @@ public enum SnodeAPIError: LocalizedError { case .signingFailed: return "Couldn't sign message." case .signatureVerificationFailed: return "Failed to verify the signature." case .invalidIP: return "Invalid IP." + case .emptySnodePool: return "Service Node pool is empty." // ONS case .decryptionFailed: return "Couldn't decrypt ONS name." diff --git a/SessionSnodeKit/Networking/SnodeAPI.swift b/SessionSnodeKit/Networking/SnodeAPI.swift new file mode 100644 index 000000000..ffae488d7 --- /dev/null +++ b/SessionSnodeKit/Networking/SnodeAPI.swift @@ -0,0 +1,1213 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import Combine +import Sodium +import GRDB +import SessionUtilitiesKit + +public final class SnodeAPI { + internal static let sodium: Atomic = Atomic(Sodium()) + + private static var hasLoadedSnodePool: Atomic = Atomic(false) + private static var loadedSwarms: Atomic> = Atomic([]) + private static var getSnodePoolPublisher: Atomic, Error>?> = Atomic(nil) + + /// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions. + internal static var snodeFailureCount: Atomic<[Snode: UInt]> = Atomic([:]) + /// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions. + internal static var snodePool: Atomic> = Atomic([]) + + /// The offset between the user's clock and the Service Node's clock. Used in cases where the + /// user's clock is incorrect. + /// + /// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions. + public static var clockOffset: Atomic = Atomic(0) + /// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions. + public static var swarmCache: Atomic<[String: Set]> = Atomic([:]) + + // MARK: - Hardfork version + + public static var hardfork = UserDefaults.standard[.hardfork] + public static var softfork = UserDefaults.standard[.softfork] + + // MARK: - Settings + + private static let maxRetryCount: Int = 8 + private static let minSwarmSnodeCount: Int = 3 + private static let seedNodePool: Set = { + guard !Features.useTestnet else { + return [ "http://public.loki.foundation:38157" ] + } + + return [ + "https://storage.seed1.loki.network:4433", + "https://storage.seed3.loki.network:4433", + "https://public.loki.foundation:4433" + ] + }() + private static let snodeFailureThreshold: Int = 3 + private static let targetSwarmSnodeCount: Int = 2 + private static let minSnodePoolCount: Int = 12 + + private static func offsetTimestampMsNow() -> UInt64 { + return UInt64( + Int64(floor(Date().timeIntervalSince1970 * 1000)) + + SnodeAPI.clockOffset.wrappedValue + ) + } + + // MARK: Snode Pool Interaction + + private static var hasInsufficientSnodes: Bool { snodePool.wrappedValue.count < minSnodePoolCount } + + private static func loadSnodePoolIfNeeded() { + guard !hasLoadedSnodePool.wrappedValue else { return } + + let fetchedSnodePool: Set = Storage.shared + .read { db in try Snode.fetchSet(db) } + .defaulting(to: []) + + snodePool.mutate { $0 = fetchedSnodePool } + hasLoadedSnodePool.mutate { $0 = true } + } + + private static func setSnodePool(to newValue: Set, db: Database? = nil) { + snodePool.mutate { $0 = newValue } + + if let db: Database = db { + _ = try? Snode.deleteAll(db) + newValue.forEach { try? $0.save(db) } + } + else { + Storage.shared.write { db in + _ = try? Snode.deleteAll(db) + newValue.forEach { try? $0.save(db) } + } + } + } + + private static func dropSnodeFromSnodePool(_ snode: Snode) { + #if DEBUG + dispatchPrecondition(condition: .onQueue(Threading.workQueue)) + #endif + var snodePool = SnodeAPI.snodePool.wrappedValue + snodePool.remove(snode) + setSnodePool(to: snodePool) + } + + @objc public static func clearSnodePool() { + snodePool.mutate { $0.removeAll() } + + Threading.workQueue.async { + setSnodePool(to: []) + } + } + + // MARK: - Swarm Interaction + + private static func loadSwarmIfNeeded(for publicKey: String) { + guard !loadedSwarms.wrappedValue.contains(publicKey) else { return } + + let updatedCacheForKey: Set = Storage.shared + .read { db in try Snode.fetchSet(db, publicKey: publicKey) } + .defaulting(to: []) + + swarmCache.mutate { $0[publicKey] = updatedCacheForKey } + loadedSwarms.mutate { $0.insert(publicKey) } + } + + private static func setSwarm(to newValue: Set, for publicKey: String, persist: Bool = true) { + swarmCache.mutate { $0[publicKey] = newValue } + + guard persist else { return } + + Storage.shared.write { db in + try? newValue.save(db, key: publicKey) + } + } + + public static func dropSnodeFromSwarmIfNeeded(_ snode: Snode, publicKey: String) { + let swarmOrNil = swarmCache.wrappedValue[publicKey] + guard var swarm = swarmOrNil, let index = swarm.firstIndex(of: snode) else { return } + swarm.remove(at: index) + setSwarm(to: swarm, for: publicKey) + } + + // MARK: - Public API + + public static func hasCachedSnodesInclusingExpired() -> Bool { + loadSnodePoolIfNeeded() + + return !hasInsufficientSnodes + } + + public static func getSnodePool() -> AnyPublisher, Error> { + loadSnodePoolIfNeeded() + + let now: Date = Date() + let hasSnodePoolExpired: Bool = given(Storage.shared[.lastSnodePoolRefreshDate]) { + now.timeIntervalSince($0) > 2 * 60 * 60 + }.defaulting(to: true) + let snodePool: Set = SnodeAPI.snodePool.wrappedValue + + guard hasInsufficientSnodes || hasSnodePoolExpired else { + return Just(snodePool) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + + if let getSnodePoolPublisher: AnyPublisher, Error> = getSnodePoolPublisher.wrappedValue { + return getSnodePoolPublisher + } + + let publisher: AnyPublisher, Error> + if snodePool.count < minSnodePoolCount { + publisher = getSnodePoolFromSeedNode() + } + else { + publisher = getSnodePoolFromSnode() + .catch { _ in getSnodePoolFromSeedNode() } + .eraseToAnyPublisher() + } + + getSnodePoolPublisher.mutate { $0 = publisher } + + return publisher + .flatMap { snodePool -> AnyPublisher, Error> in + guard !snodePool.isEmpty else { + return Fail(error: SnodeAPIError.snodePoolUpdatingFailed) + .eraseToAnyPublisher() + } + + return Storage.shared + .writePublisher { db in + db[.lastSnodePoolRefreshDate] = now + setSnodePool(to: snodePool, db: db) + + return snodePool + } + .eraseToAnyPublisher() + } + .handleEvents( + receiveCompletion: { _ in getSnodePoolPublisher.mutate { $0 = nil } } + ) + .eraseToAnyPublisher() + } + + public static func getSessionID(for onsName: String) -> AnyPublisher { + let validationCount = 3 + + // The name must be lowercased + let onsName = onsName.lowercased() + + // Hash the ONS name using BLAKE2b + let nameAsData = [UInt8](onsName.data(using: String.Encoding.utf8)!) + + guard let nameHash = sodium.wrappedValue.genericHash.hash(message: nameAsData) else { + return Fail(error: SnodeAPIError.hashingFailed) + .eraseToAnyPublisher() + } + + // Ask 3 different snodes for the Session ID associated with the given name hash + let base64EncodedNameHash = nameHash.toBase64() + + return Publishers + .MergeMany( + (0.. AnyPublisher in + SnodeAPI + .send( + request: SnodeRequest( + endpoint: .oxenDaemonRPCCall, + body: OxenDaemonRPCRequest( + endpoint: .daemonOnsResolve, + body: ONSResolveRequest( + type: 0, // type 0 means Session + base64EncodedNameHash: base64EncodedNameHash + ) + ) + ), + to: snode, + associatedWith: nil + ) + .decoded(as: ONSResolveResponse.self) + .flatMap { _, response -> AnyPublisher in + do { + let result: String = try response.sessionId( + sodium: sodium.wrappedValue, + nameBytes: nameAsData, + nameHashBytes: nameHash + ) + + return Just(result) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + catch { + return Fail(error: error) + .eraseToAnyPublisher() + } + } + .retry(4) + .eraseToAnyPublisher() + } + } + ) + .subscribe(on: Threading.workQueue) + .collect() + .mapError { _ in SnodeAPIError.validationFailed } + .flatMap { results -> AnyPublisher in + guard results.count == validationCount, Set(results).count == 1 else { + return Fail(error: SnodeAPIError.validationFailed) + .eraseToAnyPublisher() + } + + return Just(results[0]) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + .eraseToAnyPublisher() + } + + public static func getTargetSnodes(for publicKey: String) -> AnyPublisher<[Snode], Error> { + // shuffled() uses the system's default random generator, which is cryptographically secure + return getSwarm(for: publicKey) + .map { Array($0.shuffled().prefix(targetSwarmSnodeCount)) } + .eraseToAnyPublisher() + } + + public static func getSwarm( + for publicKey: String, + using dependencies: SSKDependencies = SSKDependencies() + ) -> AnyPublisher, Error> { + loadSwarmIfNeeded(for: publicKey) + + if let cachedSwarm = swarmCache.wrappedValue[publicKey], cachedSwarm.count >= minSwarmSnodeCount { + return Just(cachedSwarm) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + + SNLog("Getting swarm for: \((publicKey == getUserHexEncodedPublicKey()) ? "self" : publicKey).") + let targetPublicKey: String = (Features.useTestnet ? + publicKey.removingIdPrefixIfNeeded() : + publicKey + ) + + return getRandomSnode() + .flatMap { snode in + SnodeAPI.send( + request: SnodeRequest( + endpoint: .getSwarm, + body: GetSwarmRequest(pubkey: targetPublicKey) + ), + to: snode, + associatedWith: publicKey, + using: dependencies + ) + .retry(4) + .eraseToAnyPublisher() + } + .map { _, responseData in parseSnodes(from: responseData) } + .handleEvents( + receiveOutput: { swarm in setSwarm(to: swarm, for: publicKey) } + ) + .eraseToAnyPublisher() + } + + // MARK: - Retrieve + + public static func getMessages( + in namespaces: [SnodeAPI.Namespace], + from snode: Snode, + associatedWith publicKey: String, + using dependencies: SSKDependencies = SSKDependencies() + ) -> AnyPublisher<[SnodeAPI.Namespace: (info: ResponseInfoType, data: (messages: [SnodeReceivedMessage], lastHash: String?)?)], Error> { + let targetPublicKey: String = (Features.useTestnet ? + publicKey.removingIdPrefixIfNeeded() : + publicKey + ) + var userED25519KeyPair: Box.KeyPair? + + return Just(()) + .setFailureType(to: Error.self) + .flatMap { _ -> Future<[SnodeAPI.Namespace: String], Error> in + Future<[SnodeAPI.Namespace: String], Error> { resolver in + let namespaceLastHash: [SnodeAPI.Namespace: String] = namespaces + .reduce(into: [:]) { result, namespace in + // Prune expired message hashes for this namespace on this service node + SnodeReceivedMessageInfo.pruneExpiredMessageHashInfo( + for: snode, + namespace: namespace, + associatedWith: publicKey + ) + + let maybeLastHash: String? = SnodeReceivedMessageInfo + .fetchLastNotExpired( + for: snode, + namespace: namespace, + associatedWith: publicKey + )? + .hash + + guard let lastHash: String = maybeLastHash else { return } + + result[namespace] = lastHash + } + + resolver(Result.success(namespaceLastHash)) + } + } + .flatMap { namespaceLastHash -> AnyPublisher<[SnodeAPI.Namespace: (info: ResponseInfoType, data: (messages: [SnodeReceivedMessage], lastHash: String?)?)], Error> in + let requests: [SnodeAPI.BatchRequest.Info] + + do { + requests = try namespaces + .map { namespace -> SnodeAPI.BatchRequest.Info in + // Check if this namespace requires authentication + guard namespace.requiresReadAuthentication else { + return BatchRequest.Info( + request: SnodeRequest( + endpoint: .getMessages, + body: LegacyGetMessagesRequest( + pubkey: targetPublicKey, + lastHash: (namespaceLastHash[namespace] ?? ""), + namespace: namespace + ) + ), + responseType: GetMessagesResponse.self + ) + } + + // Generate the signature + guard let keyPair: Box.KeyPair = (userED25519KeyPair ?? Storage.shared.read { db in Identity.fetchUserEd25519KeyPair(db) }) else { + throw SnodeAPIError.signingFailed + } + + userED25519KeyPair = keyPair + + return BatchRequest.Info( + request: SnodeRequest( + endpoint: .getMessages, + body: GetMessagesRequest( + lastHash: (namespaceLastHash[namespace] ?? ""), + namespace: namespace, + pubkey: targetPublicKey, + subkey: nil, + timestampMs: SnodeAPI.offsetTimestampMsNow(), + ed25519PublicKey: keyPair.publicKey, + ed25519SecretKey: keyPair.secretKey + ) + ), + responseType: GetMessagesResponse.self + ) + } + } + catch { + return Fail(error: error) + .eraseToAnyPublisher() + } + + let responseTypes = requests.map { $0.responseType } + + return SnodeAPI + .send( + request: SnodeRequest( + endpoint: .batch, + body: BatchRequest(requests: requests) + ), + to: snode, + associatedWith: publicKey, + using: dependencies + ) + .decoded(as: responseTypes, using: dependencies) + .map { batchResponse -> [SnodeAPI.Namespace: (info: ResponseInfoType, data: (messages: [SnodeReceivedMessage], lastHash: String?)?)] in + zip(namespaces, batchResponse) + .reduce(into: [:]) { result, next in + guard let messageResponse: GetMessagesResponse = (next.1.1 as? HTTP.BatchSubResponse)?.body else { + return + } + + let namespace: SnodeAPI.Namespace = next.0 + let requestInfo: ResponseInfoType = next.1.0 + + result[namespace] = ( + requestInfo, + ( + messageResponse.messages + .compactMap { rawMessage -> SnodeReceivedMessage? in + SnodeReceivedMessage( + snode: snode, + publicKey: publicKey, + namespace: namespace, + rawMessage: rawMessage + ) + }, + namespaceLastHash[namespace] + ) + ) + } + } + .eraseToAnyPublisher() + } + .eraseToAnyPublisher() + } + + // MARK: Store + + public static func sendMessage( + _ message: SnodeMessage, + in namespace: Namespace, + using dependencies: SSKDependencies = SSKDependencies() + ) -> AnyPublisher<(Result<(ResponseInfoType, SendMessagesResponse), Error>, Int), Error> { + let publicKey: String = (Features.useTestnet ? + message.recipient.removingIdPrefixIfNeeded() : + message.recipient + ) + + // Create a convenience method to send a message to an individual Snode + func sendMessage(to snode: Snode) -> AnyPublisher<(any ResponseInfoType, SendMessagesResponse), Error> { + guard namespace.requiresWriteAuthentication else { + return SnodeAPI + .send( + request: SnodeRequest( + endpoint: .sendMessage, + body: LegacySendMessagesRequest( + message: message, + namespace: namespace + ) + ), + to: snode, + associatedWith: publicKey, + using: dependencies + ) + .decoded(as: SendMessagesResponse.self, using: dependencies) + .eraseToAnyPublisher() + } + + guard let userED25519KeyPair: Box.KeyPair = Storage.shared.read({ db in Identity.fetchUserEd25519KeyPair(db) }) else { + return Fail(error: SnodeAPIError.noKeyPair) + .eraseToAnyPublisher() + } + + return SnodeAPI + .send( + request: SnodeRequest( + endpoint: .sendMessage, + body: SendMessageRequest( + message: message, + namespace: namespace, + subkey: nil, + timestampMs: SnodeAPI.offsetTimestampMsNow(), + ed25519PublicKey: userED25519KeyPair.publicKey, + ed25519SecretKey: userED25519KeyPair.secretKey + ) + ), + to: snode, + associatedWith: publicKey, + using: dependencies + ) + .decoded(as: SendMessagesResponse.self, using: dependencies) + .eraseToAnyPublisher() + } + + return getTargetSnodes(for: publicKey) + .subscribe(on: Threading.workQueue) + .flatMap { targetSnodes -> AnyPublisher<(Result<(ResponseInfoType, SendMessagesResponse), Error>, Int), Error> in + Publishers + .MergeMany( + targetSnodes + .map { targetSnode -> AnyPublisher, Never> in + return sendMessage(to: targetSnode) + .retry(maxRetryCount) + .eraseToAnyPublisher() + .asResult() + } + ) + .map { result in (result, targetSnodes.count) } + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + .eraseToAnyPublisher() + } + + // MARK: Edit + + public static func updateExpiry( + publicKey: String, + serverHashes: [String], + updatedExpiryMs: UInt64, + using dependencies: SSKDependencies = SSKDependencies() + ) -> AnyPublisher<[String: (hashes: [String], expiry: UInt64)], Error> { + guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else { + return Fail(error: SnodeAPIError.noKeyPair) + .eraseToAnyPublisher() + } + + let publicKey: String = (Features.useTestnet ? + publicKey.removingIdPrefixIfNeeded() : + publicKey + ) + + return getSwarm(for: publicKey) + .subscribe(on: Threading.workQueue) + .flatMap { swarm -> AnyPublisher<[String: (hashes: [String], expiry: UInt64)], Error> in + guard let snode: Snode = swarm.randomElement() else { + return Fail(error: SnodeAPIError.generic) + .eraseToAnyPublisher() + } + + return SnodeAPI + .send( + request: SnodeRequest( + endpoint: .expire, + body: UpdateExpiryRequest( + messageHashes: serverHashes, + expiryMs: updatedExpiryMs, + pubkey: publicKey, + ed25519PublicKey: userED25519KeyPair.publicKey, + ed25519SecretKey: userED25519KeyPair.secretKey, + subkey: nil + ) + ), + to: snode, + associatedWith: publicKey, + using: dependencies + ) + .decoded(as: UpdateExpiryResponse.self, using: dependencies) + .flatMap { _, response -> AnyPublisher<[String: (hashes: [String], expiry: UInt64)], Error> in + do { + let result: [String: (hashes: [String], expiry: UInt64)] = try response.validResultMap( + userX25519PublicKey: getUserHexEncodedPublicKey(), + messageHashes: serverHashes, + sodium: sodium.wrappedValue + ) + + return Just(result) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + catch { + return Fail(error: error) + .eraseToAnyPublisher() + } + } + .retry(maxRetryCount) + .eraseToAnyPublisher() + } + .retry(maxRetryCount) + .eraseToAnyPublisher() + } + + public static func revokeSubkey( + publicKey: String, + subkeyToRevoke: String, + using dependencies: SSKDependencies = SSKDependencies() + ) -> AnyPublisher { + guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else { + return Fail(error: SnodeAPIError.noKeyPair) + .eraseToAnyPublisher() + } + + let publicKey: String = (Features.useTestnet ? + publicKey.removingIdPrefixIfNeeded() : + publicKey + ) + + return getSwarm(for: publicKey) + .subscribe(on: Threading.workQueue) + .flatMap { swarm -> AnyPublisher in + guard let snode: Snode = swarm.randomElement() else { + return Fail(error: SnodeAPIError.generic) + .eraseToAnyPublisher() + } + + return SnodeAPI + .send( + request: SnodeRequest( + endpoint: .revokeSubkey, + body: RevokeSubkeyRequest( + subkeyToRevoke: subkeyToRevoke, + pubkey: publicKey, + ed25519PublicKey: userED25519KeyPair.publicKey, + ed25519SecretKey: userED25519KeyPair.secretKey + ) + ), + to: snode, + associatedWith: publicKey, + using: dependencies + ) + .decoded(as: RevokeSubkeyResponse.self, using: dependencies) + .flatMap { _, response -> AnyPublisher in + do { + try response.validateResult( + userX25519PublicKey: getUserHexEncodedPublicKey(), + subkeyToRevoke: subkeyToRevoke, + sodium: sodium.wrappedValue + ) + } + catch { + return Fail(error: error) + .eraseToAnyPublisher() + } + + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + .retry(maxRetryCount) + .eraseToAnyPublisher() + } + .retry(maxRetryCount) + .eraseToAnyPublisher() + } + + // MARK: Delete + + public static func deleteMessages( + publicKey: String, + serverHashes: [String], + using dependencies: SSKDependencies = SSKDependencies() + ) -> AnyPublisher<[String: Bool], Error> { + guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else { + return Fail(error: SnodeAPIError.noKeyPair) + .eraseToAnyPublisher() + } + + let publicKey: String = (Features.useTestnet ? + publicKey.removingIdPrefixIfNeeded() : + publicKey + ) + let userX25519PublicKey: String = getUserHexEncodedPublicKey() + + return getSwarm(for: publicKey) + .subscribe(on: Threading.workQueue) + .flatMap { swarm -> AnyPublisher<[String: Bool], Error> in + Just(()) + .setFailureType(to: Error.self) + .flatMap { _ -> AnyPublisher in + guard let snode: Snode = swarm.randomElement() else { + return Fail(error: SnodeAPIError.generic) + .eraseToAnyPublisher() + } + + return Just(snode) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + .flatMap { snode -> AnyPublisher<[String: Bool], Error> in + SnodeAPI + .send( + request: SnodeRequest( + endpoint: .deleteMessages, + body: DeleteMessagesRequest( + messageHashes: serverHashes, + requireSuccessfulDeletion: false, + pubkey: userX25519PublicKey, + ed25519PublicKey: userED25519KeyPair.publicKey, + ed25519SecretKey: userED25519KeyPair.secretKey + ) + ), + to: snode, + associatedWith: publicKey, + using: dependencies + ) + .subscribe(on: Threading.workQueue) + .eraseToAnyPublisher() + .decoded(as: DeleteMessagesResponse.self, using: dependencies) + .map { _, response -> [String: Bool] in + let validResultMap: [String: Bool] = response.validResultMap( + userX25519PublicKey: userX25519PublicKey, + serverHashes: serverHashes, + sodium: sodium.wrappedValue + ) + + // If at least one service node deleted successfully then we should + // mark the hash as invalid so we don't try to fetch updates using + // that hash going forward (if we do we would end up re-fetching + // all old messages) + if validResultMap.values.contains(true) { + Storage.shared.writeAsync { db in + try? SnodeReceivedMessageInfo.handlePotentialDeletedOrInvalidHash( + db, + potentiallyInvalidHashes: serverHashes + ) + } + } + + return validResultMap + } + .retry(maxRetryCount) + .eraseToAnyPublisher() + } + .eraseToAnyPublisher() + } + .retry(maxRetryCount) + .eraseToAnyPublisher() + } + + /// Clears all the user's data from their swarm. Returns a dictionary of snode public key to deletion confirmation. + public static func deleteAllMessages( + namespace: SnodeAPI.Namespace? = nil, + using dependencies: SSKDependencies = SSKDependencies() + ) -> AnyPublisher<[String: Bool], Error> { + guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else { + return Fail(error: SnodeAPIError.noKeyPair) + .eraseToAnyPublisher() + } + + let userX25519PublicKey: String = getUserHexEncodedPublicKey() + + return getSwarm(for: userX25519PublicKey) + .subscribe(on: Threading.workQueue) + .flatMap { swarm -> AnyPublisher<[String: Bool], Error> in + guard let snode: Snode = swarm.randomElement() else { + return Fail(error: SnodeAPIError.generic) + .eraseToAnyPublisher() + } + + return getNetworkTime(from: snode) + .flatMap { timestampMs -> AnyPublisher<[String: Bool], Error> in + SnodeAPI + .send( + request: SnodeRequest( + endpoint: .deleteAll, + body: DeleteAllMessagesRequest( + namespace: namespace, + pubkey: userX25519PublicKey, + timestampMs: timestampMs, + ed25519PublicKey: userED25519KeyPair.publicKey, + ed25519SecretKey: userED25519KeyPair.secretKey + ) + ), + to: snode, + associatedWith: nil, + using: dependencies + ) + .decoded(as: DeleteAllMessagesResponse.self, using: dependencies) + .map { _, response in + let validResultMap: [String: Bool] = response.validResultMap( + userX25519PublicKey: userX25519PublicKey, + timestampMs: timestampMs, + sodium: sodium.wrappedValue + ) + + return validResultMap + } + .eraseToAnyPublisher() + } + .retry(maxRetryCount) + .eraseToAnyPublisher() + } + .retry(maxRetryCount) + .eraseToAnyPublisher() + } + + /// Clears all the user's data from their swarm. Returns a dictionary of snode public key to deletion confirmation. + public static func deleteAllMessages( + beforeMs: UInt64, + namespace: SnodeAPI.Namespace? = nil, + using dependencies: SSKDependencies = SSKDependencies() + ) -> AnyPublisher<[String: Bool], Error> { + guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else { + return Fail(error: SnodeAPIError.noKeyPair) + .eraseToAnyPublisher() + } + + let userX25519PublicKey: String = getUserHexEncodedPublicKey() + + return getSwarm(for: userX25519PublicKey) + .subscribe(on: Threading.workQueue) + .flatMap { swarm -> AnyPublisher<[String: Bool], Error> in + guard let snode: Snode = swarm.randomElement() else { + return Fail(error: SnodeAPIError.generic) + .eraseToAnyPublisher() + } + + return getNetworkTime(from: snode) + .flatMap { timestampMs -> AnyPublisher<[String: Bool], Error> in + SnodeAPI + .send( + request: SnodeRequest( + endpoint: .deleteAllBefore, + body: DeleteAllBeforeRequest( + beforeMs: beforeMs, + namespace: namespace, + pubkey: userX25519PublicKey, + timestampMs: timestampMs, + ed25519PublicKey: userED25519KeyPair.publicKey, + ed25519SecretKey: userED25519KeyPair.secretKey + ) + ), + to: snode, + associatedWith: nil, + using: dependencies + ) + .decoded(as: DeleteAllBeforeResponse.self, using: dependencies) + .map { _, response in + let validResultMap: [String: Bool] = response.validResultMap( + userX25519PublicKey: userX25519PublicKey, + beforeMs: beforeMs, + sodium: sodium.wrappedValue + ) + + return validResultMap + } + .eraseToAnyPublisher() + } + .retry(maxRetryCount) + .eraseToAnyPublisher() + } + .retry(maxRetryCount) + .eraseToAnyPublisher() + } + + // MARK: - Internal API + + private static func getNetworkTime( + from snode: Snode, + using dependencies: SSKDependencies = SSKDependencies() + ) -> AnyPublisher { + return SnodeAPI + .send( + request: SnodeRequest<[String: String]>( + endpoint: .getInfo, + body: [:] + ), + to: snode, + associatedWith: nil + ) + .decoded(as: GetNetworkTimestampResponse.self, using: dependencies) + .map { _, response in response.timestamp } + .eraseToAnyPublisher() + } + + internal static func getRandomSnode() -> AnyPublisher { + // randomElement() uses the system's default random generator, which is cryptographically secure + return getSnodePool() + .map { $0.randomElement()! } + .eraseToAnyPublisher() + } + + private static func getSnodePoolFromSeedNode( + dependencies: SSKDependencies = SSKDependencies() + ) -> AnyPublisher, Error> { + let request: SnodeRequest = SnodeRequest( + endpoint: .jsonGetNServiceNodes, + body: GetServiceNodesRequest( + activeOnly: true, + limit: 256, + fields: GetServiceNodesRequest.Fields( + publicIp: true, + storagePort: true, + pubkeyEd25519: true, + pubkeyX25519: true + ) + ) + ) + + guard let target: String = seedNodePool.randomElement() else { + return Fail(error: SnodeAPIError.snodePoolUpdatingFailed) + .eraseToAnyPublisher() + } + guard let payload: Data = try? JSONEncoder().encode(request) else { + return Fail(error: HTTPError.invalidJSON) + .eraseToAnyPublisher() + } + + SNLog("Populating snode pool using seed node: \(target).") + + return HTTP + .execute( + .post, + "\(target)/json_rpc", + body: payload, + useSeedNodeURLSession: true + ) + .decoded(as: SnodePoolResponse.self, using: dependencies) + .subscribe(on: Threading.workQueue) + .mapError { error in + switch error { + case HTTPError.parsingFailed: return SnodeAPIError.snodePoolUpdatingFailed + default: return error + } + } + .map { snodePool -> Set in + snodePool.result + .serviceNodeStates + .compactMap { $0.value } + .asSet() + } + .retry(4) + .handleEvents( + receiveCompletion: { result in + switch result { + case .finished: SNLog("Got snode pool from seed node: \(target).") + case .failure: SNLog("Failed to contact seed node at: \(target).") + } + } + ) + .eraseToAnyPublisher() + } + + private static func getSnodePoolFromSnode( + dependencies: SSKDependencies = SSKDependencies() + ) -> AnyPublisher, Error> { + var snodePool = SnodeAPI.snodePool.wrappedValue + var snodes: Set = [] + (0..<3).forEach { _ in + guard let snode = snodePool.randomElement() else { return } + + snodePool.remove(snode) + snodes.insert(snode) + } + + return Publishers + .MergeMany( + snodes + .map { snode -> AnyPublisher, Error> in + // Don't specify a limit in the request. Service nodes return a shuffled + // list of nodes so if we specify a limit the 3 responses we get might have + // very little overlap. + SnodeAPI + .send( + request: SnodeRequest( + endpoint: .oxenDaemonRPCCall, + body: OxenDaemonRPCRequest( + endpoint: .daemonGetServiceNodes, + body: GetServiceNodesRequest( + activeOnly: true, + limit: nil, + fields: GetServiceNodesRequest.Fields( + publicIp: true, + storagePort: true, + pubkeyEd25519: true, + pubkeyX25519: true + ) + ) + ) + ), + to: snode, + associatedWith: nil + ) + .decoded(as: SnodePoolResponse.self, using: dependencies) + .mapError { error -> Error in + switch error { + case HTTPError.parsingFailed: + return SnodeAPIError.snodePoolUpdatingFailed + + default: return error + } + } + .map { _, snodePool -> Set in + snodePool.result + .serviceNodeStates + .compactMap { $0.value } + .asSet() + } + .retry(4) + .eraseToAnyPublisher() + } + ) + .collect() + .flatMap { results -> AnyPublisher, Error> in + let result: Set = results.reduce(Set()) { prev, next in prev.intersection(next) } + + // We want the snodes to agree on at least this many snodes + guard result.count > 24 else { + return Fail(error: SnodeAPIError.inconsistentSnodePools) + .eraseToAnyPublisher() + } + + // Limit the snode pool size to 256 so that we don't go too long without + // refreshing it + return Just(Set(result.prefix(256))) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + .eraseToAnyPublisher() + } + + private static func send( + request: SnodeRequest, + to snode: Snode, + associatedWith publicKey: String?, + using dependencies: SSKDependencies = SSKDependencies() + ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { + guard let payload: Data = try? JSONEncoder().encode(request) else { + return Fail(error: HTTPError.invalidJSON) + .eraseToAnyPublisher() + } + + guard Features.useOnionRequests else { + return HTTP + .execute( + .post, + "\(snode.address):\(snode.port)/storage_rpc/v1", + body: payload + ) + .map { response in (HTTP.ResponseInfo(code: -1, headers: [:]), response) } + .mapError { error in + switch error { + case HTTPError.httpRequestFailed(let statusCode, let data): + return (SnodeAPI.handleError(withStatusCode: statusCode, data: data, forSnode: snode, associatedWith: publicKey) ?? error) + + default: return error + } + } + .eraseToAnyPublisher() + } + + return dependencies.onionApi + .sendOnionRequest( + payload, + to: snode + ) + .mapError { error in + switch error { + case HTTPError.httpRequestFailed(let statusCode, let data): + return (SnodeAPI.handleError(withStatusCode: statusCode, data: data, forSnode: snode, associatedWith: publicKey) ?? error) + + default: return error + } + } + .handleEvents( + receiveOutput: { _, maybeData in + // Extract and store hard fork information if returned + guard + let data: Data = maybeData, + let snodeResponse: SnodeResponse = try? JSONDecoder() + .decode(SnodeResponse.self, from: data) + else { return } + + if snodeResponse.hardFork[1] > softfork { + softfork = snodeResponse.hardFork[1] + UserDefaults.standard[.softfork] = softfork + } + + if snodeResponse.hardFork[0] > hardfork { + hardfork = snodeResponse.hardFork[0] + UserDefaults.standard[.hardfork] = hardfork + softfork = snodeResponse.hardFork[1] + UserDefaults.standard[.softfork] = softfork + } + } + ) + .eraseToAnyPublisher() + } + + // MARK: - Parsing + + // The parsing utilities below use a best attempt approach to parsing; they warn for parsing + // failures but don't throw exceptions. + + private static func parseSnodes(from responseData: Data?) -> Set { + guard + let responseData: Data = responseData, + let responseJson: JSON = try? JSONSerialization.jsonObject( + with: responseData, + options: [ .fragmentsAllowed ] + ) as? JSON + else { + SNLog("Failed to parse snodes from response data.") + return [] + } + guard let rawSnodes = responseJson["snodes"] as? [JSON] else { + SNLog("Failed to parse snodes from: \(responseJson).") + return [] + } + + guard let snodeData: Data = try? JSONSerialization.data(withJSONObject: rawSnodes, options: []) else { + return [] + } + + // FIXME: Hopefully at some point this different Snode structure will be deprecated and can be removed + if + let swarmSnodes: [SwarmSnode] = try? JSONDecoder().decode([Failable].self, from: snodeData).compactMap({ $0.value }), + !swarmSnodes.isEmpty + { + return swarmSnodes.map { $0.toSnode() }.asSet() + } + + return ((try? JSONDecoder().decode([Failable].self, from: snodeData)) ?? []) + .compactMap { $0.value } + .asSet() + } + + // MARK: - Error Handling + + /// - Note: Should only be invoked from `Threading.workQueue` to avoid race conditions. + @discardableResult + internal static func handleError( + withStatusCode statusCode: UInt, + data: Data?, + forSnode snode: Snode, + associatedWith publicKey: String? = nil + ) -> Error? { + func handleBadSnode() { + let oldFailureCount = (SnodeAPI.snodeFailureCount.wrappedValue[snode] ?? 0) + let newFailureCount = oldFailureCount + 1 + SnodeAPI.snodeFailureCount.mutate { $0[snode] = newFailureCount } + SNLog("Couldn't reach snode at: \(snode); setting failure count to \(newFailureCount).") + if newFailureCount >= SnodeAPI.snodeFailureThreshold { + SNLog("Failure threshold reached for: \(snode); dropping it.") + if let publicKey = publicKey { + SnodeAPI.dropSnodeFromSwarmIfNeeded(snode, publicKey: publicKey) + } + SnodeAPI.dropSnodeFromSnodePool(snode) + SNLog("Snode pool count: \(snodePool.wrappedValue.count).") + SnodeAPI.snodeFailureCount.mutate { $0[snode] = 0 } + } + } + + switch statusCode { + case 500, 502, 503: + // The snode is unreachable + handleBadSnode() + + case 404: + // May caused by invalid open groups + SNLog("Can't reach the server.") + + case 406: + SNLog("The user's clock is out of sync with the service node network.") + return SnodeAPIError.clockOutOfSync + + case 421: + // The snode isn't associated with the given public key anymore + if let publicKey = publicKey { + func invalidateSwarm() { + SNLog("Invalidating swarm for: \(publicKey).") + SnodeAPI.dropSnodeFromSwarmIfNeeded(snode, publicKey: publicKey) + } + + if let data: Data = data { + let snodes = parseSnodes(from: data) + + if !snodes.isEmpty { + setSwarm(to: snodes, for: publicKey) + } + else { + invalidateSwarm() + } + } + else { + invalidateSwarm() + } + } + else { + SNLog("Got a 421 without an associated public key.") + } + + default: + handleBadSnode() + SNLog("Unhandled response code: \(statusCode).") + } + + return nil + } +} diff --git a/SessionSnodeKit/OnionRequestAPI+Encryption.swift b/SessionSnodeKit/OnionRequestAPI+Encryption.swift index 8652b28d8..08ddddc71 100644 --- a/SessionSnodeKit/OnionRequestAPI+Encryption.swift +++ b/SessionSnodeKit/OnionRequestAPI+Encryption.swift @@ -1,12 +1,14 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +import Foundation +import Combine import CryptoSwift import PromiseKit import SessionUtilitiesKit internal extension OnionRequestAPI { - static func encode(ciphertext: Data, json: JSON) throws -> Data { + static func encodeLegacy(ciphertext: Data, json: JSON) throws -> Data { // The encoding of V2 onion requests looks like: | 4 bytes: size N of ciphertext | N bytes: ciphertext | json as utf8 | guard JSONSerialization.isValidJSONObject(json) else { throw HTTP.Error.invalidJSON } let jsonAsData = try JSONSerialization.data(withJSONObject: json, options: [ .fragmentsAllowed ]) @@ -14,6 +16,24 @@ internal extension OnionRequestAPI { let ciphertextSizeAsData = withUnsafePointer(to: ciphertextSize) { Data(bytes: $0, count: MemoryLayout.size) } return ciphertextSizeAsData + ciphertext + jsonAsData } + + static func encode(ciphertext: Data, json: JSON) -> AnyPublisher { + // The encoding of V2 onion requests looks like: | 4 bytes: size N of ciphertext | N bytes: ciphertext | json as utf8 | + guard + JSONSerialization.isValidJSONObject(json), + let jsonAsData = try? JSONSerialization.data(withJSONObject: json, options: [ .fragmentsAllowed ]) + else { + return Fail(error: HTTPError.invalidJSON) + .eraseToAnyPublisher() + } + + let ciphertextSize = Int32(ciphertext.count).littleEndian + let ciphertextSizeAsData = withUnsafePointer(to: ciphertextSize) { Data(bytes: $0, count: MemoryLayout.size) } + + return Just(ciphertextSizeAsData + ciphertext + jsonAsData) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } /// Encrypts `payload` for `destination` and returns the result. Use this to build the core of an onion request. static func encrypt(_ payload: Data, for destination: OnionRequestAPIDestination) -> Promise { @@ -23,7 +43,7 @@ internal extension OnionRequestAPI { switch destination { case .snode(let snode): // Need to wrap the payload for snode requests - let data: Data = try encode(ciphertext: payload, json: [ "headers" : "" ]) + let data: Data = try encodeLegacy(ciphertext: payload, json: [ "headers" : "" ]) let result: AESGCM.EncryptionResult = try AESGCM.encrypt(data, for: snode.x25519PublicKey) seal.fulfill(result) @@ -39,6 +59,34 @@ internal extension OnionRequestAPI { return promise } + + /// Encrypts `payload` for `destination` and returns the result. Use this to build the core of an onion request. + static func encrypt( + _ payload: Data, + for destination: OnionRequestAPIDestination + ) -> AnyPublisher { + return Future { resolver in + DispatchQueue.global(qos: .userInitiated).async { + do { + switch destination { + case .snode(let snode): + // Need to wrap the payload for snode requests + let data: Data = try encodeLegacy(ciphertext: payload, json: [ "headers" : "" ]) + let result: AESGCM.EncryptionResult = try AESGCM.encrypt(data, for: snode.x25519PublicKey) + resolver(Swift.Result.success(result)) + + case .server(_, _, let serverX25519PublicKey, _, _): + let result: AESGCM.EncryptionResult = try AESGCM.encrypt(payload, for: serverX25519PublicKey) + resolver(Swift.Result.success(result)) + } + } + catch (let error) { + resolver(Swift.Result.failure(error)) + } + } + } + .eraseToAnyPublisher() + } /// Encrypts the previous encryption result (i.e. that of the hop after this one) for this hop. Use this to build the layers of an onion request. static func encryptHop(from lhs: OnionRequestAPIDestination, to rhs: OnionRequestAPIDestination, using previousEncryptionResult: AESGCM.EncryptionResult) -> Promise { @@ -72,7 +120,7 @@ internal extension OnionRequestAPI { } do { - let plaintext = try encode(ciphertext: previousEncryptionResult.ciphertext, json: parameters) + let plaintext = try encodeLegacy(ciphertext: previousEncryptionResult.ciphertext, json: parameters) let result = try AESGCM.encrypt(plaintext, for: x25519PublicKey) seal.fulfill(result) } @@ -83,4 +131,51 @@ internal extension OnionRequestAPI { return promise } + + /// Encrypts the previous encryption result (i.e. that of the hop after this one) for this hop. Use this to build the layers of an onion request. + static func encryptHop( + from lhs: OnionRequestAPIDestination, + to rhs: OnionRequestAPIDestination, + using previousEncryptionResult: AESGCM.EncryptionResult + ) -> AnyPublisher { + return Future { resolver in + DispatchQueue.global(qos: .userInitiated).async { + var parameters: JSON + + switch rhs { + case .snode(let snode): + let snodeED25519PublicKey = snode.ed25519PublicKey + parameters = [ "destination" : snodeED25519PublicKey ] + + case .server(let host, let target, _, let scheme, let port): + let scheme = scheme ?? "https" + let port = port ?? (scheme == "https" ? 443 : 80) + parameters = [ "host" : host, "target" : target, "method" : "POST", "protocol" : scheme, "port" : port ] + } + + parameters["ephemeral_key"] = previousEncryptionResult.ephemeralPublicKey.toHexString() + + let x25519PublicKey: String + + switch lhs { + case .snode(let snode): + let snodeX25519PublicKey = snode.x25519PublicKey + x25519PublicKey = snodeX25519PublicKey + + case .server(_, _, let serverX25519PublicKey, _, _): + x25519PublicKey = serverX25519PublicKey + } + + do { + let plaintext = try encodeLegacy(ciphertext: previousEncryptionResult.ciphertext, json: parameters) + let result = try AESGCM.encrypt(plaintext, for: x25519PublicKey) + resolver(Swift.Result.success(result)) + } + catch (let error) { + resolver(Swift.Result.failure(error)) + } + } + } + .eraseToAnyPublisher() + } } diff --git a/SessionSnodeKit/OnionRequestAPI.swift b/SessionSnodeKit/OnionRequestAPI.swift index 379633e21..3458e38e9 100644 --- a/SessionSnodeKit/OnionRequestAPI.swift +++ b/SessionSnodeKit/OnionRequestAPI.swift @@ -1,25 +1,24 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import Combine import CryptoSwift import GRDB import PromiseKit import SessionUtilitiesKit public protocol OnionRequestAPIType { - static func sendOnionRequest(to snode: Snode, invoking method: SnodeAPIEndpoint, with parameters: JSON, associatedWith publicKey: String?) -> Promise - static func sendOnionRequest(_ request: URLRequest, to server: String, using version: OnionRequestAPIVersion, with x25519PublicKey: String) -> Promise<(OnionRequestResponseInfoType, Data?)> -} - -public extension OnionRequestAPIType { - static func sendOnionRequest(_ request: URLRequest, to server: String, with x25519PublicKey: String) -> Promise<(OnionRequestResponseInfoType, Data?)> { - sendOnionRequest(request, to: server, using: .v4, with: x25519PublicKey) - } +// static func sendOnionRequest(_ payload: Data, to snode: Snode) -> Promise<(ResponseInfoType, Data?)> +// static func sendOnionRequest(_ request: URLRequest, to server: String, with x25519PublicKey: String) -> Promise<(ResponseInfoType, Data?)> + + static func sendOnionRequest(_ payload: Data, to snode: Snode) -> AnyPublisher<(ResponseInfoType, Data?), Error> + static func sendOnionRequest(_ request: URLRequest, to server: String, with x25519PublicKey: String) -> AnyPublisher<(ResponseInfoType, Data?), Error> } /// See the "Onion Requests" section of [The Session Whitepaper](https://arxiv.org/pdf/2002.04609.pdf) for more information. public enum OnionRequestAPI: OnionRequestAPIType { private static var buildPathsPromise: Promise<[[Snode]]>? = nil + private static var buildPathsPublisher: Atomic?> = Atomic(nil) /// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions. private static var pathFailureCount: [[Snode]: UInt] = [:] @@ -66,6 +65,7 @@ public enum OnionRequestAPI: OnionRequestAPIType { private typealias OnionBuildingResult = (guardSnode: Snode, finalEncryptionResult: AESGCM.EncryptionResult, destinationSymmetricKey: Data) // MARK: - Private API + /// Tests the given snode. The returned promise errors out if the snode is faulty; the promise is fulfilled otherwise. private static func testSnode(_ snode: Snode) -> Promise { let (promise, seal) = Promise.pending() @@ -74,8 +74,9 @@ public enum OnionRequestAPI: OnionRequestAPIType { let url = "\(snode.address):\(snode.port)/get_stats/v1" let timeout: TimeInterval = 3 // Use a shorter timeout for testing - HTTP.execute(.get, url, timeout: timeout) + HTTP.executeLegacy(.get, url, timeout: timeout) .done2 { responseData in + // TODO: Remove JSON usage guard let responseJson: JSON = try? JSONSerialization.jsonObject(with: responseData, options: [ .fragmentsAllowed ]) as? JSON else { throw HTTP.Error.invalidJSON } @@ -98,9 +99,39 @@ public enum OnionRequestAPI: OnionRequestAPIType { return promise } + + /// Tests the given snode. The returned promise errors out if the snode is faulty; the promise is fulfilled otherwise. + private static func testSnode(_ snode: Snode) -> AnyPublisher { + let url = "\(snode.address):\(snode.port)/get_stats/v1" + let timeout: TimeInterval = 3 // Use a shorter timeout for testing + + return HTTP.execute(.get, url, timeout: timeout) + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) + .flatMap { responseData -> AnyPublisher in + // TODO: Remove JSON usage + guard let responseJson: JSON = try? JSONSerialization.jsonObject(with: responseData, options: [ .fragmentsAllowed ]) as? JSON else { + return Fail(error: HTTPError.invalidJSON) + .eraseToAnyPublisher() + } + guard let version = responseJson["version"] as? String else { + return Fail(error: OnionRequestAPIError.missingSnodeVersion) + .eraseToAnyPublisher() + } + guard version >= "2.0.7" else { + SNLog("Unsupported snode version: \(version).") + return Fail(error: OnionRequestAPIError.unsupportedSnodeVersion(version)) + .eraseToAnyPublisher() + } + + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + .eraseToAnyPublisher() + } - /// Finds `targetGuardSnodeCount` guard snodes to use for path building. The returned promise errors out with `Error.insufficientSnodes` - /// if not enough (reliable) snodes are available. + /// Finds `targetGuardSnodeCount` guard snodes to use for path building. The returned promise errors out with + /// `Error.insufficientSnodes` if not enough (reliable) snodes are available. private static func getGuardSnodes(reusing reusableGuardSnodes: [Snode]) -> Promise> { if guardSnodes.count >= targetGuardSnodeCount { return Promise> { $0.fulfill(guardSnodes) } @@ -143,6 +174,62 @@ public enum OnionRequestAPI: OnionRequestAPIType { } } } + + /// Finds `targetGuardSnodeCount` guard snodes to use for path building. The returned promise errors out with + /// `Error.insufficientSnodes` if not enough (reliable) snodes are available. + private static func getGuardSnodes(reusing reusableGuardSnodes: [Snode]) -> AnyPublisher, Error> { + guard guardSnodes.count < targetGuardSnodeCount else { + return Just(guardSnodes) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + + SNLog("Populating guard snode cache.") + // Sync on LokiAPI.workQueue + var unusedSnodes = SnodeAPI.snodePool.wrappedValue.subtracting(reusableGuardSnodes) + let reusableGuardSnodeCount = UInt(reusableGuardSnodes.count) + + guard unusedSnodes.count >= (targetGuardSnodeCount - reusableGuardSnodeCount) else { + return Fail(error: OnionRequestAPIError.insufficientSnodes) + .eraseToAnyPublisher() + } + + func getGuardSnode() -> AnyPublisher { + // randomElement() uses the system's default random generator, which + // is cryptographically secure + guard let candidate = unusedSnodes.randomElement() else { + return Fail(error: OnionRequestAPIError.insufficientSnodes) + .eraseToAnyPublisher() + } + + unusedSnodes.remove(candidate) // All used snodes should be unique + SNLog("Testing guard snode: \(candidate).") + + // Loop until a reliable guard snode is found + return testSnode(candidate) + .map { _ in candidate } + .catch { _ in + return Just(()) + .setFailureType(to: Error.self) + .delay(for: .milliseconds(100), scheduler: Threading.workQueue) + .flatMap { _ in getGuardSnode() } + } + .eraseToAnyPublisher() + } + + let publishers = (0..<(targetGuardSnodeCount - reusableGuardSnodeCount)) + .map { _ in getGuardSnode() } + + return Publishers.MergeMany(publishers) + .collect() + .map { output in Set(output) } + .handleEvents( + receiveOutput: { output in + OnionRequestAPI.guardSnodes = output + } + ) + .eraseToAnyPublisher() + } /// Builds and returns `targetPathCount` paths. The returned promise errors out with `Error.insufficientSnodes` /// if not enough (reliable) snodes are available. @@ -196,6 +283,73 @@ public enum OnionRequestAPI: OnionRequestAPIType { buildPathsPromise = promise return promise } + + /// Builds and returns `targetPathCount` paths. The returned promise errors out with `Error.insufficientSnodes` + /// if not enough (reliable) snodes are available. + @discardableResult + private static func buildPaths(reusing reusablePaths: [[Snode]]) -> AnyPublisher<[[Snode]], Error> { + if let existingBuildPathsPublisher = buildPathsPublisher.wrappedValue { + return existingBuildPathsPublisher + } + + SNLog("Building onion request paths.") + DispatchQueue.main.async { + NotificationCenter.default.post(name: .buildingPaths, object: nil) + } + let reusableGuardSnodes = reusablePaths.map { $0[0] } + let publisher: AnyPublisher<[[Snode]], Error> = getGuardSnodes(reusing: reusableGuardSnodes) + .flatMap { guardSnodes -> AnyPublisher<[[Snode]], Error> in + var unusedSnodes = SnodeAPI.snodePool.wrappedValue + .subtracting(guardSnodes) + .subtracting(reusablePaths.flatMap { $0 }) + let reusableGuardSnodeCount = UInt(reusableGuardSnodes.count) + let pathSnodeCount = (targetGuardSnodeCount - reusableGuardSnodeCount) * pathSize - (targetGuardSnodeCount - reusableGuardSnodeCount) + + guard unusedSnodes.count >= pathSnodeCount else { + return Fail<[[Snode]], Error>(error: OnionRequestAPIError.insufficientSnodes) + .eraseToAnyPublisher() + } + + // Don't test path snodes as this would reveal the user's IP to them + return Just( + guardSnodes + .subtracting(reusableGuardSnodes) + .map { guardSnode in + let result = [ guardSnode ] + (0..<(pathSize - 1)).map { _ in + // randomElement() uses the system's default random generator, which is cryptographically secure + let pathSnode = unusedSnodes.randomElement()! // Safe because of the pathSnodeCount check above + unusedSnodes.remove(pathSnode) // All used snodes should be unique + return pathSnode + } + + SNLog("Built new onion request path: \(result.prettifiedDescription).") + return result + } + ) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + .handleEvents( + receiveOutput: { output in + OnionRequestAPI.paths = (output + reusablePaths) + + Storage.shared.write { db in + SNLog("Persisting onion request paths to database.") + try? output.save(db) + } + + DispatchQueue.main.async { + NotificationCenter.default.post(name: .pathsBuilt, object: nil) + } + }, + receiveCompletion: { _ in buildPathsPublisher.mutate { $0 = nil } } + ) + .eraseToAnyPublisher() + + buildPathsPublisher.mutate { $0 = publisher } + + return publisher + } /// Returns a `Path` to be used for building an onion request. Builds new paths as needed. private static func getPath(excluding snode: Snode?) -> Promise<[Snode]> { @@ -223,7 +377,7 @@ public enum OnionRequestAPI: OnionRequestAPIType { else if !paths.isEmpty { if let snode = snode { if let path = paths.first(where: { !$0.contains(snode) }) { - buildPaths(reusing: paths) // Re-build paths in the background + let tmp: Promise<[[Snode]]> = buildPaths(reusing: paths) // Re-build paths in the background return Promise { $0.fulfill(path) } } else { @@ -237,7 +391,7 @@ public enum OnionRequestAPI: OnionRequestAPIType { } } else { - buildPaths(reusing: paths) // Re-build paths in the background + let tmp: Promise<[[Snode]]> = buildPaths(reusing: paths) // Re-build paths in the background guard let path: [Snode] = paths.randomElement() else { return Promise(error: OnionRequestAPIError.insufficientSnodes) @@ -264,6 +418,100 @@ public enum OnionRequestAPI: OnionRequestAPIType { } } } + + /// Returns a `Path` to be used for building an onion request. Builds new paths as needed. + private static func getPath(excluding snode: Snode?) -> AnyPublisher<[Snode], Error> { + guard pathSize >= 1 else { preconditionFailure("Can't build path of size zero.") } + + let paths: [[Snode]] = OnionRequestAPI.paths + var cancellable: [AnyCancellable] = [] + + if !paths.isEmpty { + guardSnodes.formUnion([ paths[0][0] ]) + + if paths.count >= 2 { + guardSnodes.formUnion([ paths[1][0] ]) + } + } + + // randomElement() uses the system's default random generator, which is cryptographically secure + if + paths.count >= targetPathCount, + let targetPath: [Snode] = paths + .filter({ snode == nil || !$0.contains(snode!) }) + .randomElement() + { + return Just(targetPath) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + else if !paths.isEmpty { + if let snode = snode { + if let path = paths.first(where: { !$0.contains(snode) }) { + buildPaths(reusing: paths) // Re-build paths in the background + .sink(receiveCompletion: { _ in cancellable = [] }, receiveValue: { _ in }) + .store(in: &cancellable) + + return Just(path) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + else { + return buildPaths(reusing: paths) + .flatMap { paths in + guard let path: [Snode] = paths.filter({ !$0.contains(snode) }).randomElement() else { + return Fail<[Snode], Error>(error: OnionRequestAPIError.insufficientSnodes) + .eraseToAnyPublisher() + } + + return Just(path) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + .eraseToAnyPublisher() + } + } + else { + buildPaths(reusing: paths) // Re-build paths in the background + .sink(receiveCompletion: { _ in cancellable = [] }, receiveValue: { _ in }) + .store(in: &cancellable) + + guard let path: [Snode] = paths.randomElement() else { + return Fail<[Snode], Error>(error: OnionRequestAPIError.insufficientSnodes) + .eraseToAnyPublisher() + } + + return Just(path) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + } + else { + return buildPaths(reusing: []) + .flatMap { paths in + if let snode = snode { + if let path = paths.filter({ !$0.contains(snode) }).randomElement() { + return Just(path) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + + return Fail<[Snode], Error>(error: OnionRequestAPIError.insufficientSnodes) + .eraseToAnyPublisher() + } + + guard let path: [Snode] = paths.randomElement() else { + return Fail<[Snode], Error>(error: OnionRequestAPIError.insufficientSnodes) + .eraseToAnyPublisher() + } + + return Just(path) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + .eraseToAnyPublisher() + } + } private static func dropGuardSnode(_ snode: Snode) { #if DEBUG @@ -365,17 +613,66 @@ public enum OnionRequestAPI: OnionRequestAPIType { } .map2 { _ in (guardSnode, encryptionResult, targetSnodeSymmetricKey) } } + + /// Builds an onion around `payload` and returns the result. + private static func buildOnion( + around payload: Data, + targetedAt destination: OnionRequestAPIDestination + ) -> AnyPublisher { + var guardSnode: Snode! + var targetSnodeSymmetricKey: Data! // Needed by invoke(_:on:with:) to decrypt the response sent back by the destination + var encryptionResult: AESGCM.EncryptionResult! + var snodeToExclude: Snode? + + if case .snode(let snode) = destination { snodeToExclude = snode } + + return getPath(excluding: snodeToExclude) + .flatMap { path -> AnyPublisher in + guardSnode = path.first! + + // Encrypt in reverse order, i.e. the destination first + return encrypt(payload, for: destination) + .flatMap { r -> AnyPublisher in + targetSnodeSymmetricKey = r.symmetricKey + + // Recursively encrypt the layers of the onion (again in reverse order) + encryptionResult = r + var path = path + var rhs = destination + + func addLayer() -> AnyPublisher { + guard !path.isEmpty else { + return Just(encryptionResult) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + + let lhs = OnionRequestAPIDestination.snode(path.removeLast()) + return OnionRequestAPI + .encryptHop(from: lhs, to: rhs, using: encryptionResult) + .flatMap { r -> AnyPublisher in + encryptionResult = r + rhs = lhs + return addLayer() + } + .eraseToAnyPublisher() + } + + return addLayer() + } + .eraseToAnyPublisher() + } + .map { _ in (guardSnode, encryptionResult, targetSnodeSymmetricKey) } + .eraseToAnyPublisher() + } // MARK: - Public API /// Sends an onion request to `snode`. Builds new paths as needed. - public static func sendOnionRequest(to snode: Snode, invoking method: SnodeAPIEndpoint, with parameters: JSON, associatedWith publicKey: String? = nil) -> Promise { - let payloadJson: JSON = [ "method" : method.rawValue, "params" : parameters ] - - guard let payload: Data = try? JSONSerialization.data(withJSONObject: payloadJson, options: []) else { - return Promise(error: HTTP.Error.invalidJSON) - } - + public static func sendOnionRequest( + _ payload: Data, + to snode: Snode + ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { /// **Note:** Currently the service nodes only support V3 Onion Requests return sendOnionRequest(with: payload, to: OnionRequestAPIDestination.snode(snode), version: .v3) .map { _, maybeData in @@ -391,160 +688,169 @@ public enum OnionRequestAPI: OnionRequestAPIType { throw SnodeAPI.handleError(withStatusCode: statusCode, data: data, forSnode: snode, associatedWith: publicKey) ?? error } } - + /// Sends an onion request to `server`. Builds new paths as needed. public static func sendOnionRequest( _ request: URLRequest, to server: String, // TODO: Remove this 'server' value (unused) with x25519PublicKey: String - ) -> Promise<(ResponseInfoType, Data?)> { + ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { guard let url = request.url, let host = request.url?.host else { - return Promise(error: OnionRequestAPIError.invalidURL) + return Fail(error: OnionRequestAPIError.invalidURL) + .eraseToAnyPublisher() } let scheme: String? = url.scheme let port: UInt16? = url.port.map { UInt16($0) } - guard let payload: Data = generatePayload(for: request, with: version) else { - return Promise(error: OnionRequestAPIError.invalidRequestInfo) + guard let payload: Data = generateV4Payload(for: request) else { + return Fail(error: OnionRequestAPIError.invalidRequestInfo) + .eraseToAnyPublisher() } - let destination = OnionRequestAPIDestination.server( - host: host, - target: version.rawValue, - x25519PublicKey: x25519PublicKey, - scheme: scheme, - port: port - ) - let promise = sendOnionRequest(with: payload, to: destination, version: version) - promise.catch2 { error in - SNLog("Couldn't reach server: \(url) due to error: \(error).") - } - return promise + return OnionRequestAPI + .sendOnionRequest( + with: payload, + to: OnionRequestAPIDestination.server( + host: host, + target: OnionRequestAPIVersion.v4.rawValue, + x25519PublicKey: x25519PublicKey, + scheme: scheme, + port: port + ), + version: .v4 + ) + .handleEvents( + receiveCompletion: { result in + switch result { + case .finished: break + case .failure(let error): + SNLog("Couldn't reach server: \(url) due to error: \(error).") + } + } + ) + .eraseToAnyPublisher() } - - public static func sendOnionRequest(with payload: Data, to destination: OnionRequestAPIDestination, version: OnionRequestAPIVersion) -> Promise<(OnionRequestResponseInfoType, Data?)> { - let (promise, seal) = Promise<(OnionRequestResponseInfoType, Data?)>.pending() + + public static func sendOnionRequest( + with payload: Data, + to destination: OnionRequestAPIDestination, + version: OnionRequestAPIVersion + ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { var guardSnode: Snode? - Threading.workQueue.async { // Avoid race conditions on `guardSnodes` and `paths` - buildOnion(around: payload, targetedAt: destination) - .done2 { intermediate in - guardSnode = intermediate.guardSnode - let url = "\(guardSnode!.address):\(guardSnode!.port)/onion_req/v2" - let finalEncryptionResult = intermediate.finalEncryptionResult - let onion = finalEncryptionResult.ciphertext - if case OnionRequestAPIDestination.server = destination, Double(onion.count) > 0.75 * Double(maxRequestSize) { - SNLog("Approaching request size limit: ~\(onion.count) bytes.") - } - let parameters: JSON = [ - "ephemeral_key" : finalEncryptionResult.ephemeralPublicKey.toHexString() - ] - let body: Data - do { - body = try encode(ciphertext: onion, json: parameters) - } catch { - return seal.reject(error) - } - let destinationSymmetricKey = intermediate.destinationSymmetricKey - - HTTP.execute(.post, url, body: body) - .done2 { responseData in - handleResponse( - responseData: responseData, - destinationSymmetricKey: destinationSymmetricKey, - version: version, - destination: destination, - seal: seal - ) - } - .catch2 { error in - seal.reject(error) - } + return buildOnion(around: payload, targetedAt: destination) + .subscribe(on: Threading.workQueue) + .flatMap { intermediate -> AnyPublisher<(ResponseInfoType, Data?), Error> in + guardSnode = intermediate.guardSnode + let url = "\(guardSnode!.address):\(guardSnode!.port)/onion_req/v2" + let finalEncryptionResult = intermediate.finalEncryptionResult + let onion = finalEncryptionResult.ciphertext + if case OnionRequestAPIDestination.server = destination, Double(onion.count) > 0.75 * Double(maxRequestSize) { + SNLog("Approaching request size limit: ~\(onion.count) bytes.") } - .catch2 { error in - seal.reject(error) - } - } - - promise.catch2 { error in // Must be invoked on Threading.workQueue - guard case HTTP.Error.httpRequestFailed(let statusCode, let data) = error, let guardSnode = guardSnode else { - return - } - - let path = paths.first { $0.contains(guardSnode) } - - func handleUnspecificError() { - guard let path = path else { return } + let parameters: JSON = [ + "ephemeral_key" : finalEncryptionResult.ephemeralPublicKey.toHexString() + ] + let destinationSymmetricKey = intermediate.destinationSymmetricKey - var pathFailureCount = OnionRequestAPI.pathFailureCount[path] ?? 0 - pathFailureCount += 1 - - if pathFailureCount >= pathFailureThreshold { - dropGuardSnode(guardSnode) - path.forEach { snode in - SnodeAPI.handleError(withStatusCode: statusCode, data: data, forSnode: snode) // Intentionally don't throw + // TODO: Replace 'json' with a codable typed + return encode(ciphertext: onion, json: parameters) + .flatMap { body in HTTP.execute(.post, url, body: body) } + .flatMap { responseData in + handleResponse( + responseData: responseData, + destinationSymmetricKey: destinationSymmetricKey, + version: version, + destination: destination + ) } - - drop(path) - } - else { - OnionRequestAPI.pathFailureCount[path] = pathFailureCount - } + .eraseToAnyPublisher() } - - let prefix = "Next node not found: " - let json: JSON? - - if let data: Data = data, let processedJson = try? JSONSerialization.jsonObject(with: data, options: [ .fragmentsAllowed ]) as? JSON { - json = processedJson - } - else if let data: Data = data, let result: String = String(data: data, encoding: .utf8) { - json = [ "result": result ] - } - else { - json = nil - } - - if let message = json?["result"] as? String, message.hasPrefix(prefix) { - let ed25519PublicKey = message[message.index(message.startIndex, offsetBy: prefix.count)..= snodeFailureThreshold { - SnodeAPI.handleError(withStatusCode: statusCode, data: data, forSnode: snode) // Intentionally don't throw - do { - try drop(snode) - } - catch { - handleUnspecificError() - } + .handleEvents( + receiveCompletion: { result in + switch result { + case .finished: break + case .failure(let error): + guard + case HTTPError.httpRequestFailed(let statusCode, let data) = error, + let guardSnode: Snode = guardSnode + else { return } + + let path = paths.first { $0.contains(guardSnode) } + + func handleUnspecificError() { + guard let path = path else { return } + + var pathFailureCount = OnionRequestAPI.pathFailureCount[path] ?? 0 + pathFailureCount += 1 + + if pathFailureCount >= pathFailureThreshold { + dropGuardSnode(guardSnode) + path.forEach { snode in + SnodeAPI.handleError(withStatusCode: statusCode, data: data, forSnode: snode) // Intentionally don't throw + } + + drop(path) + } + else { + OnionRequestAPI.pathFailureCount[path] = pathFailureCount + } + } + + let prefix = "Next node not found: " + let json: JSON? + + if let data: Data = data, let processedJson = try? JSONSerialization.jsonObject(with: data, options: [ .fragmentsAllowed ]) as? JSON { + json = processedJson + } + else if let data: Data = data, let result: String = String(data: data, encoding: .utf8) { + json = [ "result": result ] + } + else { + json = nil + } + + if let message = json?["result"] as? String, message.hasPrefix(prefix) { + let ed25519PublicKey = message[message.index(message.startIndex, offsetBy: prefix.count)..= snodeFailureThreshold { + SnodeAPI.handleError(withStatusCode: statusCode, data: data, forSnode: snode) // Intentionally don't throw + do { + try drop(snode) + } + catch { + handleUnspecificError() + } + } + else { + OnionRequestAPI.snodeFailureCount[snode] = snodeFailureCount + } + } else { + // Do nothing + } + } + else if let message = json?["result"] as? String, message == "Loki Server error" { + // Do nothing + } + else if case .server(let host, _, _, _, _) = destination, host == "116.203.70.33" && statusCode == 0 { + // FIXME: Temporary thing to kick out nodes that can't talk to the V2 OGS yet + handleUnspecificError() + } + else if statusCode == 0 { // Timeout + // Do nothing + } + else { + handleUnspecificError() + } } - else { - OnionRequestAPI.snodeFailureCount[snode] = snodeFailureCount - } - } else { - // Do nothing } - } - else if let message = json?["result"] as? String, message == "Loki Server error" { - // Do nothing - } - else if case .server(let host, _, _, _, _) = destination, host == "116.203.70.33" && statusCode == 0 { - // FIXME: Temporary thing to kick out nodes that can't talk to the V2 OGS yet - handleUnspecificError() - } - else if statusCode == 0 { // Timeout - // Do nothing - } - else { - handleUnspecificError() - } - } - - return promise + ) + .eraseToAnyPublisher() } // MARK: - Version Handling @@ -740,9 +1046,160 @@ public enum OnionRequestAPI: OnionRequestAPIType { } } - public static func process(bencodedData data: Data) -> (info: ResponseInfo, body: Data?)? { - // The data will be in the form of `l123:jsone` or `l123:json456:bodye` so we need to break the data - // into parts to properly process it + private static func handleResponse( + responseData: Data, + destinationSymmetricKey: Data, + version: OnionRequestAPIVersion, + destination: OnionRequestAPIDestination + ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { + switch version { + // V2 and V3 Onion Requests have the same structure for responses + case .v2, .v3: + let json: JSON + + if let processedJson = try? JSONSerialization.jsonObject(with: responseData, options: [ .fragmentsAllowed ]) as? JSON { + json = processedJson + } + else if let result: String = String(data: responseData, encoding: .utf8) { + json = [ "result": result ] + } + else { + return Fail(error: HTTPError.invalidJSON) + .eraseToAnyPublisher() + } + + guard let base64EncodedIVAndCiphertext = json["result"] as? String, let ivAndCiphertext = Data(base64Encoded: base64EncodedIVAndCiphertext), ivAndCiphertext.count >= AESGCM.ivSize else { + return Fail(error: HTTPError.invalidJSON) + .eraseToAnyPublisher() + } + + do { + let data = try AESGCM.decrypt(ivAndCiphertext, with: destinationSymmetricKey) + + guard let json = try JSONSerialization.jsonObject(with: data, options: [ .fragmentsAllowed ]) as? JSON, let statusCode = json["status_code"] as? Int ?? json["status"] as? Int else { + return Fail(error: HTTPError.invalidJSON) + .eraseToAnyPublisher() + } + + if statusCode == 406 { // Clock out of sync + SNLog("The user's clock is out of sync with the service node network.") + return Fail(error: SnodeAPIError.clockOutOfSync) + .eraseToAnyPublisher() + } + + if statusCode == 401 { // Signature verification failed + SNLog("Failed to verify the signature.") + return Fail(error: SnodeAPIError.signatureVerificationFailed) + .eraseToAnyPublisher() + } + + if let bodyAsString = json["body"] as? String { + guard let bodyAsData = bodyAsString.data(using: .utf8) else { + return Fail(error: HTTPError.invalidResponse) + .eraseToAnyPublisher() + } + guard let body = try? JSONSerialization.jsonObject(with: bodyAsData, options: [ .fragmentsAllowed ]) as? JSON else { + return Fail( + error: OnionRequestAPIError.httpRequestFailedAtDestination( + statusCode: UInt(statusCode), + data: bodyAsData, + destination: destination + ) + ).eraseToAnyPublisher() + } + + if let timestamp = body["t"] as? Int64 { + let offset = timestamp - Int64(floor(Date().timeIntervalSince1970 * 1000)) + SnodeAPI.clockOffset.mutate { $0 = offset } + } + + guard 200...299 ~= statusCode else { + return Fail( + error: OnionRequestAPIError.httpRequestFailedAtDestination( + statusCode: UInt(statusCode), + data: bodyAsData, + destination: destination + ) + ).eraseToAnyPublisher() + } + + return Just((HTTP.ResponseInfo(code: statusCode, headers: [:]), bodyAsData)) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + + guard 200...299 ~= statusCode else { + return Fail( + error: OnionRequestAPIError.httpRequestFailedAtDestination( + statusCode: UInt(statusCode), + data: data, + destination: destination + ) + ).eraseToAnyPublisher() + } + + return Just((HTTP.ResponseInfo(code: statusCode, headers: [:]), data)) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + catch { + return Fail(error: error) + .eraseToAnyPublisher() + } + + // V4 Onion Requests have a very different structure for responses + case .v4: + guard responseData.count >= AESGCM.ivSize else { + return Fail(error: HTTPError.invalidResponse) + .eraseToAnyPublisher() + } + + do { + let data: Data = try AESGCM.decrypt(responseData, with: destinationSymmetricKey) + + // Process the bencoded response + guard let processedResponse: (info: ResponseInfoType, body: Data?) = process(bencodedData: data) else { + return Fail(error: HTTPError.invalidResponse) + .eraseToAnyPublisher() + } + + // Custom handle a clock out of sync error (v4 returns '425' but included the '406' + // just in case) + guard processedResponse.info.code != 406 && processedResponse.info.code != 425 else { + SNLog("The user's clock is out of sync with the service node network.") + return Fail(error: SnodeAPIError.clockOutOfSync) + .eraseToAnyPublisher() + } + + guard processedResponse.info.code != 401 else { // Signature verification failed + SNLog("Failed to verify the signature.") + return Fail(error: SnodeAPIError.signatureVerificationFailed) + .eraseToAnyPublisher() + } + + // Handle error status codes + guard 200...299 ~= processedResponse.info.code else { + return Fail(error: OnionRequestAPIError.httpRequestFailedAtDestination( + statusCode: UInt(processedResponse.info.code), + data: data, + destination: destination + )).eraseToAnyPublisher() + } + + return Just(processedResponse) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + catch { + return Fail(error: error) + .eraseToAnyPublisher() + } + } + } + + public static func process(bencodedData data: Data) -> (info: ResponseInfoType, body: Data?)? { + // The data will be in the form of `l123:jsone` or `l123:json456:bodye` so we need to break + // the data into parts to properly process it guard let responseString: String = String(data: data, encoding: .ascii), responseString.starts(with: "l") else { return nil } @@ -761,7 +1218,8 @@ public enum OnionRequestAPI: OnionRequestAPIType { return nil } - // Custom handle a clock out of sync error (v4 returns '425' but included the '406' just in case) + // Custom handle a clock out of sync error (v4 returns '425' but included the '406' just + // in case) guard responseInfo.code != 406 && responseInfo.code != 425 else { return nil } guard responseInfo.code != 401 else { return nil } diff --git a/SessionUtilitiesKit/Combine/Publisher+Utilities.swift b/SessionUtilitiesKit/Combine/Publisher+Utilities.swift index e698c9630..4e0f1ee27 100644 --- a/SessionUtilitiesKit/Combine/Publisher+Utilities.swift +++ b/SessionUtilitiesKit/Combine/Publisher+Utilities.swift @@ -59,4 +59,83 @@ public extension Publisher { return sink(into: targetSubject, includeCompletions: includeCompletions) } + + /// Automatically retains the subscription until it emits a 'completion' event + func sinkUntilComplete( + receiveCompletion: ((Subscribers.Completion) -> Void)? = nil, + receiveValue: ((Output) -> Void)? = nil + ) { + var retainCycle: Cancellable? = nil + retainCycle = self + .sink( + receiveCompletion: { result in + receiveCompletion?(result) + + // Redundant but without reading 'retainCycle' it will warn that the variable + // isn't used + if retainCycle != nil { retainCycle = nil } + }, + receiveValue: (receiveValue ?? { _ in }) + ) + } +} + +public extension AnyPublisher { + /// Converts the publisher to output a Result instead of throwing an error, can be used to ensure a subscription never + /// closes due to a failure + func asResult() -> AnyPublisher, Never> { + self + .map { Result.success($0) } + .catch { Just(Result.failure($0)).eraseToAnyPublisher() } + .eraseToAnyPublisher() + } +} + +// MARK: - Data Decoding + +public extension AnyPublisher where Output == Data, Failure == Error { + func decoded( + as type: R.Type, + using dependencies: Dependencies = Dependencies() + ) -> AnyPublisher { + self + .flatMap { data -> AnyPublisher in + do { + return Just(try data.decoded(as: type, using: dependencies)) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + catch { + return Fail(error: error) + .eraseToAnyPublisher() + } + } + .eraseToAnyPublisher() + } +} + +public extension AnyPublisher where Output == (ResponseInfoType, Data?), Failure == Error { + func decoded( + as type: R.Type, + using dependencies: Dependencies = Dependencies() + ) -> AnyPublisher<(ResponseInfoType, R), Error> { + self + .flatMap { responseInfo, maybeData -> AnyPublisher<(ResponseInfoType, R), Error> in + guard let data: Data = maybeData else { + return Fail(error: HTTPError.parsingFailed) + .eraseToAnyPublisher() + } + + do { + return Just((responseInfo, try data.decoded(as: type, using: dependencies))) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + catch { + return Fail(error: HTTPError.parsingFailed) + .eraseToAnyPublisher() + } + } + .eraseToAnyPublisher() + } } diff --git a/SessionUtilitiesKit/Database/Storage.swift b/SessionUtilitiesKit/Database/Storage.swift index b022f3fdf..8050944bc 100644 --- a/SessionUtilitiesKit/Database/Storage.swift +++ b/SessionUtilitiesKit/Database/Storage.swift @@ -336,6 +336,26 @@ open class Storage { ) } + open func writePublisher(updates: @escaping (Database) throws -> T) -> AnyPublisher { + guard isValid, let dbWriter: DatabaseWriter = dbWriter else { + return Fail(error: StorageError.databaseInvalid) + .eraseToAnyPublisher() + } + + return dbWriter.writePublisher(updates: updates) + .eraseToAnyPublisher() + } + + open func readPublisher(value: @escaping (Database) throws -> T) -> AnyPublisher { + guard isValid, let dbWriter: DatabaseWriter = dbWriter else { + return Fail(error: StorageError.databaseInvalid) + .eraseToAnyPublisher() + } + + return dbWriter.readPublisher(value: value) + .eraseToAnyPublisher() + } + @discardableResult public final func read(_ value: (Database) throws -> T?) -> T? { guard isValid, let dbWriter: DatabaseWriter = dbWriter else { return nil } @@ -440,6 +460,20 @@ public extension Storage { // MARK: - Combine Extensions +public extension Storage { + func readPublisherFlatMap(value: @escaping (Database) throws -> AnyPublisher) -> AnyPublisher { + return readPublisher(value: value) + .flatMap { resultPublisher -> AnyPublisher in resultPublisher } + .eraseToAnyPublisher() + } + + func writePublisherFlatMap(updates: @escaping (Database) throws -> AnyPublisher) -> AnyPublisher { + return writePublisher(updates: updates) + .flatMap { resultPublisher -> AnyPublisher in resultPublisher } + .eraseToAnyPublisher() + } +} + public extension ValueObservation { func publisher( in storage: Storage, diff --git a/SessionUtilitiesKit/Networking/BatchResponse.swift b/SessionUtilitiesKit/Networking/BatchResponse.swift index d0407810f..da3ccdbfa 100644 --- a/SessionUtilitiesKit/Networking/BatchResponse.swift +++ b/SessionUtilitiesKit/Networking/BatchResponse.swift @@ -1,7 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation -import PromiseKit +import Combine public extension HTTP { // MARK: - Convenience Aliases @@ -64,43 +64,66 @@ public extension Decodable { } } -public extension Promise where T == (ResponseInfoType, Data?) { - func decoded(as types: HTTP.BatchResponseTypes, on queue: DispatchQueue? = nil, using dependencies: Dependencies = Dependencies()) -> Promise { - self.map(on: queue) { responseInfo, maybeData -> HTTP.BatchResponse in - // Need to split the data into an array of data so each item can be Decoded correctly - guard let data: Data = maybeData else { throw HTTPError.parsingFailed } - guard let jsonObject: Any = try? JSONSerialization.jsonObject(with: data, options: [.fragmentsAllowed]) else { - throw HTTPError.parsingFailed +public extension AnyPublisher where Output == (ResponseInfoType, Data?), Failure == Error { + func decoded( + as types: HTTP.BatchResponseTypes, + using dependencies: Dependencies = Dependencies() + ) -> AnyPublisher { + self + .flatMap { responseInfo, maybeData -> AnyPublisher in + // Need to split the data into an array of data so each item can be Decoded correctly + guard let data: Data = maybeData else { + return Fail(error: HTTPError.parsingFailed) + .eraseToAnyPublisher() + } + guard let jsonObject: Any = try? JSONSerialization.jsonObject(with: data, options: [.fragmentsAllowed]) else { + return Fail(error: HTTPError.parsingFailed) + .eraseToAnyPublisher() + } + + let dataArray: [Data] + + switch jsonObject { + case let anyArray as [Any]: + dataArray = anyArray.compactMap { try? JSONSerialization.data(withJSONObject: $0) } + + guard dataArray.count == types.count else { + return Fail(error: HTTPError.parsingFailed) + .eraseToAnyPublisher() + } + + case let anyDict as [String: Any]: + guard + let resultsArray: [Data] = (anyDict["results"] as? [Any])? + .compactMap({ try? JSONSerialization.data(withJSONObject: $0) }), + resultsArray.count == types.count + else { + return Fail(error: HTTPError.parsingFailed) + .eraseToAnyPublisher() + } + + dataArray = resultsArray + + default: + return Fail(error: HTTPError.parsingFailed) + .eraseToAnyPublisher() + } + + do { + // TODO: Remove the 'Swift.' + let result: HTTP.BatchResponse = try Swift.zip(dataArray, types) + .map { data, type in try type.decoded(from: data, using: dependencies) } + .map { data in (responseInfo, data) } + + return Just(result) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + catch { + return Fail(error: HTTPError.parsingFailed) + .eraseToAnyPublisher() + } } - - let dataArray: [Data] - - switch jsonObject { - case let anyArray as [Any]: - dataArray = anyArray.compactMap { try? JSONSerialization.data(withJSONObject: $0) } - - guard dataArray.count == types.count else { throw HTTPError.parsingFailed } - - case let anyDict as [String: Any]: - guard - let resultsArray: [Data] = (anyDict["results"] as? [Any])? - .compactMap({ try? JSONSerialization.data(withJSONObject: $0) }), - resultsArray.count == types.count - else { throw HTTPError.parsingFailed } - - dataArray = resultsArray - - default: throw HTTPError.parsingFailed - } - - do { - return try zip(dataArray, types) - .map { data, type in try type.decoded(from: data, using: dependencies) } - .map { data in (responseInfo, data) } - } - catch { - throw HTTPError.parsingFailed - } - } + .eraseToAnyPublisher() } } diff --git a/SessionUtilitiesKit/Networking/HTTP.swift b/SessionUtilitiesKit/Networking/HTTP.swift index 9e5946735..d8bac7ded 100644 --- a/SessionUtilitiesKit/Networking/HTTP.swift +++ b/SessionUtilitiesKit/Networking/HTTP.swift @@ -66,64 +66,25 @@ public enum HTTP { completionHandler(.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!)) } } - - // MARK: - Verb - public enum Verb: String, Codable { - case get = "GET" - case put = "PUT" - case post = "POST" - case delete = "DELETE" - } - - // MARK: - Error - - public enum Error: LocalizedError, Equatable { - case generic - case invalidURL - case invalidJSON - case parsingFailed - case invalidResponse - case maxFileSizeExceeded - case httpRequestFailed(statusCode: UInt, data: Data?) - case timeout - - public var errorDescription: String? { - switch self { - case .generic: return "An error occurred." - case .invalidURL: return "Invalid URL." - case .invalidJSON: return "Invalid JSON." - case .parsingFailed, .invalidResponse: return "Invalid response." - case .maxFileSizeExceeded: return "Maximum file size exceeded." - case .httpRequestFailed(let statusCode, _): return "HTTP request failed with status code: \(statusCode)." - case .timeout: return "The request timed out." - } - } - } - // MARK: - Main - public static func execute(_ verb: Verb, _ url: String, timeout: TimeInterval = HTTP.timeout, useSeedNodeURLSession: Bool = false) -> Promise { - return execute(verb, url, body: nil, timeout: timeout, useSeedNodeURLSession: useSeedNodeURLSession) + public static func executeLegacy( + _ method: HTTPMethod, + _ url: String, + timeout: TimeInterval = HTTP.defaultTimeout, + useSeedNodeURLSession: Bool = false + ) -> Promise { + return executeLegacy(method, url, body: nil, timeout: timeout, useSeedNodeURLSession: useSeedNodeURLSession) } - - public static func execute(_ verb: Verb, _ url: String, parameters: JSON?, timeout: TimeInterval = HTTP.timeout, useSeedNodeURLSession: Bool = false) -> Promise { - if let parameters = parameters { - do { - guard JSONSerialization.isValidJSONObject(parameters) else { return Promise(error: Error.invalidJSON) } - let body = try JSONSerialization.data(withJSONObject: parameters, options: [ .fragmentsAllowed ]) - return execute(verb, url, body: body, timeout: timeout, useSeedNodeURLSession: useSeedNodeURLSession) - } - catch (let error) { - return Promise(error: error) - } - } - else { - return execute(verb, url, body: nil, timeout: timeout, useSeedNodeURLSession: useSeedNodeURLSession) - } - } - - public static func execute(_ verb: Verb, _ url: String, body: Data?, timeout: TimeInterval = HTTP.timeout, useSeedNodeURLSession: Bool = false) -> Promise { + + public static func executeLegacy( + _ method: HTTPMethod, + _ url: String, + body: Data?, + timeout: TimeInterval = HTTP.defaultTimeout, + useSeedNodeURLSession: Bool = false + ) -> Promise { var request = URLRequest(url: URL(string: url)!) request.httpMethod = verb.rawValue request.httpBody = body @@ -174,4 +135,84 @@ public enum HTTP { task.resume() return promise } + + // MARK: - Execution + + public static func execute( + _ method: HTTPMethod, + _ url: String, + timeout: TimeInterval = HTTP.defaultTimeout, + useSeedNodeURLSession: Bool = false + ) -> AnyPublisher { + return execute( + method, + url, + body: nil, + timeout: timeout, + useSeedNodeURLSession: useSeedNodeURLSession + ) + } + + public static func execute( + _ method: HTTPMethod, + _ url: String, + body: Data?, // TODO: Default Value? + timeout: TimeInterval = HTTP.defaultTimeout, + useSeedNodeURLSession: Bool = false + ) -> AnyPublisher { + guard let url: URL = URL(string: url) else { + return Fail(error: HTTPError.invalidURL) + .eraseToAnyPublisher() + } + + let urlSession: URLSession = (useSeedNodeURLSession ? seedNodeURLSession : snodeURLSession) + var request = URLRequest(url: url) + request.httpMethod = method.rawValue + request.httpBody = body + request.timeoutInterval = timeout + request.allHTTPHeaderFields?.removeValue(forKey: "User-Agent") + request.setValue("WhatsApp", forHTTPHeaderField: "User-Agent") // Set a fake value + request.setValue("en-us", forHTTPHeaderField: "Accept-Language") // Set a fake value + + return urlSession + .dataTaskPublisher(for: request) + .mapError { error in + SNLog("\(method.rawValue) request to \(url) failed due to error: \(error).") + + // Override the actual error so that we can correctly catch failed requests + // in sendOnionRequest(invoking:on:with:) + switch (error as NSError).code { + case NSURLErrorTimedOut: return HTTPError.timeout + default: return HTTPError.httpRequestFailed(statusCode: 0, data: nil) + } + } + .flatMap { data, response in + guard let response = response as? HTTPURLResponse else { + SNLog("\(method.rawValue) request to \(url) failed.") + return Fail(error: HTTPError.httpRequestFailed(statusCode: 0, data: data)) + .eraseToAnyPublisher() + } + let statusCode = UInt(response.statusCode) +// TODO: Remove all the JSON handling? + guard 200...299 ~= statusCode else { + var json: JSON? = nil + if let processedJson: JSON = try? JSONSerialization.jsonObject(with: data, options: [ .fragmentsAllowed ]) as? JSON { + json = processedJson + } + else if let result: String = String(data: data, encoding: .utf8) { + json = [ "result": result ] + } + + let jsonDescription: String = (json?.prettifiedDescription ?? "no debugging info provided") + SNLog("\(method.rawValue) request to \(url) failed with status code: \(statusCode) (\(jsonDescription)).") + return Fail(error: HTTPError.httpRequestFailed(statusCode: statusCode, data: data)) + .eraseToAnyPublisher() + } + + return Just(data) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + .eraseToAnyPublisher() + } } From 8ac6b25ced9cd20b4cdd7a5f8f60abb81f467bf2 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 2 Dec 2022 17:32:15 +1100 Subject: [PATCH 010/135] Further refactoring work Refactored usages and removed AFNetworking Updated the media screens to use Combine # Conflicts: # Session/Closed Groups/GroupMembersViewModel.swift # Session/Media Viewing & Editing/GIFs/GifPickerCell.swift # Session/Media Viewing & Editing/GIFs/GiphyAPI.swift # Session/Media Viewing & Editing/PhotoCapture.swift --- Podfile | 2 - Podfile.lock | 20 +- Session.xcodeproj/project.pbxproj | 2 - .../Calls/Call Management/SessionCall.swift | 1 - Session/Closed Groups/NewClosedGroupVC.swift | 58 +- .../Conversations/Input View/InputView.swift | 35 +- .../New Conversation/NewConversationVC.swift | 1 - Session/Home/New Conversation/NewDMVC.swift | 78 +- .../GIFs/GifPickerCell.swift | 42 +- .../GIFs/GifPickerViewController.swift | 153 +- .../GIFs/GiphyAPI.swift | 227 +- .../GIFs/GiphyDownloader.swift | 6 +- .../ImagePickerController.swift | 22 +- .../MediaPageViewController.swift | 20 +- .../PhotoCapture.swift | 179 +- .../PhotoCaptureViewController.swift | 56 +- .../PhotoLibrary.swift | 65 +- .../SendMediaNavigationController.swift | 78 +- .../MediaDismissAnimationController.swift | 1 - .../Settings.bundle/Acknowledgements.plist | 1337 ---------- Session/Meta/Settings.bundle/Root.plist | 53 + .../Settings2.bundle/Acknowledgements.plist | 2160 +++++++++++++++ ...nMessagingKitTests-settings-metadata.plist | 2340 +++++++++++++++++ ...essionMessagingKit-settings-metadata.plist | 1854 +++++++++++++ ...sionShareExtension-settings-metadata.plist | 1618 ++++++++++++ ...nUtilitiesKitTests-settings-metadata.plist | 2103 +++++++++++++++ ...essionUtilitiesKit-settings-metadata.plist | 1685 ++++++++++++ ...SignalUtilitiesKit-settings-metadata.plist | 1951 ++++++++++++++ ...onServiceExtension-settings-metadata.plist | 1364 ++++++++++ ...es-SessionSnodeKit-settings-metadata.plist | 1364 ++++++++++ ...ssion-SessionTests-settings-metadata.plist | 1221 +++++++++ ...pendencies-Session-settings-metadata.plist | 2160 +++++++++++++++ ...ncies-SessionUIKit-settings-metadata.plist | 462 ++++ Session/Meta/Settings2.bundle/Root.plist | 19 + .../en.lproj/Acknowledgements.strings | 0 .../Settings2.bundle/en.lproj/Root.strings | Bin 0 -> 546 bytes .../Database/Models/LinkPreview.swift | 284 +- SessionSnodeKit/Networking/SnodeAPI.swift | 1 - SessionSnodeKit/OnionRequestAPI.swift | 3 - .../Networking/ContentProxy.swift | 76 +- .../Networking/ProxiedContentDownloader.swift | 35 + .../MediaMessageView.swift | 85 +- SignalUtilitiesKit/Utilities/AppSetup.swift | 2 +- 43 files changed, 21166 insertions(+), 2057 deletions(-) delete mode 100644 Session/Meta/Settings.bundle/Acknowledgements.plist create mode 100644 Session/Meta/Settings2.bundle/Acknowledgements.plist create mode 100644 Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests-settings-metadata.plist create mode 100644 Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-settings-metadata.plist create mode 100644 Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension-settings-metadata.plist create mode 100644 Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests-settings-metadata.plist create mode 100644 Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-settings-metadata.plist create mode 100644 Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit-settings-metadata.plist create mode 100644 Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension-settings-metadata.plist create mode 100644 Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit-settings-metadata.plist create mode 100644 Session/Meta/Settings2.bundle/Pods-GlobalDependencies-Session-SessionTests-settings-metadata.plist create mode 100644 Session/Meta/Settings2.bundle/Pods-GlobalDependencies-Session-settings-metadata.plist create mode 100644 Session/Meta/Settings2.bundle/Pods-GlobalDependencies-SessionUIKit-settings-metadata.plist create mode 100644 Session/Meta/Settings2.bundle/Root.plist rename Session/Meta/{Settings.bundle => Settings2.bundle}/en.lproj/Acknowledgements.strings (100%) create mode 100644 Session/Meta/Settings2.bundle/en.lproj/Root.strings diff --git a/Podfile b/Podfile index 133a7c87a..9ebc39636 100644 --- a/Podfile +++ b/Podfile @@ -19,7 +19,6 @@ abstract_target 'GlobalDependencies' do pod 'SocketRocket', '~> 0.5.1' target 'Session' do - pod 'AFNetworking' pod 'Reachability' pod 'PureLayout', '~> 3.1.8' pod 'NVActivityIndicatorView' @@ -45,7 +44,6 @@ abstract_target 'GlobalDependencies' do # Dependencies that are shared across a number of extensions/frameworks but not all abstract_target 'ExtendedDependencies' do - pod 'AFNetworking' pod 'PureLayout', '~> 3.1.8' target 'SessionShareExtension' do diff --git a/Podfile.lock b/Podfile.lock index 8cc73eae6..603dc6ae7 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,19 +1,4 @@ PODS: - - AFNetworking (4.0.1): - - AFNetworking/NSURLSession (= 4.0.1) - - AFNetworking/Reachability (= 4.0.1) - - AFNetworking/Security (= 4.0.1) - - AFNetworking/Serialization (= 4.0.1) - - AFNetworking/UIKit (= 4.0.1) - - AFNetworking/NSURLSession (4.0.1): - - AFNetworking/Reachability - - AFNetworking/Security - - AFNetworking/Serialization - - AFNetworking/Reachability (4.0.1) - - AFNetworking/Security (4.0.1) - - AFNetworking/Serialization (4.0.1) - - AFNetworking/UIKit (4.0.1): - - AFNetworking/NSURLSession - CocoaLumberjack (3.7.4): - CocoaLumberjack/Core (= 3.7.4) - CocoaLumberjack/Core (3.7.4) @@ -139,7 +124,6 @@ PODS: - ZXingObjC/All (3.6.5) DEPENDENCIES: - - AFNetworking - CryptoSwift - Curve25519Kit (from `https://github.com/oxen-io/session-ios-curve-25519-kit.git`, branch `session-version`) - DifferenceKit @@ -163,7 +147,6 @@ DEPENDENCIES: SPEC REPOS: https://github.com/CocoaPods/Specs.git: - - AFNetworking - CocoaLumberjack - CryptoSwift - DifferenceKit @@ -217,7 +200,6 @@ CHECKOUT OPTIONS: :git: https://github.com/signalapp/YYImage SPEC CHECKSUMS: - AFNetworking: 7864c38297c79aaca1500c33288e429c3451fdce CocoaLumberjack: 543c79c114dadc3b1aba95641d8738b06b05b646 CryptoSwift: a532e74ed010f8c95f611d00b8bbae42e9fe7c17 Curve25519Kit: e63f9859ede02438ae3defc5e1a87e09d1ec7ee6 @@ -242,6 +224,6 @@ SPEC CHECKSUMS: YYImage: f1ddd15ac032a58b78bbed1e012b50302d318331 ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: 7452ce88370eadd58d21fdf6a4c4945d6554ee95 +PODFILE CHECKSUM: ce9209e5c7ea252b767ee08075d4e4f8b49e487b COCOAPODS: 1.11.3 diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index ac13f45d1..e008bab72 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -1724,7 +1724,6 @@ FD245C612850664300B966DD /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; FD28A4F527EAD44C00FF65E7 /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = ""; }; FD2AAAEF28ED57B500A49611 /* SynchronousStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SynchronousStorage.swift; sourceTree = ""; }; - FD2AAAF328EE882B00A49611 /* HTTPError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPError.swift; sourceTree = ""; }; FD37E9C228A1C6F3003AE748 /* ThemeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = ""; }; FD37E9C528A1D4EC003AE748 /* Theme+ClassicDark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+ClassicDark.swift"; sourceTree = ""; }; FD37E9C728A1D73F003AE748 /* Theme+Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+Colors.swift"; sourceTree = ""; }; @@ -1753,7 +1752,6 @@ FD37EA1428AB42CB003AE748 /* IdentitySpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentitySpec.swift; sourceTree = ""; }; FD37EA1628AC5605003AE748 /* NotificationContentViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationContentViewModel.swift; sourceTree = ""; }; FD37EA1828AC5CCA003AE748 /* NotificationSoundViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSoundViewModel.swift; sourceTree = ""; }; - FD37EA1A28ACB51F003AE748 /* _007_HomeQueryOptimisationIndexes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _007_HomeQueryOptimisationIndexes.swift; sourceTree = ""; }; FD37F5572908F5C3005A5E92 /* RemoveUsersModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveUsersModal.swift; sourceTree = ""; }; FD37F559290F9E9A005A5E92 /* GroupMembersViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMembersViewModel.swift; sourceTree = ""; }; FD37F55F291867A8005A5E92 /* ImagePickerHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePickerHandler.swift; sourceTree = ""; }; diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index 1fc8620a5..4f5255d12 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -4,7 +4,6 @@ import Foundation import CallKit import GRDB import WebRTC -import PromiseKit import SignalUtilitiesKit import SessionMessagingKit diff --git a/Session/Closed Groups/NewClosedGroupVC.swift b/Session/Closed Groups/NewClosedGroupVC.swift index ee417e542..32aa77af9 100644 --- a/Session/Closed Groups/NewClosedGroupVC.swift +++ b/Session/Closed Groups/NewClosedGroupVC.swift @@ -3,7 +3,6 @@ import UIKit import GRDB import DifferenceKit -import PromiseKit import SessionUIKit import SessionMessagingKit import SignalUtilitiesKit @@ -317,34 +316,39 @@ final class NewClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegate let message: String? = (selectedContacts.count > 20 ? "GROUP_CREATION_PLEASE_WAIT".localized() : nil) ModalActivityIndicatorViewController.present(fromViewController: navigationController!, message: message) { [weak self] _ in Storage.shared - .writeAsync { db in - try MessageSender.createClosedGroup(db, name: name, members: selectedContacts) + .writePublisherFlatMap { db in + MessageSender.createClosedGroup(db, name: name, members: selectedContacts) } - .done(on: DispatchQueue.main) { thread in - Storage.shared.writeAsync { db in - try? MessageSender - .syncConfiguration(db, forceSyncNow: true) - .sinkUntilComplete() + .sinkUntilComplete( + receiveCompletion: { result in + switch result { + case .finished: break + case .failure: + self?.dismiss(animated: true, completion: nil) // Dismiss the loader + + let modal: ConfirmationModal = ConfirmationModal( + targetView: self?.view, + info: ConfirmationModal.Info( + title: "GROUP_CREATION_ERROR_TITLE".localized(), + explanation: "GROUP_CREATION_ERROR_MESSAGE".localized(), + cancelTitle: "BUTTON_OK".localized(), + cancelStyle: .alert_text + ) + ) + self?.present(modal, animated: true) + } + }, + receiveValue: { thread in + Storage.shared.writeAsync { db in + try? MessageSender + .syncConfiguration(db, forceSyncNow: true) + .sinkUntilComplete() + } + + self?.presentingViewController?.dismiss(animated: true, completion: nil) + SessionApp.presentConversation(for: thread.id, action: .compose, animated: false) } - - self?.presentingViewController?.dismiss(animated: true, completion: nil) - SessionApp.presentConversation(for: thread.id, action: .compose, animated: false) - } - .catch(on: DispatchQueue.main) { [weak self] _ in - self?.dismiss(animated: true, completion: nil) // Dismiss the loader - - let modal: ConfirmationModal = ConfirmationModal( - targetView: self?.view, - info: ConfirmationModal.Info( - title: "GROUP_CREATION_ERROR_TITLE".localized(), - explanation: "GROUP_CREATION_ERROR_MESSAGE".localized(), - cancelTitle: "BUTTON_OK".localized(), - cancelStyle: .alert_text - ) - ) - self?.present(modal, animated: true) - } - .retainUntilComplete() + ) } } } diff --git a/Session/Conversations/Input View/InputView.swift b/Session/Conversations/Input View/InputView.swift index b1b40c892..7ca5bde8a 100644 --- a/Session/Conversations/Input View/InputView.swift +++ b/Session/Conversations/Input View/InputView.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit +import Combine import SessionUIKit import SessionMessagingKit @@ -9,6 +10,7 @@ final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, M private static let linkPreviewViewInset: CGFloat = 6 + private var disposables: Set = Set() private let threadVariant: SessionThread.Variant private weak var delegate: InputViewDelegate? @@ -326,19 +328,26 @@ final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, M // Build the link preview LinkPreview.tryToBuildPreviewInfo(previewUrl: linkPreviewURL) - .done { [weak self] draft in - guard self?.linkPreviewInfo?.url == linkPreviewURL else { return } // Obsolete - - self?.linkPreviewInfo = (url: linkPreviewURL, draft: draft) - self?.linkPreviewView.update(with: LinkPreview.DraftState(linkPreviewDraft: draft), isOutgoing: false) - } - .catch { [weak self] _ in - guard self?.linkPreviewInfo?.url == linkPreviewURL else { return } // Obsolete - - self?.linkPreviewInfo = nil - self?.additionalContentContainer.subviews.forEach { $0.removeFromSuperview() } - } - .retainUntilComplete() + .receive(on: DispatchQueue.main) + .sink( + receiveCompletion: { [weak self] result in + switch result { + case .finished: break + case .failure: + guard self?.linkPreviewInfo?.url == linkPreviewURL else { return } // Obsolete + + self?.linkPreviewInfo = nil + self?.additionalContentContainer.subviews.forEach { $0.removeFromSuperview() } + } + }, + receiveValue: { [weak self] draft in + guard self?.linkPreviewInfo?.url == linkPreviewURL else { return } // Obsolete + + self?.linkPreviewInfo = (url: linkPreviewURL, draft: draft) + self?.linkPreviewView.update(with: LinkPreview.DraftState(linkPreviewDraft: draft), isOutgoing: false) + } + ) + .store(in: &disposables) } func setEnabledMessageTypes(_ messageTypes: MessageInputTypes, message: String?) { diff --git a/Session/Home/New Conversation/NewConversationVC.swift b/Session/Home/New Conversation/NewConversationVC.swift index 0e739dbfe..4862bd4fd 100644 --- a/Session/Home/New Conversation/NewConversationVC.swift +++ b/Session/Home/New Conversation/NewConversationVC.swift @@ -2,7 +2,6 @@ import UIKit import GRDB -import PromiseKit import SessionUIKit import SessionMessagingKit diff --git a/Session/Home/New Conversation/NewDMVC.swift b/Session/Home/New Conversation/NewDMVC.swift index 88381416e..410b4fa92 100644 --- a/Session/Home/New Conversation/NewDMVC.swift +++ b/Session/Home/New Conversation/NewDMVC.swift @@ -184,44 +184,50 @@ final class NewDMVC: BaseVC, UIPageViewControllerDataSource, UIPageViewControlle .present(fromViewController: navigationController!, canCancel: false) { [weak self] modalActivityIndicator in SnodeAPI .getSessionID(for: onsNameOrPublicKey) - .done { sessionID in - modalActivityIndicator.dismiss { - self?.startNewDM(with: sessionID) - } - } - .catch { error in - modalActivityIndicator.dismiss { - var messageOrNil: String? - if let error = error as? SnodeAPIError { - switch error { - case .decryptionFailed, .hashingFailed, .validationFailed: - messageOrNil = error.errorDescription - default: break - } + .sinkUntilComplete( + receiveCompletion: { result in + switch result { + case .finished: break + case .failure(let error): + modalActivityIndicator.dismiss { + var messageOrNil: String? + if let error = error as? SnodeAPIError { + switch error { + case .decryptionFailed, .hashingFailed, .validationFailed: + messageOrNil = error.errorDescription + default: break + } + } + let message: String = { + if let messageOrNil: String = messageOrNil { + return messageOrNil + } + + return (maybeSessionId?.prefix == .blinded ? + "DM_ERROR_DIRECT_BLINDED_ID".localized() : + "DM_ERROR_INVALID".localized() + ) + }() + + let modal: ConfirmationModal = ConfirmationModal( + targetView: self?.view, + info: ConfirmationModal.Info( + title: "ALERT_ERROR_TITLE".localized(), + explanation: message, + cancelTitle: "BUTTON_OK".localized(), + cancelStyle: .alert_text + ) + ) + self?.present(modal, animated: true) + } + } + }, + receiveValue: { sessionId in + modalActivityIndicator.dismiss { + self?.startNewDM(with: sessionId) } - let message: String = { - if let messageOrNil: String = messageOrNil { - return messageOrNil - } - - return (maybeSessionId?.prefix == .blinded ? - "DM_ERROR_DIRECT_BLINDED_ID".localized() : - "DM_ERROR_INVALID".localized() - ) - }() - - let modal: ConfirmationModal = ConfirmationModal( - targetView: self?.view, - info: ConfirmationModal.Info( - title: "ALERT_ERROR_TITLE".localized(), - explanation: message, - cancelTitle: "BUTTON_OK".localized(), - cancelStyle: .alert_text - ) - ) - self?.present(modal, animated: true) } - } + ) } } diff --git a/Session/Media Viewing & Editing/GIFs/GifPickerCell.swift b/Session/Media Viewing & Editing/GIFs/GifPickerCell.swift index 6c8a5ae08..15b0c80d0 100644 --- a/Session/Media Viewing & Editing/GIFs/GifPickerCell.swift +++ b/Session/Media Viewing & Editing/GIFs/GifPickerCell.swift @@ -1,13 +1,9 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation -import PromiseKit -import SignalUtilitiesKit -import SignalUtilitiesKit +import Combine import YYImage -import SignalCoreKit +import SignalUtilitiesKit class GifPickerCell: UICollectionViewCell { @@ -245,29 +241,27 @@ class GifPickerCell: UICollectionViewCell { } } - public func requestRenditionForSending() -> Promise { + public func requestRenditionForSending() -> AnyPublisher { guard let renditionForSending = self.renditionForSending else { owsFailDebug("renditionForSending was unexpectedly nil") - return Promise(error: GiphyError.assertionError(description: "renditionForSending was unexpectedly nil")) + return Fail(error: GiphyError.assertionError(description: "renditionForSending was unexpectedly nil")) + .eraseToAnyPublisher() } - let (promise, resolver) = Promise.pending() - // We don't retain a handle on the asset request, since there will only ever // be one selected asset, and we never want to cancel it. - _ = GiphyDownloader.giphyDownloader.requestAsset(assetDescription: renditionForSending, - priority: .high, - success: { _, asset in - resolver.fulfill(asset) - }, - failure: { _ in - // TODO GiphyDownloader API should pass through a useful failing error - // so we can pass it through here - Logger.error("request failed") - resolver.reject(GiphyError.fetchFailure) - }) - - return promise + return GiphyDownloader.giphyDownloader + .requestAsset( + assetDescription: renditionForSending, + priority: .high + ) + .mapError { _ -> Error in + // TODO: GiphyDownloader API should pass through a useful failing error so we can pass it through here + Logger.error("request failed") + return GiphyError.fetchFailure + } + .map { asset, _ in asset } + .eraseToAnyPublisher() } private func clearViewState() { diff --git a/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift b/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift index 672ce1f54..94c86d784 100644 --- a/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift +++ b/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift @@ -1,11 +1,9 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import Combine import Reachability import SignalUtilitiesKit -import PromiseKit import SessionUIKit import SignalCoreKit @@ -43,6 +41,8 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect private let kCellReuseIdentifier = "kCellReuseIdentifier" var progressiveSearchTimer: Timer? + + private var disposables: Set = Set() // MARK: - Initialization @@ -360,47 +360,52 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect public func getFileForCell(_ cell: GifPickerCell) { GiphyDownloader.giphyDownloader.cancelAllRequests() - - firstly { - cell.requestRenditionForSending() - }.done { [weak self] (asset: ProxiedContentAsset) in - guard let strongSelf = self else { - Logger.info("ignoring send, since VC was dismissed before fetching finished.") - return - } - guard let rendition = asset.assetDescription as? GiphyRendition else { - owsFailDebug("Invalid asset description.") - return - } - - let filePath = asset.filePath - guard let dataSource = DataSourcePath.dataSource(withFilePath: filePath, - shouldDeleteOnDeallocation: false) else { - owsFailDebug("couldn't load asset.") - return - } - let attachment = SignalAttachment.attachment(dataSource: dataSource, dataUTI: rendition.utiType, imageQuality: .medium) - - strongSelf.dismiss(animated: true) { - // Delegate presents view controllers, so it's important that *this* controller be dismissed before that occurs. - strongSelf.delegate?.gifPickerDidSelect(attachment: attachment) - } - }.catch { [weak self] error in - let modal: ConfirmationModal = ConfirmationModal( - targetView: self?.view, - info: ConfirmationModal.Info( - title: "GIF_PICKER_FAILURE_ALERT_TITLE".localized(), - explanation: error.localizedDescription, - confirmTitle: CommonStrings.retryButton, - cancelTitle: CommonStrings.dismissButton, - cancelStyle: .alert_text, - onConfirm: { _ in - self?.getFileForCell(cell) + + cell + .requestRenditionForSending() + .receive(on: DispatchQueue.main) + .sink( + receiveCompletion: { [weak self] result in + switch result { + case .finished: break + case .failure(let error): + let modal: ConfirmationModal = ConfirmationModal( + targetView: self?.view, + info: ConfirmationModal.Info( + title: "GIF_PICKER_FAILURE_ALERT_TITLE".localized(), + explanation: error.localizedDescription, + confirmTitle: CommonStrings.retryButton, + cancelTitle: CommonStrings.dismissButton, + cancelStyle: .alert_text, + onConfirm: { _ in + self?.getFileForCell(cell) + } + ) + ) + self?.present(modal, animated: true) } - ) + }, + receiveValue: { [weak self] asset in + guard let rendition = asset.assetDescription as? GiphyRendition else { + owsFailDebug("Invalid asset description.") + return + } + + let filePath = asset.filePath + guard let dataSource = DataSourcePath.dataSource(withFilePath: filePath, + shouldDeleteOnDeallocation: false) else { + owsFailDebug("couldn't load asset.") + return + } + let attachment = SignalAttachment.attachment(dataSource: dataSource, dataUTI: rendition.utiType, imageQuality: .medium) + + self?.dismiss(animated: true) { + // Delegate presents view controllers, so it's important that *this* controller be dismissed before that occurs. + self?.delegate?.gifPickerDidSelect(attachment: attachment) + } + } ) - self?.present(modal, animated: true) - }.retainUntilComplete() + .store(in: &disposables) } public func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { @@ -487,22 +492,30 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect assert(progressiveSearchTimer == nil) assert(searchBar.text == nil || searchBar.text?.count == 0) - GiphyAPI.sharedInstance.trending() - .done { [weak self] imageInfos in - Logger.info("showing trending") - - if imageInfos.count > 0 { - self?.imageInfos = imageInfos - self?.viewMode = .results + GiphyAPI.trending() + .receive(on: DispatchQueue.main) + .sink( + receiveCompletion: { result in + switch result { + case .finished: break + case .failure(let error): + // Don't both showing error UI feedback for default "trending" results. + Logger.error("error: \(error)") + } + }, + receiveValue: { [weak self] imageInfos in + Logger.info("showing trending") + + if imageInfos.count > 0 { + self?.imageInfos = imageInfos + self?.viewMode = .results + } + else { + owsFailDebug("trending results was unexpectedly empty") + } } - else { - owsFailDebug("trending results was unexpectedly empty") - } - } - .catch { error in - // Don't both showing error UI feedback for default "trending" results. - Logger.error("error: \(error)") - } + ) + .store(in: &disposables) } private func search(query: String) { @@ -515,10 +528,20 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect lastQuery = query self.collectionView.contentOffset = CGPoint.zero - GiphyAPI.sharedInstance - .search( - query: query, - success: { [weak self] imageInfos in + GiphyAPI + .search(query: query) + .receive(on: DispatchQueue.main) + .sink( + receiveCompletion: { [weak self] result in + switch result { + case .finished: break + case .failure: + Logger.info("search failed.") + // TODO: Present this error to the user. + self?.viewMode = .error + } + }, + receiveValue: { [weak self] imageInfos in Logger.info("search complete") self?.imageInfos = imageInfos @@ -528,13 +551,9 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect else { self?.viewMode = .noResults } - }, - failure: { [weak self] _ in - Logger.info("search failed.") - // TODO: Present this error to the user. - self?.viewMode = .error } ) + .store(in: &disposables) } // MARK: - GifPickerLayoutDelegate diff --git a/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift b/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift index 28c010523..3b563a7dd 100644 --- a/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift +++ b/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift @@ -1,10 +1,9 @@ -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. -import AFNetworking import Foundation -import PromiseKit +import Combine import CoreServices -import SignalCoreKit +import SessionUtilitiesKit // There's no UTI type for webp! enum GiphyFormat { @@ -15,13 +14,12 @@ enum GiphyError: Error { case assertionError(description: String) case fetchFailure } + extension GiphyError: LocalizedError { public var errorDescription: String? { switch self { - case .assertionError: - return NSLocalizedString("GIF_PICKER_ERROR_GENERIC", comment: "Generic error displayed when picking a GIF") - case .fetchFailure: - return NSLocalizedString("GIF_PICKER_ERROR_FETCH_FAILURE", comment: "Error displayed when there is a failure fetching a GIF from the remote service.") + case .assertionError: return "GIF_PICKER_ERROR_GENERIC".localized() + case .fetchFailure: return "GIF_PICKER_ERROR_FETCH_FAILURE".localized() } } } @@ -31,7 +29,7 @@ extension GiphyError: LocalizedError { // They vary in content size (i.e. width, height), // format (.jpg, .gif, .mp4, webp, etc.), // quality, etc. -@objc class GiphyRendition: ProxiedContentAssetDescription { +class GiphyRendition: ProxiedContentAssetDescription { let format: GiphyFormat let name: String let width: UInt @@ -90,7 +88,7 @@ extension GiphyError: LocalizedError { } // Represents a single Giphy image. -@objc class GiphyImageInfo: NSObject { +class GiphyImageInfo: NSObject { let giphyId: String let renditions: [GiphyRendition] // We special-case the "original" rendition because it is the @@ -264,115 +262,114 @@ extension GiphyError: LocalizedError { } } -@objc class GiphyAPI: NSObject { +enum GiphyAPI { + private static let kGiphyBaseURL = "https://api.giphy.com" + private static let urlSession: URLSession = { + let configuration: URLSessionConfiguration = ContentProxy.sessionConfiguration() + + // Don't use any caching to protect privacy of these requests. + configuration.urlCache = nil + configuration.requestCachePolicy = .reloadIgnoringCacheData + + return URLSession(configuration: configuration) + }() - // MARK: - Properties - - static let sharedInstance = GiphyAPI() - - // Force usage as a singleton - override private init() { - super.init() - - SwiftSingletons.register(self) - } - - deinit { - NotificationCenter.default.removeObserver(self) - } - - private let kGiphyBaseURL = "https://api.giphy.com/" - - private func giphyAPISessionManager() -> AFHTTPSessionManager? { - return AFHTTPSessionManager(baseURL: URL(string: kGiphyBaseURL), sessionConfiguration: .ephemeral) - } - - // MARK: Search - // This is the Signal iOS API key. - let kGiphyApiKey = "ZsUpUm2L6cVbvei347EQNp7HrROjbOdc" - let kGiphyPageSize = 20 + // MARK: - Search - public func trending() -> Promise<[GiphyImageInfo]> { - guard let sessionManager = giphyAPISessionManager() else { - Logger.error("Couldn't create session manager.") - return Promise.value([]) - } + // This is the Signal iOS API key. + private static let kGiphyApiKey = "ZsUpUm2L6cVbvei347EQNp7HrROjbOdc" + private static let kGiphyPageSize = 20 + + public static func trending() -> AnyPublisher<[GiphyImageInfo], Error> { let urlString = "/v1/gifs/trending?api_key=\(kGiphyApiKey)&limit=\(kGiphyPageSize)" - let (promise, resolver) = Promise<[GiphyImageInfo]>.pending() - sessionManager.get(urlString, - parameters: [String: AnyObject](), - headers:nil, - progress: nil, - success: { _, value in - Logger.error("search request succeeded") - if let imageInfos = self.parseGiphyImages(responseJson: value) { - resolver.fulfill(imageInfos) - } else { - Logger.error("unable to parse trending images") - resolver.fulfill([]) - } - - }, - failure: { _, error in - Logger.error("search request failed: \(error)") - resolver.reject(error) - }) - - return promise + + guard let url: URL = URL(string: "\(kGiphyBaseURL)\(urlString)") else { + return Fail(error: HTTPError.invalidURL) + .eraseToAnyPublisher() + } + + return urlSession + .dataTaskPublisher(for: url) + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) + .mapError { urlError in + Logger.error("search request failed: \(urlError)") + + // URLError codes are negative values + return HTTPError.generic + } + .map { data, _ in + Logger.error("search request succeeded") + + guard let imageInfos = self.parseGiphyImages(responseData: data) else { + Logger.error("unable to parse trending images") + return [] + } + + return imageInfos + } + .eraseToAnyPublisher() } - public func search(query: String, success: @escaping (([GiphyImageInfo]) -> Void), failure: @escaping ((NSError?) -> Void)) { - guard let sessionManager = giphyAPISessionManager() else { - Logger.error("Couldn't create session manager.") - failure(nil) - return - } - guard NSURL(string: kGiphyBaseURL) != nil else { - Logger.error("Invalid base URL.") - failure(nil) - return - } - + public static func search(query: String) -> AnyPublisher<[GiphyImageInfo], Error> { let kGiphyPageOffset = 0 - guard let queryEncoded = query.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else { - Logger.error("Could not URL encode query: \(query).") - failure(nil) - return + + guard + let queryEncoded = query.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed), + let url: URL = URL( + string: [ + kGiphyBaseURL, + "/v1/gifs/search?api_key=\(kGiphyApiKey)", + "&offset=\(kGiphyPageOffset)", + "&limit=\(kGiphyPageSize)", + "&q=\(queryEncoded)" + ].joined() + ) + else { + return Fail(error: HTTPError.invalidURL) + .eraseToAnyPublisher() } - let urlString = "/v1/gifs/search?api_key=\(kGiphyApiKey)&offset=\(kGiphyPageOffset)&limit=\(kGiphyPageSize)&q=\(queryEncoded)" - - guard ContentProxy.configureSessionManager(sessionManager: sessionManager, forUrl: urlString) else { + + var request: URLRequest = URLRequest(url: url) + + guard ContentProxy.configureProxiedRequest(request: &request) else { owsFailDebug("Could not configure query: \(query).") - failure(nil) - return + return Fail(error: HTTPError.generic) + .eraseToAnyPublisher() } - - sessionManager.get(urlString, - parameters: [String: AnyObject](), - headers: nil, - progress: nil, - success: { _, value in - Logger.error("search request succeeded") - guard let imageInfos = self.parseGiphyImages(responseJson: value) else { - failure(nil) - return - } - success(imageInfos) - }, - failure: { _, error in - Logger.error("search request failed: \(error)") - failure(error as NSError) - }) + + return urlSession + .dataTaskPublisher(for: request) + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) + .mapError { urlError in + Logger.error("search request failed: \(urlError)") + + // URLError codes are negative values + return HTTPError.generic + } + .flatMap { data, _ -> AnyPublisher<[GiphyImageInfo], Error> in + Logger.error("search request succeeded") + + guard let imageInfos = self.parseGiphyImages(responseData: data) else { + return Fail(error: HTTPError.invalidResponse) + .eraseToAnyPublisher() + } + + return Just(imageInfos) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + .eraseToAnyPublisher() } - // MARK: Parse API Responses + // MARK: - Parse API Responses - private func parseGiphyImages(responseJson: Any?) -> [GiphyImageInfo]? { - guard let responseJson = responseJson else { + private static func parseGiphyImages(responseData: Data?) -> [GiphyImageInfo]? { + guard let responseData: Data = responseData else { Logger.error("Missing response.") return nil } - guard let responseDict = responseJson as? [String: Any] else { + guard let responseDict: [String: Any] = try? JSONSerialization + .jsonObject(with: responseData, options: [ .fragmentsAllowed ]) as? [String: Any] else { Logger.error("Invalid response.") return nil } @@ -386,7 +383,7 @@ extension GiphyError: LocalizedError { } // Giphy API results are often incomplete or malformed, so we need to be defensive. - private func parseGiphyImage(imageDict: [String: Any]) -> GiphyImageInfo? { + private static func parseGiphyImage(imageDict: [String: Any]) -> GiphyImageInfo? { guard let giphyId = imageDict["id"] as? String else { Logger.warn("Image dict missing id.") return nil @@ -421,12 +418,14 @@ extension GiphyError: LocalizedError { return nil } - return GiphyImageInfo(giphyId: giphyId, - renditions: renditions, - originalRendition: originalRendition) + return GiphyImageInfo( + giphyId: giphyId, + renditions: renditions, + originalRendition: originalRendition + ) } - private func findOriginalRendition(renditions: [GiphyRendition]) -> GiphyRendition? { + private static func findOriginalRendition(renditions: [GiphyRendition]) -> GiphyRendition? { for rendition in renditions where rendition.name == "original" { return rendition } @@ -436,8 +435,10 @@ extension GiphyError: LocalizedError { // Giphy API results are often incomplete or malformed, so we need to be defensive. // // We should discard renditions which are missing or have invalid properties. - private func parseGiphyRendition(renditionName: String, - renditionDict: [String: Any]) -> GiphyRendition? { + private static func parseGiphyRendition( + renditionName: String, + renditionDict: [String: Any] + ) -> GiphyRendition? { guard let width = parsePositiveUInt(dict: renditionDict, key: "width", typeName: "rendition") else { return nil } @@ -485,7 +486,7 @@ extension GiphyError: LocalizedError { ) } - private func parsePositiveUInt(dict: [String: Any], key: String, typeName: String) -> UInt? { + private static func parsePositiveUInt(dict: [String: Any], key: String, typeName: String) -> UInt? { guard let value = dict[key] else { return nil } @@ -502,7 +503,7 @@ extension GiphyError: LocalizedError { return parsedValue } - private func parseLenientUInt(dict: [String: Any], key: String) -> UInt { + private static func parseLenientUInt(dict: [String: Any], key: String) -> UInt { let defaultValue = UInt(0) guard let value = dict[key] else { diff --git a/Session/Media Viewing & Editing/GIFs/GiphyDownloader.swift b/Session/Media Viewing & Editing/GIFs/GiphyDownloader.swift index 1f509322d..3dd50b0c0 100644 --- a/Session/Media Viewing & Editing/GIFs/GiphyDownloader.swift +++ b/Session/Media Viewing & Editing/GIFs/GiphyDownloader.swift @@ -1,15 +1,11 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation import SignalUtilitiesKit -@objc public class GiphyDownloader: ProxiedContentDownloader { // MARK: - Properties - @objc public static let giphyDownloader = GiphyDownloader(downloadFolderName: "GIFs") } diff --git a/Session/Media Viewing & Editing/ImagePickerController.swift b/Session/Media Viewing & Editing/ImagePickerController.swift index 34c756f7e..55662283f 100644 --- a/Session/Media Viewing & Editing/ImagePickerController.swift +++ b/Session/Media Viewing & Editing/ImagePickerController.swift @@ -1,10 +1,8 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import Combine import Photos -import PromiseKit import SessionUIKit import SignalUtilitiesKit import SignalCoreKit @@ -14,7 +12,7 @@ protocol ImagePickerGridControllerDelegate: AnyObject { func imagePickerDidCancel(_ imagePicker: ImagePickerGridController) func imagePicker(_ imagePicker: ImagePickerGridController, isAssetSelected asset: PHAsset) -> Bool - func imagePicker(_ imagePicker: ImagePickerGridController, didSelectAsset asset: PHAsset, attachmentPromise: Promise) + func imagePicker(_ imagePicker: ImagePickerGridController, didSelectAsset asset: PHAsset, attachmentPublisher: AnyPublisher) func imagePicker(_ imagePicker: ImagePickerGridController, didDeselectAsset asset: PHAsset) var isInBatchSelectMode: Bool { get } @@ -181,8 +179,11 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat return } - let attachmentPromise: Promise = photoCollectionContents.outgoingAttachment(for: asset) - delegate.imagePicker(self, didSelectAsset: asset, attachmentPromise: attachmentPromise) + delegate.imagePicker( + self, + didSelectAsset: asset, + attachmentPublisher: photoCollectionContents.outgoingAttachment(for: asset) + ) collectionView.selectItem(at: indexPath, animated: true, scrollPosition: []) case .deselect: delegate.imagePicker(self, didDeselectAsset: asset) @@ -492,8 +493,11 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat } let asset: PHAsset = photoCollectionContents.asset(at: indexPath.item) - let attachmentPromise: Promise = photoCollectionContents.outgoingAttachment(for: asset) - delegate.imagePicker(self, didSelectAsset: asset, attachmentPromise: attachmentPromise) + delegate.imagePicker( + self, + didSelectAsset: asset, + attachmentPublisher: photoCollectionContents.outgoingAttachment(for: asset) + ) if !delegate.isInBatchSelectMode { // Don't show "selected" badge unless we're in batch mode diff --git a/Session/Media Viewing & Editing/MediaPageViewController.swift b/Session/Media Viewing & Editing/MediaPageViewController.swift index 1cb0e1d95..eb6fd7d9b 100644 --- a/Session/Media Viewing & Editing/MediaPageViewController.swift +++ b/Session/Media Viewing & Editing/MediaPageViewController.swift @@ -2,7 +2,6 @@ import UIKit import GRDB -import PromiseKit import SessionUIKit import SessionMessagingKit import SignalUtilitiesKit @@ -922,24 +921,17 @@ extension MediaGalleryViewModel.Item: GalleryRailItem { let imageView: UIImageView = UIImageView() imageView.contentMode = .scaleAspectFill - getRailImage() - .map { [weak imageView] image in - guard let imageView = imageView else { return } - imageView.image = image - } - .retainUntilComplete() + self.thumbnailImage { [weak imageView] image in + imageView?.image = image + } return imageView } - - public func getRailImage() -> Guarantee { - return Guarantee { fulfill in - self.thumbnailImage(async: { image in fulfill(image) }) - } - } public func isEqual(to other: GalleryRailItem?) -> Bool { - guard let otherItem: MediaGalleryViewModel.Item = other as? MediaGalleryViewModel.Item else { return false } + guard let otherItem: MediaGalleryViewModel.Item = other as? MediaGalleryViewModel.Item else { + return false + } return (self == otherItem) } diff --git a/Session/Media Viewing & Editing/PhotoCapture.swift b/Session/Media Viewing & Editing/PhotoCapture.swift index 4a406c066..d6964ac56 100644 --- a/Session/Media Viewing & Editing/PhotoCapture.swift +++ b/Session/Media Viewing & Editing/PhotoCapture.swift @@ -1,8 +1,8 @@ -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import Combine import AVFoundation -import PromiseKit import CoreServices import SignalCoreKit @@ -81,77 +81,113 @@ class PhotoCapture: NSObject { Environment.shared?.audioSession.endAudioActivity(recordingAudioActivity) } - func startCapture() -> Promise { - return sessionQueue.async(.promise) { [weak self] in - guard let self = self else { return } + func startCapture() -> AnyPublisher { + return Just(()) + .subscribe(on: sessionQueue) + .setFailureType(to: Error.self) + .flatMap { [weak self] _ -> AnyPublisher in + self?.session.beginConfiguration() + defer { self?.session.commitConfiguration() } - self.session.beginConfiguration() - defer { self.session.commitConfiguration() } - - try self.updateCurrentInput(position: .back) - - guard let photoOutput = self.captureOutput.photoOutput else { - throw PhotoCaptureError.initializationFailed - } - - guard self.session.canAddOutput(photoOutput) else { - throw PhotoCaptureError.initializationFailed - } - - if let connection = photoOutput.connection(with: .video) { - if connection.isVideoStabilizationSupported { - connection.preferredVideoStabilizationMode = .auto + do { + try self?.updateCurrentInput(position: .back) + } + catch { + return Fail(error: error) + .eraseToAnyPublisher() } - } - self.session.addOutput(photoOutput) + guard let photoOutput = self?.captureOutput.photoOutput else { + return Fail(error: PhotoCaptureError.initializationFailed) + .eraseToAnyPublisher() + } - let movieOutput = self.captureOutput.movieOutput + guard self?.session.canAddOutput(photoOutput) == true else { + return Fail(error: PhotoCaptureError.initializationFailed) + .eraseToAnyPublisher() + } - if self.session.canAddOutput(movieOutput) { - self.session.addOutput(movieOutput) - self.session.sessionPreset = .high - if let connection = movieOutput.connection(with: .video) { + if let connection = photoOutput.connection(with: .video) { if connection.isVideoStabilizationSupported { connection.preferredVideoStabilizationMode = .auto } } + + self?.session.addOutput(photoOutput) + + if + let movieOutput = self?.captureOutput.movieOutput, + self?.session.canAddOutput(movieOutput) == true + { + self?.session.addOutput(movieOutput) + self?.session.sessionPreset = .high + + if let connection = movieOutput.connection(with: .video) { + if connection.isVideoStabilizationSupported { + connection.preferredVideoStabilizationMode = .auto + } + } + } + + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } - }.done(on: sessionQueue) { - self.session.startRunning() - } + .handleEvents( + receiveCompletion: { [weak self] result in + switch result { + case .failure: break + case .finished: self?.session.startRunning() + } + } + ) + .eraseToAnyPublisher() } - func stopCapture() -> Guarantee { - return sessionQueue.async(.promise) { - self.session.stopRunning() - } + func stopCapture() -> AnyPublisher { + return Just(()) + .subscribe(on: sessionQueue) + .handleEvents( + receiveOutput: { [weak self] in self?.session.stopRunning() } + ) + .eraseToAnyPublisher() } func assertIsOnSessionQueue() { assertOnQueue(sessionQueue) } - func switchCamera() -> Promise { + func switchCamera() -> AnyPublisher { AssertIsOnMainThread() - let newPosition: AVCaptureDevice.Position - switch desiredPosition { - case .front: - newPosition = .back - case .back: - newPosition = .front - case .unspecified: - newPosition = .front - } - desiredPosition = newPosition - return sessionQueue.async(.promise) { [weak self] in - guard let self = self else { return } - - self.session.beginConfiguration() - defer { self.session.commitConfiguration() } - try self.updateCurrentInput(position: newPosition) - } + desiredPosition = { + switch desiredPosition { + case .front: return .back + case .back: return .front + case .unspecified: return .front + } + }() + + return Just(()) + .setFailureType(to: Error.self) + .subscribe(on: sessionQueue) + .flatMap { [weak self, newPosition = self.desiredPosition] _ -> AnyPublisher in + self?.session.beginConfiguration() + defer { self?.session.commitConfiguration() } + + do { + try self?.updateCurrentInput(position: newPosition) + } + catch { + return Fail(error: error) + .eraseToAnyPublisher() + } + + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + .eraseToAnyPublisher() } // This method should be called on the serial queue, @@ -177,20 +213,29 @@ class PhotoCapture: NSObject { resetFocusAndExposure() } - func switchFlashMode() -> Guarantee { - return sessionQueue.async(.promise) { - switch self.captureOutput.flashMode { - case .auto: - Logger.debug("new flashMode: on") - self.captureOutput.flashMode = .on - case .on: - Logger.debug("new flashMode: off") - self.captureOutput.flashMode = .off - case .off: - Logger.debug("new flashMode: auto") - self.captureOutput.flashMode = .auto - } - } + func switchFlashMode() -> AnyPublisher { + return Just(()) + .subscribe(on: sessionQueue) + .handleEvents( + receiveOutput: { [weak self] _ in + switch self?.captureOutput.flashMode { + case .auto: + Logger.debug("new flashMode: on") + self?.captureOutput.flashMode = .on + + case .on: + Logger.debug("new flashMode: off") + self?.captureOutput.flashMode = .off + + case .off: + Logger.debug("new flashMode: auto") + self?.captureOutput.flashMode = .auto + + default: break + } + } + ) + .eraseToAnyPublisher() } func focus(with focusMode: AVCaptureDevice.FocusMode, diff --git a/Session/Media Viewing & Editing/PhotoCaptureViewController.swift b/Session/Media Viewing & Editing/PhotoCaptureViewController.swift index af039ea6c..66e696112 100644 --- a/Session/Media Viewing & Editing/PhotoCaptureViewController.swift +++ b/Session/Media Viewing & Editing/PhotoCaptureViewController.swift @@ -1,8 +1,8 @@ -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit +import Combine import AVFoundation -import PromiseKit import SessionUIKit import SignalUtilitiesKit import SignalCoreKit @@ -40,9 +40,15 @@ class PhotoCaptureViewController: OWSViewController { deinit { UIDevice.current.endGeneratingDeviceOrientationNotifications() if let photoCapture = photoCapture { - photoCapture.stopCapture().done { - Logger.debug("stopCapture completed") - }.retainUntilComplete() + photoCapture.stopCapture() + .sinkUntilComplete( + receiveCompletion: { result in + switch result { + case .failure: break + case .finished: Logger.debug("stopCapture completed") + } + } + ) } } @@ -187,17 +193,29 @@ class PhotoCaptureViewController: OWSViewController { let epsilonToForceCounterClockwiseRotation: CGFloat = 0.00001 self.switchCameraControl.button.transform = self.switchCameraControl.button.transform.rotate(.pi + epsilonToForceCounterClockwiseRotation) } - photoCapture.switchCamera().catch { error in - self.showFailureUI(error: error) - }.retainUntilComplete() + + photoCapture.switchCamera() + .receiveOnMain() + .sinkUntilComplete( + receiveCompletion: { [weak self] result in + switch result { + case .finished: break + case .failure(let error): self?.showFailureUI(error: error) + } + } + ) } @objc func didTapFlashMode() { Logger.debug("") - photoCapture.switchFlashMode().done { - self.updateFlashModeControl() - }.retainUntilComplete() + photoCapture.switchFlashMode() + .receiveOnMain() + .sinkUntilComplete( + receiveCompletion: { [weak self] _ in + self?.updateFlashModeControl() + } + ) } @objc @@ -288,13 +306,15 @@ class PhotoCaptureViewController: OWSViewController { previewView = CapturePreviewView(session: photoCapture.session) photoCapture.startCapture() - .done { [weak self] in - self?.showCaptureUI() - } - .catch { [weak self] error in - self?.showFailureUI(error: error) - } - .retainUntilComplete() + .receiveOnMain() + .sinkUntilComplete( + receiveCompletion: { [weak self] result in + switch result { + case .finished: self?.showCaptureUI() + case .failure(let error): self?.showFailureUI(error: error) + } + } + ) } private func showCaptureUI() { diff --git a/Session/Media Viewing & Editing/PhotoLibrary.swift b/Session/Media Viewing & Editing/PhotoLibrary.swift index 6638c04c8..056514ebd 100644 --- a/Session/Media Viewing & Editing/PhotoLibrary.swift +++ b/Session/Media Viewing & Editing/PhotoLibrary.swift @@ -1,10 +1,8 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import Combine import Photos -import PromiseKit import CoreServices import SignalUtilitiesKit import SignalCoreKit @@ -138,44 +136,45 @@ class PhotoCollectionContents { _ = imageManager.requestImage(for: asset, targetSize: thumbnailSize, contentMode: .aspectFill, options: nil, resultHandler: resultHandler) } - private func requestImageDataSource(for asset: PHAsset) -> Promise<(dataSource: DataSource, dataUTI: String)> { - return Promise { resolver in + private func requestImageDataSource(for asset: PHAsset) -> AnyPublisher<(dataSource: DataSource, dataUTI: String), Error> { + return Future { [weak self] resolver in let options: PHImageRequestOptions = PHImageRequestOptions() options.isNetworkAccessAllowed = true - _ = imageManager.requestImageData(for: asset, options: options) { imageData, dataUTI, orientation, info in + _ = self?.imageManager.requestImageData(for: asset, options: options) { imageData, dataUTI, orientation, info in guard let imageData = imageData else { - resolver.reject(PhotoLibraryError.assertionError(description: "imageData was unexpectedly nil")) + resolver(Result.failure(PhotoLibraryError.assertionError(description: "imageData was unexpectedly nil"))) return } guard let dataUTI = dataUTI else { - resolver.reject(PhotoLibraryError.assertionError(description: "dataUTI was unexpectedly nil")) + resolver(Result.failure(PhotoLibraryError.assertionError(description: "dataUTI was unexpectedly nil"))) return } guard let dataSource = DataSourceValue.dataSource(with: imageData, utiType: dataUTI) else { - resolver.reject(PhotoLibraryError.assertionError(description: "dataSource was unexpectedly nil")) + resolver(Result.failure(PhotoLibraryError.assertionError(description: "dataSource was unexpectedly nil"))) return } - resolver.fulfill((dataSource: dataSource, dataUTI: dataUTI)) + resolver(Result.success((dataSource: dataSource, dataUTI: dataUTI))) } } + .eraseToAnyPublisher() } - private func requestVideoDataSource(for asset: PHAsset) -> Promise<(dataSource: DataSource, dataUTI: String)> { - return Promise { resolver in + private func requestVideoDataSource(for asset: PHAsset) -> AnyPublisher<(dataSource: DataSource, dataUTI: String), Error> { + return Future { [weak self] resolver in let options: PHVideoRequestOptions = PHVideoRequestOptions() options.isNetworkAccessAllowed = true - _ = imageManager.requestExportSession(forVideo: asset, options: options, exportPreset: AVAssetExportPresetMediumQuality) { exportSession, foo in + _ = self?.imageManager.requestExportSession(forVideo: asset, options: options, exportPreset: AVAssetExportPresetMediumQuality) { exportSession, foo in guard let exportSession = exportSession else { - resolver.reject(PhotoLibraryError.assertionError(description: "exportSession was unexpectedly nil")) + resolver(Result.failure(PhotoLibraryError.assertionError(description: "exportSession was unexpectedly nil"))) return } @@ -191,28 +190,38 @@ class PhotoCollectionContents { Logger.debug("Completed video export") guard let dataSource = DataSourcePath.dataSource(with: exportURL, shouldDeleteOnDeallocation: true) else { - resolver.reject(PhotoLibraryError.assertionError(description: "Failed to build data source for exported video URL")) + resolver(Result.failure(PhotoLibraryError.assertionError(description: "Failed to build data source for exported video URL"))) return } - resolver.fulfill((dataSource: dataSource, dataUTI: kUTTypeMPEG4 as String)) + resolver(Result.success((dataSource: dataSource, dataUTI: kUTTypeMPEG4 as String))) } } } + .eraseToAnyPublisher() } - func outgoingAttachment(for asset: PHAsset) -> Promise { + func outgoingAttachment(for asset: PHAsset) -> AnyPublisher { switch asset.mediaType { - case .image: - return requestImageDataSource(for: asset).map { (dataSource: DataSource, dataUTI: String) in - return SignalAttachment.attachment(dataSource: dataSource, dataUTI: dataUTI, imageQuality: .medium) - } - case .video: - return requestVideoDataSource(for: asset).map { (dataSource: DataSource, dataUTI: String) in - return SignalAttachment.attachment(dataSource: dataSource, dataUTI: dataUTI) - } - default: - return Promise(error: PhotoLibraryError.unsupportedMediaType) + case .image: + return requestImageDataSource(for: asset) + .map { (dataSource: DataSource, dataUTI: String) in + SignalAttachment + .attachment(dataSource: dataSource, dataUTI: dataUTI, imageQuality: .medium) + } + .eraseToAnyPublisher() + + case .video: + return requestVideoDataSource(for: asset) + .map { (dataSource: DataSource, dataUTI: String) in + SignalAttachment + .attachment(dataSource: dataSource, dataUTI: dataUTI) + } + .eraseToAnyPublisher() + + default: + return Fail(error: PhotoLibraryError.unsupportedMediaType) + .eraseToAnyPublisher() } } } diff --git a/Session/Media Viewing & Editing/SendMediaNavigationController.swift b/Session/Media Viewing & Editing/SendMediaNavigationController.swift index 415f930bd..a452a3cbf 100644 --- a/Session/Media Viewing & Editing/SendMediaNavigationController.swift +++ b/Session/Media Viewing & Editing/SendMediaNavigationController.swift @@ -1,8 +1,8 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit +import Combine import Photos -import PromiseKit import SignalUtilitiesKit import SignalCoreKit @@ -16,6 +16,7 @@ class SendMediaNavigationController: UINavigationController { static let bottomButtonsCenterOffset: CGFloat = -50 private let threadId: String + private var disposables: Set = Set() // MARK: - Initialization @@ -325,32 +326,40 @@ extension SendMediaNavigationController: ImagePickerGridControllerDelegate { func showApprovalAfterProcessingAnyMediaLibrarySelections() { let mediaLibrarySelections: [MediaLibrarySelection] = self.mediaLibrarySelections.orderedValues - let backgroundBlock: (ModalActivityIndicatorViewController) -> Void = { modal in - let attachmentPromises: [Promise] = mediaLibrarySelections.map { $0.promise } - - when(fulfilled: attachmentPromises) - .map { attachments in - Logger.debug("built all attachments") - modal.dismiss { - self.attachmentDraftCollection.selectedFromPicker(attachments: attachments) - self.pushApprovalViewController() + let backgroundBlock: (ModalActivityIndicatorViewController) -> Void = { [weak self] modal in + guard let strongSelf = self else { return } + + Publishers + .MergeMany(mediaLibrarySelections.map { $0.publisher }) + .collect() + .sink( + receiveCompletion: { result in + switch result { + case .finished: break + case .failure(let error): + Logger.error("failed to prepare attachments. error: \(error)") + modal.dismiss { [weak self] in + let modal: ConfirmationModal = ConfirmationModal( + targetView: self?.view, + info: ConfirmationModal.Info( + title: "IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS".localized(), + cancelTitle: "BUTTON_OK".localized(), + cancelStyle: .alert_text + ) + ) + self?.present(modal, animated: true) + } + } + }, + receiveValue: { attachments in + Logger.debug("built all attachments") + modal.dismiss { + self?.attachmentDraftCollection.selectedFromPicker(attachments: attachments) + self?.pushApprovalViewController() + } } - } - .catch { error in - Logger.error("failed to prepare attachments. error: \(error)") - modal.dismiss { [weak self] in - let modal: ConfirmationModal = ConfirmationModal( - targetView: self?.view, - info: ConfirmationModal.Info( - title: "IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS".localized(), - cancelTitle: "BUTTON_OK".localized(), - cancelStyle: .alert_text - ) - ) - self?.present(modal, animated: true) - } - } - .retainUntilComplete() + ) + .store(in: &strongSelf.disposables) } ModalActivityIndicatorViewController.present( @@ -364,10 +373,13 @@ extension SendMediaNavigationController: ImagePickerGridControllerDelegate { return mediaLibrarySelections.hasValue(forKey: asset) } - func imagePicker(_ imagePicker: ImagePickerGridController, didSelectAsset asset: PHAsset, attachmentPromise: Promise) { + func imagePicker(_ imagePicker: ImagePickerGridController, didSelectAsset asset: PHAsset, attachmentPublisher: AnyPublisher) { guard !mediaLibrarySelections.hasValue(forKey: asset) else { return } - let libraryMedia = MediaLibrarySelection(asset: asset, signalAttachmentPromise: attachmentPromise) + let libraryMedia = MediaLibrarySelection( + asset: asset, + signalAttachmentPublisher: attachmentPublisher + ) mediaLibrarySelections.append(key: asset, value: libraryMedia) updateButtons(topViewController: imagePicker) } @@ -512,17 +524,17 @@ private final class AttachmentDraftCollection { private struct MediaLibrarySelection: Hashable, Equatable { let asset: PHAsset - let signalAttachmentPromise: Promise + let signalAttachmentPublisher: AnyPublisher var hashValue: Int { return asset.hashValue } - var promise: Promise { + var publisher: AnyPublisher { let asset = self.asset - return signalAttachmentPromise.map { signalAttachment in - return MediaLibraryAttachment(asset: asset, signalAttachment: signalAttachment) - } + return signalAttachmentPublisher + .map { MediaLibraryAttachment(asset: asset, signalAttachment: $0) } + .eraseToAnyPublisher() } static func ==(lhs: MediaLibrarySelection, rhs: MediaLibrarySelection) -> Bool { diff --git a/Session/Media Viewing & Editing/Transitions/MediaDismissAnimationController.swift b/Session/Media Viewing & Editing/Transitions/MediaDismissAnimationController.swift index 8a3c8a8db..d5e192e4f 100644 --- a/Session/Media Viewing & Editing/Transitions/MediaDismissAnimationController.swift +++ b/Session/Media Viewing & Editing/Transitions/MediaDismissAnimationController.swift @@ -1,7 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit -import PromiseKit class MediaDismissAnimationController: NSObject { private let mediaItem: Media diff --git a/Session/Meta/Settings.bundle/Acknowledgements.plist b/Session/Meta/Settings.bundle/Acknowledgements.plist deleted file mode 100644 index 128448f23..000000000 --- a/Session/Meta/Settings.bundle/Acknowledgements.plist +++ /dev/null @@ -1,1337 +0,0 @@ - - - - - StringsTable - Acknowledgements - PreferenceSpecifiers - - - Type - PSGroupSpecifier - FooterText - 25519 - - - Type - PSGroupSpecifier - FooterText - 255192 - - - Type - PSGroupSpecifier - FooterText - 255193 - - - Type - PSGroupSpecifier - FooterText - 255194 - - - Type - PSGroupSpecifier - FooterText - 255195 - - - Type - PSGroupSpecifier - FooterText - 255196 - - - Type - PSGroupSpecifier - FooterText - 255197 - - - Type - PSGroupSpecifier - FooterText - 255198 - - - Type - PSGroupSpecifier - FooterText - 255199 - - - Type - PSGroupSpecifier - FooterText - 2551910 - - - Type - PSGroupSpecifier - FooterText - 2551911 - - - Type - PSGroupSpecifier - FooterText - 2551912 - - - Type - PSGroupSpecifier - FooterText - 2551913 - - - Type - PSGroupSpecifier - FooterText - 2551914 - - - Type - PSGroupSpecifier - FooterText - 2551915 - - - Type - PSGroupSpecifier - FooterText - 2551916 - - - Type - PSGroupSpecifier - FooterText - 2551917 - - - Type - PSGroupSpecifier - FooterText - 2551918 - - - Type - PSGroupSpecifier - FooterText - 2551919 - - - Type - PSGroupSpecifier - FooterText - 2551920 - - - Type - PSGroupSpecifier - FooterText - 2551921 - - - Type - PSGroupSpecifier - FooterText - 2551922 - - - Type - PSGroupSpecifier - FooterText - 2551923 - - - Type - PSGroupSpecifier - FooterText - 2551924 - - - Type - PSGroupSpecifier - FooterText - 2551925 - - - Type - PSGroupSpecifier - FooterText - 2551926 - - - Type - PSGroupSpecifier - FooterText - 2551927 - - - Type - PSGroupSpecifier - FooterText - 2551928 - - - Type - PSGroupSpecifier - FooterText - 2551929 - - - Type - PSGroupSpecifier - FooterText - 2551930 - - - Type - PSGroupSpecifier - FooterText - 2551931 - - - Type - PSGroupSpecifier - FooterText - 2551932 - - - Type - PSGroupSpecifier - FooterText - 2551933 - - - Type - PSGroupSpecifier - FooterText - 2551934 - - - Type - PSGroupSpecifier - FooterText - 2551935 - - - Type - PSGroupSpecifier - FooterText - 2551936 - - - Type - PSGroupSpecifier - FooterText - 2551937 - - - Type - PSGroupSpecifier - FooterText - 2551938 - - - Type - PSGroupSpecifier - FooterText - 2551939 - - - Type - PSGroupSpecifier - FooterText - 2551940 - - - Type - PSGroupSpecifier - FooterText - 2551941 - - - Type - PSGroupSpecifier - FooterText - 2551942 - - - Type - PSGroupSpecifier - FooterText - 2551943 - - - Type - PSGroupSpecifier - FooterText - 2551944 - - - Type - PSGroupSpecifier - FooterText - 2551945 - - - Type - PSGroupSpecifier - FooterText - 2551946 - - - Type - PSGroupSpecifier - FooterText - 2551947 - - - Type - PSGroupSpecifier - FooterText - 2551948 - - - Type - PSGroupSpecifier - FooterText - 2551949 - - - Type - PSGroupSpecifier - FooterText - 2551950 - - - Type - PSGroupSpecifier - FooterText - 2551951 - - - Type - PSGroupSpecifier - FooterText - 2551952 - - - Type - PSGroupSpecifier - FooterText - 2551953 - - - Type - PSGroupSpecifier - FooterText - 2551954 - - - Type - PSGroupSpecifier - FooterText - 2551955 - - - Type - PSGroupSpecifier - FooterText - 2551956 - - - Type - PSGroupSpecifier - FooterText - 2551957 - - - Type - PSGroupSpecifier - FooterText - 2551958 - - - Type - PSGroupSpecifier - FooterText - 2551959 - - - Type - PSGroupSpecifier - FooterText - 2551960 - - - Type - PSGroupSpecifier - FooterText - AFNetworking - - - Type - PSGroupSpecifier - FooterText - AFNetworking2 - - - Type - PSGroupSpecifier - FooterText - AFNetworking3 - - - Type - PSGroupSpecifier - FooterText - AFNetworking4 - - - Type - PSGroupSpecifier - FooterText - AFNetworking5 - - - Type - PSGroupSpecifier - FooterText - APDropDownNavToolbar - - - Type - PSGroupSpecifier - FooterText - APDropDownNavToolbar2 - - - Type - PSGroupSpecifier - FooterText - APDropDownNavToolbar3 - - - Type - PSGroupSpecifier - FooterText - APDropDownNavToolbar4 - - - Type - PSGroupSpecifier - FooterText - APDropDownNavToolbar5 - - - Type - PSGroupSpecifier - FooterText - APDropDownNavToolbar6 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit - - - Type - PSGroupSpecifier - FooterText - AxolotlKit2 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit3 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit4 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit5 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit6 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit7 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit8 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit9 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit10 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit11 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit12 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit13 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit14 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit15 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit16 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit17 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit18 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit19 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit20 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit21 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit22 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit23 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit24 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit25 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit26 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit27 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit28 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit29 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit30 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit31 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit32 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit33 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit34 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit35 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit36 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit37 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit38 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit39 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit40 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit41 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit42 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit43 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit44 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit45 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit46 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit47 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit48 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit49 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit50 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit51 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit52 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit53 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit54 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit55 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit56 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit57 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit58 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit59 - - - Type - PSGroupSpecifier - FooterText - AxolotlKit60 - - - Type - PSGroupSpecifier - FooterText - CocoaLumberjack - - - Type - PSGroupSpecifier - FooterText - CocoaLumberjack2 - - - Type - PSGroupSpecifier - FooterText - CocoaLumberjack3 - - - Type - PSGroupSpecifier - FooterText - CocoaLumberjack4 - - - Type - PSGroupSpecifier - FooterText - CocoaLumberjack5 - - - Type - PSGroupSpecifier - FooterText - CocoaLumberjack6 - - - Type - PSGroupSpecifier - FooterText - CocoaLumberjack7 - - - Type - PSGroupSpecifier - FooterText - DJWActionSheet - - - Type - PSGroupSpecifier - FooterText - DJWActionSheet2 - - - Type - PSGroupSpecifier - FooterText - DJWActionSheet3 - - - Type - PSGroupSpecifier - FooterText - DJWActionSheet4 - - - Type - PSGroupSpecifier - FooterText - DJWActionSheet5 - - - Type - PSGroupSpecifier - FooterText - DJWActionSheet6 - - - Type - PSGroupSpecifier - FooterText - FFCircularProgressView - - - Type - PSGroupSpecifier - FooterText - FFCircularProgressView2 - - - Type - PSGroupSpecifier - FooterText - FFCircularProgressView3 - - - Type - PSGroupSpecifier - FooterText - FFCircularProgressView4 - - - Type - PSGroupSpecifier - FooterText - FFCircularProgressView5 - - - Type - PSGroupSpecifier - FooterText - JSQMessagesViewController - - - Type - PSGroupSpecifier - FooterText - JSQMessagesViewController2 - - - Type - PSGroupSpecifier - FooterText - JSQMessagesViewController3 - - - Type - PSGroupSpecifier - FooterText - JSQMessagesViewController4 - - - Type - PSGroupSpecifier - FooterText - JSQMessagesViewController5 - - - Type - PSGroupSpecifier - FooterText - JSQMessagesViewController6 - - - Type - PSGroupSpecifier - FooterText - Mantle - - - Type - PSGroupSpecifier - FooterText - Mantle2 - - - Type - PSGroupSpecifier - FooterText - Mantle3 - - - Type - PSGroupSpecifier - FooterText - Mantle4 - - - Type - PSGroupSpecifier - FooterText - Mantle5 - - - Type - PSGroupSpecifier - FooterText - Mantle6 - - - Type - PSGroupSpecifier - FooterText - Mantle7 - - - Type - PSGroupSpecifier - FooterText - Mantle8 - - - Type - PSGroupSpecifier - FooterText - Mantle9 - - - Type - PSGroupSpecifier - FooterText - Mantle10 - - - Type - PSGroupSpecifier - FooterText - OpenSSL - - - Type - PSGroupSpecifier - FooterText - OpenSSL2 - - - Type - PSGroupSpecifier - FooterText - OpenSSL3 - - - Type - PSGroupSpecifier - FooterText - OpenSSL4 - - - Type - PSGroupSpecifier - FooterText - OpenSSL5 - - - Type - PSGroupSpecifier - FooterText - OpenSSL6 - - - Type - PSGroupSpecifier - FooterText - OpenSSL7 - - - Type - PSGroupSpecifier - FooterText - SQLCipher - - - Type - PSGroupSpecifier - FooterText - SQLCipher2 - - - Type - PSGroupSpecifier - FooterText - SQLCipher3 - - - Type - PSGroupSpecifier - FooterText - SQLCipher4 - - - Type - PSGroupSpecifier - FooterText - SSKeychain - - - Type - PSGroupSpecifier - FooterText - SSKeychain2 - - - Type - PSGroupSpecifier - FooterText - SSKeychain3 - - - Type - PSGroupSpecifier - FooterText - SSKeychain4 - - - Type - PSGroupSpecifier - FooterText - SSKeychain5 - - - Type - PSGroupSpecifier - FooterText - SocketRocket - - - Type - PSGroupSpecifier - FooterText - SocketRocket2 - - - Type - PSGroupSpecifier - FooterText - SocketRocket3 - - - Type - PSGroupSpecifier - FooterText - SocketRocket4 - - - Type - PSGroupSpecifier - FooterText - SocketRocket5 - - - Type - PSGroupSpecifier - FooterText - YapDatabase - - - Type - PSGroupSpecifier - FooterText - YapDatabase2 - - - Type - PSGroupSpecifier - FooterText - YapDatabase3 - - - Type - PSGroupSpecifier - FooterText - YapDatabase4 - - - Type - PSGroupSpecifier - FooterText - YapDatabase5 - - - Type - PSGroupSpecifier - FooterText - YapDatabase6 - - - Type - PSGroupSpecifier - FooterText - YapDatabase7 - - - Type - PSGroupSpecifier - FooterText - libPhoneNumber-iOS - - - Type - PSGroupSpecifier - FooterText - libPhoneNumber-iOS2 - - - Type - PSGroupSpecifier - FooterText - libPhoneNumber-iOS3 - - - Type - PSGroupSpecifier - FooterText - libPhoneNumber-iOS4 - - - Type - PSGroupSpecifier - FooterText - libPhoneNumber-iOS5 - - - Type - PSGroupSpecifier - FooterText - libPhoneNumber-iOS6 - - - Type - PSGroupSpecifier - FooterText - libPhoneNumber-iOS7 - - - Type - PSGroupSpecifier - FooterText - libPhoneNumber-iOS8 - - - Type - PSGroupSpecifier - FooterText - libPhoneNumber-iOS9 - - - Type - PSGroupSpecifier - FooterText - libPhoneNumber-iOS10 - - - Type - PSGroupSpecifier - FooterText - libPhoneNumber-iOS11 - - - Type - PSGroupSpecifier - FooterText - libPhoneNumber-iOS12 - - - Type - PSGroupSpecifier - FooterText - libPhoneNumber-iOS13 - - - Type - PSGroupSpecifier - FooterText - libPhoneNumber-iOS14 - - - Type - PSGroupSpecifier - FooterText - libPhoneNumber-iOS15 - - - Type - PSGroupSpecifier - FooterText - libPhoneNumber-iOS16 - - - Type - PSGroupSpecifier - FooterText - libPhoneNumber-iOS17 - - - Type - PSGroupSpecifier - FooterText - libPhoneNumber-iOS18 - - - Type - PSGroupSpecifier - FooterText - libPhoneNumber-iOS19 - - - Type - PSGroupSpecifier - FooterText - libPhoneNumber-iOS20 - - - Type - PSGroupSpecifier - FooterText - libPhoneNumber-iOS21 - - - Type - PSGroupSpecifier - FooterText - libPhoneNumber-iOS22 - - - Type - PSGroupSpecifier - FooterText - libPhoneNumber-iOS23 - - - Type - PSGroupSpecifier - FooterText - libPhoneNumber-iOS24 - - - Type - PSGroupSpecifier - FooterText - libPhoneNumber-iOS25 - - - Type - PSGroupSpecifier - FooterText - libPhoneNumber-iOS26 - - - Type - PSGroupSpecifier - FooterText - libPhoneNumber-iOS27 - - - Type - PSGroupSpecifier - FooterText - libPhoneNumber-iOS28 - - - - diff --git a/Session/Meta/Settings.bundle/Root.plist b/Session/Meta/Settings.bundle/Root.plist index 300fb6413..b1b6fea5d 100644 --- a/Session/Meta/Settings.bundle/Root.plist +++ b/Session/Meta/Settings.bundle/Root.plist @@ -4,5 +4,58 @@ StringsTable Root + PreferenceSpecifiers + + + Type + PSGroupSpecifier + Title + Group + + + Type + PSTextFieldSpecifier + Title + Name + Key + name_preference + DefaultValue + + IsSecure + + KeyboardType + Alphabet + AutocapitalizationType + None + AutocorrectionType + No + + + Type + PSToggleSwitchSpecifier + Title + Enabled + Key + enabled_preference + DefaultValue + + + + Type + PSSliderSpecifier + Key + slider_preference + DefaultValue + 0.5 + MinimumValue + 0 + MaximumValue + 1 + MinimumValueImage + + MaximumValueImage + + + diff --git a/Session/Meta/Settings2.bundle/Acknowledgements.plist b/Session/Meta/Settings2.bundle/Acknowledgements.plist new file mode 100644 index 000000000..d17d35ab8 --- /dev/null +++ b/Session/Meta/Settings2.bundle/Acknowledgements.plist @@ -0,0 +1,2160 @@ + + + + + PreferenceSpecifiers + + + FooterText + This application makes use of the following third party libraries: + Title + Acknowledgements + Type + PSGroupSpecifier + + + FooterText + BSD 3-Clause License + +Copyright (c) 2010-2021, Deusty, LLC +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of Deusty nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Deusty, LLC. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + CocoaLumberjack + Type + PSGroupSpecifier + + + FooterText + Copyright (C) 2014-2017 Marcin Krzyżanowski <marcin.krzyzanowski@gmail.com> +This software is provided 'as-is', without any express or implied warranty. + +In no event will the authors be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: + +- The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +- Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +- This notice may not be removed or altered from any source or binary distribution. +- Redistributions of any form whatsoever must retain the following acknowledgment: 'This product includes software developed by the "Marcin Krzyzanowski" (http://krzyzanowskim.com/).' + + Title + CryptoSwift + Type + PSGroupSpecifier + + + FooterText + Copyright (C) 2015-2020 Gwendal Roué + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + GRDB.swift + Type + PSGroupSpecifier + + + FooterText + Copyright 2016-present, Max Howell; mxcl@me.com + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + PromiseKit + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2008, ZETETIC LLC +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the ZETETIC LLC nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + SQLCipher + Type + PSGroupSpecifier + + + FooterText + + Copyright 2012 Square Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + Title + SocketRocket + Type + PSGroupSpecifier + + + FooterText + ISC License + +Copyright (c) 2014-2020, Frank Denis <j at pureftpd dot org> + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + + Title + Sodium + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2011, The WebRTC project authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Google nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + WebRTC-lib + Type + PSGroupSpecifier + + + FooterText + Software License Agreement (BSD License) + +Copyright (c) 2013, yap.TV Inc. +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Neither the name of yap.TV nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of yap.TV Inc. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + Title + YapDatabase + Type + PSGroupSpecifier + + + FooterText + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + Title + DifferenceKit + Type + PSGroupSpecifier + + + FooterText + The MIT License (MIT) + +Copyright (c) 2016 Vinh Nguyen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + Title + NVActivityIndicatorView + Type + PSGroupSpecifier + + + FooterText + This code is distributed under the terms and conditions of the MIT license. + +Copyright (c) 2014-2015 Tyler Fox + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + PureLayout + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2011, Tony Million. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + Reachability + Type + PSGroupSpecifier + + + FooterText + The MIT License (MIT) + +Copyright (c) 2015 ibireme <ibireme@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + + Title + YYImage + Type + PSGroupSpecifier + + + FooterText + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Title + ZXingObjC + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2010, Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Google nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Title + libwebp + Type + PSGroupSpecifier + + + FooterText + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/> + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {description} + Copyright (C) {year} {fullname} + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. + Title + Curve25519Kit + Type + PSGroupSpecifier + + + FooterText + + LICENSE ISSUES + ============== + + The OpenSSL toolkit stays under a dual license, i.e. both the conditions of + the OpenSSL License and the original SSLeay license apply to the toolkit. + See below for the actual license texts. Actually both licenses are BSD-style + Open Source licenses. In case of any license issues related to OpenSSL + please contact openssl-core@openssl.org. + + OpenSSL License + --------------- + +/* ==================================================================== + * Copyright (c) 1998-2008 The OpenSSL Project. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" + * + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * openssl-core@openssl.org. + * + * 5. Products derived from this software may not be called "OpenSSL" + * nor may "OpenSSL" appear in their names without prior written + * permission of the OpenSSL Project. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit (http://www.openssl.org/)" + * + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * ==================================================================== + * + * This product includes cryptographic software written by Eric Young + * (eay@cryptsoft.com). This product includes software written by Tim + * Hudson (tjh@cryptsoft.com). + * + */ + + Original SSLeay License + ----------------------- + +/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ + + + Title + OpenSSL-Universal + Type + PSGroupSpecifier + + + FooterText + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + Title + SignalCoreKit + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2010-2016 Sam Soffes, http://soff.es + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + SAMKeychain + Type + PSGroupSpecifier + + + FooterText + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + +## Runtime Library Exception to the Apache 2.0 License: ## + + + As an exception, if you use this Software to compile your source code and + portions of this Software are embedded into the binary product as a result, + you may redistribute such product without providing attribution as would + otherwise be required by Sections 4(a), 4(b) and 4(d) of the License. + + Title + SwiftProtobuf + Type + PSGroupSpecifier + + + FooterText + Generated by CocoaPods - https://cocoapods.org + Title + + Type + PSGroupSpecifier + + + StringsTable + Acknowledgements + Title + Acknowledgements + + diff --git a/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests-settings-metadata.plist b/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests-settings-metadata.plist new file mode 100644 index 000000000..11e81ea5a --- /dev/null +++ b/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests-settings-metadata.plist @@ -0,0 +1,2340 @@ + + + + + PreferenceSpecifiers + + + FooterText + This application makes use of the following third party libraries: + Title + Acknowledgements + Type + PSGroupSpecifier + + + FooterText + BSD 3-Clause License + +Copyright (c) 2010-2021, Deusty, LLC +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of Deusty nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Deusty, LLC. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + CocoaLumberjack + Type + PSGroupSpecifier + + + FooterText + Copyright (C) 2014-2017 Marcin Krzyżanowski <marcin.krzyzanowski@gmail.com> +This software is provided 'as-is', without any express or implied warranty. + +In no event will the authors be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: + +- The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +- Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +- This notice may not be removed or altered from any source or binary distribution. +- Redistributions of any form whatsoever must retain the following acknowledgment: 'This product includes software developed by the "Marcin Krzyzanowski" (http://krzyzanowskim.com/).' + + Title + CryptoSwift + Type + PSGroupSpecifier + + + FooterText + Copyright (C) 2015-2020 Gwendal Roué + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + GRDB.swift + Type + PSGroupSpecifier + + + FooterText + Copyright 2016-present, Max Howell; mxcl@me.com + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + PromiseKit + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2008, ZETETIC LLC +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the ZETETIC LLC nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + SQLCipher + Type + PSGroupSpecifier + + + FooterText + + Copyright 2012 Square Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + Title + SocketRocket + Type + PSGroupSpecifier + + + FooterText + ISC License + +Copyright (c) 2014-2020, Frank Denis <j at pureftpd dot org> + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + + Title + Sodium + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2011, The WebRTC project authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Google nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + WebRTC-lib + Type + PSGroupSpecifier + + + FooterText + Software License Agreement (BSD License) + +Copyright (c) 2013, yap.TV Inc. +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Neither the name of yap.TV nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of yap.TV Inc. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + Title + YapDatabase + Type + PSGroupSpecifier + + + FooterText + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + Title + DifferenceKit + Type + PSGroupSpecifier + + + FooterText + This code is distributed under the terms and conditions of the MIT license. + +Copyright (c) 2014-2015 Tyler Fox + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + PureLayout + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2011, Tony Million. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + Reachability + Type + PSGroupSpecifier + + + FooterText + The MIT License (MIT) + +Copyright (c) 2015 ibireme <ibireme@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + + Title + YYImage + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2010, Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Google nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Title + libwebp + Type + PSGroupSpecifier + + + FooterText + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/> + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {description} + Copyright (C) {year} {fullname} + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. + Title + Curve25519Kit + Type + PSGroupSpecifier + + + FooterText + + LICENSE ISSUES + ============== + + The OpenSSL toolkit stays under a dual license, i.e. both the conditions of + the OpenSSL License and the original SSLeay license apply to the toolkit. + See below for the actual license texts. Actually both licenses are BSD-style + Open Source licenses. In case of any license issues related to OpenSSL + please contact openssl-core@openssl.org. + + OpenSSL License + --------------- + +/* ==================================================================== + * Copyright (c) 1998-2008 The OpenSSL Project. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" + * + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * openssl-core@openssl.org. + * + * 5. Products derived from this software may not be called "OpenSSL" + * nor may "OpenSSL" appear in their names without prior written + * permission of the OpenSSL Project. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit (http://www.openssl.org/)" + * + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * ==================================================================== + * + * This product includes cryptographic software written by Eric Young + * (eay@cryptsoft.com). This product includes software written by Tim + * Hudson (tjh@cryptsoft.com). + * + */ + + Original SSLeay License + ----------------------- + +/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ + + + Title + OpenSSL-Universal + Type + PSGroupSpecifier + + + FooterText + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + Title + SignalCoreKit + Type + PSGroupSpecifier + + + FooterText + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2016 Quick Team + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Title + Nimble + Type + PSGroupSpecifier + + + FooterText + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2014, Quick Team + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Title + Quick + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2010-2016 Sam Soffes, http://soff.es + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + SAMKeychain + Type + PSGroupSpecifier + + + FooterText + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + +## Runtime Library Exception to the Apache 2.0 License: ## + + + As an exception, if you use this Software to compile your source code and + portions of this Software are embedded into the binary product as a result, + you may redistribute such product without providing attribution as would + otherwise be required by Sections 4(a), 4(b) and 4(d) of the License. + + Title + SwiftProtobuf + Type + PSGroupSpecifier + + + FooterText + Generated by CocoaPods - https://cocoapods.org + Title + + Type + PSGroupSpecifier + + + StringsTable + Acknowledgements + Title + Acknowledgements + + diff --git a/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-settings-metadata.plist b/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-settings-metadata.plist new file mode 100644 index 000000000..a56df3b9f --- /dev/null +++ b/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-settings-metadata.plist @@ -0,0 +1,1854 @@ + + + + + PreferenceSpecifiers + + + FooterText + This application makes use of the following third party libraries: + Title + Acknowledgements + Type + PSGroupSpecifier + + + FooterText + BSD 3-Clause License + +Copyright (c) 2010-2021, Deusty, LLC +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of Deusty nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Deusty, LLC. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + CocoaLumberjack + Type + PSGroupSpecifier + + + FooterText + Copyright (C) 2014-2017 Marcin Krzyżanowski <marcin.krzyzanowski@gmail.com> +This software is provided 'as-is', without any express or implied warranty. + +In no event will the authors be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: + +- The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +- Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +- This notice may not be removed or altered from any source or binary distribution. +- Redistributions of any form whatsoever must retain the following acknowledgment: 'This product includes software developed by the "Marcin Krzyzanowski" (http://krzyzanowskim.com/).' + + Title + CryptoSwift + Type + PSGroupSpecifier + + + FooterText + Copyright (C) 2015-2020 Gwendal Roué + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + GRDB.swift + Type + PSGroupSpecifier + + + FooterText + Copyright 2016-present, Max Howell; mxcl@me.com + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + PromiseKit + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2008, ZETETIC LLC +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the ZETETIC LLC nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + SQLCipher + Type + PSGroupSpecifier + + + FooterText + + Copyright 2012 Square Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + Title + SocketRocket + Type + PSGroupSpecifier + + + FooterText + ISC License + +Copyright (c) 2014-2020, Frank Denis <j at pureftpd dot org> + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + + Title + Sodium + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2011, The WebRTC project authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Google nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + WebRTC-lib + Type + PSGroupSpecifier + + + FooterText + Software License Agreement (BSD License) + +Copyright (c) 2013, yap.TV Inc. +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Neither the name of yap.TV nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of yap.TV Inc. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + Title + YapDatabase + Type + PSGroupSpecifier + + + FooterText + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + Title + DifferenceKit + Type + PSGroupSpecifier + + + FooterText + This code is distributed under the terms and conditions of the MIT license. + +Copyright (c) 2014-2015 Tyler Fox + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + PureLayout + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2011, Tony Million. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + Reachability + Type + PSGroupSpecifier + + + FooterText + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/> + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {description} + Copyright (C) {year} {fullname} + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. + Title + Curve25519Kit + Type + PSGroupSpecifier + + + FooterText + + LICENSE ISSUES + ============== + + The OpenSSL toolkit stays under a dual license, i.e. both the conditions of + the OpenSSL License and the original SSLeay license apply to the toolkit. + See below for the actual license texts. Actually both licenses are BSD-style + Open Source licenses. In case of any license issues related to OpenSSL + please contact openssl-core@openssl.org. + + OpenSSL License + --------------- + +/* ==================================================================== + * Copyright (c) 1998-2008 The OpenSSL Project. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" + * + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * openssl-core@openssl.org. + * + * 5. Products derived from this software may not be called "OpenSSL" + * nor may "OpenSSL" appear in their names without prior written + * permission of the OpenSSL Project. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit (http://www.openssl.org/)" + * + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * ==================================================================== + * + * This product includes cryptographic software written by Eric Young + * (eay@cryptsoft.com). This product includes software written by Tim + * Hudson (tjh@cryptsoft.com). + * + */ + + Original SSLeay License + ----------------------- + +/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ + + + Title + OpenSSL-Universal + Type + PSGroupSpecifier + + + FooterText + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + Title + SignalCoreKit + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2010-2016 Sam Soffes, http://soff.es + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + SAMKeychain + Type + PSGroupSpecifier + + + FooterText + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + +## Runtime Library Exception to the Apache 2.0 License: ## + + + As an exception, if you use this Software to compile your source code and + portions of this Software are embedded into the binary product as a result, + you may redistribute such product without providing attribution as would + otherwise be required by Sections 4(a), 4(b) and 4(d) of the License. + + Title + SwiftProtobuf + Type + PSGroupSpecifier + + + FooterText + Generated by CocoaPods - https://cocoapods.org + Title + + Type + PSGroupSpecifier + + + StringsTable + Acknowledgements + Title + Acknowledgements + + diff --git a/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension-settings-metadata.plist b/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension-settings-metadata.plist new file mode 100644 index 000000000..e24e0494c --- /dev/null +++ b/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension-settings-metadata.plist @@ -0,0 +1,1618 @@ + + + + + PreferenceSpecifiers + + + FooterText + This application makes use of the following third party libraries: + Title + Acknowledgements + Type + PSGroupSpecifier + + + FooterText + BSD 3-Clause License + +Copyright (c) 2010-2021, Deusty, LLC +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of Deusty nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Deusty, LLC. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + CocoaLumberjack + Type + PSGroupSpecifier + + + FooterText + Copyright (C) 2014-2017 Marcin Krzyżanowski <marcin.krzyzanowski@gmail.com> +This software is provided 'as-is', without any express or implied warranty. + +In no event will the authors be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: + +- The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +- Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +- This notice may not be removed or altered from any source or binary distribution. +- Redistributions of any form whatsoever must retain the following acknowledgment: 'This product includes software developed by the "Marcin Krzyzanowski" (http://krzyzanowskim.com/).' + + Title + CryptoSwift + Type + PSGroupSpecifier + + + FooterText + Copyright (C) 2015-2020 Gwendal Roué + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + GRDB.swift + Type + PSGroupSpecifier + + + FooterText + Copyright 2016-present, Max Howell; mxcl@me.com + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + PromiseKit + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2008, ZETETIC LLC +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the ZETETIC LLC nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + SQLCipher + Type + PSGroupSpecifier + + + FooterText + + Copyright 2012 Square Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + Title + SocketRocket + Type + PSGroupSpecifier + + + FooterText + ISC License + +Copyright (c) 2014-2020, Frank Denis <j at pureftpd dot org> + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + + Title + Sodium + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2011, The WebRTC project authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Google nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + WebRTC-lib + Type + PSGroupSpecifier + + + FooterText + Software License Agreement (BSD License) + +Copyright (c) 2013, yap.TV Inc. +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Neither the name of yap.TV nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of yap.TV Inc. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + Title + YapDatabase + Type + PSGroupSpecifier + + + FooterText + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + Title + DifferenceKit + Type + PSGroupSpecifier + + + FooterText + The MIT License (MIT) + +Copyright (c) 2016 Vinh Nguyen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + Title + NVActivityIndicatorView + Type + PSGroupSpecifier + + + FooterText + This code is distributed under the terms and conditions of the MIT license. + +Copyright (c) 2014-2015 Tyler Fox + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + PureLayout + Type + PSGroupSpecifier + + + FooterText + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/> + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {description} + Copyright (C) {year} {fullname} + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. + Title + Curve25519Kit + Type + PSGroupSpecifier + + + FooterText + + LICENSE ISSUES + ============== + + The OpenSSL toolkit stays under a dual license, i.e. both the conditions of + the OpenSSL License and the original SSLeay license apply to the toolkit. + See below for the actual license texts. Actually both licenses are BSD-style + Open Source licenses. In case of any license issues related to OpenSSL + please contact openssl-core@openssl.org. + + OpenSSL License + --------------- + +/* ==================================================================== + * Copyright (c) 1998-2008 The OpenSSL Project. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" + * + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * openssl-core@openssl.org. + * + * 5. Products derived from this software may not be called "OpenSSL" + * nor may "OpenSSL" appear in their names without prior written + * permission of the OpenSSL Project. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit (http://www.openssl.org/)" + * + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * ==================================================================== + * + * This product includes cryptographic software written by Eric Young + * (eay@cryptsoft.com). This product includes software written by Tim + * Hudson (tjh@cryptsoft.com). + * + */ + + Original SSLeay License + ----------------------- + +/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ + + + Title + OpenSSL-Universal + Type + PSGroupSpecifier + + + FooterText + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + Title + SignalCoreKit + Type + PSGroupSpecifier + + + FooterText + Generated by CocoaPods - https://cocoapods.org + Title + + Type + PSGroupSpecifier + + + StringsTable + Acknowledgements + Title + Acknowledgements + + diff --git a/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests-settings-metadata.plist b/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests-settings-metadata.plist new file mode 100644 index 000000000..9b01bc78c --- /dev/null +++ b/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests-settings-metadata.plist @@ -0,0 +1,2103 @@ + + + + + PreferenceSpecifiers + + + FooterText + This application makes use of the following third party libraries: + Title + Acknowledgements + Type + PSGroupSpecifier + + + FooterText + BSD 3-Clause License + +Copyright (c) 2010-2021, Deusty, LLC +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of Deusty nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Deusty, LLC. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + CocoaLumberjack + Type + PSGroupSpecifier + + + FooterText + Copyright (C) 2014-2017 Marcin Krzyżanowski <marcin.krzyzanowski@gmail.com> +This software is provided 'as-is', without any express or implied warranty. + +In no event will the authors be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: + +- The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +- Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +- This notice may not be removed or altered from any source or binary distribution. +- Redistributions of any form whatsoever must retain the following acknowledgment: 'This product includes software developed by the "Marcin Krzyzanowski" (http://krzyzanowskim.com/).' + + Title + CryptoSwift + Type + PSGroupSpecifier + + + FooterText + Copyright (C) 2015-2020 Gwendal Roué + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + GRDB.swift + Type + PSGroupSpecifier + + + FooterText + Copyright 2016-present, Max Howell; mxcl@me.com + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + PromiseKit + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2008, ZETETIC LLC +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the ZETETIC LLC nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + SQLCipher + Type + PSGroupSpecifier + + + FooterText + + Copyright 2012 Square Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + Title + SocketRocket + Type + PSGroupSpecifier + + + FooterText + ISC License + +Copyright (c) 2014-2020, Frank Denis <j at pureftpd dot org> + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + + Title + Sodium + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2011, The WebRTC project authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Google nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + WebRTC-lib + Type + PSGroupSpecifier + + + FooterText + Software License Agreement (BSD License) + +Copyright (c) 2013, yap.TV Inc. +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Neither the name of yap.TV nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of yap.TV Inc. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + Title + YapDatabase + Type + PSGroupSpecifier + + + FooterText + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + Title + DifferenceKit + Type + PSGroupSpecifier + + + FooterText + This code is distributed under the terms and conditions of the MIT license. + +Copyright (c) 2014-2015 Tyler Fox + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + PureLayout + Type + PSGroupSpecifier + + + FooterText + The MIT License (MIT) + +Copyright (c) 2015 ibireme <ibireme@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + + Title + YYImage + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2010, Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Google nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Title + libwebp + Type + PSGroupSpecifier + + + FooterText + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/> + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {description} + Copyright (C) {year} {fullname} + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. + Title + Curve25519Kit + Type + PSGroupSpecifier + + + FooterText + + LICENSE ISSUES + ============== + + The OpenSSL toolkit stays under a dual license, i.e. both the conditions of + the OpenSSL License and the original SSLeay license apply to the toolkit. + See below for the actual license texts. Actually both licenses are BSD-style + Open Source licenses. In case of any license issues related to OpenSSL + please contact openssl-core@openssl.org. + + OpenSSL License + --------------- + +/* ==================================================================== + * Copyright (c) 1998-2008 The OpenSSL Project. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" + * + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * openssl-core@openssl.org. + * + * 5. Products derived from this software may not be called "OpenSSL" + * nor may "OpenSSL" appear in their names without prior written + * permission of the OpenSSL Project. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit (http://www.openssl.org/)" + * + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * ==================================================================== + * + * This product includes cryptographic software written by Eric Young + * (eay@cryptsoft.com). This product includes software written by Tim + * Hudson (tjh@cryptsoft.com). + * + */ + + Original SSLeay License + ----------------------- + +/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ + + + Title + OpenSSL-Universal + Type + PSGroupSpecifier + + + FooterText + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + Title + SignalCoreKit + Type + PSGroupSpecifier + + + FooterText + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2016 Quick Team + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Title + Nimble + Type + PSGroupSpecifier + + + FooterText + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2014, Quick Team + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Title + Quick + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2010-2016 Sam Soffes, http://soff.es + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + SAMKeychain + Type + PSGroupSpecifier + + + FooterText + Generated by CocoaPods - https://cocoapods.org + Title + + Type + PSGroupSpecifier + + + StringsTable + Acknowledgements + Title + Acknowledgements + + diff --git a/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-settings-metadata.plist b/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-settings-metadata.plist new file mode 100644 index 000000000..f3fdd43da --- /dev/null +++ b/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-settings-metadata.plist @@ -0,0 +1,1685 @@ + + + + + PreferenceSpecifiers + + + FooterText + This application makes use of the following third party libraries: + Title + Acknowledgements + Type + PSGroupSpecifier + + + FooterText + BSD 3-Clause License + +Copyright (c) 2010-2021, Deusty, LLC +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of Deusty nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Deusty, LLC. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + CocoaLumberjack + Type + PSGroupSpecifier + + + FooterText + Copyright (C) 2014-2017 Marcin Krzyżanowski <marcin.krzyzanowski@gmail.com> +This software is provided 'as-is', without any express or implied warranty. + +In no event will the authors be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: + +- The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +- Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +- This notice may not be removed or altered from any source or binary distribution. +- Redistributions of any form whatsoever must retain the following acknowledgment: 'This product includes software developed by the "Marcin Krzyzanowski" (http://krzyzanowskim.com/).' + + Title + CryptoSwift + Type + PSGroupSpecifier + + + FooterText + Copyright (C) 2015-2020 Gwendal Roué + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + GRDB.swift + Type + PSGroupSpecifier + + + FooterText + Copyright 2016-present, Max Howell; mxcl@me.com + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + PromiseKit + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2008, ZETETIC LLC +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the ZETETIC LLC nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + SQLCipher + Type + PSGroupSpecifier + + + FooterText + + Copyright 2012 Square Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + Title + SocketRocket + Type + PSGroupSpecifier + + + FooterText + ISC License + +Copyright (c) 2014-2020, Frank Denis <j at pureftpd dot org> + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + + Title + Sodium + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2011, The WebRTC project authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Google nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + WebRTC-lib + Type + PSGroupSpecifier + + + FooterText + Software License Agreement (BSD License) + +Copyright (c) 2013, yap.TV Inc. +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Neither the name of yap.TV nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of yap.TV Inc. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + Title + YapDatabase + Type + PSGroupSpecifier + + + FooterText + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + Title + DifferenceKit + Type + PSGroupSpecifier + + + FooterText + This code is distributed under the terms and conditions of the MIT license. + +Copyright (c) 2014-2015 Tyler Fox + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + PureLayout + Type + PSGroupSpecifier + + + FooterText + The MIT License (MIT) + +Copyright (c) 2015 ibireme <ibireme@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + + Title + YYImage + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2010, Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Google nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Title + libwebp + Type + PSGroupSpecifier + + + FooterText + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/> + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {description} + Copyright (C) {year} {fullname} + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. + Title + Curve25519Kit + Type + PSGroupSpecifier + + + FooterText + + LICENSE ISSUES + ============== + + The OpenSSL toolkit stays under a dual license, i.e. both the conditions of + the OpenSSL License and the original SSLeay license apply to the toolkit. + See below for the actual license texts. Actually both licenses are BSD-style + Open Source licenses. In case of any license issues related to OpenSSL + please contact openssl-core@openssl.org. + + OpenSSL License + --------------- + +/* ==================================================================== + * Copyright (c) 1998-2008 The OpenSSL Project. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" + * + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * openssl-core@openssl.org. + * + * 5. Products derived from this software may not be called "OpenSSL" + * nor may "OpenSSL" appear in their names without prior written + * permission of the OpenSSL Project. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit (http://www.openssl.org/)" + * + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * ==================================================================== + * + * This product includes cryptographic software written by Eric Young + * (eay@cryptsoft.com). This product includes software written by Tim + * Hudson (tjh@cryptsoft.com). + * + */ + + Original SSLeay License + ----------------------- + +/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ + + + Title + OpenSSL-Universal + Type + PSGroupSpecifier + + + FooterText + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + Title + SignalCoreKit + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2010-2016 Sam Soffes, http://soff.es + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + SAMKeychain + Type + PSGroupSpecifier + + + FooterText + Generated by CocoaPods - https://cocoapods.org + Title + + Type + PSGroupSpecifier + + + StringsTable + Acknowledgements + Title + Acknowledgements + + diff --git a/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit-settings-metadata.plist b/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit-settings-metadata.plist new file mode 100644 index 000000000..e4847df30 --- /dev/null +++ b/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit-settings-metadata.plist @@ -0,0 +1,1951 @@ + + + + + PreferenceSpecifiers + + + FooterText + This application makes use of the following third party libraries: + Title + Acknowledgements + Type + PSGroupSpecifier + + + FooterText + BSD 3-Clause License + +Copyright (c) 2010-2021, Deusty, LLC +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of Deusty nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Deusty, LLC. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + CocoaLumberjack + Type + PSGroupSpecifier + + + FooterText + Copyright (C) 2014-2017 Marcin Krzyżanowski <marcin.krzyzanowski@gmail.com> +This software is provided 'as-is', without any express or implied warranty. + +In no event will the authors be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: + +- The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +- Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +- This notice may not be removed or altered from any source or binary distribution. +- Redistributions of any form whatsoever must retain the following acknowledgment: 'This product includes software developed by the "Marcin Krzyzanowski" (http://krzyzanowskim.com/).' + + Title + CryptoSwift + Type + PSGroupSpecifier + + + FooterText + Copyright (C) 2015-2020 Gwendal Roué + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + GRDB.swift + Type + PSGroupSpecifier + + + FooterText + Copyright 2016-present, Max Howell; mxcl@me.com + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + PromiseKit + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2008, ZETETIC LLC +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the ZETETIC LLC nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + SQLCipher + Type + PSGroupSpecifier + + + FooterText + + Copyright 2012 Square Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + Title + SocketRocket + Type + PSGroupSpecifier + + + FooterText + ISC License + +Copyright (c) 2014-2020, Frank Denis <j at pureftpd dot org> + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + + Title + Sodium + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2011, The WebRTC project authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Google nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + WebRTC-lib + Type + PSGroupSpecifier + + + FooterText + Software License Agreement (BSD License) + +Copyright (c) 2013, yap.TV Inc. +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Neither the name of yap.TV nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of yap.TV Inc. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + Title + YapDatabase + Type + PSGroupSpecifier + + + FooterText + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + Title + DifferenceKit + Type + PSGroupSpecifier + + + FooterText + The MIT License (MIT) + +Copyright (c) 2016 Vinh Nguyen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + Title + NVActivityIndicatorView + Type + PSGroupSpecifier + + + FooterText + This code is distributed under the terms and conditions of the MIT license. + +Copyright (c) 2014-2015 Tyler Fox + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + PureLayout + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2011, Tony Million. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + Reachability + Type + PSGroupSpecifier + + + FooterText + The MIT License (MIT) + +Copyright (c) 2015 ibireme <ibireme@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + + Title + YYImage + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2010, Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Google nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Title + libwebp + Type + PSGroupSpecifier + + + FooterText + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/> + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {description} + Copyright (C) {year} {fullname} + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. + Title + Curve25519Kit + Type + PSGroupSpecifier + + + FooterText + + LICENSE ISSUES + ============== + + The OpenSSL toolkit stays under a dual license, i.e. both the conditions of + the OpenSSL License and the original SSLeay license apply to the toolkit. + See below for the actual license texts. Actually both licenses are BSD-style + Open Source licenses. In case of any license issues related to OpenSSL + please contact openssl-core@openssl.org. + + OpenSSL License + --------------- + +/* ==================================================================== + * Copyright (c) 1998-2008 The OpenSSL Project. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" + * + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * openssl-core@openssl.org. + * + * 5. Products derived from this software may not be called "OpenSSL" + * nor may "OpenSSL" appear in their names without prior written + * permission of the OpenSSL Project. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit (http://www.openssl.org/)" + * + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * ==================================================================== + * + * This product includes cryptographic software written by Eric Young + * (eay@cryptsoft.com). This product includes software written by Tim + * Hudson (tjh@cryptsoft.com). + * + */ + + Original SSLeay License + ----------------------- + +/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ + + + Title + OpenSSL-Universal + Type + PSGroupSpecifier + + + FooterText + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + Title + SignalCoreKit + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2010-2016 Sam Soffes, http://soff.es + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + SAMKeychain + Type + PSGroupSpecifier + + + FooterText + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + +## Runtime Library Exception to the Apache 2.0 License: ## + + + As an exception, if you use this Software to compile your source code and + portions of this Software are embedded into the binary product as a result, + you may redistribute such product without providing attribution as would + otherwise be required by Sections 4(a), 4(b) and 4(d) of the License. + + Title + SwiftProtobuf + Type + PSGroupSpecifier + + + FooterText + Generated by CocoaPods - https://cocoapods.org + Title + + Type + PSGroupSpecifier + + + StringsTable + Acknowledgements + Title + Acknowledgements + + diff --git a/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension-settings-metadata.plist b/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension-settings-metadata.plist new file mode 100644 index 000000000..2579983fc --- /dev/null +++ b/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension-settings-metadata.plist @@ -0,0 +1,1364 @@ + + + + + PreferenceSpecifiers + + + FooterText + This application makes use of the following third party libraries: + Title + Acknowledgements + Type + PSGroupSpecifier + + + FooterText + BSD 3-Clause License + +Copyright (c) 2010-2021, Deusty, LLC +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of Deusty nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Deusty, LLC. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + CocoaLumberjack + Type + PSGroupSpecifier + + + FooterText + Copyright (C) 2014-2017 Marcin Krzyżanowski <marcin.krzyzanowski@gmail.com> +This software is provided 'as-is', without any express or implied warranty. + +In no event will the authors be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: + +- The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +- Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +- This notice may not be removed or altered from any source or binary distribution. +- Redistributions of any form whatsoever must retain the following acknowledgment: 'This product includes software developed by the "Marcin Krzyzanowski" (http://krzyzanowskim.com/).' + + Title + CryptoSwift + Type + PSGroupSpecifier + + + FooterText + Copyright (C) 2015-2020 Gwendal Roué + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + GRDB.swift + Type + PSGroupSpecifier + + + FooterText + Copyright 2016-present, Max Howell; mxcl@me.com + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + PromiseKit + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2008, ZETETIC LLC +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the ZETETIC LLC nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + SQLCipher + Type + PSGroupSpecifier + + + FooterText + + Copyright 2012 Square Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + Title + SocketRocket + Type + PSGroupSpecifier + + + FooterText + ISC License + +Copyright (c) 2014-2020, Frank Denis <j at pureftpd dot org> + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + + Title + Sodium + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2011, The WebRTC project authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Google nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + WebRTC-lib + Type + PSGroupSpecifier + + + FooterText + Software License Agreement (BSD License) + +Copyright (c) 2013, yap.TV Inc. +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Neither the name of yap.TV nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of yap.TV Inc. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + Title + YapDatabase + Type + PSGroupSpecifier + + + FooterText + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/> + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {description} + Copyright (C) {year} {fullname} + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. + Title + Curve25519Kit + Type + PSGroupSpecifier + + + FooterText + + LICENSE ISSUES + ============== + + The OpenSSL toolkit stays under a dual license, i.e. both the conditions of + the OpenSSL License and the original SSLeay license apply to the toolkit. + See below for the actual license texts. Actually both licenses are BSD-style + Open Source licenses. In case of any license issues related to OpenSSL + please contact openssl-core@openssl.org. + + OpenSSL License + --------------- + +/* ==================================================================== + * Copyright (c) 1998-2008 The OpenSSL Project. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" + * + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * openssl-core@openssl.org. + * + * 5. Products derived from this software may not be called "OpenSSL" + * nor may "OpenSSL" appear in their names without prior written + * permission of the OpenSSL Project. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit (http://www.openssl.org/)" + * + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * ==================================================================== + * + * This product includes cryptographic software written by Eric Young + * (eay@cryptsoft.com). This product includes software written by Tim + * Hudson (tjh@cryptsoft.com). + * + */ + + Original SSLeay License + ----------------------- + +/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ + + + Title + OpenSSL-Universal + Type + PSGroupSpecifier + + + FooterText + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + Title + SignalCoreKit + Type + PSGroupSpecifier + + + FooterText + Generated by CocoaPods - https://cocoapods.org + Title + + Type + PSGroupSpecifier + + + StringsTable + Acknowledgements + Title + Acknowledgements + + diff --git a/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit-settings-metadata.plist b/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit-settings-metadata.plist new file mode 100644 index 000000000..2579983fc --- /dev/null +++ b/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit-settings-metadata.plist @@ -0,0 +1,1364 @@ + + + + + PreferenceSpecifiers + + + FooterText + This application makes use of the following third party libraries: + Title + Acknowledgements + Type + PSGroupSpecifier + + + FooterText + BSD 3-Clause License + +Copyright (c) 2010-2021, Deusty, LLC +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of Deusty nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Deusty, LLC. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + CocoaLumberjack + Type + PSGroupSpecifier + + + FooterText + Copyright (C) 2014-2017 Marcin Krzyżanowski <marcin.krzyzanowski@gmail.com> +This software is provided 'as-is', without any express or implied warranty. + +In no event will the authors be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: + +- The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +- Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +- This notice may not be removed or altered from any source or binary distribution. +- Redistributions of any form whatsoever must retain the following acknowledgment: 'This product includes software developed by the "Marcin Krzyzanowski" (http://krzyzanowskim.com/).' + + Title + CryptoSwift + Type + PSGroupSpecifier + + + FooterText + Copyright (C) 2015-2020 Gwendal Roué + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + GRDB.swift + Type + PSGroupSpecifier + + + FooterText + Copyright 2016-present, Max Howell; mxcl@me.com + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + PromiseKit + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2008, ZETETIC LLC +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the ZETETIC LLC nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + SQLCipher + Type + PSGroupSpecifier + + + FooterText + + Copyright 2012 Square Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + Title + SocketRocket + Type + PSGroupSpecifier + + + FooterText + ISC License + +Copyright (c) 2014-2020, Frank Denis <j at pureftpd dot org> + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + + Title + Sodium + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2011, The WebRTC project authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Google nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + WebRTC-lib + Type + PSGroupSpecifier + + + FooterText + Software License Agreement (BSD License) + +Copyright (c) 2013, yap.TV Inc. +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Neither the name of yap.TV nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of yap.TV Inc. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + Title + YapDatabase + Type + PSGroupSpecifier + + + FooterText + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/> + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {description} + Copyright (C) {year} {fullname} + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. + Title + Curve25519Kit + Type + PSGroupSpecifier + + + FooterText + + LICENSE ISSUES + ============== + + The OpenSSL toolkit stays under a dual license, i.e. both the conditions of + the OpenSSL License and the original SSLeay license apply to the toolkit. + See below for the actual license texts. Actually both licenses are BSD-style + Open Source licenses. In case of any license issues related to OpenSSL + please contact openssl-core@openssl.org. + + OpenSSL License + --------------- + +/* ==================================================================== + * Copyright (c) 1998-2008 The OpenSSL Project. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" + * + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * openssl-core@openssl.org. + * + * 5. Products derived from this software may not be called "OpenSSL" + * nor may "OpenSSL" appear in their names without prior written + * permission of the OpenSSL Project. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit (http://www.openssl.org/)" + * + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * ==================================================================== + * + * This product includes cryptographic software written by Eric Young + * (eay@cryptsoft.com). This product includes software written by Tim + * Hudson (tjh@cryptsoft.com). + * + */ + + Original SSLeay License + ----------------------- + +/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ + + + Title + OpenSSL-Universal + Type + PSGroupSpecifier + + + FooterText + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + Title + SignalCoreKit + Type + PSGroupSpecifier + + + FooterText + Generated by CocoaPods - https://cocoapods.org + Title + + Type + PSGroupSpecifier + + + StringsTable + Acknowledgements + Title + Acknowledgements + + diff --git a/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-Session-SessionTests-settings-metadata.plist b/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-Session-SessionTests-settings-metadata.plist new file mode 100644 index 000000000..77e9f98d6 --- /dev/null +++ b/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-Session-SessionTests-settings-metadata.plist @@ -0,0 +1,1221 @@ + + + + + PreferenceSpecifiers + + + FooterText + This application makes use of the following third party libraries: + Title + Acknowledgements + Type + PSGroupSpecifier + + + FooterText + BSD 3-Clause License + +Copyright (c) 2010-2021, Deusty, LLC +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of Deusty nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Deusty, LLC. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + CocoaLumberjack + Type + PSGroupSpecifier + + + FooterText + Copyright (C) 2014-2017 Marcin Krzyżanowski <marcin.krzyzanowski@gmail.com> +This software is provided 'as-is', without any express or implied warranty. + +In no event will the authors be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: + +- The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +- Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +- This notice may not be removed or altered from any source or binary distribution. +- Redistributions of any form whatsoever must retain the following acknowledgment: 'This product includes software developed by the "Marcin Krzyzanowski" (http://krzyzanowskim.com/).' + + Title + CryptoSwift + Type + PSGroupSpecifier + + + FooterText + Copyright (C) 2015-2020 Gwendal Roué + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + GRDB.swift + Type + PSGroupSpecifier + + + FooterText + Copyright 2016-present, Max Howell; mxcl@me.com + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + PromiseKit + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2008, ZETETIC LLC +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the ZETETIC LLC nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + SQLCipher + Type + PSGroupSpecifier + + + FooterText + + Copyright 2012 Square Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + Title + SocketRocket + Type + PSGroupSpecifier + + + FooterText + ISC License + +Copyright (c) 2014-2020, Frank Denis <j at pureftpd dot org> + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + + Title + Sodium + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2011, The WebRTC project authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Google nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + WebRTC-lib + Type + PSGroupSpecifier + + + FooterText + Software License Agreement (BSD License) + +Copyright (c) 2013, yap.TV Inc. +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Neither the name of yap.TV nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of yap.TV Inc. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + Title + YapDatabase + Type + PSGroupSpecifier + + + FooterText + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + Title + DifferenceKit + Type + PSGroupSpecifier + + + FooterText + The MIT License (MIT) + +Copyright (c) 2016 Vinh Nguyen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + Title + NVActivityIndicatorView + Type + PSGroupSpecifier + + + FooterText + This code is distributed under the terms and conditions of the MIT license. + +Copyright (c) 2014-2015 Tyler Fox + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + PureLayout + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2011, Tony Million. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + Reachability + Type + PSGroupSpecifier + + + FooterText + The MIT License (MIT) + +Copyright (c) 2015 ibireme <ibireme@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + + Title + YYImage + Type + PSGroupSpecifier + + + FooterText + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Title + ZXingObjC + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2010, Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Google nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Title + libwebp + Type + PSGroupSpecifier + + + FooterText + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2016 Quick Team + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Title + Nimble + Type + PSGroupSpecifier + + + FooterText + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2014, Quick Team + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Title + Quick + Type + PSGroupSpecifier + + + FooterText + Generated by CocoaPods - https://cocoapods.org + Title + + Type + PSGroupSpecifier + + + StringsTable + Acknowledgements + Title + Acknowledgements + + diff --git a/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-Session-settings-metadata.plist b/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-Session-settings-metadata.plist new file mode 100644 index 000000000..d17d35ab8 --- /dev/null +++ b/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-Session-settings-metadata.plist @@ -0,0 +1,2160 @@ + + + + + PreferenceSpecifiers + + + FooterText + This application makes use of the following third party libraries: + Title + Acknowledgements + Type + PSGroupSpecifier + + + FooterText + BSD 3-Clause License + +Copyright (c) 2010-2021, Deusty, LLC +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of Deusty nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Deusty, LLC. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + CocoaLumberjack + Type + PSGroupSpecifier + + + FooterText + Copyright (C) 2014-2017 Marcin Krzyżanowski <marcin.krzyzanowski@gmail.com> +This software is provided 'as-is', without any express or implied warranty. + +In no event will the authors be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: + +- The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +- Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +- This notice may not be removed or altered from any source or binary distribution. +- Redistributions of any form whatsoever must retain the following acknowledgment: 'This product includes software developed by the "Marcin Krzyzanowski" (http://krzyzanowskim.com/).' + + Title + CryptoSwift + Type + PSGroupSpecifier + + + FooterText + Copyright (C) 2015-2020 Gwendal Roué + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + GRDB.swift + Type + PSGroupSpecifier + + + FooterText + Copyright 2016-present, Max Howell; mxcl@me.com + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + PromiseKit + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2008, ZETETIC LLC +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the ZETETIC LLC nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + SQLCipher + Type + PSGroupSpecifier + + + FooterText + + Copyright 2012 Square Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + Title + SocketRocket + Type + PSGroupSpecifier + + + FooterText + ISC License + +Copyright (c) 2014-2020, Frank Denis <j at pureftpd dot org> + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + + Title + Sodium + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2011, The WebRTC project authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Google nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + WebRTC-lib + Type + PSGroupSpecifier + + + FooterText + Software License Agreement (BSD License) + +Copyright (c) 2013, yap.TV Inc. +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Neither the name of yap.TV nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of yap.TV Inc. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + Title + YapDatabase + Type + PSGroupSpecifier + + + FooterText + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + Title + DifferenceKit + Type + PSGroupSpecifier + + + FooterText + The MIT License (MIT) + +Copyright (c) 2016 Vinh Nguyen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + Title + NVActivityIndicatorView + Type + PSGroupSpecifier + + + FooterText + This code is distributed under the terms and conditions of the MIT license. + +Copyright (c) 2014-2015 Tyler Fox + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + PureLayout + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2011, Tony Million. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + Reachability + Type + PSGroupSpecifier + + + FooterText + The MIT License (MIT) + +Copyright (c) 2015 ibireme <ibireme@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + + Title + YYImage + Type + PSGroupSpecifier + + + FooterText + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Title + ZXingObjC + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2010, Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Google nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Title + libwebp + Type + PSGroupSpecifier + + + FooterText + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/> + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {description} + Copyright (C) {year} {fullname} + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. + Title + Curve25519Kit + Type + PSGroupSpecifier + + + FooterText + + LICENSE ISSUES + ============== + + The OpenSSL toolkit stays under a dual license, i.e. both the conditions of + the OpenSSL License and the original SSLeay license apply to the toolkit. + See below for the actual license texts. Actually both licenses are BSD-style + Open Source licenses. In case of any license issues related to OpenSSL + please contact openssl-core@openssl.org. + + OpenSSL License + --------------- + +/* ==================================================================== + * Copyright (c) 1998-2008 The OpenSSL Project. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" + * + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * openssl-core@openssl.org. + * + * 5. Products derived from this software may not be called "OpenSSL" + * nor may "OpenSSL" appear in their names without prior written + * permission of the OpenSSL Project. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit (http://www.openssl.org/)" + * + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * ==================================================================== + * + * This product includes cryptographic software written by Eric Young + * (eay@cryptsoft.com). This product includes software written by Tim + * Hudson (tjh@cryptsoft.com). + * + */ + + Original SSLeay License + ----------------------- + +/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ + + + Title + OpenSSL-Universal + Type + PSGroupSpecifier + + + FooterText + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + Title + SignalCoreKit + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2010-2016 Sam Soffes, http://soff.es + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + SAMKeychain + Type + PSGroupSpecifier + + + FooterText + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + +## Runtime Library Exception to the Apache 2.0 License: ## + + + As an exception, if you use this Software to compile your source code and + portions of this Software are embedded into the binary product as a result, + you may redistribute such product without providing attribution as would + otherwise be required by Sections 4(a), 4(b) and 4(d) of the License. + + Title + SwiftProtobuf + Type + PSGroupSpecifier + + + FooterText + Generated by CocoaPods - https://cocoapods.org + Title + + Type + PSGroupSpecifier + + + StringsTable + Acknowledgements + Title + Acknowledgements + + diff --git a/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-SessionUIKit-settings-metadata.plist b/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-SessionUIKit-settings-metadata.plist new file mode 100644 index 000000000..38d5f8fa7 --- /dev/null +++ b/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-SessionUIKit-settings-metadata.plist @@ -0,0 +1,462 @@ + + + + + PreferenceSpecifiers + + + FooterText + This application makes use of the following third party libraries: + Title + Acknowledgements + Type + PSGroupSpecifier + + + FooterText + BSD 3-Clause License + +Copyright (c) 2010-2021, Deusty, LLC +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of Deusty nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Deusty, LLC. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + CocoaLumberjack + Type + PSGroupSpecifier + + + FooterText + Copyright (C) 2014-2017 Marcin Krzyżanowski <marcin.krzyzanowski@gmail.com> +This software is provided 'as-is', without any express or implied warranty. + +In no event will the authors be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: + +- The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. +- Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +- This notice may not be removed or altered from any source or binary distribution. +- Redistributions of any form whatsoever must retain the following acknowledgment: 'This product includes software developed by the "Marcin Krzyzanowski" (http://krzyzanowskim.com/).' + + Title + CryptoSwift + Type + PSGroupSpecifier + + + FooterText + Copyright (C) 2015-2020 Gwendal Roué + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + GRDB.swift + Type + PSGroupSpecifier + + + FooterText + Copyright 2016-present, Max Howell; mxcl@me.com + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Title + PromiseKit + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2008, ZETETIC LLC +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the ZETETIC LLC nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + SQLCipher + Type + PSGroupSpecifier + + + FooterText + + Copyright 2012 Square Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + Title + SocketRocket + Type + PSGroupSpecifier + + + FooterText + ISC License + +Copyright (c) 2014-2020, Frank Denis <j at pureftpd dot org> + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + + Title + Sodium + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2011, The WebRTC project authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Google nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + WebRTC-lib + Type + PSGroupSpecifier + + + FooterText + Software License Agreement (BSD License) + +Copyright (c) 2013, yap.TV Inc. +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Neither the name of yap.TV nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of yap.TV Inc. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + Title + YapDatabase + Type + PSGroupSpecifier + + + FooterText + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + Title + DifferenceKit + Type + PSGroupSpecifier + + + FooterText + Generated by CocoaPods - https://cocoapods.org + Title + + Type + PSGroupSpecifier + + + StringsTable + Acknowledgements + Title + Acknowledgements + + diff --git a/Session/Meta/Settings2.bundle/Root.plist b/Session/Meta/Settings2.bundle/Root.plist new file mode 100644 index 000000000..e7769b22a --- /dev/null +++ b/Session/Meta/Settings2.bundle/Root.plist @@ -0,0 +1,19 @@ + + + + + StringsTable + Root + PreferenceSpecifiers + + + Type + Child Pane + Title + Acknowledgements + Filename + Acknowledgements.plist + + + + diff --git a/Session/Meta/Settings.bundle/en.lproj/Acknowledgements.strings b/Session/Meta/Settings2.bundle/en.lproj/Acknowledgements.strings similarity index 100% rename from Session/Meta/Settings.bundle/en.lproj/Acknowledgements.strings rename to Session/Meta/Settings2.bundle/en.lproj/Acknowledgements.strings diff --git a/Session/Meta/Settings2.bundle/en.lproj/Root.strings b/Session/Meta/Settings2.bundle/en.lproj/Root.strings new file mode 100644 index 0000000000000000000000000000000000000000..8cd87b9d6b20c1fbf87bd4db3db267fca5ad4df9 GIT binary patch literal 546 zcmaixOHRW;5JYRuDMndFh#Ua1V1d}N;sVAV2TO?uC3a9aJn*VxFrY}tnon0(S66#J z-d9>G>6W!ur(SDqlp`9nn~*(m%iWnv?yq`Qfp6XbK1?+om~~#r)ZnhkYQU_VbfjuT zHNn`CX<0sd*m1A}>&5sU$akD=GTXJ1e literal 0 HcmV?d00001 diff --git a/SessionMessagingKit/Database/Models/LinkPreview.swift b/SessionMessagingKit/Database/Models/LinkPreview.swift index 21b3de9fa..712cf5323 100644 --- a/SessionMessagingKit/Database/Models/LinkPreview.swift +++ b/SessionMessagingKit/Database/Models/LinkPreview.swift @@ -1,9 +1,8 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import Combine import GRDB -import PromiseKit -import AFNetworking import SignalCoreKit import SessionUtilitiesKit @@ -298,32 +297,45 @@ public extension LinkPreview { } } - static func tryToBuildPreviewInfo(previewUrl: String?) -> Promise { + static func tryToBuildPreviewInfo(previewUrl: String?) -> AnyPublisher { guard Storage.shared[.areLinkPreviewsEnabled] else { - return Promise(error: LinkPreviewError.featureDisabled) + return Fail(error: LinkPreviewError.featureDisabled) + .eraseToAnyPublisher() } guard let previewUrl: String = previewUrl else { - return Promise(error: LinkPreviewError.invalidInput) + return Fail(error: LinkPreviewError.invalidInput) + .eraseToAnyPublisher() } if let cachedInfo = cachedLinkPreview(forPreviewUrl: previewUrl) { - return Promise.value(cachedInfo) + return Just(cachedInfo) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } return downloadLink(url: previewUrl) - .then(on: DispatchQueue.global()) { data, response -> Promise in - return parseLinkDataAndBuildDraft(linkData: data, response: response, linkUrlString: previewUrl) + .flatMap { data, response in + parseLinkDataAndBuildDraft(linkData: data, response: response, linkUrlString: previewUrl) } - .then(on: DispatchQueue.global()) { linkPreviewDraft -> Promise in - guard linkPreviewDraft.isValid() else { throw LinkPreviewError.noPreview } + .flatMap { linkPreviewDraft -> AnyPublisher in + guard linkPreviewDraft.isValid() else { + return Fail(error: LinkPreviewError.noPreview) + .eraseToAnyPublisher() + } setCachedLinkPreview(linkPreviewDraft, forPreviewUrl: previewUrl) - return Promise.value(linkPreviewDraft) + return Just(linkPreviewDraft) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } + .eraseToAnyPublisher() } - private static func downloadLink(url urlString: String, remainingRetries: UInt = 3) -> Promise<(Data, URLResponse)> { + private static func downloadLink( + url urlString: String, + remainingRetries: UInt = 3 + ) -> AnyPublisher<(Data, URLResponse), Error> { Logger.verbose("url: \(urlString)") // let sessionConfiguration = ContentProxy.sessionConfiguration() // Loki: Signal's proxy appears to have been banned by YouTube @@ -333,103 +345,103 @@ public extension LinkPreview { sessionConfiguration.requestCachePolicy = .reloadIgnoringLocalCacheData sessionConfiguration.urlCache = nil - // FIXME: Refactor to stop using AFHTTPRequest - let sessionManager = AFHTTPSessionManager(baseURL: nil, - sessionConfiguration: sessionConfiguration) - sessionManager.requestSerializer = AFHTTPRequestSerializer() - sessionManager.responseSerializer = AFHTTPResponseSerializer() - - guard ContentProxy.configureSessionManager(sessionManager: sessionManager, forUrl: urlString) else { - return Promise(error: LinkPreviewError.assertionFailure) + guard + var request: URLRequest = URL(string: urlString).map({ URLRequest(url: $0) }), + ContentProxy.configureProxiedRequest(request: &request) + else { + return Fail(error: LinkPreviewError.assertionFailure) + .eraseToAnyPublisher() } - sessionManager.requestSerializer.setValue(self.userAgentString, forHTTPHeaderField: "User-Agent") + request.setValue(self.userAgentString, forHTTPHeaderField: "User-Agent") // Set a fake value - let (promise, resolver) = Promise<(Data, URLResponse)>.pending() - sessionManager.get( - urlString, - parameters: [String: AnyObject](), - headers: nil, - progress: nil, - success: { task, value in - guard let response = task.response as? HTTPURLResponse else { - resolver.reject(LinkPreviewError.assertionFailure) - return + let session: URLSession = URLSession(configuration: sessionConfiguration) + + return session + .dataTaskPublisher(for: request) + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) + .mapError { _ -> Error in HTTPError.generic } // URLError codes are negative values + .flatMap { data, response -> AnyPublisher<(Data, URLResponse), Error> in + guard let urlResponse: HTTPURLResponse = response as? HTTPURLResponse else { + return Fail(error: LinkPreviewError.assertionFailure) + .eraseToAnyPublisher() } - if let contentType = response.allHeaderFields["Content-Type"] as? String { + if let contentType: String = urlResponse.allHeaderFields["Content-Type"] as? String { guard contentType.lowercased().hasPrefix("text/") else { - resolver.reject(LinkPreviewError.invalidContent) - return + return Fail(error: LinkPreviewError.invalidContent) + .eraseToAnyPublisher() } } - guard let data = value as? Data else { - resolver.reject(LinkPreviewError.assertionFailure) - return - } guard data.count > 0 else { - resolver.reject(LinkPreviewError.invalidContent) - return + return Fail(error: LinkPreviewError.invalidContent) + .eraseToAnyPublisher() } - resolver.fulfill((data, response)) - }, - failure: { _, error in - guard isRetryable(error: error) else { - resolver.reject(LinkPreviewError.couldNotDownload) - return - } - - guard remainingRetries > 0 else { - resolver.reject(LinkPreviewError.couldNotDownload) - return - } - - LinkPreview.downloadLink( - url: urlString, - remainingRetries: (remainingRetries - 1) - ) - .done(on: DispatchQueue.global()) { (data, response) in - resolver.fulfill((data, response)) - } - .catch(on: DispatchQueue.global()) { (error) in - resolver.reject(error) - } - .retainUntilComplete() + return Just((data, response)) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } - ) - - return promise + .catch { error -> AnyPublisher<(Data, URLResponse), Error> in + guard isRetryable(error: error), remainingRetries > 0 else { + return Fail(error: LinkPreviewError.couldNotDownload) + .eraseToAnyPublisher() + } + + return LinkPreview + .downloadLink( + url: urlString, + remainingRetries: (remainingRetries - 1) + ) + } + .eraseToAnyPublisher() } - private static func parseLinkDataAndBuildDraft(linkData: Data, response: URLResponse, linkUrlString: String) -> Promise { + private static func parseLinkDataAndBuildDraft( + linkData: Data, + response: URLResponse, + linkUrlString: String + ) -> AnyPublisher { do { let contents = try parse(linkData: linkData, response: response) let title = contents.title guard let imageUrl = contents.imageUrl else { - return Promise.value(LinkPreviewDraft(urlString: linkUrlString, title: title)) + return Just(LinkPreviewDraft(urlString: linkUrlString, title: title)) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } guard URL(string: imageUrl) != nil else { - return Promise.value(LinkPreviewDraft(urlString: linkUrlString, title: title)) + return Just(LinkPreviewDraft(urlString: linkUrlString, title: title)) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } guard let imageFileExtension = fileExtension(forImageUrl: imageUrl) else { - return Promise.value(LinkPreviewDraft(urlString: linkUrlString, title: title)) + return Just(LinkPreviewDraft(urlString: linkUrlString, title: title)) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } guard let imageMimeType = mimetype(forImageFileExtension: imageFileExtension) else { - return Promise.value(LinkPreviewDraft(urlString: linkUrlString, title: title)) + return Just(LinkPreviewDraft(urlString: linkUrlString, title: title)) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } - return downloadImage(url: imageUrl, imageMimeType: imageMimeType) - .map(on: DispatchQueue.global()) { (imageData: Data) -> LinkPreviewDraft in + return LinkPreview + .downloadImage(url: imageUrl, imageMimeType: imageMimeType) + .map { imageData -> LinkPreviewDraft in // We always recompress images to Jpeg LinkPreviewDraft(urlString: linkUrlString, title: title, jpegImageData: imageData) } - .recover(on: DispatchQueue.global()) { _ -> Promise in - Promise.value(LinkPreviewDraft(urlString: linkUrlString, title: title)) + .catch { _ -> AnyPublisher in + return Just(LinkPreviewDraft(urlString: linkUrlString, title: title)) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } + .eraseToAnyPublisher() } catch { - return Promise(error: error) + return Fail(error: error) + .eraseToAnyPublisher() } } @@ -463,68 +475,86 @@ public extension LinkPreview { return Contents(title: title, imageUrl: imageUrlString) } - private static func downloadImage(url urlString: String, imageMimeType: String) -> Promise { - guard let url = URL(string: urlString) else { return Promise(error: LinkPreviewError.invalidInput) } - guard let assetDescription = ProxiedContentAssetDescription(url: url as NSURL) else { - return Promise(error: LinkPreviewError.invalidInput) + private static func downloadImage( + url urlString: String, + imageMimeType: String + ) -> AnyPublisher { + guard + let url = URL(string: urlString), + let assetDescription: ProxiedContentAssetDescription = ProxiedContentAssetDescription( + url: url as NSURL + ) + else { + return Fail(error: LinkPreviewError.invalidInput) + .eraseToAnyPublisher() } - let (promise, resolver) = Promise.pending() - DispatchQueue.main.async { - _ = ProxiedContentDownloader.defaultDownloader.requestAsset( + return ProxiedContentDownloader.defaultDownloader + .requestAsset( assetDescription: assetDescription, priority: .high, - success: { _, asset in - resolver.fulfill(asset) - }, - failure: { _ in - resolver.reject(LinkPreviewError.couldNotDownload) - }, shouldIgnoreSignalProxy: true ) - } - - return promise.then(on: DispatchQueue.global()) { (asset: ProxiedContentAsset) -> Promise in - do { - let imageSize = NSData.imageSize(forFilePath: asset.filePath, mimeType: imageMimeType) - - guard imageSize.width > 0, imageSize.height > 0 else { - return Promise(error: LinkPreviewError.invalidContent) - } - - let data = try Data(contentsOf: URL(fileURLWithPath: asset.filePath)) - - guard let srcImage = UIImage(data: data) else { - return Promise(error: LinkPreviewError.invalidContent) - } - - // Loki: If it's a GIF then ensure its validity and don't download it as a JPG - if (imageMimeType == OWSMimeTypeImageGif && NSData(data: data).ows_isValidImage(withMimeType: OWSMimeTypeImageGif)) { return Promise.value(data) } - - let maxImageSize: CGFloat = 1024 - let shouldResize = imageSize.width > maxImageSize || imageSize.height > maxImageSize - - guard shouldResize else { - guard let dstData = srcImage.jpegData(compressionQuality: 0.8) else { - return Promise(error: LinkPreviewError.invalidContent) + .flatMap { asset, _ -> AnyPublisher in + do { + let imageSize = NSData.imageSize(forFilePath: asset.filePath, mimeType: imageMimeType) + + guard imageSize.width > 0, imageSize.height > 0 else { + return Fail(error: LinkPreviewError.invalidContent) + .eraseToAnyPublisher() } - return Promise.value(dstData) - } + let data = try Data(contentsOf: URL(fileURLWithPath: asset.filePath)) - guard let dstImage = srcImage.resized(withMaxDimensionPoints: maxImageSize) else { - return Promise(error: LinkPreviewError.invalidContent) + guard let srcImage = UIImage(data: data) else { + return Fail(error: LinkPreviewError.invalidContent) + .eraseToAnyPublisher() + } + + // Loki: If it's a GIF then ensure its validity and don't download it as a JPG + if + imageMimeType == OWSMimeTypeImageGif && + NSData(data: data).ows_isValidImage(withMimeType: OWSMimeTypeImageGif) + { + return Just(data) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + + let maxImageSize: CGFloat = 1024 + let shouldResize = imageSize.width > maxImageSize || imageSize.height > maxImageSize + + guard shouldResize else { + guard let dstData = srcImage.jpegData(compressionQuality: 0.8) else { + return Fail(error: LinkPreviewError.invalidContent) + .eraseToAnyPublisher() + } + + return Just(dstData) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + + guard let dstImage = srcImage.resized(withMaxDimensionPoints: maxImageSize) else { + return Fail(error: LinkPreviewError.invalidContent) + .eraseToAnyPublisher() + } + guard let dstData = dstImage.jpegData(compressionQuality: 0.8) else { + return Fail(error: LinkPreviewError.invalidContent) + .eraseToAnyPublisher() + } + + return Just(dstData) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } - guard let dstData = dstImage.jpegData(compressionQuality: 0.8) else { - return Promise(error: LinkPreviewError.invalidContent) + catch { + return Fail(error: LinkPreviewError.assertionFailure) + .eraseToAnyPublisher() } - - return Promise.value(dstData) } - catch { - return Promise(error: LinkPreviewError.assertionFailure) - } - } + .mapError { _ -> Error in LinkPreviewError.couldNotDownload } + .eraseToAnyPublisher() } private static func isRetryable(error: Error) -> Bool { diff --git a/SessionSnodeKit/Networking/SnodeAPI.swift b/SessionSnodeKit/Networking/SnodeAPI.swift index ffae488d7..aef9ebb3b 100644 --- a/SessionSnodeKit/Networking/SnodeAPI.swift +++ b/SessionSnodeKit/Networking/SnodeAPI.swift @@ -259,7 +259,6 @@ public final class SnodeAPI { ) .subscribe(on: Threading.workQueue) .collect() - .mapError { _ in SnodeAPIError.validationFailed } .flatMap { results -> AnyPublisher in guard results.count == validationCount, Set(results).count == 1 else { return Fail(error: SnodeAPIError.validationFailed) diff --git a/SessionSnodeKit/OnionRequestAPI.swift b/SessionSnodeKit/OnionRequestAPI.swift index 3458e38e9..d3d1e12f3 100644 --- a/SessionSnodeKit/OnionRequestAPI.swift +++ b/SessionSnodeKit/OnionRequestAPI.swift @@ -8,9 +8,6 @@ import PromiseKit import SessionUtilitiesKit public protocol OnionRequestAPIType { -// static func sendOnionRequest(_ payload: Data, to snode: Snode) -> Promise<(ResponseInfoType, Data?)> -// static func sendOnionRequest(_ request: URLRequest, to server: String, with x25519PublicKey: String) -> Promise<(ResponseInfoType, Data?)> - static func sendOnionRequest(_ payload: Data, to snode: Snode) -> AnyPublisher<(ResponseInfoType, Data?), Error> static func sendOnionRequest(_ request: URLRequest, to server: String, with x25519PublicKey: String) -> AnyPublisher<(ResponseInfoType, Data?), Error> } diff --git a/SessionUtilitiesKit/Networking/ContentProxy.swift b/SessionUtilitiesKit/Networking/ContentProxy.swift index 4dd641ae4..5c5710fb8 100644 --- a/SessionUtilitiesKit/Networking/ContentProxy.swift +++ b/SessionUtilitiesKit/Networking/ContentProxy.swift @@ -1,19 +1,10 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. -import AFNetworking import Foundation -@objc -public class ContentProxy: NSObject { +public enum ContentProxy { - @available(*, unavailable, message:"do not instantiate this class.") - private override init() { - } - - @objc - public class func sessionConfiguration() -> URLSessionConfiguration { + public static func sessionConfiguration() -> URLSessionConfiguration { let configuration = URLSessionConfiguration.ephemeral let proxyHost = "contentproxy.signal.org" let proxyPort = 443 @@ -28,32 +19,9 @@ public class ContentProxy: NSObject { return configuration } - @objc - public class func sessionManager(baseUrl baseUrlString: String?) -> AFHTTPSessionManager? { - guard let baseUrlString = baseUrlString else { - return AFHTTPSessionManager(baseURL: nil, sessionConfiguration: sessionConfiguration()) - } - guard let baseUrl = URL(string: baseUrlString) else { - return nil - } - let sessionManager = AFHTTPSessionManager(baseURL: baseUrl, - sessionConfiguration: sessionConfiguration()) - return sessionManager - } - - @objc - public class func jsonSessionManager(baseUrl: String) -> AFHTTPSessionManager? { - guard let sessionManager = self.sessionManager(baseUrl: baseUrl) else { - return nil - } - sessionManager.requestSerializer = AFJSONRequestSerializer() - sessionManager.responseSerializer = AFJSONResponseSerializer() - return sessionManager - } - static let userAgent = "Signal iOS (+https://signal.org/download)" - public class func configureProxiedRequest(request: inout URLRequest) -> Bool { + public static func configureProxiedRequest(request: inout URLRequest) -> Bool { request.addValue(userAgent, forHTTPHeaderField: "User-Agent") padRequestSize(request: &request) @@ -66,39 +34,7 @@ public class ContentProxy: NSObject { return true } - // This mutates the session manager state, so its the caller's obligation to avoid conflicts by: - // - // * Using a new session manager for each request. - // * Pooling session managers. - // * Using a single session manager on a single queue. - @objc - public class func configureSessionManager(sessionManager: AFHTTPSessionManager, - forUrl urlString: String) -> Bool { - - guard let url = URL(string: urlString, relativeTo: sessionManager.baseURL) else { - return false - } - - var request = URLRequest(url: url) - - guard configureProxiedRequest(request: &request) else { - return false - } - - // Remove all headers from the request. - for headerField in sessionManager.requestSerializer.httpRequestHeaders.keys { - sessionManager.requestSerializer.setValue(nil, forHTTPHeaderField: headerField) - } - // Honor the request's headers. - if let allHTTPHeaderFields = request.allHTTPHeaderFields { - for (headerField, headerValue) in allHTTPHeaderFields { - sessionManager.requestSerializer.setValue(headerValue, forHTTPHeaderField: headerField) - } - } - return true - } - - public class func padRequestSize(request: inout URLRequest) { + public static func padRequestSize(request: inout URLRequest) { // Generate 1-64 chars of padding. let paddingLength: Int = 1 + Int(arc4random_uniform(64)) let padding = self.padding(withLength: paddingLength) @@ -106,7 +42,7 @@ public class ContentProxy: NSObject { request.addValue(padding, forHTTPHeaderField: "X-SignalPadding") } - private class func padding(withLength length: Int) -> String { + private static func padding(withLength length: Int) -> String { // Pick a random ASCII char in the range 48-122 var result = "" // Min and max values, inclusive. diff --git a/SessionUtilitiesKit/Networking/ProxiedContentDownloader.swift b/SessionUtilitiesKit/Networking/ProxiedContentDownloader.swift index 326760296..50ffffaef 100644 --- a/SessionUtilitiesKit/Networking/ProxiedContentDownloader.swift +++ b/SessionUtilitiesKit/Networking/ProxiedContentDownloader.swift @@ -3,6 +3,7 @@ // import Foundation +import Combine import ObjectiveC // Stills should be loaded before full GIFs. @@ -496,6 +497,40 @@ open class ProxiedContentDownloader: NSObject, URLSessionTaskDelegate, URLSessio processRequestQueueAsync() return assetRequest } + + public func requestAsset( + assetDescription: ProxiedContentAssetDescription, + priority: ProxiedContentRequestPriority, + shouldIgnoreSignalProxy: Bool = false + ) -> AnyPublisher<(ProxiedContentAsset, ProxiedContentAssetRequest?), Error> { + if let asset = assetMap.get(key: assetDescription.url) { + // Synchronous cache hit. + return Just((asset, nil)) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + + // Cache miss. + // + // Asset requests are done queued and performed asynchronously. + return Future { [weak self] resolver in + let assetRequest = ProxiedContentAssetRequest( + assetDescription: assetDescription, + priority: priority, + success: { request, asset in resolver(Result.success((asset, request))) }, + failure: { request in + resolver(Result.failure(HTTPError.generic)) + } + ) + assetRequest.shouldIgnoreSignalProxy = shouldIgnoreSignalProxy + self?.assetRequestQueue.append(assetRequest) + // Process the queue (which may start this request) + // asynchronously so that the caller has time to store + // a reference to the asset request returned by this + // method before its success/failure handler is called. + self?.processRequestQueueAsync() + }.eraseToAnyPublisher() + } public func cancelAllRequests() { self.assetRequestQueue.forEach { $0.cancel() } diff --git a/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift b/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift index 7a8a1b156..904648310 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift @@ -1,6 +1,7 @@ // Copyright (c) 2019 Open Whisper Systems. All rights reserved. -import Foundation +import UIKit +import Combine import MediaPlayer import YYImage import NVActivityIndicatorView @@ -21,6 +22,7 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate { // MARK: Properties + private var disposables: Set = Set() public let mode: Mode public let attachment: SignalAttachment @@ -565,44 +567,51 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate { loadingView.startAnimating() LinkPreview.tryToBuildPreviewInfo(previewUrl: linkPreviewURL) - .done { [weak self] draft in - // TODO: Look at refactoring this behaviour to consolidate attachment mutations - self?.attachment.linkPreviewDraft = draft - self?.linkPreviewInfo = (url: linkPreviewURL, draft: draft) - - // Update the UI - self?.titleLabel.text = (draft.title ?? self?.titleLabel.text) - self?.loadingView.alpha = 0 - self?.loadingView.stopAnimating() - self?.imageView.alpha = 1 - - if let jpegImageData: Data = draft.jpegImageData, let loadedImage: UIImage = UIImage(data: jpegImageData) { - self?.imageView.image = loadedImage - self?.imageView.contentMode = .scaleAspectFill + .receive(on: DispatchQueue.main) + .sink( + receiveCompletion: { [weak self] result in + switch result { + case .finished: break + case .failure: + self?.loadingView.alpha = 0 + self?.loadingView.stopAnimating() + self?.imageView.alpha = 1 + self?.titleLabel.numberOfLines = 1 // Truncates the URL at 1 line so the error is more readable + self?.subtitleLabel.isHidden = false + + // Set the error text appropriately + if let targetUrl: URL = URL(string: linkPreviewURL), targetUrl.scheme?.lowercased() != "https" { + // This error case is handled already in the 'subtitleLabel' creation + } + else { + self?.subtitleLabel.font = UIFont.ows_regularFont(withSize: Values.verySmallFontSize) + self?.subtitleLabel.text = "vc_share_link_previews_error".localized() + self?.subtitleLabel.themeTextColor = (self?.mode == .attachmentApproval ? + .textSecondary : + .primary + ) + self?.subtitleLabel.textAlignment = .left + } + } + }, + receiveValue: { [weak self] draft in + // TODO: Look at refactoring this behaviour to consolidate attachment mutations + self?.attachment.linkPreviewDraft = draft + self?.linkPreviewInfo = (url: linkPreviewURL, draft: draft) + + // Update the UI + self?.titleLabel.text = (draft.title ?? self?.titleLabel.text) + self?.loadingView.alpha = 0 + self?.loadingView.stopAnimating() + self?.imageView.alpha = 1 + + if let jpegImageData: Data = draft.jpegImageData, let loadedImage: UIImage = UIImage(data: jpegImageData) { + self?.imageView.image = loadedImage + self?.imageView.contentMode = .scaleAspectFill + } } - } - .catch { [weak self] _ in - self?.loadingView.alpha = 0 - self?.loadingView.stopAnimating() - self?.imageView.alpha = 1 - self?.titleLabel.numberOfLines = 1 // Truncates the URL at 1 line so the error is more readable - self?.subtitleLabel.isHidden = false - - // Set the error text appropriately - if let targetUrl: URL = URL(string: linkPreviewURL), targetUrl.scheme?.lowercased() != "https" { - // This error case is handled already in the 'subtitleLabel' creation - } - else { - self?.subtitleLabel.font = UIFont.ows_regularFont(withSize: Values.verySmallFontSize) - self?.subtitleLabel.text = "vc_share_link_previews_error".localized() - self?.subtitleLabel.themeTextColor = (self?.mode == .attachmentApproval ? - .textSecondary : - .primary - ) - self?.subtitleLabel.textAlignment = .left - } - } - .retainUntilComplete() + ) + .store(in: &disposables) } // MARK: - Functions diff --git a/SignalUtilitiesKit/Utilities/AppSetup.swift b/SignalUtilitiesKit/Utilities/AppSetup.swift index f3dcac986..a0d5b7e23 100644 --- a/SignalUtilitiesKit/Utilities/AppSetup.swift +++ b/SignalUtilitiesKit/Utilities/AppSetup.swift @@ -27,7 +27,7 @@ public enum AppSetup { // initializers injected. OWSBackgroundTaskManager.shared().observeNotifications() - // AFNetworking (via CFNetworking) spools it's attachments to NSTemporaryDirectory(). + // Attachments can be stored to NSTemporaryDirectory() // If you receive a media message while the device is locked, the download will fail if // the temporary directory is NSFileProtectionComplete let success: Bool = OWSFileSystem.protectFileOrFolder( From 8b37002d893ab83224d7b454841b8806b14477db Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 2 Dec 2022 17:38:01 +1100 Subject: [PATCH 011/135] Removed some unneeded imports # Conflicts: # SessionMessagingKit/Jobs/Types/AttachmentUploadJob.swift # SessionMessagingKit/Jobs/Types/MessageSendJob.swift # SessionShareExtension/SAEScreenLockViewController.swift # SessionSnodeKit/Models/SnodeBatchRequest.swift --- Session/Onboarding/LinkDeviceVC.swift | 1 - Session/Onboarding/PNModeVC.swift | 1 - Session/Shared/LoadingViewController.swift | 1 - .../Jobs/Types/AttachmentUploadJob.swift | 3 +- .../Jobs/Types/MessageReceiveJob.swift | 1 - .../Jobs/Types/MessageSendJob.swift | 3 +- .../Jobs/Types/SendReadReceiptsJob.swift | 2 +- .../SAEScreenLockViewController.swift | 2 +- .../Models/SnodeBatchRequest.swift | 63 +++++++++++++++++++ SessionSnodeKit/SnodeMessage.swift | 2 +- .../Shared Views/GalleryRailView.swift | 1 - 11 files changed, 70 insertions(+), 10 deletions(-) create mode 100644 SessionSnodeKit/Models/SnodeBatchRequest.swift diff --git a/Session/Onboarding/LinkDeviceVC.swift b/Session/Onboarding/LinkDeviceVC.swift index fcab1aa90..9c25b8c1f 100644 --- a/Session/Onboarding/LinkDeviceVC.swift +++ b/Session/Onboarding/LinkDeviceVC.swift @@ -2,7 +2,6 @@ import UIKit import AVFoundation -import PromiseKit import SessionUIKit import SessionUtilitiesKit import SessionSnodeKit diff --git a/Session/Onboarding/PNModeVC.swift b/Session/Onboarding/PNModeVC.swift index 54f1b0e48..83751afed 100644 --- a/Session/Onboarding/PNModeVC.swift +++ b/Session/Onboarding/PNModeVC.swift @@ -1,7 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit -import PromiseKit import SessionUIKit import SessionMessagingKit import SessionSnodeKit diff --git a/Session/Shared/LoadingViewController.swift b/Session/Shared/LoadingViewController.swift index d3bd6ccce..7b5021fc2 100644 --- a/Session/Shared/LoadingViewController.swift +++ b/Session/Shared/LoadingViewController.swift @@ -1,7 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit -import PromiseKit import SessionUIKit // The initial presentation is intended to be indistinguishable from the Launch Screen. diff --git a/SessionMessagingKit/Jobs/Types/AttachmentUploadJob.swift b/SessionMessagingKit/Jobs/Types/AttachmentUploadJob.swift index f8c725f8e..4abb5ca6a 100644 --- a/SessionMessagingKit/Jobs/Types/AttachmentUploadJob.swift +++ b/SessionMessagingKit/Jobs/Types/AttachmentUploadJob.swift @@ -1,8 +1,9 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import Combine import GRDB -import PromiseKit +import SignalCoreKit import SessionUtilitiesKit public enum AttachmentUploadJob: JobExecutor { diff --git a/SessionMessagingKit/Jobs/Types/MessageReceiveJob.swift b/SessionMessagingKit/Jobs/Types/MessageReceiveJob.swift index 5ec15e21e..d845f4eb5 100644 --- a/SessionMessagingKit/Jobs/Types/MessageReceiveJob.swift +++ b/SessionMessagingKit/Jobs/Types/MessageReceiveJob.swift @@ -2,7 +2,6 @@ import Foundation import GRDB -import PromiseKit import SessionUtilitiesKit public enum MessageReceiveJob: JobExecutor { diff --git a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift index cec000718..5b1a1b7dc 100644 --- a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift +++ b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift @@ -1,8 +1,9 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import Combine import GRDB -import PromiseKit +import SignalCoreKit import SessionUtilitiesKit import SessionSnodeKit diff --git a/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift b/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift index 78f9fea9b..fa69a209a 100644 --- a/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift +++ b/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift @@ -1,8 +1,8 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import Combine import GRDB -import PromiseKit import SessionUtilitiesKit public enum SendReadReceiptsJob: JobExecutor { diff --git a/SessionShareExtension/SAEScreenLockViewController.swift b/SessionShareExtension/SAEScreenLockViewController.swift index 36fac11ea..2c205da2e 100644 --- a/SessionShareExtension/SAEScreenLockViewController.swift +++ b/SessionShareExtension/SAEScreenLockViewController.swift @@ -1,7 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit -import PromiseKit +import SignalCoreKit import SignalUtilitiesKit import SessionUIKit import SessionUtilitiesKit diff --git a/SessionSnodeKit/Models/SnodeBatchRequest.swift b/SessionSnodeKit/Models/SnodeBatchRequest.swift new file mode 100644 index 000000000..92c88ef4c --- /dev/null +++ b/SessionSnodeKit/Models/SnodeBatchRequest.swift @@ -0,0 +1,63 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import SessionUtilitiesKit + +internal extension SnodeAPI { + struct BatchRequest: Encodable { + let requests: [Child] + + init(requests: [Info]) { + self.requests = requests.map { $0.child } + } + + // MARK: - BatchRequest.Info + + struct Info { + public let responseType: Codable.Type + fileprivate let child: Child + + public init(request: SnodeRequest, responseType: R.Type) { + self.child = Child(request: request) + self.responseType = HTTP.BatchSubResponse.self + } + + public init(request: SnodeRequest) { + self.init( + request: request, + responseType: NoResponse.self + ) + } + } + + // MARK: - BatchRequest.Child + + struct Child: Encodable { + enum CodingKeys: String, CodingKey { + case method + case params + } + + let endpoint: SnodeAPI.Endpoint + + /// The `jsonBodyEncoder` is used to avoid having to make `BatchSubRequest` a generic type (haven't found + /// a good way to keep `BatchSubRequest` encodable using protocols unfortunately so need this work around) + private let jsonBodyEncoder: ((inout KeyedEncodingContainer, CodingKeys) throws -> ())? + + init(request: SnodeRequest) { + self.endpoint = request.endpoint + + self.jsonBodyEncoder = { [body = request.body] container, key in + try container.encode(body, forKey: key) + } + } + + public func encode(to encoder: Encoder) throws { + var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(endpoint.rawValue, forKey: .method) + try jsonBodyEncoder?(&container, .params) + } + } + } +} diff --git a/SessionSnodeKit/SnodeMessage.swift b/SessionSnodeKit/SnodeMessage.swift index 0b4c9cb7c..dedc33c2e 100644 --- a/SessionSnodeKit/SnodeMessage.swift +++ b/SessionSnodeKit/SnodeMessage.swift @@ -1,6 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. -import PromiseKit +import Foundation import SessionUtilitiesKit public final class SnodeMessage: Codable { diff --git a/SignalUtilitiesKit/Shared Views/GalleryRailView.swift b/SignalUtilitiesKit/Shared Views/GalleryRailView.swift index c41ccb715..11ce30b7d 100644 --- a/SignalUtilitiesKit/Shared Views/GalleryRailView.swift +++ b/SignalUtilitiesKit/Shared Views/GalleryRailView.swift @@ -1,7 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit -import PromiseKit import SessionUIKit // MARK: - GalleryRailItem From 6970ff22cc4d3336223979f52ace2d86a966d927 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Mon, 5 Dec 2022 13:52:39 +1100 Subject: [PATCH 012/135] Refactored the remaining references to PromiseKit # Conflicts: # Session.xcodeproj/project.pbxproj # Session/Media Viewing & Editing/PhotoCapture.swift # SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift # SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift # SessionMessagingKit/Utilities/Promise+Utilities.swift # SessionShareExtension/ShareVC.swift # SessionShareExtension/ThreadPickerVC.swift # SessionSnodeKit/OnionRequestAPI+Encryption.swift # SessionSnodeKit/OnionRequestAPI.swift # SessionUtilitiesKit/Database/Storage.swift # SessionUtilitiesKit/Networking/HTTP.swift --- Session.xcodeproj/project.pbxproj | 79 ++-- .../Calls/Call Management/SessionCall.swift | 25 +- .../ConversationVC+Interaction.swift | 91 +++-- .../ImagePickerController.swift | 26 +- .../PhotoCapture.swift | 27 +- Session/Meta/AppDelegate.swift | 9 +- Session/Notifications/AppNotifications.swift | 5 +- .../Calls/WebRTCSession+MessageHandling.swift | 3 +- SessionMessagingKit/Calls/WebRTCSession.swift | 287 ++++++++------ .../Database/Models/Attachment.swift | 28 ++ .../Database/Models/LinkPreview.swift | 2 +- .../Jobs/Types/GarbageCollectionJob.swift | 2 +- .../Jobs/Types/MessageSendJob.swift | 12 +- .../Jobs/Types/SendReadReceiptsJob.swift | 2 +- .../Attachments/SignalAttachment.swift | 70 ++-- .../MessageReceiver+Calls.swift | 28 +- .../MessageSender+ClosedGroups.swift | 8 +- .../MessageSender+Convenience.swift | 159 +------- .../Sending & Receiving/MessageSender.swift | 58 +-- .../Utilities/Promise+Utilities.swift | 29 -- SessionShareExtension/ShareVC.swift | 364 +++++++++++------- SessionShareExtension/ThreadPickerVC.swift | 82 ++-- .../OnionRequestAPI+Encryption.swift | 186 +++------ SessionSnodeKit/OnionRequestAPI.swift | 363 ----------------- .../Utilities/Promise+Hashing.swift | 13 - .../Utilities/Promise+Threading.swift | 91 ----- SessionUtilitiesKit/Database/Storage.swift | 46 +-- SessionUtilitiesKit/Networking/HTTP.swift | 75 +--- .../Networking/URLResponse+Utilities.swift | 14 + .../PromiseKit/AnyPromise+Conversion.swift | 10 - .../PromiseKit/Promise+Delaying.swift | 14 - .../PromiseKit/Promise+Retaining.swift | 45 --- .../PromiseKit/Promise+Retrying.swift | 14 - .../PromiseKit/Promise+Timeout.swift | 19 - .../AttachmentApprovalViewController.swift | 1 - .../AttachmentItemCollection.swift | 1 - 36 files changed, 763 insertions(+), 1525 deletions(-) delete mode 100644 SessionMessagingKit/Utilities/Promise+Utilities.swift delete mode 100644 SessionSnodeKit/Utilities/Promise+Hashing.swift delete mode 100644 SessionSnodeKit/Utilities/Promise+Threading.swift create mode 100644 SessionUtilitiesKit/Networking/URLResponse+Utilities.swift delete mode 100644 SessionUtilitiesKit/PromiseKit/AnyPromise+Conversion.swift delete mode 100644 SessionUtilitiesKit/PromiseKit/Promise+Delaying.swift delete mode 100644 SessionUtilitiesKit/PromiseKit/Promise+Retaining.swift delete mode 100644 SessionUtilitiesKit/PromiseKit/Promise+Retrying.swift delete mode 100644 SessionUtilitiesKit/PromiseKit/Promise+Timeout.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index e008bab72..35623bed2 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -107,7 +107,6 @@ 7B1B52DF28580D51006069F2 /* EmojiPickerCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1B52DB28580D50006069F2 /* EmojiPickerCollectionView.swift */; }; 7B1B52E028580D51006069F2 /* EmojiSkinTonePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1B52DC28580D50006069F2 /* EmojiSkinTonePicker.swift */; }; 7B1D74AA27BCC16E0030B423 /* NSENotificationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1D74A927BCC16E0030B423 /* NSENotificationPresenter.swift */; }; - 7B1D74AC27BDE7510030B423 /* Promise+Timeout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1D74AB27BDE7510030B423 /* Promise+Timeout.swift */; }; 7B1D74B027C365960030B423 /* Timer+MainThread.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1D74AF27C365960030B423 /* Timer+MainThread.swift */; }; 7B46AAAF28766DF4001AF2DC /* AllMediaViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B46AAAE28766DF4001AF2DC /* AllMediaViewController.swift */; }; 7B4C75CB26B37E0F0000AC89 /* UnsendRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */; }; @@ -344,7 +343,6 @@ 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, ); }; }; C33FDDD0255A582000E217F9 /* FunctionalUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDC16255A581E00E217F9 /* FunctionalUtil.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C33FDEF8255A656D00E217F9 /* Promise+Delaying.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D32553860900C340D1 /* Promise+Delaying.swift */; }; C3402FE52559036600EA6424 /* SessionUIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C331FF1B2558F9D300070591 /* SessionUIKit.framework */; }; C3471ECB2555356A00297E91 /* MessageSender+Encryption.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3471ECA2555356A00297E91 /* MessageSender+Encryption.swift */; }; C3471ED42555386B00297E91 /* AESGCM.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D72553860B00C340D1 /* AESGCM.swift */; }; @@ -433,11 +431,8 @@ C3A71D1F25589AC30043A11F /* WebSocketResources.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A71D1D25589AC30043A11F /* WebSocketResources.pb.swift */; }; C3A71F892558BA9F0043A11F /* Mnemonic.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A71F882558BA9F0043A11F /* Mnemonic.swift */; }; C3A7211A2558BCA10043A11F /* DiffieHellman.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A71D662558A0170043A11F /* DiffieHellman.swift */; }; - C3A7219A2558C1660043A11F /* AnyPromise+Conversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A721992558C1660043A11F /* AnyPromise+Conversion.swift */; }; - C3A7225E2558C38D0043A11F /* Promise+Retaining.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A7225D2558C38D0043A11F /* Promise+Retaining.swift */; }; C3AAFFF225AE99710089E6DD /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3AAFFF125AE99710089E6DD /* AppDelegate.swift */; }; - C3ADC66126426688005F1414 /* ShareVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3ADC66026426688005F1414 /* ShareVC.swift */; }; - C3BBE0A72554D4DE0050F1E3 /* Promise+Retrying.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D62553860B00C340D1 /* Promise+Retrying.swift */; }; + C3ADC66126426688005F1414 /* ShareNavController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3ADC66026426688005F1414 /* ShareNavController.swift */; }; C3BBE0A82554D4DE0050F1E3 /* JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D92553860B00C340D1 /* JSON.swift */; }; C3BBE0A92554D4DE0050F1E3 /* HTTP.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5BC255385EE00C340D1 /* HTTP.swift */; }; C3BBE0AA2554D4DE0050F1E3 /* Dictionary+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D52553860A00C340D1 /* Dictionary+Utilities.swift */; }; @@ -452,8 +447,6 @@ C3C2A5C4255385EE00C340D1 /* OnionRequestAPI+Encryption.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5BB255385ED00C340D1 /* OnionRequestAPI+Encryption.swift */; }; C3C2A5C6255385EE00C340D1 /* Notification+OnionRequestAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5BD255385EE00C340D1 /* Notification+OnionRequestAPI.swift */; }; C3C2A5C7255385EE00C340D1 /* SnodeAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5BE255385EE00C340D1 /* SnodeAPI.swift */; }; - C3C2A5DB2553860B00C340D1 /* Promise+Hashing.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5CF2553860700C340D1 /* Promise+Hashing.swift */; }; - C3C2A5DC2553860B00C340D1 /* Promise+Threading.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D02553860800C340D1 /* Promise+Threading.swift */; }; C3C2A5DE2553860B00C340D1 /* String+Trimming.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D22553860900C340D1 /* String+Trimming.swift */; }; C3C2A5E02553860B00C340D1 /* Threading.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D42553860A00C340D1 /* Threading.swift */; }; C3C2A5E42553860B00C340D1 /* Data+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D82553860B00C340D1 /* Data+Utilities.swift */; }; @@ -604,6 +597,28 @@ FD245C6B2850667400B966DD /* VisibleMessage+Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C300A5B12554AF9800555489 /* VisibleMessage+Profile.swift */; }; FD245C6C2850669200B966DD /* MessageReceiveJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = C352A31225574F5200338F3E /* MessageReceiveJob.swift */; }; FD245C6D285066A400B966DD /* NotifyPushServerJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = C352A32E2557549C00338F3E /* NotifyPushServerJob.swift */; }; + FD26FA512919F9CE005801D8 /* GroupDeleteMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26FA502919F9CE005801D8 /* GroupDeleteMessage.swift */; }; + FD26FA53291CACA9005801D8 /* BatchResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4386A27B4E88F00C60D73 /* BatchResponse.swift */; }; + FD26FA54291CAD31005801D8 /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9CD27D17A04005E1583 /* Request.swift */; }; + FD26FA55291CAD44005801D8 /* HTTPQueryParam.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4385027B4807400C60D73 /* HTTPQueryParam.swift */; }; + FD26FA57291CADAE005801D8 /* HTTPQueryParam+OpenGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26FA56291CADAE005801D8 /* HTTPQueryParam+OpenGroup.swift */; }; + FD26FA58291CAE38005801D8 /* HTTPHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4384E27B4804F00C60D73 /* HTTPHeader.swift */; }; + FD26FA5A291CAE9B005801D8 /* HTTPHeader+OpenGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26FA59291CAE9B005801D8 /* HTTPHeader+OpenGroup.swift */; }; + FD26FA5E291CAFF9005801D8 /* Data+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26FA5D291CAFF9005801D8 /* Data+Utilities.swift */; }; + FD26FA60291CB098005801D8 /* ResponseInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26FA5F291CB098005801D8 /* ResponseInfo.swift */; }; + FD26FA62291CB46D005801D8 /* RequestInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438B027BB159600C60D73 /* RequestInfo.swift */; }; + FD26FA66291CC981005801D8 /* HTTPError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26FA65291CC981005801D8 /* HTTPError.swift */; }; + FD26FA68291CC99E005801D8 /* HTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26FA67291CC99E005801D8 /* HTTPMethod.swift */; }; + FD26FA6B291DA6BC005801D8 /* SSKDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26FA6A291DA6BC005801D8 /* SSKDependencies.swift */; }; + FD26FA6D291DADAE005801D8 /* SnodeRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26FA6C291DADAE005801D8 /* SnodeRequest.swift */; }; + FD26FA6F291DB171005801D8 /* ONSResolveRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26FA6E291DB171005801D8 /* ONSResolveRequest.swift */; }; + FD26FA71291DB253005801D8 /* OxenDaemonRPCRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26FA70291DB253005801D8 /* OxenDaemonRPCRequest.swift */; }; + FD26FA73291DB5F3005801D8 /* SnodeBatchRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26FA72291DB5F3005801D8 /* SnodeBatchRequest.swift */; }; + FD26FA75291DBC8B005801D8 /* ONSResolveResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26FA74291DBC8B005801D8 /* ONSResolveResponse.swift */; }; + FD26FA77291DE2C7005801D8 /* SnodeResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26FA76291DE2C7005801D8 /* SnodeResponse.swift */; }; + FD26FA79291DEDD7005801D8 /* GetMessagesRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26FA78291DEDD7005801D8 /* GetMessagesRequest.swift */; }; + FD26FA7B291DF8F3005801D8 /* SnodeAPINamespace.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26FA7A291DF8F3005801D8 /* SnodeAPINamespace.swift */; }; + FD26FA7D291E0B10005801D8 /* GetMessagesResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26FA7C291E0B10005801D8 /* GetMessagesResponse.swift */; }; FD2AAAED28ED3E1000A49611 /* MockGeneralCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */; }; FD2AAAEE28ED3E1100A49611 /* MockGeneralCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */; }; FD2AAAF028ED57B500A49611 /* SynchronousStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2AAAEF28ED57B500A49611 /* SynchronousStorage.swift */; }; @@ -788,6 +803,7 @@ FD8ECF9029381FC200C0D1BB /* SessionUtil+UserProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF8F29381FC200C0D1BB /* SessionUtil+UserProfile.swift */; }; FD8ECF922938552800C0D1BB /* Threading.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF912938552800C0D1BB /* Threading.swift */; }; FD8ECF94293856AF00C0D1BB /* Randomness.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF93293856AF00C0D1BB /* Randomness.swift */; }; + FD8ECFA1293D8FDD00C0D1BB /* URLResponse+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECFA0293D8FDD00C0D1BB /* URLResponse+Utilities.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 */; }; @@ -1208,7 +1224,6 @@ 7B1B52DB28580D50006069F2 /* EmojiPickerCollectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmojiPickerCollectionView.swift; sourceTree = ""; }; 7B1B52DC28580D50006069F2 /* EmojiSkinTonePicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmojiSkinTonePicker.swift; sourceTree = ""; }; 7B1D74A927BCC16E0030B423 /* NSENotificationPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSENotificationPresenter.swift; sourceTree = ""; }; - 7B1D74AB27BDE7510030B423 /* Promise+Timeout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Promise+Timeout.swift"; sourceTree = ""; }; 7B1D74AF27C365960030B423 /* Timer+MainThread.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Timer+MainThread.swift"; sourceTree = ""; }; 7B2DB2AD26F1B0FF0035B509 /* si */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = si; path = si.lproj/Localizable.strings; sourceTree = ""; }; 7B46AAAE28766DF4001AF2DC /* AllMediaViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllMediaViewController.swift; sourceTree = ""; }; @@ -1577,8 +1592,6 @@ C3A71D1D25589AC30043A11F /* WebSocketResources.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebSocketResources.pb.swift; sourceTree = ""; }; C3A71D662558A0170043A11F /* DiffieHellman.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiffieHellman.swift; sourceTree = ""; }; C3A71F882558BA9F0043A11F /* Mnemonic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mnemonic.swift; sourceTree = ""; }; - C3A721992558C1660043A11F /* AnyPromise+Conversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AnyPromise+Conversion.swift"; sourceTree = ""; }; - C3A7225D2558C38D0043A11F /* Promise+Retaining.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Promise+Retaining.swift"; sourceTree = ""; }; C3A8AF752665B03900A467FE /* hi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hi; path = hi.lproj/Localizable.strings; sourceTree = ""; }; C3A8AF762665F97A00A467FE /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = ""; }; C3AAFFF125AE99710089E6DD /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -1597,14 +1610,10 @@ C3C2A5BD255385EE00C340D1 /* Notification+OnionRequestAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Notification+OnionRequestAPI.swift"; sourceTree = ""; }; C3C2A5BE255385EE00C340D1 /* SnodeAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnodeAPI.swift; sourceTree = ""; }; C3C2A5CE2553860700C340D1 /* Logging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; - C3C2A5CF2553860700C340D1 /* Promise+Hashing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Promise+Hashing.swift"; sourceTree = ""; }; - C3C2A5D02553860800C340D1 /* Promise+Threading.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Promise+Threading.swift"; sourceTree = ""; }; C3C2A5D12553860800C340D1 /* Array+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array+Utilities.swift"; sourceTree = ""; }; C3C2A5D22553860900C340D1 /* String+Trimming.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Trimming.swift"; sourceTree = ""; }; - C3C2A5D32553860900C340D1 /* Promise+Delaying.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Promise+Delaying.swift"; sourceTree = ""; }; C3C2A5D42553860A00C340D1 /* Threading.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Threading.swift; sourceTree = ""; }; C3C2A5D52553860A00C340D1 /* Dictionary+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Dictionary+Utilities.swift"; sourceTree = ""; }; - C3C2A5D62553860B00C340D1 /* Promise+Retrying.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Promise+Retrying.swift"; sourceTree = ""; }; C3C2A5D72553860B00C340D1 /* AESGCM.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AESGCM.swift; sourceTree = ""; }; C3C2A5D82553860B00C340D1 /* Data+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+Utilities.swift"; sourceTree = ""; }; C3C2A5D92553860B00C340D1 /* JSON.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSON.swift; sourceTree = ""; }; @@ -1907,6 +1916,7 @@ FD8ECF8F29381FC200C0D1BB /* SessionUtil+UserProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionUtil+UserProfile.swift"; sourceTree = ""; }; FD8ECF912938552800C0D1BB /* Threading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Threading.swift; sourceTree = ""; }; FD8ECF93293856AF00C0D1BB /* Randomness.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Randomness.swift; sourceTree = ""; }; + FD8ECFA0293D8FDD00C0D1BB /* URLResponse+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLResponse+Utilities.swift"; sourceTree = ""; }; FD90040E2818AB6D00ABAAF6 /* GetSnodePoolJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetSnodePoolJob.swift; sourceTree = ""; }; FD9004132818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_SetupStandardJobs.swift; sourceTree = ""; }; FDA8EAFD280E8B78002B68E5 /* FailedMessageSendsJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailedMessageSendsJob.swift; sourceTree = ""; }; @@ -1948,7 +1958,6 @@ FDC4386827B4E6B700C60D73 /* String+Utlities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Utlities.swift"; sourceTree = ""; }; FDC4386A27B4E88F00C60D73 /* BatchRequestInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchRequestInfo.swift; sourceTree = ""; }; FDC4387127B5BB3B00C60D73 /* FileUploadResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileUploadResponse.swift; sourceTree = ""; }; - FDC4387327B5BB9B00C60D73 /* Promise+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Promise+Utilities.swift"; sourceTree = ""; }; FDC4387727B5C35400C60D73 /* SendMessageRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMessageRequest.swift; sourceTree = ""; }; FDC4388E27B9FFC700C60D73 /* SessionMessagingKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SessionMessagingKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; FDC4389927BA002500C60D73 /* OpenGroupAPISpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupAPISpec.swift; sourceTree = ""; }; @@ -2522,18 +2531,6 @@ path = Crypto; sourceTree = ""; }; - B8A582AD258C655E00AFD84C /* PromiseKit */ = { - isa = PBXGroup; - children = ( - C3A721992558C1660043A11F /* AnyPromise+Conversion.swift */, - C3C2A5D32553860900C340D1 /* Promise+Delaying.swift */, - C3A7225D2558C38D0043A11F /* Promise+Retaining.swift */, - C3C2A5D62553860B00C340D1 /* Promise+Retrying.swift */, - 7B1D74AB27BDE7510030B423 /* Promise+Timeout.swift */, - ); - path = PromiseKit; - sourceTree = ""; - }; B8A582AE258C65D000AFD84C /* Networking */ = { isa = PBXGroup; children = ( @@ -2542,6 +2539,8 @@ B8FF8EA525C11FEF004D1F22 /* IPv4.swift */, C3C2A5D92553860B00C340D1 /* JSON.swift */, C33FDAF2255A580500E217F9 /* ProxiedContentDownloader.swift */, + C3C2A5BC255385EE00C340D1 /* HTTP.swift */, + FD8ECFA0293D8FDD00C0D1BB /* URLResponse+Utilities.swift */, ); path = Networking; sourceTree = ""; @@ -3252,8 +3251,6 @@ isa = PBXGroup; children = ( C3C2A5D82553860B00C340D1 /* Data+Utilities.swift */, - C3C2A5CF2553860700C340D1 /* Promise+Hashing.swift */, - C3C2A5D02553860800C340D1 /* Promise+Threading.swift */, C3C2A5D22553860900C340D1 /* String+Trimming.swift */, C3C2A5D42553860A00C340D1 /* Threading.swift */, ); @@ -3271,7 +3268,6 @@ FD9004102818ABB000ABAAF6 /* JobRunner */, B8A582AF258C665E00AFD84C /* Media */, B8A582AE258C65D000AFD84C /* Networking */, - B8A582AD258C655E00AFD84C /* PromiseKit */, FD09796527F6B0A800936362 /* Utilities */, FD37E9FE28A5F2CD003AE748 /* Configuration.swift */, ); @@ -5370,11 +5366,19 @@ C3C2A5C6255385EE00C340D1 /* Notification+OnionRequestAPI.swift in Sources */, FD17D7AA27F41BF500122BE0 /* SnodeSet.swift in Sources */, FD17D7A427F40F8100122BE0 /* _003_YDBToGRDBMigration.swift in Sources */, - C3C2A5DC2553860B00C340D1 /* Promise+Threading.swift in Sources */, C3C2A5C4255385EE00C340D1 /* OnionRequestAPI+Encryption.swift in Sources */, FD17D7D227F5797A00122BE0 /* SnodeAPIEndpoint.swift in Sources */, C3C2A5DE2553860B00C340D1 /* String+Trimming.swift in Sources */, - C3C2A5DB2553860B00C340D1 /* Promise+Hashing.swift in Sources */, + FD26FA7B291DF8F3005801D8 /* SnodeAPINamespace.swift in Sources */, + FD8ECF6E292C9EA100C0D1BB /* GetServiceNodesRequest.swift in Sources */, + C3C2A5C4255385EE00C340D1 /* OnionRequestAPI+Encryption.swift in Sources */, + FD17D7D227F5797A00122BE0 /* SnodeAPIEndpoint.swift in Sources */, + C3C2A5DE2553860B00C340D1 /* String+Trimming.swift in Sources */, + FD8ECF56292C327700C0D1BB /* LegacyGetMessagesRequest.swift in Sources */, + FD8ECF54292C2DB000C0D1BB /* SnodeAuthenticatedRequestBody.swift in Sources */, + FD26FA6D291DADAE005801D8 /* SnodeRequest.swift in Sources */, + FD8ECF50292C2B2B00C0D1BB /* DeleteAllBeforeRequest.swift in Sources */, + FD8ECF5A292C431B00C0D1BB /* GetSwarmRequest.swift in Sources */, C3C2A5E42553860B00C340D1 /* Data+Utilities.swift in Sources */, FD17D7A727F41AF000122BE0 /* SSKLegacy.swift in Sources */, FDC438B327BB15B400C60D73 /* ResponseInfo.swift in Sources */, @@ -5395,7 +5399,6 @@ buildActionMask = 2147483647; files = ( FDFD645927F26C6800808CA1 /* Array+Utilities.swift in Sources */, - 7B1D74AC27BDE7510030B423 /* Promise+Timeout.swift in Sources */, C32C5A47256DB8F0003C73A2 /* ECKeyPair+Hexadecimal.swift in Sources */, 7B1D74B027C365960030B423 /* Timer+MainThread.swift in Sources */, C32C5D83256DD5B6003C73A2 /* SSKKeychainStorage.swift in Sources */, @@ -5422,7 +5425,7 @@ FD71160228C8255900B47552 /* UIControl+Combine.swift in Sources */, FD9004152818B46300ABAAF6 /* JobRunner.swift in Sources */, C3A7211A2558BCA10043A11F /* DiffieHellman.swift in Sources */, - C3A7225E2558C38D0043A11F /* Promise+Retaining.swift in Sources */, + FD26FA53291CACA9005801D8 /* BatchResponse.swift in Sources */, FD17D7CA27F546D900122BE0 /* _001_InitialSetupMigration.swift in Sources */, C3D9E4D12567777D0040E4F3 /* OWSMediaUtils.swift in Sources */, C3BBE0AA2554D4DE0050F1E3 /* Dictionary+Utilities.swift in Sources */, @@ -5440,6 +5443,7 @@ FD848B9328420164000E298B /* UnicodeScalar+Utilities.swift in Sources */, FD09796B27F6C67500936362 /* Failable.swift in Sources */, FD7115FA28C8153400B47552 /* UIBarButtonItem+Combine.swift in Sources */, + FD8ECFA1293D8FDD00C0D1BB /* URLResponse+Utilities.swift in Sources */, FD705A92278D051200F16121 /* ReusableView.swift in Sources */, FD17D7BA27F51F2100122BE0 /* TargetMigrations.swift in Sources */, FD17D7C327F5204C00122BE0 /* Database+Utilities.swift in Sources */, @@ -5458,14 +5462,12 @@ C32C5DDB256DD9FF003C73A2 /* ContentProxy.swift in Sources */, C3A71F892558BA9F0043A11F /* Mnemonic.swift in Sources */, B8F5F58325EC94A6003BF8D4 /* Collection+Utilities.swift in Sources */, - C33FDEF8255A656D00E217F9 /* Promise+Delaying.swift in Sources */, 7BD477A827EC39F5004E2822 /* Atomic.swift in Sources */, B8BC00C0257D90E30032E807 /* General.swift in Sources */, FD17D7A127F40D2500122BE0 /* Storage.swift in Sources */, FD1A94FB2900D1C2000D73D3 /* PersistableRecord+Utilities.swift in Sources */, FD5D201E27B0D87C00FEA984 /* SessionId.swift in Sources */, C32C5A24256DB7DB003C73A2 /* SNUserDefaults.swift in Sources */, - C3BBE0A72554D4DE0050F1E3 /* Promise+Retrying.swift in Sources */, FD8ECF922938552800C0D1BB /* Threading.swift in Sources */, B8856D7B256F14F4001CE70E /* UIView+OWS.m in Sources */, FDF22211281B5E0B000A4995 /* TableRecord+Utilities.swift in Sources */, @@ -5492,8 +5494,7 @@ FD9004162818B46700ABAAF6 /* JobRunnerError.swift in Sources */, FD09797227FAA2F500936362 /* Optional+Utilities.swift in Sources */, FD7162DB281B6C440060647B /* TypedTableAlias.swift in Sources */, - FDFD645B27F26D4600808CA1 /* Data+Utilities.swift in Sources */, - C3A7219A2558C1660043A11F /* AnyPromise+Conversion.swift in Sources */, + FD26FA5E291CAFF9005801D8 /* Data+Utilities.swift in Sources */, FD7115F828C8151C00B47552 /* DisposableBarButtonItem.swift in Sources */, FD17D7E727F6A16700122BE0 /* _003_YDBToGRDBMigration.swift in Sources */, ); diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index 4f5255d12..21bf4ac58 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import Combine import CallKit import GRDB import WebRTC @@ -223,6 +224,7 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate { .inserted(db) self.callInteractionId = interaction?.id + try? self.webRTCSession .sendPreOffer( db, @@ -230,14 +232,19 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate { interactionId: interaction?.id, in: thread ) - .done { [weak self] _ in - Storage.shared.writeAsync { db in - self?.webRTCSession.sendOffer(db, to: sessionId) + .sinkUntilComplete( + receiveCompletion: { [weak self] result in + switch result { + case .failure: break + case .finished: + Storage.shared.writeAsync { db in + self?.webRTCSession.sendOffer(db, to: sessionId) + } + + self?.setupTimeoutTimer() + } } - - self?.setupTimeoutTimer() - } - .retainUntilComplete() + ) } func answerSessionCall() { @@ -406,8 +413,8 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate { let webRTCSession: WebRTCSession = self.webRTCSession Storage.shared - .read { db in webRTCSession.sendOffer(db, to: sessionId, isRestartingICEConnection: true) } - .retainUntilComplete() + .readPublisherFlatMap { db in webRTCSession.sendOffer(db, to: sessionId, isRestartingICEConnection: true) } + .sinkUntilComplete() } // MARK: - Timeout diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index e9f9568b0..130616234 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -345,23 +345,24 @@ extension ConversationVC: dataSource: dataSource, dataUTI: kUTTypeMPEG4 as String ) - .attachmentPromise - .done { attachment in - guard - !modalActivityIndicator.wasCancelled, - let attachment = attachment as? SignalAttachment - else { return } - - modalActivityIndicator.dismiss { - guard !attachment.hasError else { - self?.showErrorAlert(for: attachment, onDismiss: nil) - return - } + .attachmentPublisher + .sinkUntilComplete( + receiveValue: { [weak self] attachment in + guard + !modalActivityIndicator.wasCancelled, + let attachment = attachment as? SignalAttachment + else { return } - self?.showAttachmentApprovalDialog(for: [ attachment ]) + modalActivityIndicator.dismiss { + guard !attachment.hasError else { + self?.showErrorAlert(for: attachment, onDismiss: nil) + return + } + + self?.showAttachmentApprovalDialog(for: [ attachment ]) + } } - } - .retainUntilComplete() + ) } } @@ -422,8 +423,8 @@ extension ConversationVC: ) // Send the message - Storage.shared.writeAsync( - updates: { [weak self] db in + Storage.shared + .writePublisher { [weak self] db in guard let thread: SessionThread = try SessionThread.fetchOne(db, id: threadId) else { return } @@ -485,11 +486,12 @@ extension ConversationVC: interaction: interaction, in: thread ) - }, - completion: { [weak self] _, _ in - self?.handleMessageSent() } - ) + .sinkUntilComplete( + receiveCompletion: { [weak self] _ in + self?.handleMessageSent() + } + ) } func sendAttachments(_ attachments: [SignalAttachment], with text: String, hasPermissionToSendSeed: Bool = false, onComplete: (() -> ())? = nil) { @@ -545,8 +547,8 @@ extension ConversationVC: ) // Send the message - Storage.shared.writeAsync( - updates: { [weak self] db in + Storage.shared + .writePublisher { [weak self] db in guard let thread: SessionThread = try SessionThread.fetchOne(db, id: threadId) else { return } @@ -574,23 +576,36 @@ extension ConversationVC: .asRequest(of: TimeInterval.self) .fetchOne(db) ).inserted(db) - - try MessageSender.send( - db, - interaction: interaction, - with: attachments, - in: thread - ) - }, - completion: { [weak self] _, _ in - self?.handleMessageSent() - // Attachment successfully sent - dismiss the screen - DispatchQueue.main.async { - onComplete?() + guard let interactionId: Int64 = interaction.id else { + return } + + // Prepare any attachments + try Attachment.prepare( + db, + attachments: attachments, + for: interactionId + ) + + // Prepare the message send data + try MessageSender + .send( + db, + interaction: interaction, + in: thread + ) } - ) + .sinkUntilComplete( + receiveCompletion: { [weak self] _ in + self?.handleMessageSent() + + // Attachment successfully sent - dismiss the screen + DispatchQueue.main.async { + onComplete?() + } + } + ) } func handleMessageSent() { @@ -1419,7 +1434,7 @@ extension ConversationVC: .eraseToAnyPublisher() } - return MessageSender.sendImmediate(data: sendData) + return MessageSender.sendImmediate(preparedSendData: sendData) } .sinkUntilComplete() } diff --git a/Session/Media Viewing & Editing/ImagePickerController.swift b/Session/Media Viewing & Editing/ImagePickerController.swift index 55662283f..bb3bd5d42 100644 --- a/Session/Media Viewing & Editing/ImagePickerController.swift +++ b/Session/Media Viewing & Editing/ImagePickerController.swift @@ -449,11 +449,11 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat // Initially position offscreen, we'll animate it in. collectionPickerView.frame = collectionPickerView.frame.offsetBy(dx: 0, dy: collectionPickerView.frame.height) - - UIView.animate(.promise, duration: 0.25, delay: 0, options: .curveEaseInOut) { + + UIView.animate(withDuration: 0.25) { collectionPickerView.superview?.layoutIfNeeded() self.titleView.rotateIcon(.up) - }.retainUntilComplete() + } } func hideCollectionPicker() { @@ -461,14 +461,18 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat assert(isShowingCollectionPickerController) isShowingCollectionPickerController = false - - UIView.animate(.promise, duration: 0.25, delay: 0, options: .curveEaseInOut) { - self.collectionPickerController.view.frame = self.view.frame.offsetBy(dx: 0, dy: self.view.frame.height) - self.titleView.rotateIcon(.down) - }.done { _ in - self.collectionPickerController.view.removeFromSuperview() - self.collectionPickerController.removeFromParent() - }.retainUntilComplete() + + UIView.animate( + withDuration: 0.25, + animations: { + self.collectionPickerController.view.frame = self.view.frame.offsetBy(dx: 0, dy: self.view.frame.height) + self.titleView.rotateIcon(.down) + }, + completion: { [weak self] _ in + self?.collectionPickerController.view.removeFromSuperview() + self?.collectionPickerController.removeFromParent() + } + ) } // MARK: - UICollectionView diff --git a/Session/Media Viewing & Editing/PhotoCapture.swift b/Session/Media Viewing & Editing/PhotoCapture.swift index d6964ac56..f8f01c87f 100644 --- a/Session/Media Viewing & Editing/PhotoCapture.swift +++ b/Session/Media Viewing & Editing/PhotoCapture.swift @@ -4,7 +4,7 @@ import Foundation import Combine import AVFoundation import CoreServices -import SignalCoreKit +import SessionUtilitiesKit protocol PhotoCaptureDelegate: AnyObject { func photoCapture(_ photoCapture: PhotoCapture, didFinishProcessingAttachment attachment: SignalAttachment) @@ -368,14 +368,23 @@ extension PhotoCapture: CaptureButtonDelegate { AssertIsOnMainThread() Logger.verbose("") - sessionQueue.async(.promise) { - try self.startAudioCapture() - self.captureOutput.beginVideo(delegate: self) - }.done { - self.delegate?.photoCaptureDidBeginVideo(self) - }.catch { error in - self.delegate?.photoCapture(self, processingDidError: error) - }.retainUntilComplete() + + Just(()) + .subscribe(on: sessionQueue) + .sinkUntilComplete( + receiveCompletion: { [weak self] _ in + guard let strongSelf = self else { return } + + do { + try strongSelf.startAudioCapture() + strongSelf.captureOutput.beginVideo(delegate: strongSelf) + strongSelf.delegate?.photoCaptureDidBeginVideo(strongSelf) + } + catch { + strongSelf.delegate?.photoCapture(strongSelf, processingDidError: error) + } + } + ) } func didCompleteLongPressCaptureButton(_ captureButton: CaptureButton) { diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 7dfa06113..bf09767f5 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -1,16 +1,13 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. -import Foundation +import UIKit +import Combine +import UserNotifications import GRDB -import PromiseKit import WebRTC import SessionUIKit -import UIKit import SessionMessagingKit import SessionUtilitiesKit -import SessionUIKit -import UserNotifications -import UIKit import SignalUtilitiesKit import SignalCoreKit diff --git a/Session/Notifications/AppNotifications.swift b/Session/Notifications/AppNotifications.swift index 2b9da963b..eb1830bec 100644 --- a/Session/Notifications/AppNotifications.swift +++ b/Session/Notifications/AppNotifications.swift @@ -559,15 +559,14 @@ class NotificationActionHandler { includingOlder: true, trySendReadReceipt: true ) - // TODO: Will need to split the attachment upload from the message preparation logic + return try MessageSender.preparedSendData( db, interaction: interaction, in: thread ) } - .flatMap { MessageSender.performUploadsIfNeeded(preparedSendData: $0) } - .flatMap { MessageSender.sendImmediate(data: $0) } + .flatMap { MessageSender.sendImmediate(preparedSendData: $0) } .handleEvents( receiveCompletion: { result in switch result { diff --git a/SessionMessagingKit/Calls/WebRTCSession+MessageHandling.swift b/SessionMessagingKit/Calls/WebRTCSession+MessageHandling.swift index b0786bf30..ddbe690b1 100644 --- a/SessionMessagingKit/Calls/WebRTCSession+MessageHandling.swift +++ b/SessionMessagingKit/Calls/WebRTCSession+MessageHandling.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import Combine import WebRTC import SessionUtilitiesKit @@ -21,7 +22,7 @@ extension WebRTCSession { else { guard sdp.type == .offer else { return } - self?.sendAnswer(to: sessionId).retainUntilComplete() + self?.sendAnswer(to: sessionId).sinkUntilComplete() } }) } diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index f6e4bf48a..e25513c46 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -1,8 +1,8 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import Combine import GRDB -import PromiseKit import WebRTC import SessionUtilitiesKit @@ -80,7 +80,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { // MARK: - Error - public enum Error : LocalizedError { + public enum WebRTCSessionError: LocalizedError { case noThread public var errorDescription: String? { @@ -124,94 +124,48 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { message: CallMessage, interactionId: Int64?, in thread: SessionThread - ) throws -> Promise { + ) throws -> AnyPublisher { SNLog("[Calls] Sending pre-offer message.") - return try MessageSender - .sendNonDurably( - db, - message: message, - interactionId: interactionId, - in: thread + return MessageSender + .sendImmediate( + preparedSendData: try MessageSender + .preparedSendData( + db, + message: message, + to: try Message.Destination.from(db, thread: thread), + interactionId: interactionId + ) ) - .done2 { - SNLog("[Calls] Pre-offer message has been sent.") - } + .handleEvents( + receiveCompletion: { result in + switch result { + case .failure: break + case .finished: SNLog("[Calls] Pre-offer message has been sent.") + } + } + ) + .eraseToAnyPublisher() } public func sendOffer( _ db: Database, to sessionId: String, isRestartingICEConnection: Bool = false - ) -> Promise { + ) -> AnyPublisher { SNLog("[Calls] Sending offer message.") - let (promise, seal) = Promise.pending() let uuid: String = self.uuid let mediaConstraints: RTCMediaConstraints = mediaConstraints(isRestartingICEConnection) guard let thread: SessionThread = try? SessionThread.fetchOne(db, id: sessionId) else { - return Promise(error: Error.noThread) + return Fail(error: WebRTCSessionError.noThread) + .eraseToAnyPublisher() } - self.peerConnection?.offer(for: mediaConstraints) { [weak self] sdp, error in - if let error = error { - seal.reject(error) - return - } - - guard let sdp: RTCSessionDescription = self?.correctSessionDescription(sdp: sdp) else { - preconditionFailure() - } - - self?.peerConnection?.setLocalDescription(sdp) { error in + return Future { [weak self] resolver in + self?.peerConnection?.offer(for: mediaConstraints) { sdp, error in if let error = error { - print("Couldn't initiate call due to error: \(error).") - return seal.reject(error) - } - } - - Storage.shared - .writeAsync { db in - try MessageSender - .sendNonDurably( - db, - message: CallMessage( - uuid: uuid, - kind: .offer, - sdps: [ sdp.sdp ], - sentTimestampMs: UInt64(floor(Date().timeIntervalSince1970 * 1000)) - ), - interactionId: nil, - in: thread - ) - } - .done2 { - seal.fulfill(()) - } - .catch2 { error in - seal.reject(error) - } - .retainUntilComplete() - } - - return promise - } - - public func sendAnswer(to sessionId: String) -> Promise { - SNLog("[Calls] Sending answer message.") - let (promise, seal) = Promise.pending() - let uuid: String = self.uuid - let mediaConstraints: RTCMediaConstraints = mediaConstraints(false) - - Storage.shared.writeAsync { [weak self] db in - guard let thread: SessionThread = try? SessionThread.fetchOne(db, id: sessionId) else { - seal.reject(Error.noThread) - return - } - - self?.peerConnection?.answer(for: mediaConstraints) { [weak self] sdp, error in - if let error = error { - seal.reject(error) + resolver(Result.failure(error)) return } @@ -221,33 +175,103 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { self?.peerConnection?.setLocalDescription(sdp) { error in if let error = error { - print("Couldn't accept call due to error: \(error).") - return seal.reject(error) + print("Couldn't initiate call due to error: \(error).") + resolver(Result.failure(error)) + return } } - try? MessageSender - .sendNonDurably( - db, - message: CallMessage( - uuid: uuid, - kind: .answer, - sdps: [ sdp.sdp ] - ), - interactionId: nil, - in: thread + Storage.shared + .writePublisher { db in + try MessageSender + .preparedSendData( + db, + message: CallMessage( + uuid: uuid, + kind: .offer, + sdps: [ sdp.sdp ], + sentTimestampMs: UInt64(floor(Date().timeIntervalSince1970 * 1000)) + ), + to: try Message.Destination.from(db, thread: thread), + interactionId: nil + ) + } + .flatMap { MessageSender.sendImmediate(preparedSendData: $0) } + .sinkUntilComplete( + receiveCompletion: { result in + switch result { + case .finished: resolver(Result.success(())) + case .failure(let error): resolver(Result.failure(error)) + } + } ) - .done2 { - seal.fulfill(()) - } - .catch2 { error in - seal.reject(error) - } - .retainUntilComplete() } } + .eraseToAnyPublisher() + } + + public func sendAnswer(to sessionId: String) -> AnyPublisher { + SNLog("[Calls] Sending answer message.") + let uuid: String = self.uuid + let mediaConstraints: RTCMediaConstraints = mediaConstraints(false) - return promise + return Storage.shared + .readPublisherFlatMap { db -> AnyPublisher in + guard let thread: SessionThread = try? SessionThread.fetchOne(db, id: sessionId) else { + return Fail(error: WebRTCSessionError.noThread) + .eraseToAnyPublisher() + } + + return Just(thread) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + .flatMap { [weak self] thread in + Future { resolver in + self?.peerConnection?.answer(for: mediaConstraints) { [weak self] sdp, error in + if let error = error { + resolver(Result.failure(error)) + return + } + + guard let sdp: RTCSessionDescription = self?.correctSessionDescription(sdp: sdp) else { + preconditionFailure() + } + + self?.peerConnection?.setLocalDescription(sdp) { error in + if let error = error { + print("Couldn't accept call due to error: \(error).") + return resolver(Result.failure(error)) + } + } + + Storage.shared + .writePublisher { db in + try MessageSender + .preparedSendData( + db, + message: CallMessage( + uuid: uuid, + kind: .answer, + sdps: [ sdp.sdp ] + ), + to: try Message.Destination.from(db, thread: thread), + interactionId: nil + ) + } + .flatMap { MessageSender.sendImmediate(preparedSendData: $0) } + .sinkUntilComplete( + receiveCompletion: { result in + switch result { + case .finished: resolver(Result.success(())) + case .failure(let error): resolver(Result.failure(error)) + } + } + ) + } + } + } + .eraseToAnyPublisher() } private func queueICECandidateForSending(_ candidate: RTCIceCandidate) { @@ -268,26 +292,36 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { // Empty the queue self.queuedICECandidates.removeAll() - Storage.shared.writeAsync { db in - guard let thread: SessionThread = try SessionThread.fetchOne(db, id: contactSessionId) else { return } - - SNLog("[Calls] Batch sending \(candidates.count) ICE candidates.") - - try MessageSender.sendNonDurably( - db, - message: CallMessage( - uuid: uuid, - kind: .iceCandidates( - sdpMLineIndexes: candidates.map { UInt32($0.sdpMLineIndex) }, - sdpMids: candidates.map { $0.sdpMid! } - ), - sdps: candidates.map { $0.sdp } - ), - interactionId: nil, - in: thread - ) - .retainUntilComplete() - } + Storage.shared + .writePublisherFlatMap { db in + guard let thread: SessionThread = try SessionThread.fetchOne(db, id: contactSessionId) else { + return Fail(error: WebRTCSessionError.noThread) + .eraseToAnyPublisher() + } + + SNLog("[Calls] Batch sending \(candidates.count) ICE candidates.") + + return Just( + try MessageSender + .preparedSendData( + db, + message: CallMessage( + uuid: uuid, + kind: .iceCandidates( + sdpMLineIndexes: candidates.map { UInt32($0.sdpMLineIndex) }, + sdpMids: candidates.map { $0.sdpMid! } + ), + sdps: candidates.map { $0.sdp } + ), + to: try Message.Destination.from(db, thread: thread), + interactionId: nil + ) + ) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + .flatMap { MessageSender.sendImmediate(preparedSendData: $0) } + .sinkUntilComplete() } public func endCall(_ db: Database, with sessionId: String) throws { @@ -295,17 +329,22 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { SNLog("[Calls] Sending end call message.") - try MessageSender.sendNonDurably( - db, - message: CallMessage( - uuid: self.uuid, - kind: .endCall, - sdps: [] - ), - interactionId: nil, - in: thread - ) - .retainUntilComplete() + let preparedSendData: MessageSender.PreparedSendData = try MessageSender + .preparedSendData( + db, + message: CallMessage( + uuid: self.uuid, + kind: .endCall, + sdps: [] + ), + to: try Message.Destination.from(db, thread: thread), + interactionId: nil + ) + + MessageSender + .sendImmediate(preparedSendData: preparedSendData) + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) + .sinkUntilComplete() } public func dropConnection() { diff --git a/SessionMessagingKit/Database/Models/Attachment.swift b/SessionMessagingKit/Database/Models/Attachment.swift index ce26bc32b..84d9cf806 100644 --- a/SessionMessagingKit/Database/Models/Attachment.swift +++ b/SessionMessagingKit/Database/Models/Attachment.swift @@ -972,6 +972,34 @@ extension Attachment { // MARK: - Upload extension Attachment { + public static func prepare(_ db: Database, attachments: [SignalAttachment], for interactionId: Int64) throws { + // Prepare any attachments + try attachments.enumerated() + .forEach { index, signalAttachment in + let maybeAttachment: Attachment? = Attachment( + variant: (signalAttachment.isVoiceMessage ? + .voiceMessage : + .standard + ), + contentType: signalAttachment.mimeType, + dataSource: signalAttachment.dataSource, + sourceFilename: signalAttachment.sourceFilename, + caption: signalAttachment.captionText + ) + + guard let attachment: Attachment = maybeAttachment else { return } + + let interactionAttachment: InteractionAttachment = InteractionAttachment( + albumIndex: index, + interactionId: interactionId, + attachmentId: attachment.id + ) + + try attachment.insert(db) + try interactionAttachment.insert(db) + } + } + internal func upload( _ db: Database? = nil, queue: DispatchQueue, diff --git a/SessionMessagingKit/Database/Models/LinkPreview.swift b/SessionMessagingKit/Database/Models/LinkPreview.swift index 712cf5323..604973a8d 100644 --- a/SessionMessagingKit/Database/Models/LinkPreview.swift +++ b/SessionMessagingKit/Database/Models/LinkPreview.swift @@ -446,7 +446,7 @@ public extension LinkPreview { } private static func parse(linkData: Data, response: URLResponse) throws -> Contents { - guard let linkText = String(data: linkData, urlResponse: response) else { + guard let linkText = String(bytes: linkData, encoding: response.stringEncoding ?? .utf8) else { print("Could not parse link text.") throw LinkPreviewError.invalidInput } diff --git a/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift b/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift index 0f74c2209..af530f523 100644 --- a/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift +++ b/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift @@ -2,7 +2,7 @@ import Foundation import GRDB -import PromiseKit +import SignalCoreKit import SessionUtilitiesKit import SessionSnodeKit diff --git a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift index 5b1a1b7dc..d39e438f8 100644 --- a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift +++ b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift @@ -160,22 +160,22 @@ public enum MessageSendJob: JobExecutor { // Add the threadId to the message if there isn't one set details.message.threadId = (details.message.threadId ?? job.threadId) - // Perform the actual message sending + /// Perform the actual message sending + /// + /// **Note:** No need to upload attachments as part of this process as the above logic splits that out into it's own job + /// so we shouldn't get here until attachments have already been uploaded Storage.shared .writePublisher { db in - // TODO: Will need to split the attachment upload from the message preparation logic try MessageSender.preparedSendData( db, message: details.message, to: details.destination - .with(fileIds: messageFileIds), // TODO: This??? + .with(fileIds: messageFileIds), interactionId: job.interactionId ) } .subscribe(on: queue) - // TODO: Is this needed? (should be caught before this??) -// .flatMap { MessageSender.performUploadsIfNeeded(preparedSendData: $0) } - .flatMap { MessageSender.sendImmediate(data: $0) } + .flatMap { MessageSender.sendImmediate(preparedSendData: $0) } .sinkUntilComplete( receiveCompletion: { result in switch result { diff --git a/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift b/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift index fa69a209a..e2fd0f7a2 100644 --- a/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift +++ b/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift @@ -47,8 +47,8 @@ public enum SendReadReceiptsJob: JobExecutor { ) } .subscribe(on: queue) + .flatMap { MessageSender.sendImmediate(preparedSendData: $0) } .receive(on: queue) - .flatMap { MessageSender.sendImmediate(data: $0) } .sinkUntilComplete( receiveCompletion: { result in switch result { diff --git a/SessionMessagingKit/Sending & Receiving/Attachments/SignalAttachment.swift b/SessionMessagingKit/Sending & Receiving/Attachments/SignalAttachment.swift index 20e033815..08881a282 100644 --- a/SessionMessagingKit/Sending & Receiving/Attachments/SignalAttachment.swift +++ b/SessionMessagingKit/Sending & Receiving/Attachments/SignalAttachment.swift @@ -3,9 +3,8 @@ // import Foundation +import Combine import MobileCoreServices - -import PromiseKit import AVFoundation import SessionUtilitiesKit @@ -887,11 +886,16 @@ public class SignalAttachment: Equatable, Hashable { return videoDir } - public class func compressVideoAsMp4(dataSource: DataSource, dataUTI: String) -> (Promise, AVAssetExportSession?) { + public class func compressVideoAsMp4(dataSource: DataSource, dataUTI: String) -> (AnyPublisher, AVAssetExportSession?) { guard let url = dataSource.dataUrl() else { let attachment = SignalAttachment(dataSource: DataSourceValue.emptyDataSource(), dataUTI: dataUTI) attachment.error = .missingData - return (Promise.value(attachment), nil) + return ( + Just(attachment) + .setFailureType(to: Error.self) + .eraseToAnyPublisher(), + nil + ) } let asset = AVAsset(url: url) @@ -899,7 +903,12 @@ public class SignalAttachment: Equatable, Hashable { guard let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetMediumQuality) else { let attachment = SignalAttachment(dataSource: DataSourceValue.emptyDataSource(), dataUTI: dataUTI) attachment.error = .couldNotConvertToMpeg4 - return (Promise.value(attachment), nil) + return ( + Just(attachment) + .setFailureType(to: Error.self) + .eraseToAnyPublisher(), + nil + ) } exportSession.shouldOptimizeForNetworkUse = true @@ -909,48 +918,43 @@ public class SignalAttachment: Equatable, Hashable { let exportURL = videoTempPath.appendingPathComponent(UUID().uuidString).appendingPathExtension("mp4") exportSession.outputURL = exportURL - let (promise, resolver) = Promise.pending() + let publisher = Future { resolver in + exportSession.exportAsynchronously { + let baseFilename = dataSource.sourceFilename + let mp4Filename = baseFilename?.filenameWithoutExtension.appendingFileExtension("mp4") - exportSession.exportAsynchronously { - let baseFilename = dataSource.sourceFilename - let mp4Filename = baseFilename?.filenameWithoutExtension.appendingFileExtension("mp4") + guard let dataSource = DataSourcePath.dataSource(with: exportURL, + shouldDeleteOnDeallocation: true) else { + let attachment = SignalAttachment(dataSource: DataSourceValue.emptyDataSource(), dataUTI: dataUTI) + attachment.error = .couldNotConvertToMpeg4 + resolver(Result.success(attachment)) + return + } - guard let dataSource = DataSourcePath.dataSource(with: exportURL, - shouldDeleteOnDeallocation: true) else { - let attachment = SignalAttachment(dataSource: DataSourceValue.emptyDataSource(), dataUTI: dataUTI) - attachment.error = .couldNotConvertToMpeg4 - resolver.fulfill(attachment) - return + dataSource.sourceFilename = mp4Filename + + let attachment = SignalAttachment(dataSource: dataSource, dataUTI: kUTTypeMPEG4 as String) + resolver(Result.success(attachment)) } - - dataSource.sourceFilename = mp4Filename - - let attachment = SignalAttachment(dataSource: dataSource, dataUTI: kUTTypeMPEG4 as String) - resolver.fulfill(attachment) } + .eraseToAnyPublisher() - return (promise, exportSession) + return (publisher, exportSession) } - @objc - public class VideoCompressionResult: NSObject { - @objc - public let attachmentPromise: AnyPromise - - @objc + public struct VideoCompressionResult { + public let attachmentPublisher: AnyPublisher public let exportSession: AVAssetExportSession? - fileprivate init(attachmentPromise: Promise, exportSession: AVAssetExportSession?) { - self.attachmentPromise = AnyPromise(attachmentPromise) + fileprivate init(attachmentPublisher: AnyPublisher, exportSession: AVAssetExportSession?) { + self.attachmentPublisher = attachmentPublisher self.exportSession = exportSession - super.init() } } - @objc public class func compressVideoAsMp4(dataSource: DataSource, dataUTI: String) -> VideoCompressionResult { - let (attachmentPromise, exportSession) = compressVideoAsMp4(dataSource: dataSource, dataUTI: dataUTI) - return VideoCompressionResult(attachmentPromise: attachmentPromise, exportSession: exportSession) + let (attachmentPublisher, exportSession) = compressVideoAsMp4(dataSource: dataSource, dataUTI: dataUTI) + return VideoCompressionResult(attachmentPublisher: attachmentPublisher, exportSession: exportSession) } @objc diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift index 8dc60c126..f366fc6c9 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift @@ -194,19 +194,21 @@ extension MessageReceiver { ) .inserted(db) - try MessageSender - .sendNonDurably( - db, - message: CallMessage( - uuid: message.uuid, - kind: .endCall, - sdps: [], - sentTimestampMs: nil // Explicitly nil as it's a separate message from above - ), - interactionId: nil, // Explicitly nil as it's a separate message from above - in: thread - ) - .retainUntilComplete() + MessageSender.sendImmediate( + preparedSendData: try MessageSender + .preparedSendData( + db, + message: CallMessage( + uuid: message.uuid, + kind: .endCall, + sdps: [], + sentTimestampMs: nil // Explicitly nil as it's a separate message from above + ), + to: try Message.Destination.from(db, thread: thread), + interactionId: nil // Explicitly nil as it's a separate message from above + ) + ) + .sinkUntilComplete() } @discardableResult public static func insertCallInfoMessage( diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift index 657073676..fc2748805 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift @@ -113,7 +113,7 @@ extension MessageSender { .MergeMany( // Send a closed group update message to all members individually memberSendData - .map { MessageSender.sendImmediate(data: $0) } + .map { MessageSender.sendImmediate(preparedSendData: $0) } .appending( // Notify the PN server PushNotificationAPI.performOperation( @@ -209,7 +209,7 @@ extension MessageSender { .eraseToAnyPublisher() } - return MessageSender.sendImmediate(data: sendData) + return MessageSender.sendImmediate(preparedSendData: sendData) .map { _ in newKeyPair } .eraseToAnyPublisher() .handleEvents( @@ -490,7 +490,7 @@ extension MessageSender { // Send the update to the group and generate + distribute a new encryption key pair return MessageSender .sendImmediate( - data: try MessageSender + preparedSendData: try MessageSender .preparedSendData( db, message: LegacyClosedGroupControlMessage( @@ -593,7 +593,7 @@ extension MessageSender { } return MessageSender - .sendImmediate(data: sendData) + .sendImmediate(preparedSendData: sendData) .handleEvents( receiveCompletion: { result in switch result { diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift b/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift index f1099d816..f387ffbc5 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift @@ -3,26 +3,12 @@ import Foundation import Combine import GRDB -import PromiseKit import SessionUtilitiesKit extension MessageSender { // MARK: - Durable - public static func send(_ db: Database, interaction: Interaction, with attachments: [SignalAttachment], in thread: SessionThread) throws { - guard let interactionId: Int64 = interaction.id else { throw StorageError.objectNotSaved } - - try prep(db, signalAttachments: attachments, for: interactionId) - send( - db, - message: VisibleMessage.from(db, interaction: interaction), - threadId: thread.id, - interactionId: interactionId, - to: try Message.Destination.from(db, thread: thread) - ) - } - public static func send(_ db: Database, interaction: Interaction, in thread: SessionThread) throws { // Only 'VisibleMessage' types can be sent via this method guard interaction.variant == .standardOutgoing else { throw MessageSenderError.invalidMessage } @@ -64,33 +50,6 @@ extension MessageSender { // MARK: - Non-Durable - public static func sendNonDurably(_ db: Database, interaction: Interaction, with attachments: [SignalAttachment], in thread: SessionThread) throws -> Promise { - guard let interactionId: Int64 = interaction.id else { return Promise(error: StorageError.objectNotSaved) } - - try prep(db, signalAttachments: attachments, for: interactionId) - - return sendNonDurably( - db, - message: VisibleMessage.from(db, interaction: interaction), - interactionId: interactionId, - to: try Message.Destination.from(db, thread: thread) - ) - } - - - public static func sendNonDurably(_ db: Database, interaction: Interaction, in thread: SessionThread) throws -> Promise { - // Only 'VisibleMessage' types can be sent via this method - guard interaction.variant == .standardOutgoing else { throw MessageSenderError.invalidMessage } - guard let interactionId: Int64 = interaction.id else { throw StorageError.objectNotSaved } - - return sendNonDurably( - db, - message: VisibleMessage.from(db, interaction: interaction), - interactionId: interactionId, - to: try Message.Destination.from(db, thread: thread) - ) - } - public static func preparedSendData( _ db: Database, interaction: Interaction, @@ -108,105 +67,6 @@ extension MessageSender { ) } - public static func sendNonDurably(_ db: Database, message: Message, interactionId: Int64?, in thread: SessionThread) throws -> Promise { - return sendNonDurably( - db, - message: message, - interactionId: interactionId, - to: try Message.Destination.from(db, thread: thread) - ) - } - - public static func sendNonDurably(_ db: Database, message: Message, interactionId: Int64?, to destination: Message.Destination) -> Promise { - var attachmentUploadPromises: [Promise] = [Promise.value(nil)] - - // If we have an interactionId then check if it has any attachments and process them first - if let interactionId: Int64 = interactionId { - let threadId: String = { - switch destination { - case .contact(let publicKey, _): return publicKey - case .closedGroup(let groupPublicKey, _): return groupPublicKey - case .openGroup(let roomToken, let server, _, _, _): - return OpenGroup.idFor(roomToken: roomToken, server: server) - - case .openGroupInbox(_, _, let blindedPublicKey): return blindedPublicKey - } - }() - let openGroup: OpenGroup? = try? OpenGroup.fetchOne(db, id: threadId) - let attachmentStateInfo: [Attachment.StateInfo] = (try? Attachment - .stateInfo(interactionId: interactionId, state: .uploading) - .fetchAll(db)) - .defaulting(to: []) - - attachmentUploadPromises = (try? Attachment - .filter(ids: attachmentStateInfo.map { $0.attachmentId }) - .fetchAll(db)) - .defaulting(to: []) - .map { attachment -> Promise in - let (promise, seal) = Promise.pending() - - attachment.upload( - db, - queue: DispatchQueue.global(qos: .userInitiated), - using: { db, data in - if let openGroup: OpenGroup = openGroup { - return OpenGroupAPI - .uploadFile( - db, - bytes: data.bytes, - to: openGroup.roomToken, - on: openGroup.server - ) - .map { _, response -> String in response.id } - .eraseToAnyPublisher() - } - - return FileServerAPI.upload(data) - .map { response -> String in response.id } - .eraseToAnyPublisher() - }, - encrypt: (openGroup == nil), - success: { fileId in seal.fulfill(fileId) }, - failure: { seal.reject($0) } - ) - - return promise - } - } - - // Once the attachments are processed then send the message - // TODO: Need to update all usages of this method - preconditionFailure() -// return when(resolved: attachmentUploadPromises) -// .then { results -> Promise in -// let errors: [Error] = results -// .compactMap { result -> Error? in -// if case .rejected(let error) = result { return error } -// -// return nil -// } -// -// if let error: Error = errors.first { return Promise(error: error) } -// -// return Storage.shared.writeAsync { db in -// let fileIds: [String] = results -// .compactMap { result -> String? in -// if case .fulfilled(let value) = result { return value } -// -// return nil -// } -// -// return try MessageSender.sendImmediate( -// db, -// message: message, -// to: destination -// .with(fileIds: fileIds), -// interactionId: interactionId -// ) -// } -// } - } - public static func performUploadsIfNeeded( preparedSendData: PreparedSendData ) -> AnyPublisher { @@ -242,7 +102,7 @@ extension MessageSender { // If there is no attachment data then just return early guard !attachmentStateInfo.isEmpty else { return nil } - + // TODO: Just run an AttachmentUploadJob directly??? // Otherwise we need to generate the upload requests let openGroup: OpenGroup? = try? OpenGroup.fetchOne(db, id: threadId) @@ -385,23 +245,14 @@ extension MessageSender { .retainUntilComplete() // TODO: Test this (does it break anything? want to stop the db write asap) + /// We don't want to block the db write thread so we trigger the actual message sending after the query has + /// finished return Future { resolver in db.afterNextTransaction { _ in - // TODO: Remove the 'Swift.' - resolver(Swift.Result.success(())) + resolver(Result.success(())) } } - .flatMap { _ in MessageSender.sendImmediate(data: sendData) } + .flatMap { _ in MessageSender.sendImmediate(preparedSendData: sendData) } .eraseToAnyPublisher() - -// return MessageSender -// .sendImmediate( -// data: try MessageSender.preparedSendData( -// db, -// message: configurationMessage, -// to: destination, -// interactionId: nil -// ) -// ) } } diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index 469c440c1..5dbc6213b 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -8,52 +8,6 @@ import SessionUtilitiesKit import Sodium public final class MessageSender { - // MARK: - Preparation - - public static func prep( - _ db: Database, - signalAttachments: [SignalAttachment], - for interactionId: Int64 - ) throws { - try signalAttachments.enumerated().forEach { index, signalAttachment in - let maybeAttachment: Attachment? = Attachment( - variant: (signalAttachment.isVoiceMessage ? - .voiceMessage : - .standard - ), - contentType: signalAttachment.mimeType, - dataSource: signalAttachment.dataSource, - sourceFilename: signalAttachment.sourceFilename, - caption: signalAttachment.captionText - ) - - guard let attachment: Attachment = maybeAttachment else { return } - - let interactionAttachment: InteractionAttachment = InteractionAttachment( - albumIndex: index, - interactionId: interactionId, - attachmentId: attachment.id - ) - - try attachment.insert(db) - try interactionAttachment.insert(db) - } - } - - // MARK: - Convenience - -// public static func sendImmediate(_ db: Database, message: Message, to destination: Message.Destination, interactionId: Int64?) throws -> Promise { -// switch destination { -// case .contact, .closedGroup: -// return try sendToSnodeDestination(db, message: message, to: destination, interactionId: interactionId) -// -// case .openGroup: -// return sendToOpenGroupDestination(db, message: message, to: destination, interactionId: interactionId) -// -// case .openGroupInbox: -// return sendToOpenGroupInboxDestination(db, message: message, to: destination, interactionId: interactionId) -// } -// } // MARK: - Message Preparation public struct PreparedSendData { @@ -644,19 +598,19 @@ public final class MessageSender { // MARK: - Sending public static func sendImmediate( - data: PreparedSendData, + preparedSendData: PreparedSendData, using dependencies: SMKDependencies = SMKDependencies() ) -> AnyPublisher { - guard data.shouldSend else { + guard preparedSendData.shouldSend else { return Just(()) .setFailureType(to: Error.self) .eraseToAnyPublisher() } - switch data.destination { - case .contact, .closedGroup: return sendToSnodeDestination(data: data, using: dependencies) - case .openGroup: return sendToOpenGroupDestination(data: data, using: dependencies) - case .openGroupInbox: return sendToOpenGroupInbox(data: data, using: dependencies) + switch preparedSendData.destination { + case .contact, .closedGroup: return sendToSnodeDestination(data: preparedSendData, using: dependencies) + case .openGroup: return sendToOpenGroupDestination(data: preparedSendData, using: dependencies) + case .openGroupInbox: return sendToOpenGroupInbox(data: preparedSendData, using: dependencies) case .none: return Fail(error: MessageSenderError.invalidMessage) .eraseToAnyPublisher() diff --git a/SessionMessagingKit/Utilities/Promise+Utilities.swift b/SessionMessagingKit/Utilities/Promise+Utilities.swift deleted file mode 100644 index ed2f23f4b..000000000 --- a/SessionMessagingKit/Utilities/Promise+Utilities.swift +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import Foundation -import PromiseKit -import SessionSnodeKit -import SessionUtilitiesKit - -extension Promise where T == Data { - func decoded(as type: R.Type, on queue: DispatchQueue? = nil, using dependencies: Dependencies = Dependencies()) -> Promise { - self.map(on: queue) { data -> R in - try data.decoded(as: type, using: dependencies) - } - } -} - -extension Promise where T == (OnionRequestResponseInfoType, Data?) { - func decoded(as type: R.Type, on queue: DispatchQueue? = nil, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, R)> { - self.map(on: queue) { responseInfo, maybeData -> (OnionRequestResponseInfoType, R) in - guard let data: Data = maybeData else { throw HTTP.Error.parsingFailed } - - do { - return (responseInfo, try data.decoded(as: type, using: dependencies)) - } - catch { - throw HTTP.Error.parsingFailed - } - } - } -} diff --git a/SessionShareExtension/ShareVC.swift b/SessionShareExtension/ShareVC.swift index b471dfaec..47dac8d38 100644 --- a/SessionShareExtension/ShareVC.swift +++ b/SessionShareExtension/ShareVC.swift @@ -1,15 +1,15 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit +import Combine import CoreServices -import PromiseKit import SignalUtilitiesKit import SessionUIKit import SignalCoreKit final class ShareVC: UINavigationController, ShareViewDelegate { private var areVersionMigrationsComplete = false - public static var attachmentPrepPromise: Promise<[SignalAttachment]>? + public static var attachmentPrepPublisher: AnyPublisher<[SignalAttachment], Error>? // MARK: - Error @@ -187,20 +187,19 @@ final class ShareVC: UINavigationController, ShareViewDelegate { setViewControllers([ threadPickerVC ], animated: false) - let promise = buildAttachments() - ModalActivityIndicatorViewController.present( - fromViewController: self, - canCancel: false, - message: "vc_share_loading_message".localized()) { activityIndicator in - promise - .done { _ in - activityIndicator.dismiss { } - } - .catch { _ in - activityIndicator.dismiss { } - } - } - ShareVC.attachmentPrepPromise = promise + let publisher = buildAttachments() + ModalActivityIndicatorViewController + .present( + fromViewController: self, + canCancel: false, + message: "vc_share_loading_message".localized() + ) { activityIndicator in + publisher + .sinkUntilComplete( + receiveCompletion: { _ in activityIndicator.dismiss { } } + ) + } + ShareNavController.attachmentPrepPublisher = publisher } func shareViewWasUnlocked() { @@ -365,10 +364,11 @@ final class ShareVC: UINavigationController, ShareViewDelegate { return [] } - private func selectItemProviders() -> Promise<[NSItemProvider]> { + private func selectItemProviders() -> AnyPublisher<[NSItemProvider], Error> { guard let inputItems = self.extensionContext?.inputItems else { let error = ShareViewControllerError.assertionError(description: "no input item") - return Promise(error: error) + return Fail(error: error) + .eraseToAnyPublisher() } for inputItemRaw in inputItems { @@ -377,12 +377,15 @@ final class ShareVC: UINavigationController, ShareViewDelegate { continue } - if let itemProviders = ShareVC.preferredItemProviders(inputItem: inputItem) { - return Promise.value(itemProviders) + if let itemProviders = ShareNavController.preferredItemProviders(inputItem: inputItem) { + return Just(itemProviders) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } } let error = ShareViewControllerError.assertionError(description: "no input item") - return Promise(error: error) + return Fail(error: error) + .eraseToAnyPublisher() } // MARK: - LoadedItem @@ -412,7 +415,7 @@ final class ShareVC: UINavigationController, ShareViewDelegate { } } - private func loadItemProvider(itemProvider: NSItemProvider) -> Promise { + private func loadItemProvider(itemProvider: NSItemProvider) -> AnyPublisher { Logger.info("attachment: \(itemProvider)") // We need to be very careful about which UTI type we use. @@ -426,115 +429,173 @@ final class ShareVC: UINavigationController, ShareViewDelegate { // using the file extension. guard let srcUtiType = ShareVC.utiType(itemProvider: itemProvider) else { let error = ShareViewControllerError.unsupportedMedia - return Promise(error: error) + return Fail(error: error) + .eraseToAnyPublisher() } Logger.debug("matched utiType: \(srcUtiType)") - let (promise, resolver) = Promise.pending() - - let loadCompletion: NSItemProvider.CompletionHandler = { [weak self] - (value, error) in - - guard let _ = self else { return } - guard error == nil else { - resolver.reject(error!) - return - } - - guard let value = value else { - let missingProviderError = ShareViewControllerError.assertionError(description: "missing item provider") - resolver.reject(missingProviderError) - return - } - - Logger.info("value type: \(type(of: value))") - - if let data = value as? Data { - let customFileName = "Contact.vcf" - - let customFileExtension = MIMETypeUtil.fileExtension(forUTIType: srcUtiType) - guard let tempFilePath = OWSFileSystem.writeData(toTemporaryFile: data, fileExtension: customFileExtension) else { - let writeError = ShareViewControllerError.assertionError(description: "Error writing item data: \(String(describing: error))") - resolver.reject(writeError) - return - } - let fileUrl = URL(fileURLWithPath: tempFilePath) - resolver.fulfill(LoadedItem(itemProvider: itemProvider, - itemUrl: fileUrl, - utiType: srcUtiType, - customFileName: customFileName, - isConvertibleToContactShare: false)) - } else if let string = value as? String { - Logger.debug("string provider: \(string)") - guard let data = string.filterStringForDisplay().data(using: String.Encoding.utf8) else { - let writeError = ShareViewControllerError.assertionError(description: "Error writing item data: \(String(describing: error))") - resolver.reject(writeError) - return - } - guard let tempFilePath = OWSFileSystem.writeData(toTemporaryFile: data, fileExtension: "txt") else { - let writeError = ShareViewControllerError.assertionError(description: "Error writing item data: \(String(describing: error))") - resolver.reject(writeError) + return Future { resolver in + let loadCompletion: NSItemProvider.CompletionHandler = { [weak self] value, error in + guard self != nil else { return } + if let error: Error = error { + resolver(Result.failure(error)) return } - let fileUrl = URL(fileURLWithPath: tempFilePath) + guard let value = value else { + resolver( + Result.failure(ShareViewControllerError.assertionError(description: "missing item provider")) + ) + return + } - let isConvertibleToTextMessage = !itemProvider.registeredTypeIdentifiers.contains(kUTTypeFileURL as String) + Logger.info("value type: \(type(of: value))") + + switch value { + case let data as Data: + let customFileName = "Contact.vcf" - if UTTypeConformsTo(srcUtiType as CFString, kUTTypeText) { - resolver.fulfill(LoadedItem(itemProvider: itemProvider, - itemUrl: fileUrl, - utiType: srcUtiType, - isConvertibleToTextMessage: isConvertibleToTextMessage)) - } else { - resolver.fulfill(LoadedItem(itemProvider: itemProvider, - itemUrl: fileUrl, - utiType: kUTTypeText as String, - isConvertibleToTextMessage: isConvertibleToTextMessage)) + let customFileExtension = MIMETypeUtil.fileExtension(forUTIType: srcUtiType) + guard let tempFilePath = OWSFileSystem.writeData(toTemporaryFile: data, fileExtension: customFileExtension) else { + resolver( + Result.failure(ShareViewControllerError.assertionError(description: "Error writing item data: \(String(describing: error))")) + ) + return + } + let fileUrl = URL(fileURLWithPath: tempFilePath) + + resolver( + Result.success( + LoadedItem( + itemProvider: itemProvider, + itemUrl: fileUrl, + utiType: srcUtiType, + customFileName: customFileName, + isConvertibleToContactShare: false + ) + ) + ) + + case let string as String: + Logger.debug("string provider: \(string)") + guard let data = string.filterStringForDisplay().data(using: String.Encoding.utf8) else { + resolver( + Result.failure(ShareViewControllerError.assertionError(description: "Error writing item data: \(String(describing: error))")) + ) + return + } + guard let tempFilePath = OWSFileSystem.writeData(toTemporaryFile: data, fileExtension: "txt") else { + resolver( + Result.failure(ShareViewControllerError.assertionError(description: "Error writing item data: \(String(describing: error))")) + ) + return + } + + let fileUrl = URL(fileURLWithPath: tempFilePath) + + let isConvertibleToTextMessage = !itemProvider.registeredTypeIdentifiers.contains(kUTTypeFileURL as String) + + if UTTypeConformsTo(srcUtiType as CFString, kUTTypeText) { + resolver( + Result.success( + LoadedItem( + itemProvider: itemProvider, + itemUrl: fileUrl, + utiType: srcUtiType, + isConvertibleToTextMessage: isConvertibleToTextMessage + ) + ) + ) + } + else { + resolver( + Result.success( + LoadedItem( + itemProvider: itemProvider, + itemUrl: fileUrl, + utiType: kUTTypeText as String, + isConvertibleToTextMessage: isConvertibleToTextMessage + ) + ) + ) + } + + case let url as URL: + // If the share itself is a URL (e.g. a link from Safari), try to send this as a text message. + let isConvertibleToTextMessage = ( + itemProvider.registeredTypeIdentifiers.contains(kUTTypeURL as String) && + !itemProvider.registeredTypeIdentifiers.contains(kUTTypeFileURL as String) + ) + + if isConvertibleToTextMessage { + resolver( + Result.success( + LoadedItem( + itemProvider: itemProvider, + itemUrl: url, + utiType: kUTTypeURL as String, + isConvertibleToTextMessage: isConvertibleToTextMessage + ) + ) + ) + } + else { + resolver( + Result.success( + LoadedItem( + itemProvider: itemProvider, + itemUrl: url, + utiType: srcUtiType, + isConvertibleToTextMessage: isConvertibleToTextMessage + ) + ) + ) + } + + case let image as UIImage: + if let data = image.pngData() { + let tempFilePath = OWSFileSystem.temporaryFilePath(withFileExtension: "png") + do { + let url = NSURL.fileURL(withPath: tempFilePath) + try data.write(to: url) + + resolver( + Result.success( + LoadedItem( + itemProvider: itemProvider, + itemUrl: url, + utiType: srcUtiType + ) + ) + ) + } + catch { + resolver( + Result.failure(ShareViewControllerError.assertionError(description: "couldn't write UIImage: \(String(describing: error))")) + ) + } + } + else { + resolver( + Result.failure(ShareViewControllerError.assertionError(description: "couldn't convert UIImage to PNG: \(String(describing: error))")) + ) + } + + default: + // It's unavoidable that we may sometimes receives data types that we + // don't know how to handle. + resolver( + Result.failure(ShareViewControllerError.assertionError(description: "unexpected value: \(String(describing: value))")) + ) } - } else if let url = value as? URL { - // If the share itself is a URL (e.g. a link from Safari), try to send this as a text message. - let isConvertibleToTextMessage = (itemProvider.registeredTypeIdentifiers.contains(kUTTypeURL as String) && - !itemProvider.registeredTypeIdentifiers.contains(kUTTypeFileURL as String)) - if isConvertibleToTextMessage { - resolver.fulfill(LoadedItem(itemProvider: itemProvider, - itemUrl: url, - utiType: kUTTypeURL as String, - isConvertibleToTextMessage: isConvertibleToTextMessage)) - } else { - resolver.fulfill(LoadedItem(itemProvider: itemProvider, - itemUrl: url, - utiType: srcUtiType, - isConvertibleToTextMessage: isConvertibleToTextMessage)) - } - } else if let image = value as? UIImage { - if let data = image.pngData() { - let tempFilePath = OWSFileSystem.temporaryFilePath(withFileExtension: "png") - do { - let url = NSURL.fileURL(withPath: tempFilePath) - try data.write(to: url) - resolver.fulfill(LoadedItem(itemProvider: itemProvider, itemUrl: url, - utiType: srcUtiType)) - } catch { - resolver.reject(ShareViewControllerError.assertionError(description: "couldn't write UIImage: \(String(describing: error))")) - } - } else { - resolver.reject(ShareViewControllerError.assertionError(description: "couldn't convert UIImage to PNG: \(String(describing: error))")) - } - } else { - // It's unavoidable that we may sometimes receives data types that we - // don't know how to handle. - let unexpectedTypeError = ShareViewControllerError.assertionError(description: "unexpected value: \(String(describing: value))") - resolver.reject(unexpectedTypeError) } + + itemProvider.loadItem(forTypeIdentifier: srcUtiType, options: nil, completionHandler: loadCompletion) } - - itemProvider.loadItem(forTypeIdentifier: srcUtiType, options: nil, completionHandler: loadCompletion) - - return promise + .eraseToAnyPublisher() } - private func buildAttachment(forLoadedItem loadedItem: LoadedItem) -> Promise { + private func buildAttachment(forLoadedItem loadedItem: LoadedItem) -> AnyPublisher { let itemProvider = loadedItem.itemProvider let itemUrl = loadedItem.itemUrl let utiType = loadedItem.utiType @@ -546,14 +607,16 @@ final class ShareVC: UINavigationController, ShareViewDelegate { } } catch { let error = ShareViewControllerError.assertionError(description: "Could not copy video") - return Promise(error: error) + return Fail(error: error) + .eraseToAnyPublisher() } Logger.debug("building DataSource with url: \(url), utiType: \(utiType)") guard let dataSource = ShareVC.createDataSource(utiType: utiType, url: url, customFileName: loadedItem.customFileName) else { let error = ShareViewControllerError.assertionError(description: "Unable to read attachment data") - return Promise(error: error) + return Fail(error: error) + .eraseToAnyPublisher() } // start with base utiType, but it might be something generic like "image" @@ -572,8 +635,8 @@ final class ShareVC: UINavigationController, ShareViewDelegate { guard !SignalAttachment.isInvalidVideo(dataSource: dataSource, dataUTI: specificUTIType) else { // This can happen, e.g. when sharing a quicktime-video from iCloud drive. - let (promise, _) = SignalAttachment.compressVideoAsMp4(dataSource: dataSource, dataUTI: specificUTIType) - return promise + let (publisher, _) = SignalAttachment.compressVideoAsMp4(dataSource: dataSource, dataUTI: specificUTIType) + return publisher } let attachment = SignalAttachment.attachment(dataSource: dataSource, dataUTI: specificUTIType, imageQuality: .medium) @@ -584,34 +647,49 @@ final class ShareVC: UINavigationController, ShareViewDelegate { Logger.info("isConvertibleToTextMessage") attachment.isConvertibleToTextMessage = true } - return Promise.value(attachment) + return Just(attachment) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } - private func buildAttachments() -> Promise<[SignalAttachment]> { - return selectItemProviders().then { [weak self] (itemProviders) -> Promise<[SignalAttachment]> in - guard let strongSelf = self else { - let error = ShareViewControllerError.assertionError(description: "expired") - return Promise(error: error) - } + private func buildAttachments() -> AnyPublisher<[SignalAttachment], Error> { + return selectItemProviders() + .flatMap { [weak self] itemProviders -> AnyPublisher<[SignalAttachment], Error> in + guard let strongSelf = self else { + let error = ShareViewControllerError.assertionError(description: "expired") + return Fail(error: error) + .eraseToAnyPublisher() + } - var loadPromises = [Promise]() + var loadPublishers = [AnyPublisher]() - for itemProvider in itemProviders.prefix(SignalAttachment.maxAttachmentsAllowed) { - let loadPromise = strongSelf.loadItemProvider(itemProvider: itemProvider) - .then({ (loadedItem) -> Promise in - return strongSelf.buildAttachment(forLoadedItem: loadedItem) - }) + for itemProvider in itemProviders.prefix(SignalAttachment.maxAttachmentsAllowed) { + let loadPublisher = strongSelf.loadItemProvider(itemProvider: itemProvider) + .flatMap { loadedItem -> AnyPublisher in + return strongSelf.buildAttachment(forLoadedItem: loadedItem) + } + .eraseToAnyPublisher() - loadPromises.append(loadPromise) + loadPublishers.append(loadPublisher) + } + + return Publishers + .MergeMany(loadPublishers) + .collect() + .eraseToAnyPublisher() } - return when(fulfilled: loadPromises) - }.map { (signalAttachments) -> [SignalAttachment] in - guard signalAttachments.count > 0 else { - let error = ShareViewControllerError.assertionError(description: "no valid attachments") - throw error + .flatMap { signalAttachments -> AnyPublisher<[SignalAttachment], Error> in + guard signalAttachments.count > 0 else { + return Fail(error: ShareViewControllerError.assertionError(description: "no valid attachments")) + .eraseToAnyPublisher() + } + + return Just(signalAttachments) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } - return signalAttachments - } + .shareReplay(1) + .eraseToAnyPublisher() } // Some host apps (e.g. iOS Photos.app) sometimes auto-converts some video formats (e.g. com.apple.quicktime-movie) diff --git a/SessionShareExtension/ThreadPickerVC.swift b/SessionShareExtension/ThreadPickerVC.swift index cae2bcf95..8981335ef 100644 --- a/SessionShareExtension/ThreadPickerVC.swift +++ b/SessionShareExtension/ThreadPickerVC.swift @@ -1,8 +1,8 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit +import Combine import GRDB -import PromiseKit import DifferenceKit import SessionUIKit import SignalUtilitiesKit @@ -149,14 +149,21 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) - guard let attachments: [SignalAttachment] = ShareVC.attachmentPrepPromise?.value else { return } - - let approvalVC: UINavigationController = AttachmentApprovalViewController.wrappedInNavController( - threadId: self.viewModel.viewData[indexPath.row].threadId, - attachments: attachments, - approvalDelegate: self - ) - self.navigationController?.present(approvalVC, animated: true, completion: nil) + ShareNavController.attachmentPrepPublisher? + .receiveOnMain(immediately: true) + .sinkUntilComplete( + receiveValue: { [weak self] attachments in + guard let strongSelf = self else { return } + + // TODO: Test this + let approvalVC: UINavigationController = AttachmentApprovalViewController.wrappedInNavController( + threadId: strongSelf.viewModel.viewData[indexPath.row].threadId, + attachments: attachments, + approvalDelegate: strongSelf + ) + strongSelf.navigationController?.present(approvalVC, animated: true, completion: nil) + } + ) } func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, didApproveAttachments attachments: [SignalAttachment], forThreadId threadId: String, messageText: String?) { @@ -181,12 +188,11 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView ModalActivityIndicatorViewController.present(fromViewController: shareVC!, canCancel: false, message: "vc_share_sending_message".localized()) { activityIndicator in // Resume database NotificationCenter.default.post(name: Database.resumeNotification, object: self) + Storage.shared - .writeAsync { [weak self] db -> Promise in + .writePublisher { [weak self] db -> MessageSender.PreparedSendData in guard let thread: SessionThread = try SessionThread.fetchOne(db, id: threadId) else { - activityIndicator.dismiss { } - self?.shareVC?.shareViewFailed(error: MessageSenderError.noThread) - return Promise(error: MessageSenderError.noThread) + throw MessageSenderError.noThread } // Create the interaction @@ -205,7 +211,11 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView .fetchOne(db), linkPreviewUrl: (isSharingUrl ? attachments.first?.linkPreviewDraft?.urlString : nil) ).inserted(db) - + + guard let interactionId: Int64 = interaction.id else { + throw StorageError.failedToSave + } + // If the user is sharing a Url, there is a LinkPreview and it doesn't match an existing // one then add it now if @@ -223,26 +233,36 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView ) ).insert(db) } - - return try MessageSender.sendNonDurably( + + // Prepare any attachments + try Attachment.prepare( db, - interaction: interaction, - with: finalAttachments, - in: thread + attachments: finalAttachments, + for: interactionId ) + + // Prepare the message send data + return try MessageSender + .preparedSendData( + db, + interaction: interaction, + in: thread + ) } - .done { [weak self] _ in - // Suspend the database - NotificationCenter.default.post(name: Database.suspendNotification, object: self) - activityIndicator.dismiss { } - self?.shareVC?.shareViewWasCompleted() - } - .catch { [weak self] error in - // Suspend the database - NotificationCenter.default.post(name: Database.suspendNotification, object: self) - activityIndicator.dismiss { } - self?.shareVC?.shareViewFailed(error: error) - } + .flatMap { MessageSender.performUploadsIfNeeded(preparedSendData: $0) } + .flatMap { MessageSender.sendImmediate(preparedSendData: $0) } + .sinkUntilComplete( + receiveCompletion: { [weak self] result in + // Suspend the database + NotificationCenter.default.post(name: Database.suspendNotification, object: self) + activityIndicator.dismiss { } + + switch result { + case .finished: self?.shareNavController?.shareViewWasCompleted() + case .failure(let error): self?.shareNavController?.shareViewFailed(error: error) + } + } + ) } } diff --git a/SessionSnodeKit/OnionRequestAPI+Encryption.swift b/SessionSnodeKit/OnionRequestAPI+Encryption.swift index 08ddddc71..2878973fe 100644 --- a/SessionSnodeKit/OnionRequestAPI+Encryption.swift +++ b/SessionSnodeKit/OnionRequestAPI+Encryption.swift @@ -3,20 +3,9 @@ import Foundation import Combine import CryptoSwift -import PromiseKit import SessionUtilitiesKit internal extension OnionRequestAPI { - - static func encodeLegacy(ciphertext: Data, json: JSON) throws -> Data { - // The encoding of V2 onion requests looks like: | 4 bytes: size N of ciphertext | N bytes: ciphertext | json as utf8 | - guard JSONSerialization.isValidJSONObject(json) else { throw HTTP.Error.invalidJSON } - let jsonAsData = try JSONSerialization.data(withJSONObject: json, options: [ .fragmentsAllowed ]) - let ciphertextSize = Int32(ciphertext.count).littleEndian - let ciphertextSizeAsData = withUnsafePointer(to: ciphertextSize) { Data(bytes: $0, count: MemoryLayout.size) } - return ciphertextSizeAsData + ciphertext + jsonAsData - } - static func encode(ciphertext: Data, json: JSON) -> AnyPublisher { // The encoding of V2 onion requests looks like: | 4 bytes: size N of ciphertext | N bytes: ciphertext | json as utf8 | guard @@ -34,102 +23,41 @@ internal extension OnionRequestAPI { .setFailureType(to: Error.self) .eraseToAnyPublisher() } - - /// Encrypts `payload` for `destination` and returns the result. Use this to build the core of an onion request. - static func encrypt(_ payload: Data, for destination: OnionRequestAPIDestination) -> Promise { - let (promise, seal) = Promise.pending() - DispatchQueue.global(qos: .userInitiated).async { - do { - switch destination { - case .snode(let snode): - // Need to wrap the payload for snode requests - let data: Data = try encodeLegacy(ciphertext: payload, json: [ "headers" : "" ]) - let result: AESGCM.EncryptionResult = try AESGCM.encrypt(data, for: snode.x25519PublicKey) - seal.fulfill(result) - - case .server(_, _, let serverX25519PublicKey, _, _): - let result: AESGCM.EncryptionResult = try AESGCM.encrypt(payload, for: serverX25519PublicKey) - seal.fulfill(result) - } - } - catch (let error) { - seal.reject(error) - } - } - - return promise - } /// Encrypts `payload` for `destination` and returns the result. Use this to build the core of an onion request. static func encrypt( _ payload: Data, for destination: OnionRequestAPIDestination ) -> AnyPublisher { - return Future { resolver in - DispatchQueue.global(qos: .userInitiated).async { - do { - switch destination { - case .snode(let snode): - // Need to wrap the payload for snode requests - let data: Data = try encodeLegacy(ciphertext: payload, json: [ "headers" : "" ]) - let result: AESGCM.EncryptionResult = try AESGCM.encrypt(data, for: snode.x25519PublicKey) - resolver(Swift.Result.success(result)) - - case .server(_, _, let serverX25519PublicKey, _, _): - let result: AESGCM.EncryptionResult = try AESGCM.encrypt(payload, for: serverX25519PublicKey) - resolver(Swift.Result.success(result)) + // TODO: Test performance + switch destination { + case .snode(let snode): + // Need to wrap the payload for snode requests + return encode(ciphertext: payload, json: [ "headers" : "" ]) + .flatMap { data -> AnyPublisher in + do { + return Just(try AESGCM.encrypt(data, for: snode.x25519PublicKey)) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + catch { + return Fail(error: error) + .eraseToAnyPublisher() + } } + .eraseToAnyPublisher() + + case .server(_, _, let serverX25519PublicKey, _, _): + do { + return Just(try AESGCM.encrypt(payload, for: serverX25519PublicKey)) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } - catch (let error) { - resolver(Swift.Result.failure(error)) + catch { + return Fail(error: error) + .eraseToAnyPublisher() } - } } - .eraseToAnyPublisher() - } - - /// Encrypts the previous encryption result (i.e. that of the hop after this one) for this hop. Use this to build the layers of an onion request. - static func encryptHop(from lhs: OnionRequestAPIDestination, to rhs: OnionRequestAPIDestination, using previousEncryptionResult: AESGCM.EncryptionResult) -> Promise { - let (promise, seal) = Promise.pending() - - DispatchQueue.global(qos: .userInitiated).async { - var parameters: JSON - - switch rhs { - case .snode(let snode): - let snodeED25519PublicKey = snode.ed25519PublicKey - parameters = [ "destination" : snodeED25519PublicKey ] - - case .server(let host, let target, _, let scheme, let port): - let scheme = scheme ?? "https" - let port = port ?? (scheme == "https" ? 443 : 80) - parameters = [ "host" : host, "target" : target, "method" : "POST", "protocol" : scheme, "port" : port ] - } - - parameters["ephemeral_key"] = previousEncryptionResult.ephemeralPublicKey.toHexString() - - let x25519PublicKey: String - - switch lhs { - case .snode(let snode): - let snodeX25519PublicKey = snode.x25519PublicKey - x25519PublicKey = snodeX25519PublicKey - - case .server(_, _, let serverX25519PublicKey, _, _): - x25519PublicKey = serverX25519PublicKey - } - - do { - let plaintext = try encodeLegacy(ciphertext: previousEncryptionResult.ciphertext, json: parameters) - let result = try AESGCM.encrypt(plaintext, for: x25519PublicKey) - seal.fulfill(result) - } - catch (let error) { - seal.reject(error) - } - } - - return promise } /// Encrypts the previous encryption result (i.e. that of the hop after this one) for this hop. Use this to build the layers of an onion request. @@ -138,44 +66,42 @@ internal extension OnionRequestAPI { to rhs: OnionRequestAPIDestination, using previousEncryptionResult: AESGCM.EncryptionResult ) -> AnyPublisher { - return Future { resolver in - DispatchQueue.global(qos: .userInitiated).async { - var parameters: JSON - - switch rhs { - case .snode(let snode): - let snodeED25519PublicKey = snode.ed25519PublicKey - parameters = [ "destination" : snodeED25519PublicKey ] - - case .server(let host, let target, _, let scheme, let port): - let scheme = scheme ?? "https" - let port = port ?? (scheme == "https" ? 443 : 80) - parameters = [ "host" : host, "target" : target, "method" : "POST", "protocol" : scheme, "port" : port ] - } - - parameters["ephemeral_key"] = previousEncryptionResult.ephemeralPublicKey.toHexString() - - let x25519PublicKey: String - - switch lhs { - case .snode(let snode): - let snodeX25519PublicKey = snode.x25519PublicKey - x25519PublicKey = snodeX25519PublicKey - - case .server(_, _, let serverX25519PublicKey, _, _): - x25519PublicKey = serverX25519PublicKey - } + // TODO: Test performance + var parameters: JSON + + switch rhs { + case .snode(let snode): + let snodeED25519PublicKey = snode.ed25519PublicKey + parameters = [ "destination" : snodeED25519PublicKey ] + case .server(let host, let target, _, let scheme, let port): + let scheme = scheme ?? "https" + let port = port ?? (scheme == "https" ? 443 : 80) + parameters = [ "host" : host, "target" : target, "method" : "POST", "protocol" : scheme, "port" : port ] + } + + parameters["ephemeral_key"] = previousEncryptionResult.ephemeralPublicKey.toHexString() + + let x25519PublicKey: String = { + switch lhs { + case .snode(let snode): return snode.x25519PublicKey + case .server(_, _, let serverX25519PublicKey, _, _): + return serverX25519PublicKey + } + }() + + return encode(ciphertext: previousEncryptionResult.ciphertext, json: parameters) + .flatMap { data -> AnyPublisher in do { - let plaintext = try encodeLegacy(ciphertext: previousEncryptionResult.ciphertext, json: parameters) - let result = try AESGCM.encrypt(plaintext, for: x25519PublicKey) - resolver(Swift.Result.success(result)) + return Just(try AESGCM.encrypt(data, for: x25519PublicKey)) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } catch (let error) { - resolver(Swift.Result.failure(error)) + return Fail(error: error) + .eraseToAnyPublisher() } } - } - .eraseToAnyPublisher() + .eraseToAnyPublisher() } } diff --git a/SessionSnodeKit/OnionRequestAPI.swift b/SessionSnodeKit/OnionRequestAPI.swift index d3d1e12f3..73a06d741 100644 --- a/SessionSnodeKit/OnionRequestAPI.swift +++ b/SessionSnodeKit/OnionRequestAPI.swift @@ -4,7 +4,6 @@ import Foundation import Combine import CryptoSwift import GRDB -import PromiseKit import SessionUtilitiesKit public protocol OnionRequestAPIType { @@ -14,7 +13,6 @@ public protocol OnionRequestAPIType { /// See the "Onion Requests" section of [The Session Whitepaper](https://arxiv.org/pdf/2002.04609.pdf) for more information. public enum OnionRequestAPI: OnionRequestAPIType { - private static var buildPathsPromise: Promise<[[Snode]]>? = nil private static var buildPathsPublisher: Atomic?> = Atomic(nil) /// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions. @@ -63,40 +61,6 @@ public enum OnionRequestAPI: OnionRequestAPIType { // MARK: - Private API - /// Tests the given snode. The returned promise errors out if the snode is faulty; the promise is fulfilled otherwise. - private static func testSnode(_ snode: Snode) -> Promise { - let (promise, seal) = Promise.pending() - - DispatchQueue.global(qos: .userInitiated).async { - let url = "\(snode.address):\(snode.port)/get_stats/v1" - let timeout: TimeInterval = 3 // Use a shorter timeout for testing - - HTTP.executeLegacy(.get, url, timeout: timeout) - .done2 { responseData in - // TODO: Remove JSON usage - guard let responseJson: JSON = try? JSONSerialization.jsonObject(with: responseData, options: [ .fragmentsAllowed ]) as? JSON else { - throw HTTP.Error.invalidJSON - } - guard let version = responseJson["version"] as? String else { - return seal.reject(OnionRequestAPIError.missingSnodeVersion) - } - - if version >= "2.0.7" { - seal.fulfill(()) - } - else { - SNLog("Unsupported snode version: \(version).") - seal.reject(OnionRequestAPIError.unsupportedSnodeVersion(version)) - } - } - .catch2 { error in - seal.reject(error) - } - } - - return promise - } - /// Tests the given snode. The returned promise errors out if the snode is faulty; the promise is fulfilled otherwise. private static func testSnode(_ snode: Snode) -> AnyPublisher { let url = "\(snode.address):\(snode.port)/get_stats/v1" @@ -126,51 +90,6 @@ public enum OnionRequestAPI: OnionRequestAPIType { } .eraseToAnyPublisher() } - - /// Finds `targetGuardSnodeCount` guard snodes to use for path building. The returned promise errors out with - /// `Error.insufficientSnodes` if not enough (reliable) snodes are available. - private static func getGuardSnodes(reusing reusableGuardSnodes: [Snode]) -> Promise> { - if guardSnodes.count >= targetGuardSnodeCount { - return Promise> { $0.fulfill(guardSnodes) } - } - else { - SNLog("Populating guard snode cache.") - // Sync on LokiAPI.workQueue - var unusedSnodes = SnodeAPI.snodePool.wrappedValue.subtracting(reusableGuardSnodes) - let reusableGuardSnodeCount = UInt(reusableGuardSnodes.count) - - guard unusedSnodes.count >= (targetGuardSnodeCount - reusableGuardSnodeCount) else { - return Promise(error: OnionRequestAPIError.insufficientSnodes) - } - - func getGuardSnode() -> Promise { - // randomElement() uses the system's default random generator, which - // is cryptographically secure - guard let candidate = unusedSnodes.randomElement() else { - return Promise { $0.reject(OnionRequestAPIError.insufficientSnodes) } - } - - unusedSnodes.remove(candidate) // All used snodes should be unique - SNLog("Testing guard snode: \(candidate).") - - // Loop until a reliable guard snode is found - return testSnode(candidate).map2 { candidate }.recover(on: DispatchQueue.main) { _ in - withDelay(0.1, completionQueue: Threading.workQueue) { getGuardSnode() } - } - } - - let promises = (0..<(targetGuardSnodeCount - reusableGuardSnodeCount)).map { _ in - getGuardSnode() - } - - return when(fulfilled: promises).map2 { guardSnodes in - let guardSnodesAsSet = Set(guardSnodes + reusableGuardSnodes) - OnionRequestAPI.guardSnodes = guardSnodesAsSet - - return guardSnodesAsSet - } - } - } /// Finds `targetGuardSnodeCount` guard snodes to use for path building. The returned promise errors out with /// `Error.insufficientSnodes` if not enough (reliable) snodes are available. @@ -227,59 +146,6 @@ public enum OnionRequestAPI: OnionRequestAPIType { ) .eraseToAnyPublisher() } - - /// Builds and returns `targetPathCount` paths. The returned promise errors out with `Error.insufficientSnodes` - /// if not enough (reliable) snodes are available. - @discardableResult - private static func buildPaths(reusing reusablePaths: [[Snode]]) -> Promise<[[Snode]]> { - if let existingBuildPathsPromise = buildPathsPromise { return existingBuildPathsPromise } - SNLog("Building onion request paths.") - DispatchQueue.main.async { - NotificationCenter.default.post(name: .buildingPaths, object: nil) - } - let reusableGuardSnodes = reusablePaths.map { $0[0] } - let promise: Promise<[[Snode]]> = getGuardSnodes(reusing: reusableGuardSnodes) - .map2 { guardSnodes -> [[Snode]] in - var unusedSnodes = SnodeAPI.snodePool.wrappedValue - .subtracting(guardSnodes) - .subtracting(reusablePaths.flatMap { $0 }) - let reusableGuardSnodeCount = UInt(reusableGuardSnodes.count) - let pathSnodeCount = (targetGuardSnodeCount - reusableGuardSnodeCount) * pathSize - (targetGuardSnodeCount - reusableGuardSnodeCount) - - guard unusedSnodes.count >= pathSnodeCount else { throw OnionRequestAPIError.insufficientSnodes } - - // Don't test path snodes as this would reveal the user's IP to them - return guardSnodes.subtracting(reusableGuardSnodes).map { guardSnode in - let result = [ guardSnode ] + (0..<(pathSize - 1)).map { _ in - // randomElement() uses the system's default random generator, which is cryptographically secure - let pathSnode = unusedSnodes.randomElement()! // Safe because of the pathSnodeCount check above - unusedSnodes.remove(pathSnode) // All used snodes should be unique - return pathSnode - } - - SNLog("Built new onion request path: \(result.prettifiedDescription).") - return result - } - } - .map2 { paths in - OnionRequestAPI.paths = paths + reusablePaths - - Storage.shared.write { db in - SNLog("Persisting onion request paths to database.") - try? paths.save(db) - } - - DispatchQueue.main.async { - NotificationCenter.default.post(name: .pathsBuilt, object: nil) - } - return paths - } - - promise.done2 { _ in buildPathsPromise = nil } - promise.catch2 { _ in buildPathsPromise = nil } - buildPathsPromise = promise - return promise - } /// Builds and returns `targetPathCount` paths. The returned promise errors out with `Error.insufficientSnodes` /// if not enough (reliable) snodes are available. @@ -347,74 +213,6 @@ public enum OnionRequestAPI: OnionRequestAPIType { return publisher } - - /// Returns a `Path` to be used for building an onion request. Builds new paths as needed. - private static func getPath(excluding snode: Snode?) -> Promise<[Snode]> { - guard pathSize >= 1 else { preconditionFailure("Can't build path of size zero.") } - - let paths: [[Snode]] = OnionRequestAPI.paths - - if !paths.isEmpty { - guardSnodes.formUnion([ paths[0][0] ]) - - if paths.count >= 2 { - guardSnodes.formUnion([ paths[1][0] ]) - } - } - - // randomElement() uses the system's default random generator, which is cryptographically secure - if - paths.count >= targetPathCount, - let targetPath: [Snode] = paths - .filter({ snode == nil || !$0.contains(snode!) }) - .randomElement() - { - return Promise { $0.fulfill(targetPath) } - } - else if !paths.isEmpty { - if let snode = snode { - if let path = paths.first(where: { !$0.contains(snode) }) { - let tmp: Promise<[[Snode]]> = buildPaths(reusing: paths) // Re-build paths in the background - return Promise { $0.fulfill(path) } - } - else { - return buildPaths(reusing: paths).map2 { paths in - guard let path: [Snode] = paths.filter({ !$0.contains(snode) }).randomElement() else { - throw OnionRequestAPIError.insufficientSnodes - } - - return path - } - } - } - else { - let tmp: Promise<[[Snode]]> = buildPaths(reusing: paths) // Re-build paths in the background - - guard let path: [Snode] = paths.randomElement() else { - return Promise(error: OnionRequestAPIError.insufficientSnodes) - } - - return Promise { $0.fulfill(path) } - } - } - else { - return buildPaths(reusing: []).map2 { paths in - if let snode = snode { - if let path = paths.filter({ !$0.contains(snode) }).randomElement() { - return path - } - - throw OnionRequestAPIError.insufficientSnodes - } - - guard let path: [Snode] = paths.randomElement() else { - throw OnionRequestAPIError.insufficientSnodes - } - - return path - } - } - } /// Returns a `Path` to be used for building an onion request. Builds new paths as needed. private static func getPath(excluding snode: Snode?) -> AnyPublisher<[Snode], Error> { @@ -566,50 +364,6 @@ public enum OnionRequestAPI: OnionRequestAPIType { try? paths.save(db) } } - - /// Builds an onion around `payload` and returns the result. - private static func buildOnion(around payload: Data, targetedAt destination: OnionRequestAPIDestination) -> Promise { - var guardSnode: Snode! - var targetSnodeSymmetricKey: Data! // Needed by invoke(_:on:with:) to decrypt the response sent back by the destination - var encryptionResult: AESGCM.EncryptionResult! - var snodeToExclude: Snode? - - if case .snode(let snode) = destination { snodeToExclude = snode } - - return getPath(excluding: snodeToExclude) - .then2 { path -> Promise in - guardSnode = path.first! - - // Encrypt in reverse order, i.e. the destination first - return encrypt(payload, for: destination) - .then2 { r -> Promise in - targetSnodeSymmetricKey = r.symmetricKey - - // Recursively encrypt the layers of the onion (again in reverse order) - encryptionResult = r - var path = path - var rhs = destination - - func addLayer() -> Promise { - guard !path.isEmpty else { - return Promise { $0.fulfill(encryptionResult) } - } - - let lhs = OnionRequestAPIDestination.snode(path.removeLast()) - return OnionRequestAPI - .encryptHop(from: lhs, to: rhs, using: encryptionResult) - .then2 { r -> Promise in - encryptionResult = r - rhs = lhs - return addLayer() - } - } - - return addLayer() - } - } - .map2 { _ in (guardSnode, encryptionResult, targetSnodeSymmetricKey) } - } /// Builds an onion around `payload` and returns the result. private static func buildOnion( @@ -926,123 +680,6 @@ public enum OnionRequestAPI: OnionRequestAPIType { } } - private static func handleResponse( - responseData: Data, - destinationSymmetricKey: Data, - version: OnionRequestAPIVersion, - destination: OnionRequestAPIDestination, - seal: Resolver<(OnionRequestResponseInfoType, Data?)> - ) { - switch version { - // V2 and V3 Onion Requests have the same structure for responses - case .v2, .v3: - let json: JSON - - if let processedJson = try? JSONSerialization.jsonObject(with: responseData, options: [ .fragmentsAllowed ]) as? JSON { - json = processedJson - } - else if let result: String = String(data: responseData, encoding: .utf8) { - json = [ "result": result ] - } - else { - return seal.reject(HTTP.Error.invalidJSON) - } - - guard let base64EncodedIVAndCiphertext = json["result"] as? String, let ivAndCiphertext = Data(base64Encoded: base64EncodedIVAndCiphertext), ivAndCiphertext.count >= AESGCM.ivSize else { - return seal.reject(HTTP.Error.invalidJSON) - } - - do { - let data = try AESGCM.decrypt(ivAndCiphertext, with: destinationSymmetricKey) - - guard let json = try JSONSerialization.jsonObject(with: data, options: [ .fragmentsAllowed ]) as? JSON, let statusCode = json["status_code"] as? Int ?? json["status"] as? Int else { - return seal.reject(HTTP.Error.invalidJSON) - } - - if statusCode == 406 { // Clock out of sync - SNLog("The user's clock is out of sync with the service node network.") - return seal.reject(SnodeAPIError.clockOutOfSync) - } - - if statusCode == 401 { // Signature verification failed - SNLog("Failed to verify the signature.") - return seal.reject(SnodeAPIError.signatureVerificationFailed) - } - - if let bodyAsString = json["body"] as? String { - guard let bodyAsData = bodyAsString.data(using: .utf8) else { - return seal.reject(HTTP.Error.invalidResponse) - } - guard let body = try? JSONSerialization.jsonObject(with: bodyAsData, options: [ .fragmentsAllowed ]) as? JSON else { - return seal.reject(OnionRequestAPIError.httpRequestFailedAtDestination(statusCode: UInt(statusCode), data: bodyAsData, destination: destination)) - } - - if let timestamp = body["t"] as? Int64 { - let offset = timestamp - Int64(floor(Date().timeIntervalSince1970 * 1000)) - SnodeAPI.clockOffset.mutate { $0 = offset } - } - - guard 200...299 ~= statusCode else { - return seal.reject(OnionRequestAPIError.httpRequestFailedAtDestination(statusCode: UInt(statusCode), data: bodyAsData, destination: destination)) - } - - return seal.fulfill((OnionRequestAPI.ResponseInfo(code: statusCode, headers: [:]), bodyAsData)) - } - - guard 200...299 ~= statusCode else { - return seal.reject(OnionRequestAPIError.httpRequestFailedAtDestination(statusCode: UInt(statusCode), data: data, destination: destination)) - } - - return seal.fulfill((OnionRequestAPI.ResponseInfo(code: statusCode, headers: [:]), data)) - - } - catch { - return seal.reject(error) - } - - // V4 Onion Requests have a very different structure for responses - case .v4: - guard responseData.count >= AESGCM.ivSize else { return seal.reject(HTTP.Error.invalidResponse) } - - do { - let data: Data = try AESGCM.decrypt(responseData, with: destinationSymmetricKey) - - // Process the bencoded response - guard let processedResponse: (info: ResponseInfo, body: Data?) = process(bencodedData: data) else { - return seal.reject(HTTP.Error.invalidResponse) - } - - // Custom handle a clock out of sync error (v4 returns '425' but included the '406' - // just in case) - guard processedResponse.info.code != 406 && processedResponse.info.code != 425 else { - SNLog("The user's clock is out of sync with the service node network.") - return seal.reject(SnodeAPIError.clockOutOfSync) - } - - guard processedResponse.info.code != 401 else { // Signature verification failed - SNLog("Failed to verify the signature.") - return seal.reject(SnodeAPIError.signatureVerificationFailed) - } - - // Handle error status codes - guard 200...299 ~= processedResponse.info.code else { - return seal.reject( - OnionRequestAPIError.httpRequestFailedAtDestination( - statusCode: UInt(processedResponse.info.code), - data: data, - destination: destination - ) - ) - } - - return seal.fulfill(processedResponse) - } - catch { - return seal.reject(error) - } - } - } - private static func handleResponse( responseData: Data, destinationSymmetricKey: Data, diff --git a/SessionSnodeKit/Utilities/Promise+Hashing.swift b/SessionSnodeKit/Utilities/Promise+Hashing.swift deleted file mode 100644 index 47bb42c62..000000000 --- a/SessionSnodeKit/Utilities/Promise+Hashing.swift +++ /dev/null @@ -1,13 +0,0 @@ -import PromiseKit - -extension Promise : Hashable { - - public func hash(into hasher: inout Hasher) { - let reference = ObjectIdentifier(self) - hasher.combine(reference.hashValue) - } - - public static func == (lhs: Promise, rhs: Promise) -> Bool { - return ObjectIdentifier(lhs) == ObjectIdentifier(rhs) - } -} diff --git a/SessionSnodeKit/Utilities/Promise+Threading.swift b/SessionSnodeKit/Utilities/Promise+Threading.swift deleted file mode 100644 index ce7d5ab7a..000000000 --- a/SessionSnodeKit/Utilities/Promise+Threading.swift +++ /dev/null @@ -1,91 +0,0 @@ -import PromiseKit - -public extension Thenable { - - @discardableResult - func then2(_ body: @escaping (T) throws -> U) -> Promise where U : Thenable { - return then(on: Threading.workQueue, body) - } - - @discardableResult - func map2(_ transform: @escaping (T) throws -> U) -> Promise { - return map(on: Threading.workQueue, transform) - } - - @discardableResult - func done2(_ body: @escaping (T) throws -> Void) -> Promise { - return done(on: Threading.workQueue, body) - } - - @discardableResult - func get2(_ body: @escaping (T) throws -> Void) -> Promise { - return get(on: Threading.workQueue, body) - } -} - -public extension Thenable where T: Sequence { - - @discardableResult - func mapValues2(_ transform: @escaping (T.Iterator.Element) throws -> U) -> Promise<[U]> { - return mapValues(on: Threading.workQueue, transform) - } -} - -public extension Guarantee { - - @discardableResult - func then2(_ body: @escaping (T) -> Guarantee) -> Guarantee { - return then(on: Threading.workQueue, body) - } - - @discardableResult - func map2(_ body: @escaping (T) -> U) -> Guarantee { - return map(on: Threading.workQueue, body) - } - - @discardableResult - func done2(_ body: @escaping (T) -> Void) -> Guarantee { - return done(on: Threading.workQueue, body) - } - - @discardableResult - func get2(_ body: @escaping (T) -> Void) -> Guarantee { - return get(on: Threading.workQueue, body) - } -} - -public extension CatchMixin { - - @discardableResult - func catch2(_ body: @escaping (Error) -> Void) -> PMKFinalizer { - return self.catch(on: Threading.workQueue, body) - } - - @discardableResult - func recover2(_ body: @escaping(Error) throws -> U) -> Promise where U.T == T { - return recover(on: Threading.workQueue, body) - } - - @discardableResult - func recover2(_ body: @escaping(Error) -> Guarantee) -> Guarantee { - return recover(on: Threading.workQueue, body) - } - - @discardableResult - func ensure2(_ body: @escaping () -> Void) -> Promise { - return ensure(on: Threading.workQueue, body) - } -} - -public extension CatchMixin where T == Void { - - @discardableResult - func recover2(_ body: @escaping(Error) -> Void) -> Guarantee { - return recover(on: Threading.workQueue, body) - } - - @discardableResult - func recover2(_ body: @escaping(Error) throws -> Void) -> Promise { - return recover(on: Threading.workQueue, body) - } -} diff --git a/SessionUtilitiesKit/Database/Storage.swift b/SessionUtilitiesKit/Database/Storage.swift index 8050944bc..1b3e15373 100644 --- a/SessionUtilitiesKit/Database/Storage.swift +++ b/SessionUtilitiesKit/Database/Storage.swift @@ -3,7 +3,7 @@ import Foundation import Combine import GRDB -import PromiseKit +import SignalCoreKit open class Storage { private static let dbFileName: String = "Session.sqlite" @@ -414,50 +414,6 @@ open class Storage { } } -// MARK: - Promise Extensions - -public extension Storage { - // FIXME: Would be good to replace these with Swift Combine - @discardableResult func read(_ value: (Database) throws -> Promise) -> Promise { - guard isValid, let dbWriter: DatabaseWriter = dbWriter else { - return Promise(error: StorageError.databaseInvalid) - } - - do { - return try dbWriter.read(value) - } - catch { - return Promise(error: error) - } - } - - // FIXME: Can't overrwrite this in `SynchronousStorage` since it's in an extension - @discardableResult func writeAsync(updates: @escaping (Database) throws -> Promise) -> Promise { - guard isValid, let dbWriter: DatabaseWriter = dbWriter else { - return Promise(error: StorageError.databaseInvalid) - } - - let (promise, seal) = Promise.pending() - - dbWriter.asyncWrite( - { db in - try updates(db) - .done { result in seal.fulfill(result) } - .catch { error in seal.reject(error) } - .retainUntilComplete() - }, - completion: { _, result in - switch result { - case .failure(let error): seal.reject(error) - default: break - } - } - ) - - return promise - } -} - // MARK: - Combine Extensions public extension Storage { diff --git a/SessionUtilitiesKit/Networking/HTTP.swift b/SessionUtilitiesKit/Networking/HTTP.swift index d8bac7ded..3882bec0e 100644 --- a/SessionUtilitiesKit/Networking/HTTP.swift +++ b/SessionUtilitiesKit/Networking/HTTP.swift @@ -1,5 +1,7 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + import Foundation -import PromiseKit +import Combine public enum HTTP { private static let seedNodeURLSession = URLSession(configuration: .ephemeral, delegate: seedNodeURLSessionDelegate, delegateQueue: nil) @@ -67,75 +69,6 @@ public enum HTTP { } } - // MARK: - Main - - public static func executeLegacy( - _ method: HTTPMethod, - _ url: String, - timeout: TimeInterval = HTTP.defaultTimeout, - useSeedNodeURLSession: Bool = false - ) -> Promise { - return executeLegacy(method, url, body: nil, timeout: timeout, useSeedNodeURLSession: useSeedNodeURLSession) - } - - public static func executeLegacy( - _ method: HTTPMethod, - _ url: String, - body: Data?, - timeout: TimeInterval = HTTP.defaultTimeout, - useSeedNodeURLSession: Bool = false - ) -> Promise { - var request = URLRequest(url: URL(string: url)!) - request.httpMethod = verb.rawValue - request.httpBody = body - request.timeoutInterval = timeout - request.allHTTPHeaderFields?.removeValue(forKey: "User-Agent") - request.setValue("WhatsApp", forHTTPHeaderField: "User-Agent") // Set a fake value - request.setValue("en-us", forHTTPHeaderField: "Accept-Language") // Set a fake value - let (promise, seal) = Promise.pending() - let urlSession = useSeedNodeURLSession ? seedNodeURLSession : snodeURLSession - let task = urlSession.dataTask(with: request) { data, response, error in - guard let data = data, let response = response as? HTTPURLResponse else { - if let error = error { - SNLog("\(verb.rawValue) request to \(url) failed due to error: \(error).") - } else { - SNLog("\(verb.rawValue) request to \(url) failed.") - } - - // Override the actual error so that we can correctly catch failed requests in sendOnionRequest(invoking:on:with:) - switch (error as? NSError)?.code { - case NSURLErrorTimedOut: return seal.reject(Error.timeout) - default: return seal.reject(Error.httpRequestFailed(statusCode: 0, data: nil)) - } - - } - if let error = error { - SNLog("\(verb.rawValue) request to \(url) failed due to error: \(error).") - // Override the actual error so that we can correctly catch failed requests in sendOnionRequest(invoking:on:with:) - return seal.reject(Error.httpRequestFailed(statusCode: 0, data: data)) - } - let statusCode = UInt(response.statusCode) - - guard 200...299 ~= statusCode else { - var json: JSON? = nil - if let processedJson: JSON = try? JSONSerialization.jsonObject(with: data, options: [ .fragmentsAllowed ]) as? JSON { - json = processedJson - } - else if let result: String = String(data: data, encoding: .utf8) { - json = [ "result": result ] - } - - let jsonDescription: String = (json?.prettifiedDescription ?? "no debugging info provided") - SNLog("\(verb.rawValue) request to \(url) failed with status code: \(statusCode) (\(jsonDescription)).") - return seal.reject(Error.httpRequestFailed(statusCode: statusCode, data: data)) - } - - seal.fulfill(data) - } - task.resume() - return promise - } - // MARK: - Execution public static func execute( @@ -193,7 +126,7 @@ public enum HTTP { .eraseToAnyPublisher() } let statusCode = UInt(response.statusCode) -// TODO: Remove all the JSON handling? + // TODO: Remove all the JSON handling? guard 200...299 ~= statusCode else { var json: JSON? = nil if let processedJson: JSON = try? JSONSerialization.jsonObject(with: data, options: [ .fragmentsAllowed ]) as? JSON { diff --git a/SessionUtilitiesKit/Networking/URLResponse+Utilities.swift b/SessionUtilitiesKit/Networking/URLResponse+Utilities.swift new file mode 100644 index 000000000..219f446b4 --- /dev/null +++ b/SessionUtilitiesKit/Networking/URLResponse+Utilities.swift @@ -0,0 +1,14 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public extension URLResponse { + var stringEncoding: String.Encoding? { + guard let encodingName = textEncodingName else { return nil } + + let encoding = CFStringConvertIANACharSetNameToEncoding(encodingName as CFString) + guard encoding != kCFStringEncodingInvalidId else { return nil } + + return String.Encoding(rawValue: CFStringConvertEncodingToNSStringEncoding(encoding)) + } +} diff --git a/SessionUtilitiesKit/PromiseKit/AnyPromise+Conversion.swift b/SessionUtilitiesKit/PromiseKit/AnyPromise+Conversion.swift deleted file mode 100644 index eccf1bd7b..000000000 --- a/SessionUtilitiesKit/PromiseKit/AnyPromise+Conversion.swift +++ /dev/null @@ -1,10 +0,0 @@ -import PromiseKit - -public extension AnyPromise { - - static func from(_ promise: Promise) -> AnyPromise { - let result = AnyPromise(promise) - result.retainUntilComplete() - return result - } -} diff --git a/SessionUtilitiesKit/PromiseKit/Promise+Delaying.swift b/SessionUtilitiesKit/PromiseKit/Promise+Delaying.swift deleted file mode 100644 index 02401a14e..000000000 --- a/SessionUtilitiesKit/PromiseKit/Promise+Delaying.swift +++ /dev/null @@ -1,14 +0,0 @@ -import PromiseKit - -/// Delay the execution of the promise constructed in `body` by `delay` seconds. -public func withDelay(_ delay: TimeInterval, completionQueue: DispatchQueue, body: @escaping () -> Promise) -> Promise { - let (promise, seal) = Promise.pending() - Timer.scheduledTimerOnMainThread(withTimeInterval: delay, repeats: false) { _ in - body().done(on: completionQueue) { - seal.fulfill($0) - }.catch(on: completionQueue) { - seal.reject($0) - } - } - return promise -} diff --git a/SessionUtilitiesKit/PromiseKit/Promise+Retaining.swift b/SessionUtilitiesKit/PromiseKit/Promise+Retaining.swift deleted file mode 100644 index cb7262521..000000000 --- a/SessionUtilitiesKit/PromiseKit/Promise+Retaining.swift +++ /dev/null @@ -1,45 +0,0 @@ -import PromiseKit - -public extension AnyPromise { - - @objc func retainUntilComplete() { - var retainCycle: AnyPromise? = self - _ = self.ensure { - assert(retainCycle != nil) - retainCycle = nil - } - } -} - -public extension PMKFinalizer { - - func retainUntilComplete() { - var retainCycle: PMKFinalizer? = self - self.finally { - assert(retainCycle != nil) - retainCycle = nil - } - } -} - -public extension Promise { - - func retainUntilComplete() { - var retainCycle: Promise? = self - _ = self.ensure { - assert(retainCycle != nil) - retainCycle = nil - } - } -} - -public extension Guarantee { - - func retainUntilComplete() { - var retainCycle: Guarantee? = self - _ = self.done { _ in - assert(retainCycle != nil) - retainCycle = nil - } - } -} diff --git a/SessionUtilitiesKit/PromiseKit/Promise+Retrying.swift b/SessionUtilitiesKit/PromiseKit/Promise+Retrying.swift deleted file mode 100644 index acf707586..000000000 --- a/SessionUtilitiesKit/PromiseKit/Promise+Retrying.swift +++ /dev/null @@ -1,14 +0,0 @@ -import PromiseKit - -/// Retry the promise constructed in `body` up to `maxRetryCount` times. -public func attempt(maxRetryCount: UInt, recoveringOn queue: DispatchQueue, body: @escaping () -> Promise) -> Promise { - var retryCount = 0 - func attempt() -> Promise { - return body().recover(on: queue) { error -> Promise in - guard retryCount < maxRetryCount else { throw error } - retryCount += 1 - return attempt() - } - } - return attempt() -} diff --git a/SessionUtilitiesKit/PromiseKit/Promise+Timeout.swift b/SessionUtilitiesKit/PromiseKit/Promise+Timeout.swift deleted file mode 100644 index e4b843df6..000000000 --- a/SessionUtilitiesKit/PromiseKit/Promise+Timeout.swift +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import PromiseKit - -public extension Promise { - - func timeout(seconds: TimeInterval, timeoutError: Error) -> Promise { - return Promise { seal in - after(seconds: seconds).done { - seal.reject(timeoutError) - } - self.done { result in - seal.fulfill(result) - }.catch { err in - seal.reject(err) - } - } - } -} diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalViewController.swift b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalViewController.swift index 9ea069caa..a8c34aec9 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalViewController.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalViewController.swift @@ -6,7 +6,6 @@ import Foundation import AVFoundation import MediaPlayer import CoreServices -import PromiseKit import SessionUIKit import SessionMessagingKit import SignalCoreKit diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentItemCollection.swift b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentItemCollection.swift index 2bdc5377e..aa6485845 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentItemCollection.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentItemCollection.swift @@ -1,7 +1,6 @@ // Copyright (c) 2019 Open Whisper Systems. All rights reserved. import Foundation -import PromiseKit import SessionMessagingKit import SignalCoreKit From 503373899403d2efea66c5e12ea18da91bfdd342 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Mon, 5 Dec 2022 17:39:40 +1100 Subject: [PATCH 013/135] Fixed a few issues caused by the PromiseKit refactor Started cleaning up the TODOs Fixed a couple of merge conflict issues Fixed a bug with the state of attachments which failed to download # Conflicts: # SessionMessagingKit/Database/Models/Attachment.swift --- Session/Closed Groups/NewClosedGroupVC.swift | 2 + .../ConversationVC+Interaction.swift | 9 +- Session/Conversations/ConversationVC.swift | 26 +- Session/Home/New Conversation/NewDMVC.swift | 1 + .../PhotoCaptureViewController.swift | 6 +- Session/Notifications/SyncPushTokensJob.swift | 6 +- Session/Open Groups/JoinOpenGroupVC.swift | 2 +- .../Open Groups/OpenGroupSuggestionGrid.swift | 31 +- Session/Settings/SettingsViewModel.swift | 63 ---- Session/Utilities/BackgroundPoller.swift | 1 - .../Database/Models/Attachment.swift | 272 +++++++++--------- .../Database/Models/Contact.swift | 19 -- .../Jobs/Types/AttachmentUploadJob.swift | 31 +- .../Jobs/Types/MessageSendJob.swift | 4 +- .../Visible Messages/VisibleMessage.swift | 8 +- .../Open Groups/OpenGroupAPI.swift | 7 +- .../Open Groups/OpenGroupManager.swift | 15 +- .../Errors/MessageSenderError.swift | 2 + .../MessageSender+ClosedGroups.swift | 2 +- .../MessageSender+Convenience.swift | 95 +++--- .../Sending & Receiving/MessageSender.swift | 26 ++ .../Pollers/OpenGroupPoller.swift | 10 +- .../Utilities/ProfileManager.swift | 2 +- SessionShareExtension/ThreadPickerVC.swift | 4 +- SessionSnodeKit/Networking/SnodeAPI.swift | 3 - .../OnionRequestAPI+Encryption.swift | 2 - SessionUtilitiesKit/Networking/HTTP.swift | 2 +- 27 files changed, 268 insertions(+), 383 deletions(-) diff --git a/Session/Closed Groups/NewClosedGroupVC.swift b/Session/Closed Groups/NewClosedGroupVC.swift index 32aa77af9..eee544e92 100644 --- a/Session/Closed Groups/NewClosedGroupVC.swift +++ b/Session/Closed Groups/NewClosedGroupVC.swift @@ -319,6 +319,8 @@ final class NewClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegate .writePublisherFlatMap { db in MessageSender.createClosedGroup(db, name: name, members: selectedContacts) } + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) + .receive(on: DispatchQueue.main) .sinkUntilComplete( receiveCompletion: { result in switch result { diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 130616234..91d739cf8 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -348,10 +348,7 @@ extension ConversationVC: .attachmentPublisher .sinkUntilComplete( receiveValue: { [weak self] attachment in - guard - !modalActivityIndicator.wasCancelled, - let attachment = attachment as? SignalAttachment - else { return } + guard !modalActivityIndicator.wasCancelled else { return } modalActivityIndicator.dismiss { guard !attachment.hasError else { @@ -1680,12 +1677,10 @@ extension ConversationVC: // Remote deletion logic func deleteRemotely(from viewController: UIViewController?, request: AnyPublisher, onComplete: (() -> ())?) { - // TODO: Test that this works // Show a loading indicator Future { resolver in ModalActivityIndicatorViewController.present(fromViewController: viewController, canCancel: false) { _ in - // TODO: Remove the 'Swift.' - resolver(Swift.Result.success(())) + resolver(Result.success(())) } } .flatMap { _ in request } diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index e906db92d..87376ffa3 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -291,7 +291,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl result.textAlignment = .center result.numberOfLines = 0 result.isHidden = ( - !self.messageRequestView.isHidden || + !self.messageRequestStackView.isHidden || self.viewModel.threadData.threadRequiresApproval == false ) @@ -350,7 +350,8 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl // Message requests view & scroll to bottom view.addSubview(scrollButton) - view.addSubview(messageRequestView) + view.addSubview(messageRequestBackgroundView) + view.addSubview(messageRequestStackView) view.addSubview(pendingMessageRequestExplanationLabel) messageRequestView.addSubview(messageRequestBlockButton) @@ -364,7 +365,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl self.messageRequestsViewBotomConstraint = messageRequestView.pin(.bottom, to: .bottom, of: view, withInset: -16) self.scrollButtonBottomConstraint = scrollButton.pin(.bottom, to: .bottom, of: view, withInset: -16) self.scrollButtonBottomConstraint?.isActive = false // Note: Need to disable this to avoid a conflict with the other bottom constraint - self.scrollButtonMessageRequestsBottomConstraint = scrollButton.pin(.bottom, to: .top, of: messageRequestView, withInset: -16) + self.scrollButtonMessageRequestsBottomConstraint = scrollButton.pin(.bottom, to: .top, of: messageRequestStackView) self.scrollButtonPendingMessageRequestInfoBottomConstraint = scrollButton.pin(.bottom, to: .top, of: pendingMessageRequestExplanationLabel, withInset: -16) messageRequestBlockButton.pin(.top, to: .top, of: messageRequestView, withInset: 10) @@ -383,10 +384,14 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl messageRequestDeleteButton.pin(.right, to: .right, of: messageRequestView, withInset: -20) messageRequestDeleteButton.pin(.bottom, to: .bottom, of: messageRequestView) messageRequestDeleteButton.set(.width, to: .width, of: messageRequestAcceptButton) + messageRequestBackgroundView.pin(.top, to: .top, of: messageRequestStackView) + messageRequestBackgroundView.pin(.leading, to: .leading, of: view) + messageRequestBackgroundView.pin(.trailing, to: .trailing, of: view) + messageRequestBackgroundView.pin(.bottom, to: .bottom, of: view) - pendingMessageRequestExplanationLabel.pin(.left, to: .left, of: messageRequestView, withInset: 40) - pendingMessageRequestExplanationLabel.pin(.right, to: .right, of: messageRequestView, withInset: -40) - pendingMessageRequestExplanationLabel.pin(.bottom, to: .bottom, of: messageRequestView, withInset: -16) + pendingMessageRequestExplanationLabel.pin(.left, to: .left, of: messageRequestStackView, withInset: 40) + pendingMessageRequestExplanationLabel.pin(.right, to: .right, of: messageRequestStackView, withInset: -40) + pendingMessageRequestExplanationLabel.pin(.bottom, to: .bottom, of: messageRequestStackView, withInset: -16) // Unread count view view.addSubview(unreadCountView) @@ -621,7 +626,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl updateNavBarButtons(threadData: updatedThreadData, initialVariant: viewModel.initialThreadVariant) let messageRequestsViewWasVisible: Bool = ( - messageRequestView.isHidden == false + messageRequestStackView.isHidden == false ) let pendingMessageRequestInfoWasVisible: Bool = ( pendingMessageRequestExplanationLabel.isHidden == false @@ -632,13 +637,14 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl updatedThreadData.threadIsMessageRequest == false || updatedThreadData.threadRequiresApproval == true ) + self?.messageRequestBackgroundView.isHidden = (self?.messageRequestStackView.isHidden == true) self?.pendingMessageRequestExplanationLabel.isHidden = ( - self?.messageRequestView.isHidden == false || + self?.messageRequestStackView.isHidden == false || updatedThreadData.threadRequiresApproval == false ) self?.scrollButtonMessageRequestsBottomConstraint?.isActive = ( - self?.messageRequestView.isHidden == false + self?.messageRequestStackView.isHidden == false ) self?.scrollButtonPendingMessageRequestInfoBottomConstraint?.isActive = ( self?.scrollButtonPendingMessageRequestInfoBottomConstraint?.isActive == false && @@ -1162,7 +1168,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl } let keyboardTop = (UIScreen.main.bounds.height - keyboardRect.minY) - let messageRequestsOffset: CGFloat = (messageRequestView.isHidden ? 0 : messageRequestView.bounds.height + 16) + let messageRequestsOffset: CGFloat = (messageRequestStackView.isHidden ? 0 : messageRequestStackView.bounds.height + 16) let pendingMessageRequestsOffset: CGFloat = (pendingMessageRequestExplanationLabel.isHidden ? 0 : (pendingMessageRequestExplanationLabel.bounds.height + (16 * 2))) let oldContentInset: UIEdgeInsets = tableView.contentInset let newContentInset: UIEdgeInsets = UIEdgeInsets( diff --git a/Session/Home/New Conversation/NewDMVC.swift b/Session/Home/New Conversation/NewDMVC.swift index 410b4fa92..b7497acb9 100644 --- a/Session/Home/New Conversation/NewDMVC.swift +++ b/Session/Home/New Conversation/NewDMVC.swift @@ -184,6 +184,7 @@ final class NewDMVC: BaseVC, UIPageViewControllerDataSource, UIPageViewControlle .present(fromViewController: navigationController!, canCancel: false) { [weak self] modalActivityIndicator in SnodeAPI .getSessionID(for: onsNameOrPublicKey) + .receive(on: DispatchQueue.main) .sinkUntilComplete( receiveCompletion: { result in switch result { diff --git a/Session/Media Viewing & Editing/PhotoCaptureViewController.swift b/Session/Media Viewing & Editing/PhotoCaptureViewController.swift index 66e696112..19fd7b31b 100644 --- a/Session/Media Viewing & Editing/PhotoCaptureViewController.swift +++ b/Session/Media Viewing & Editing/PhotoCaptureViewController.swift @@ -195,7 +195,7 @@ class PhotoCaptureViewController: OWSViewController { } photoCapture.switchCamera() - .receiveOnMain() + .receive(on: DispatchQueue.main) .sinkUntilComplete( receiveCompletion: { [weak self] result in switch result { @@ -210,7 +210,7 @@ class PhotoCaptureViewController: OWSViewController { func didTapFlashMode() { Logger.debug("") photoCapture.switchFlashMode() - .receiveOnMain() + .receive(on: DispatchQueue.main) .sinkUntilComplete( receiveCompletion: { [weak self] _ in self?.updateFlashModeControl() @@ -306,7 +306,7 @@ class PhotoCaptureViewController: OWSViewController { previewView = CapturePreviewView(session: photoCapture.session) photoCapture.startCapture() - .receiveOnMain() + .receive(on: DispatchQueue.main) .sinkUntilComplete( receiveCompletion: { [weak self] result in switch result { diff --git a/Session/Notifications/SyncPushTokensJob.swift b/Session/Notifications/SyncPushTokensJob.swift index c2fe63429..19fb41e43 100644 --- a/Session/Notifications/SyncPushTokensJob.swift +++ b/Session/Notifications/SyncPushTokensJob.swift @@ -78,10 +78,8 @@ public enum SyncPushTokensJob: JobExecutor { pushToken: pushToken, voipToken: voipToken, isForcedUpdate: shouldUploadTokens, - // TODO: Remove the 'Swift.' - success: { resolver(Swift.Result.success(())) }, - // TODO: Remove the 'Swift.' - failure: { resolver(Swift.Result.failure($0)) } + success: { resolver(Result.success(())) }, + failure: { resolver(Result.failure($0)) } ) } .handleEvents( diff --git a/Session/Open Groups/JoinOpenGroupVC.swift b/Session/Open Groups/JoinOpenGroupVC.swift index 8e2ff7134..60923ce50 100644 --- a/Session/Open Groups/JoinOpenGroupVC.swift +++ b/Session/Open Groups/JoinOpenGroupVC.swift @@ -168,7 +168,7 @@ final class JoinOpenGroupVC: BaseVC, UIPageViewControllerDataSource, UIPageViewC ModalActivityIndicatorViewController.present(fromViewController: navigationController, canCancel: false) { [weak self] _ in Storage.shared - .writePublisher { db in + .writePublisherFlatMap { db in OpenGroupManager.shared.add( db, roomToken: roomToken, diff --git a/Session/Open Groups/OpenGroupSuggestionGrid.swift b/Session/Open Groups/OpenGroupSuggestionGrid.swift index e62f8ec18..eb12b2e66 100644 --- a/Session/Open Groups/OpenGroupSuggestionGrid.swift +++ b/Session/Open Groups/OpenGroupSuggestionGrid.swift @@ -143,6 +143,7 @@ final class OpenGroupSuggestionGrid: UIView, UICollectionViewDataSource, UIColle widthAnchor.constraint(greaterThanOrEqualToConstant: OpenGroupSuggestionGrid.cellHeight).isActive = true OpenGroupManager.getDefaultRoomsIfNeeded() + .receive(on: DispatchQueue.main) .sinkUntilComplete( receiveCompletion: { [weak self] _ in self?.update() }, receiveValue: { [weak self] rooms in self?.rooms = rooms } @@ -316,7 +317,7 @@ extension OpenGroupSuggestionGrid { return } - imageView.image = nil // TODO: Test this + imageView.image = nil Publishers .MergeMany( @@ -331,15 +332,19 @@ extension OpenGroupSuggestionGrid { // we can ignore this 'Just' call which is used to hide the image while loading Just((Data(), false)) .setFailureType(to: Error.self) -// .delay(for: .milliseconds(10), scheduler: DispatchQueue.main) + .delay(for: .milliseconds(10), scheduler: DispatchQueue.main) .eraseToAnyPublisher() ) .receiveOnMain(immediately: true) .sinkUntilComplete( receiveValue: { [weak self] imageData, hasData in - // TODO: Test this behaviour guard hasData else { - self?.imageView.isHidden = true + // This will emit twice (once with the data and once without it), if we + // have actually received the images then we don't want the second emission + // to hide the imageView anymore + if self?.imageView.image == nil { + self?.imageView.isHidden = true + } return } @@ -347,24 +352,6 @@ extension OpenGroupSuggestionGrid { self?.imageView.isHidden = (self?.imageView.image == nil) } ) - -// OpenGroupManager.roomImage(db, fileId: imageId, for: room.token, on: OpenGroupAPI.defaultServer) -// .values -// -// if let imageData: Data = promise.value { -// imageView.image = UIImage(data: imageData) -// imageView.isHidden = (imageView.image == nil) -// } -// else { -// imageView.isHidden = true -// -// _ = promise.done { [weak self] imageData in -// DispatchQueue.main.async { -// self?.imageView.image = UIImage(data: imageData) -// self?.imageView.isHidden = (self?.imageView.image == nil) -// } -// } -// } } } } diff --git a/Session/Settings/SettingsViewModel.swift b/Session/Settings/SettingsViewModel.swift index a1277a29c..44003c2d3 100644 --- a/Session/Settings/SettingsViewModel.swift +++ b/Session/Settings/SettingsViewModel.swift @@ -575,66 +575,3 @@ class SettingsViewModel: SessionTableViewModel Void) { - // TODO: Test this works Publishers .MergeMany( [pollForMessages()] diff --git a/SessionMessagingKit/Database/Models/Attachment.swift b/SessionMessagingKit/Database/Models/Attachment.swift index 84d9cf806..1129c7b26 100644 --- a/SessionMessagingKit/Database/Models/Attachment.swift +++ b/SessionMessagingKit/Database/Models/Attachment.swift @@ -972,6 +972,18 @@ extension Attachment { // MARK: - Upload extension Attachment { + public enum Destination { + case fileServer + case openGroup(OpenGroup) + + var shouldEncrypt: Bool { + switch self { + case .fileServer: return true + case .openGroup: return false + } + } + } + public static func prepare(_ db: Database, attachments: [SignalAttachment], for interactionId: Int64) throws { // Prepare any attachments try attachments.enumerated() @@ -979,7 +991,7 @@ extension Attachment { let maybeAttachment: Attachment? = Attachment( variant: (signalAttachment.isVoiceMessage ? .voiceMessage : - .standard + .standard ), contentType: signalAttachment.mimeType, dataSource: signalAttachment.dataSource, @@ -1001,176 +1013,160 @@ extension Attachment { } internal func upload( - _ db: Database? = nil, - queue: DispatchQueue, - using upload: @escaping (Database, Data) -> AnyPublisher, - encrypt: Bool, - success: ((String?) -> Void)?, - failure: ((Error) -> Void)? - ) { + to destination: Attachment.Destination, + queue: DispatchQueue + ) -> AnyPublisher { // This can occur if an AttachmnetUploadJob was explicitly created for a message // dependant on the attachment being uploaded (in this case the attachment has // already been uploaded so just succeed) guard state != .uploaded else { - success?(Attachment.fileId(for: self.downloadUrl)) - return + return Just(Attachment.fileId(for: self.downloadUrl)) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } // Get the attachment guard var data = try? readDataFromFile() else { SNLog("Couldn't read attachment from disk.") - failure?(AttachmentError.noAttachment) - return + return Fail(error: AttachmentError.noAttachment) + .eraseToAnyPublisher() } let attachmentId: String = self.id - // If the attachment is a downloaded attachment, check if it came from the server - // and if so just succeed immediately (no use re-uploading an attachment that is - // already present on the server) - or if we want it to be encrypted and it's not - // then encrypt it - // - // Note: The most common cases for this will be for LinkPreviews or Quotes - guard - state != .downloaded || - serverId == nil || - downloadUrl == nil || - !encrypt || - encryptionKey == nil || - digest == nil - else { - // Save the final upload info - let uploadedAttachment: Attachment? = { - guard let db: Database = db else { - Storage.shared.write { db in - try? Attachment - .filter(id: attachmentId) - .updateAll(db, Attachment.Columns.state.set(to: Attachment.State.uploaded)) + return Storage.shared + .writePublisherFlatMap { db -> AnyPublisher<(String?, Data?, Data?), Error> in + // If the attachment is a downloaded attachment, check if it came from + // the server and if so just succeed immediately (no use re-uploading + // an attachment that is already present on the server) - or if we want + // it to be encrypted and it's not then encrypt it + // + // Note: The most common cases for this will be for LinkPreviews or Quotes + guard + state != .downloaded || + serverId == nil || + downloadUrl == nil || + !destination.shouldEncrypt || + encryptionKey == nil || + digest == nil + else { + // Save the final upload info + _ = try? Attachment + .filter(id: attachmentId) + .updateAll(db, Attachment.Columns.state.set(to: Attachment.State.uploaded)) + + return Just((Attachment.fileId(for: self.downloadUrl), nil, nil)) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + + var encryptionKey: NSData = NSData() + var digest: NSData = NSData() + + // Encrypt the attachment if needed + if destination.shouldEncrypt { + guard let ciphertext = Cryptography.encryptAttachmentData(data, shouldPad: true, outKey: &encryptionKey, outDigest: &digest) else { + SNLog("Couldn't encrypt attachment.") + return Fail(error: AttachmentError.encryptionFailed) + .eraseToAnyPublisher() } - return self.with(state: .uploaded) + data = ciphertext } + // Check the file size + SNLog("File size: \(data.count) bytes.") + if Double(data.count) > Double(FileServerAPI.maxFileSize) / FileServerAPI.fileSizeORMultiplier { + return Fail(error: HTTPError.maxFileSizeExceeded) + .eraseToAnyPublisher() + } + + // Update the attachment to the 'uploading' state _ = try? Attachment .filter(id: attachmentId) - .updateAll(db, Attachment.Columns.state.set(to: Attachment.State.uploaded)) + .updateAll(db, Attachment.Columns.state.set(to: Attachment.State.uploading)) - return self.with(state: .uploaded) - }() - - guard uploadedAttachment != nil else { - SNLog("Couldn't update attachmentUpload job.") - failure?(StorageError.failedToSave) - return - } - - success?(Attachment.fileId(for: self.downloadUrl)) - return - } - - var processedAttachment: Attachment = self - - // Encrypt the attachment if needed - if encrypt { - var encryptionKey: NSData = NSData() - var digest: NSData = NSData() - - guard let ciphertext = Cryptography.encryptAttachmentData(data, shouldPad: true, outKey: &encryptionKey, outDigest: &digest) else { - SNLog("Couldn't encrypt attachment.") - failure?(AttachmentError.encryptionFailed) - return - } - - processedAttachment = processedAttachment.with( - encryptionKey: encryptionKey as Data, - digest: digest as Data - ) - data = ciphertext - } - - // Check the file size - SNLog("File size: \(data.count) bytes.") - if Double(data.count) > Double(FileServerAPI.maxFileSize) / FileServerAPI.fileSizeORMultiplier { - failure?(HTTP.Error.maxFileSizeExceeded) - return - } - - // Update the attachment to the 'uploading' state - let updatedAttachment: Attachment? = { - guard let db: Database = db else { - Storage.shared.write { db in - try? Attachment - .filter(id: attachmentId) - .updateAll(db, Attachment.Columns.state.set(to: Attachment.State.uploading)) + switch destination { + case .openGroup(let openGroup): + return OpenGroupAPI + .uploadFile( + db, + bytes: data.bytes, + to: openGroup.roomToken, + on: openGroup.server + ) + .map { _, response -> (String, Data?, Data?) in + ( + response.id, + (destination.shouldEncrypt ? encryptionKey as Data : nil), + (destination.shouldEncrypt ? digest as Data : nil) + ) + } + .eraseToAnyPublisher() + + case .fileServer: + /// **Note:** FileServer uploads don't need database access so + return Just(( + nil, + (destination.shouldEncrypt ? encryptionKey as Data : nil), + (destination.shouldEncrypt ? digest as Data : nil) + )) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } - - return processedAttachment.with(state: .uploading) } - - _ = try? Attachment - .filter(id: attachmentId) - .updateAll(db, Attachment.Columns.state.set(to: Attachment.State.uploading)) - - return processedAttachment.with(state: .uploading) - }() - - guard updatedAttachment != nil else { - SNLog("Couldn't update attachmentUpload job.") - failure?(StorageError.failedToSave) - return - } - - // Perform the upload - let uploadPublisher: AnyPublisher = { - guard let db: Database = db else { - return Storage.shared.readPublisherFlatMap { db in upload(db, data) } + .flatMap { maybeFileId, encryptionKey, digest -> AnyPublisher<(String?, Data?, Data?), Error> in + switch destination { + case .openGroup: + /// **Note:** OpenGroup uploads need database access so this should + /// have already been uploaded + return Just((maybeFileId, encryptionKey, digest)) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + + case .fileServer: + return FileServerAPI.upload(data) + .map { response -> (String, Data?, Data?) in (response.id, encryptionKey, digest) } + .eraseToAnyPublisher() + } } - - return upload(db, data) - }() - - uploadPublisher - .sinkUntilComplete( + .flatMap { fileId, encryptionKey, digest -> AnyPublisher in + /// Save the final upload info + /// + /// **Note:** We **MUST** use the `.with` function here to ensure the `isValid` flag is + /// updated correctly + Storage.shared + .writePublisher { db in + try self + .with( + serverId: fileId, + state: .uploaded, + creationTimestamp: ( + self.creationTimestamp ?? + Date().timeIntervalSince1970 + ), + downloadUrl: fileId.map { "\(FileServerAPI.server)/file/\($0)" }, + encryptionKey: encryptionKey, + digest: digest + ) + .saved(db) + } + .map { _ in fileId } + .eraseToAnyPublisher() + } + .handleEvents( receiveCompletion: { result in switch result { case .finished: break - case .failure(let error): + case .failure: Storage.shared.write { db in try Attachment .filter(id: attachmentId) .updateAll(db, Attachment.Columns.state.set(to: Attachment.State.failedUpload)) } - - failure?(error) } - }, - receiveValue: { fileId in - /// Save the final upload info - /// - /// **Note:** We **MUST** use the `.with` function here to ensure the `isValid` flag is - /// updated correctly - let uploadedAttachment: Attachment? = Storage.shared.write { db in - try updatedAttachment? - .with( - serverId: "\(fileId)", - state: .uploaded, - creationTimestamp: ( - updatedAttachment?.creationTimestamp ?? - Date().timeIntervalSince1970 - ), - downloadUrl: "\(FileServerAPI.server)/files/\(fileId)" - ) - .saved(db) - } - - guard uploadedAttachment != nil else { - SNLog("Couldn't update attachmentUpload job.") - failure?(StorageError.failedToSave) - return - } - - success?(fileId) } ) + .eraseToAnyPublisher() } } diff --git a/SessionMessagingKit/Database/Models/Contact.swift b/SessionMessagingKit/Database/Models/Contact.swift index ab85bb808..524fd7300 100644 --- a/SessionMessagingKit/Database/Models/Contact.swift +++ b/SessionMessagingKit/Database/Models/Contact.swift @@ -100,22 +100,3 @@ public extension Contact { return ((try? fetchOne(db, id: id)) ?? Contact(id: id)) } } - -// MARK: - Objective-C Support - -// TODO: Remove this when possible -@objc(SMKContact) -public class SMKContact: NSObject { - @objc(isBlockedFor:) - public static func isBlocked(id: String) -> Bool { - return Storage.shared - .read { db in - try Contact - .filter(id: id) - .select(.isBlocked) - .asRequest(of: Bool.self) - .fetchOne(db) - } - .defaulting(to: false) - } -} diff --git a/SessionMessagingKit/Jobs/Types/AttachmentUploadJob.swift b/SessionMessagingKit/Jobs/Types/AttachmentUploadJob.swift index 4abb5ca6a..9b1a8cfec 100644 --- a/SessionMessagingKit/Jobs/Types/AttachmentUploadJob.swift +++ b/SessionMessagingKit/Jobs/Types/AttachmentUploadJob.swift @@ -52,29 +52,16 @@ public enum AttachmentUploadJob: JobExecutor { // reentrancy issues when the success/failure closures get called before the upload as the JobRunner // will attempt to update the state of the job immediately attachment.upload( - queue: queue, - using: { db, data in - SNLog("[AttachmentUpload] Started for message \(interactionId) (\(attachment.byteCount) bytes)") - - if let openGroup: OpenGroup = openGroup { - return OpenGroupAPI - .uploadFile( - db, - bytes: data.bytes, - to: openGroup.roomToken, - on: openGroup.server - ) - .map { _, response -> String in response.id } - .eraseToAnyPublisher() + to: (openGroup.map { .openGroup($0) } ?? .fileServer), + queue: queue + ) + .sinkUntilComplete( + receiveCompletion: { result in + switch result { + case .failure(let error): failure(job, error, false) + case .finished: success(job, false) } - - return FileServerAPI.upload(data) - .map { response -> String in response.id } - .eraseToAnyPublisher() - }, - encrypt: (openGroup == nil), - success: { _ in success(job, false) }, - failure: { error in failure(job, error, false) } + } ) } } diff --git a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift index d39e438f8..27c234908 100644 --- a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift +++ b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift @@ -169,12 +169,12 @@ public enum MessageSendJob: JobExecutor { try MessageSender.preparedSendData( db, message: details.message, - to: details.destination - .with(fileIds: messageFileIds), + to: details.destination, interactionId: job.interactionId ) } .subscribe(on: queue) + .map { sendData in sendData.with(fileIds: messageFileIds) } .flatMap { MessageSender.sendImmediate(preparedSendData: $0) } .sinkUntilComplete( receiveCompletion: { result in diff --git a/SessionMessagingKit/Messages/Visible Messages/VisibleMessage.swift b/SessionMessagingKit/Messages/Visible Messages/VisibleMessage.swift index 980d90b70..e48da7f33 100644 --- a/SessionMessagingKit/Messages/Visible Messages/VisibleMessage.swift +++ b/SessionMessagingKit/Messages/Visible Messages/VisibleMessage.swift @@ -158,13 +158,7 @@ public final class VisibleMessage: Message { // Attachments - let attachments: [Attachment]? = try? Attachment.fetchAll(db, ids: self.attachmentIds) - - if !(attachments ?? []).allSatisfy({ $0.state == .uploaded }) { - #if DEBUG - preconditionFailure("Sending a message before all associated attachments have been uploaded.") - #endif - } + let attachments: [Attachment]? = try? Attachment.fetchAll(db, ids: self.attachmentIds) let attachmentProtos = (attachments ?? []).compactMap { $0.buildProto() } dataMessage.setAttachments(attachmentProtos) diff --git a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift index d08ff23bf..1261d47b3 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift @@ -1434,9 +1434,12 @@ public enum OpenGroupAPI { .eraseToAnyPublisher() } - return dependencies.onionApi - .sendOnionRequest(signedRequest, to: request.server, with: publicKey) + // We was to avoid blocking the db write thread so we dispatch the API call to a different thread + return Just(()) + .setFailureType(to: Error.self) .subscribe(on: OpenGroupAPI.workQueue) + .receive(on: OpenGroupAPI.workQueue) + .flatMap { dependencies.onionApi.sendOnionRequest(signedRequest, to: request.server, with: publicKey) } .eraseToAnyPublisher() } } diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index abbfbdde3..88f0aa8ad 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -246,11 +246,13 @@ public final class OpenGroupManager: NSObject { OpenGroup.Columns.sequenceNumber.set(to: 0) ) + // We was to avoid blocking the db write thread so we dispatch the API call to a different thread + // // Note: We don't do this after the db commit as it can fail (resulting in endless loading) - return Future { resolver in - OpenGroupAPI.workQueue.async { resolver(Result.success(())) } - } + return Just(()) + .setFailureType(to: Error.self) .subscribe(on: OpenGroupAPI.workQueue) + .receive(on: OpenGroupAPI.workQueue) .flatMap { _ in dependencies.storage .readPublisherFlatMap { db in @@ -286,8 +288,7 @@ public final class OpenGroupManager: NSObject { on: targetServer, dependencies: dependencies ) { - // TODO: Remove the 'Swift.' - resolver(Swift.Result.success(())) + resolver(Result.success(())) } } } @@ -943,10 +944,6 @@ public final class OpenGroupManager: NSObject { @discardableResult public static func getDefaultRoomsIfNeeded( using dependencies: OGMDependencies = OGMDependencies() ) -> AnyPublisher<[OpenGroupAPI.Room], Error> { - return Just([]) // TODO: Remove this - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - // Note: If we already have a 'defaultRoomsPromise' then there is no need to get it again if let existingPublisher: AnyPublisher<[OpenGroupAPI.Room], Error> = dependencies.cache.defaultRoomsPublisher { return existingPublisher diff --git a/SessionMessagingKit/Sending & Receiving/Errors/MessageSenderError.swift b/SessionMessagingKit/Sending & Receiving/Errors/MessageSenderError.swift index fb7a304d6..0ee8cd684 100644 --- a/SessionMessagingKit/Sending & Receiving/Errors/MessageSenderError.swift +++ b/SessionMessagingKit/Sending & Receiving/Errors/MessageSenderError.swift @@ -10,6 +10,7 @@ public enum MessageSenderError: LocalizedError { case signingFailed case encryptionFailed case noUsername + case attachmentsNotUploaded // Closed groups case noThread @@ -34,6 +35,7 @@ public enum MessageSenderError: LocalizedError { case .signingFailed: return "Couldn't sign message." case .encryptionFailed: return "Couldn't encrypt message." case .noUsername: return "Missing username." + case .attachmentsNotUploaded: return "Attachments for this message have not been uploaded." // Closed groups case .noThread: return "Couldn't find a thread associated with the given group public key." diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift index fc2748805..f051c4750 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift @@ -99,7 +99,7 @@ extension MessageSender { // the 'ClosedGroup' object we created sentTimestampMs: UInt64(floor(formationTimestamp * 1000)) ), - to: try Message.Destination.from(db, thread: thread), + to: .contact(publicKey: memberId), interactionId: nil ) } diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift b/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift index f387ffbc5..1b934ead5 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift @@ -93,66 +93,57 @@ extension MessageSender { case .openGroupInbox(_, _, let blindedPublicKey): return blindedPublicKey } }() - let fileIdPublisher: AnyPublisher<[String?], Error> = Storage.shared - .write { db -> AnyPublisher<[String?], Error>? in + + return Storage.shared + .readPublisherFlatMap { db -> AnyPublisher<(attachments: [Attachment], openGroup: OpenGroup?), Error> in let attachmentStateInfo: [Attachment.StateInfo] = (try? Attachment .stateInfo(interactionId: interactionId, state: .uploading) .fetchAll(db)) .defaulting(to: []) // If there is no attachment data then just return early - guard !attachmentStateInfo.isEmpty else { return nil } - // TODO: Just run an AttachmentUploadJob directly??? - // Otherwise we need to generate the upload requests - let openGroup: OpenGroup? = try? OpenGroup.fetchOne(db, id: threadId) + guard !attachmentStateInfo.isEmpty else { + return Just(([], nil)) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + + // Otherwise fetch the open group (if there is one) + return Just(( + (try? Attachment + .filter(ids: attachmentStateInfo.map { $0.attachmentId }) + .fetchAll(db)) + .defaulting(to: []), + try? OpenGroup.fetchOne(db, id: threadId) + )) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + .flatMap { attachments, openGroup -> AnyPublisher<[String?], Error> in + guard !attachments.isEmpty else { + return Just<[String?]>([]) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } return Publishers .MergeMany( - (try? Attachment - .filter(ids: attachmentStateInfo.map { $0.attachmentId }) - .fetchAll(db)) - .defaulting(to: []) + attachments .map { attachment -> AnyPublisher in - Future { resolver in - attachment.upload( - db, - queue: DispatchQueue.global(qos: .userInitiated), - using: { db, data in - if let openGroup: OpenGroup = openGroup { - return OpenGroupAPI - .uploadFile( - db, - bytes: data.bytes, - to: openGroup.roomToken, - on: openGroup.server - ) - .map { _, response -> String in response.id } - .eraseToAnyPublisher() - } - - return FileServerAPI.upload(data) - .map { response -> String in response.id } - .eraseToAnyPublisher() - }, - encrypt: (openGroup == nil), - success: { fileId in resolver(Swift.Result.success(fileId)) }, - failure: { resolver(Swift.Result.failure($0)) } + attachment + .upload( + to: ( + openGroup.map { Attachment.Destination.openGroup($0) } ?? + .fileServer + ), + queue: DispatchQueue.global(qos: .userInitiated) ) - } - .eraseToAnyPublisher() } ) .collect() .eraseToAnyPublisher() } - .defaulting( - to: Just<[String?]>([]) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - ) - - return fileIdPublisher - .map { results in + .map { results -> PreparedSendData in // Once the attachments are processed then update the PreparedSendData with // the fileIds associated to the message let fileIds: [String] = results.compactMap { result -> String? in result } @@ -244,15 +235,11 @@ extension MessageSender { .catch { _ in seal.reject(StorageError.generic) } .retainUntilComplete() - // TODO: Test this (does it break anything? want to stop the db write asap) - /// We don't want to block the db write thread so we trigger the actual message sending after the query has - /// finished - return Future { resolver in - db.afterNextTransaction { _ in - resolver(Result.success(())) - } - } - .flatMap { _ in MessageSender.sendImmediate(preparedSendData: sendData) } - .eraseToAnyPublisher() + /// We want to avoid blocking the db write thread so we dispatch the API call to a different thread + return Just(()) + .setFailureType(to: Error.self) + .receive(on: DispatchQueue.global(qos: .userInitiated)) + .flatMap { _ in MessageSender.sendImmediate(preparedSendData: sendData) } + .eraseToAnyPublisher() } } diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index 5dbc6213b..5b8eccd82 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -17,6 +17,7 @@ public final class MessageSender { let destination: Message.Destination? let interactionId: Int64? let isSyncMessage: Bool? + let totalAttachmentsUploaded: Int let snodeMessage: SnodeMessage? let plaintext: Data? @@ -32,6 +33,7 @@ public final class MessageSender { destination: Message.Destination?, interactionId: Int64?, isSyncMessage: Bool?, + totalAttachmentsUploaded: Int = 0, snodeMessage: SnodeMessage?, plaintext: Data?, ciphertext: Data?, @@ -44,6 +46,7 @@ public final class MessageSender { self.destination = destination self.interactionId = interactionId self.isSyncMessage = isSyncMessage + self.totalAttachmentsUploaded = totalAttachmentsUploaded self.snodeMessage = snodeMessage self.plaintext = plaintext @@ -60,6 +63,7 @@ public final class MessageSender { self.destination = nil self.interactionId = nil self.isSyncMessage = nil + self.totalAttachmentsUploaded = 0 self.snodeMessage = nil self.plaintext = nil @@ -84,6 +88,7 @@ public final class MessageSender { self.destination = destination self.interactionId = interactionId self.isSyncMessage = isSyncMessage + self.totalAttachmentsUploaded = 0 self.snodeMessage = snodeMessage self.plaintext = nil @@ -105,6 +110,7 @@ public final class MessageSender { self.destination = destination self.interactionId = interactionId self.isSyncMessage = false + self.totalAttachmentsUploaded = 0 self.snodeMessage = nil self.plaintext = plaintext @@ -126,6 +132,7 @@ public final class MessageSender { self.destination = destination self.interactionId = interactionId self.isSyncMessage = false + self.totalAttachmentsUploaded = 0 self.snodeMessage = nil self.plaintext = nil @@ -143,6 +150,7 @@ public final class MessageSender { destination: destination?.with(fileIds: fileIds), interactionId: interactionId, isSyncMessage: isSyncMessage, + totalAttachmentsUploaded: fileIds.count, snodeMessage: snodeMessage, plaintext: plaintext, ciphertext: ciphertext, @@ -607,6 +615,24 @@ public final class MessageSender { .eraseToAnyPublisher() } + // We now allow the creation of message data without validating it's attachments have finished + // uploading first, this is here to ensure we don't send a message which should have uploaded + // files + // + // If you see this error then you need to call `MessageSender.performUploadsIfNeeded(preparedSendData:)` + // before calling this function + switch preparedSendData.message { + case let visibleMessage as VisibleMessage: + guard visibleMessage.attachmentIds.count == preparedSendData.totalAttachmentsUploaded else { + return Fail(error: MessageSenderError.attachmentsNotUploaded) + .eraseToAnyPublisher() + } + + break + + default: break + } + switch preparedSendData.destination { case .contact, .closedGroup: return sendToSnodeDestination(data: preparedSendData, using: dependencies) case .openGroup: return sendToOpenGroupDestination(data: preparedSendData, using: dependencies) diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift index bc779d665..65827d871 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift @@ -28,7 +28,6 @@ extension OpenGroupAPI { } public func startIfNeeded(using dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies()) { - return// TODO: Remove this (reentrancy issues - looks like it could be resolved by splitting out the OpenGroupAPI request signing into it's own step) guard !hasStarted else { return } hasStarted = true @@ -113,14 +112,7 @@ extension OpenGroupAPI { .map { response in (failureCount, response) } .eraseToAnyPublisher() } - .subscribe( - // If this was run via the background poller then don't run on the pollerQueue - // TODO: Need to test if this dispatches to the next run loop or blocks (want it to block) - on: (calledFromBackgroundPoller ? - DispatchQueue.main : - Threading.pollerQueue - ) - ) + .subscribe(on: Threading.pollerQueue) .handleEvents( receiveOutput: { [weak self] failureCount, response in guard !calledFromBackgroundPoller || isBackgroundPollerValid() else { diff --git a/SessionMessagingKit/Utilities/ProfileManager.swift b/SessionMessagingKit/Utilities/ProfileManager.swift index f44ebc548..39aa0f270 100644 --- a/SessionMessagingKit/Utilities/ProfileManager.swift +++ b/SessionMessagingKit/Utilities/ProfileManager.swift @@ -472,7 +472,7 @@ public struct ProfileManager { } }, receiveValue: { fileUploadResponse in - let downloadUrl: String = "\(FileServerAPI.server)/files/\(fileUploadResponse.id)" + let downloadUrl: String = "\(FileServerAPI.server)/file/\(fileUploadResponse.id)" // Update the cached avatar image value profileAvatarCache.mutate { $0[fileName] = data } diff --git a/SessionShareExtension/ThreadPickerVC.swift b/SessionShareExtension/ThreadPickerVC.swift index 8981335ef..c5b298362 100644 --- a/SessionShareExtension/ThreadPickerVC.swift +++ b/SessionShareExtension/ThreadPickerVC.swift @@ -155,7 +155,6 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView receiveValue: { [weak self] attachments in guard let strongSelf = self else { return } - // TODO: Test this let approvalVC: UINavigationController = AttachmentApprovalViewController.wrappedInNavController( threadId: strongSelf.viewModel.viewData[indexPath.row].threadId, attachments: attachments, @@ -190,7 +189,7 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView NotificationCenter.default.post(name: Database.resumeNotification, object: self) Storage.shared - .writePublisher { [weak self] db -> MessageSender.PreparedSendData in + .writePublisher { db -> MessageSender.PreparedSendData in guard let thread: SessionThread = try SessionThread.fetchOne(db, id: threadId) else { throw MessageSenderError.noThread } @@ -251,6 +250,7 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView } .flatMap { MessageSender.performUploadsIfNeeded(preparedSendData: $0) } .flatMap { MessageSender.sendImmediate(preparedSendData: $0) } + .receive(on: DispatchQueue.main) .sinkUntilComplete( receiveCompletion: { [weak self] result in // Suspend the database diff --git a/SessionSnodeKit/Networking/SnodeAPI.swift b/SessionSnodeKit/Networking/SnodeAPI.swift index aef9ebb3b..3c88ada34 100644 --- a/SessionSnodeKit/Networking/SnodeAPI.swift +++ b/SessionSnodeKit/Networking/SnodeAPI.swift @@ -88,9 +88,6 @@ public final class SnodeAPI { } private static func dropSnodeFromSnodePool(_ snode: Snode) { - #if DEBUG - dispatchPrecondition(condition: .onQueue(Threading.workQueue)) - #endif var snodePool = SnodeAPI.snodePool.wrappedValue snodePool.remove(snode) setSnodePool(to: snodePool) diff --git a/SessionSnodeKit/OnionRequestAPI+Encryption.swift b/SessionSnodeKit/OnionRequestAPI+Encryption.swift index 2878973fe..4d39e5954 100644 --- a/SessionSnodeKit/OnionRequestAPI+Encryption.swift +++ b/SessionSnodeKit/OnionRequestAPI+Encryption.swift @@ -29,7 +29,6 @@ internal extension OnionRequestAPI { _ payload: Data, for destination: OnionRequestAPIDestination ) -> AnyPublisher { - // TODO: Test performance switch destination { case .snode(let snode): // Need to wrap the payload for snode requests @@ -66,7 +65,6 @@ internal extension OnionRequestAPI { to rhs: OnionRequestAPIDestination, using previousEncryptionResult: AESGCM.EncryptionResult ) -> AnyPublisher { - // TODO: Test performance var parameters: JSON switch rhs { diff --git a/SessionUtilitiesKit/Networking/HTTP.swift b/SessionUtilitiesKit/Networking/HTTP.swift index 3882bec0e..5dbd54a64 100644 --- a/SessionUtilitiesKit/Networking/HTTP.swift +++ b/SessionUtilitiesKit/Networking/HTTP.swift @@ -89,7 +89,7 @@ public enum HTTP { public static func execute( _ method: HTTPMethod, _ url: String, - body: Data?, // TODO: Default Value? + body: Data?, timeout: TimeInterval = HTTP.defaultTimeout, useSeedNodeURLSession: Bool = false ) -> AnyPublisher { From ca4ce524025f4d2c485a08042db2e7fb1bbfe77f Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 6 Dec 2022 10:13:04 +1100 Subject: [PATCH 014/135] Fixing the broken unit tests, resolved the remaining TODOs # Conflicts: # Session.xcodeproj/project.pbxproj # SessionMessagingKitTests/Open Groups/Models/BatchRequestInfoSpec.swift # SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift # SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift # SessionMessagingKitTests/_TestUtilities/TestOnionRequestAPI.swift # SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift # SessionUtilitiesKit/Networking/BatchResponse.swift # SessionUtilitiesKitTests/Networking/BatchResponseSpec.swift --- Session.xcodeproj/project.pbxproj | 13 +- .../PushRegistrationManager.swift | 1 + Session/Notifications/SyncPushTokensJob.swift | 1 - .../Open Groups/OpenGroupManager.swift | 5 +- .../Models/BatchRequestInfoSpec.swift | 162 ++-- .../Open Groups/OpenGroupAPISpec.swift | 723 +++++++++++------- .../Open Groups/OpenGroupManagerSpec.swift | 66 +- .../MockAeadXChaCha20Poly1305Ietf.swift | 1 - .../_TestUtilities/MockBox.swift | 1 - .../_TestUtilities/MockEd25519.swift | 1 - .../_TestUtilities/MockGenericHash.swift | 1 - .../_TestUtilities/MockOGMCache.swift | 10 +- .../_TestUtilities/MockSign.swift | 1 - .../_TestUtilities/MockSodium.swift | 1 - .../_TestUtilities/TestOnionRequestAPI.swift | 34 +- .../NotificationServiceExtension.swift | 1 - SessionSnodeKit/OnionRequestAPI.swift | 60 +- .../ThreadSettingsViewModelSpec.swift | 11 +- .../Utilities/Optional+Utilities.swift | 5 + .../Networking/BatchResponseSpec.swift | 81 +- .../Networking/HeaderSpec.swift | 1 + _SharedTestUtilities/SynchronousStorage.swift | 27 +- 22 files changed, 698 insertions(+), 509 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 35623bed2..de3c188b6 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -809,6 +809,7 @@ FD9004142818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9004132818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift */; }; FD9004152818B46300ABAAF6 /* JobRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B7432804EF1B004C14C5 /* JobRunner.swift */; }; FD9004162818B46700ABAAF6 /* JobRunnerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE77F68280F9EDA002CFC5D /* JobRunnerError.swift */; }; + FD9B30F3293EA0BF008DEE3E /* BatchResponseSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9B30F2293EA0BF008DEE3E /* BatchResponseSpec.swift */; }; FDA8EAFE280E8B78002B68E5 /* FailedMessageSendsJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA8EAFD280E8B78002B68E5 /* FailedMessageSendsJob.swift */; }; FDA8EB00280E8D58002B68E5 /* FailedAttachmentDownloadsJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA8EAFF280E8D58002B68E5 /* FailedAttachmentDownloadsJob.swift */; }; FDA8EB10280F8238002B68E5 /* Codable+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA8EB0F280F8238002B68E5 /* Codable+Utilities.swift */; }; @@ -1919,6 +1920,7 @@ FD8ECFA0293D8FDD00C0D1BB /* URLResponse+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLResponse+Utilities.swift"; sourceTree = ""; }; FD90040E2818AB6D00ABAAF6 /* GetSnodePoolJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetSnodePoolJob.swift; sourceTree = ""; }; FD9004132818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_SetupStandardJobs.swift; sourceTree = ""; }; + FD9B30F2293EA0BF008DEE3E /* BatchResponseSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchResponseSpec.swift; sourceTree = ""; }; FDA8EAFD280E8B78002B68E5 /* FailedMessageSendsJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailedMessageSendsJob.swift; sourceTree = ""; }; FDA8EAFF280E8D58002B68E5 /* FailedAttachmentDownloadsJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailedAttachmentDownloadsJob.swift; sourceTree = ""; }; FDA8EB0F280F8238002B68E5 /* Codable+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Codable+Utilities.swift"; sourceTree = ""; }; @@ -3968,7 +3970,7 @@ children = ( FD37EA1228AB3F60003AE748 /* Database */, FD83B9B927CF20A5005E1583 /* General */, - FD8ECF832934507500C0D1BB /* Networking */, + FD9B30F1293EA0AF008DEE3E /* Networking */, ); path = SessionUtilitiesKitTests; sourceTree = ""; @@ -4065,6 +4067,14 @@ path = JobRunner; sourceTree = ""; }; + FD9B30F1293EA0AF008DEE3E /* Networking */ = { + isa = PBXGroup; + children = ( + FD9B30F2293EA0BF008DEE3E /* BatchResponseSpec.swift */, + ); + path = Networking; + sourceTree = ""; + }; FDC2909227D710A9005DAE71 /* Types */ = { isa = PBXGroup; children = ( @@ -5916,6 +5926,7 @@ FD23EA6328ED0B260058676E /* CombineExtensions.swift in Sources */, FD8ECF852934508B00C0D1BB /* BatchResponseSpec.swift in Sources */, FD2AAAEE28ED3E1100A49611 /* MockGeneralCache.swift in Sources */, + FD9B30F3293EA0BF008DEE3E /* BatchResponseSpec.swift in Sources */, FD37EA1528AB42CB003AE748 /* IdentitySpec.swift in Sources */, FD1A94FE2900D2EA000D73D3 /* PersistableRecordUtilitiesSpec.swift in Sources */, FDC290AA27D9B6FD005DAE71 /* Mock.swift in Sources */, diff --git a/Session/Notifications/PushRegistrationManager.swift b/Session/Notifications/PushRegistrationManager.swift index 7ef51b3a6..f1ff01c69 100644 --- a/Session/Notifications/PushRegistrationManager.swift +++ b/Session/Notifications/PushRegistrationManager.swift @@ -53,6 +53,7 @@ public enum PushRegistrationError: Error { return registerUserNotificationSettings() .setFailureType(to: Error.self) + .receive(on: DispatchQueue.main) // MUST be on main thread .flatMap { _ -> AnyPublisher<(pushToken: String, voipToken: String), Error> in #if targetEnvironment(simulator) return Fail(error: PushRegistrationError.pushNotSupported(description: "Push not supported on simulators")) diff --git a/Session/Notifications/SyncPushTokensJob.swift b/Session/Notifications/SyncPushTokensJob.swift index 19fb41e43..4aa25e4b3 100644 --- a/Session/Notifications/SyncPushTokensJob.swift +++ b/Session/Notifications/SyncPushTokensJob.swift @@ -188,7 +188,6 @@ extension SyncPushTokensJob { } .sinkUntilComplete( receiveCompletion: { result in - // TODO: Test these are called correctly switch result { case .finished: break case .failure(let error): failure(error) diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index 88f0aa8ad..06a81f6e4 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -26,8 +26,7 @@ public protocol OGMCacheType { // MARK: - OpenGroupManager -@objc(SNOpenGroupManager) -public final class OpenGroupManager: NSObject { +public final class OpenGroupManager { // MARK: - Cache public class Cache: OGMCacheType { @@ -61,7 +60,7 @@ public final class OpenGroupManager: NSObject { // MARK: - Variables - @objc public static let shared: OpenGroupManager = OpenGroupManager() + public static let shared: OpenGroupManager = OpenGroupManager() /// Note: This should not be accessed directly but rather via the 'OGMDependencies' type fileprivate let mutableCache: Atomic = Atomic(Cache()) diff --git a/SessionMessagingKitTests/Open Groups/Models/BatchRequestInfoSpec.swift b/SessionMessagingKitTests/Open Groups/Models/BatchRequestInfoSpec.swift index aeda2a2f1..c6ea98995 100644 --- a/SessionMessagingKitTests/Open Groups/Models/BatchRequestInfoSpec.swift +++ b/SessionMessagingKitTests/Open Groups/Models/BatchRequestInfoSpec.swift @@ -1,7 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation -import PromiseKit +import Combine import SessionSnodeKit import SessionUtilitiesKit @@ -19,97 +19,117 @@ class BatchRequestInfoSpec: QuickSpec { // MARK: - Spec override func spec() { - // MARK: - BatchSubRequest + // MARK: - BatchRequest.Child describe("a BatchRequest.Child") { - var subRequest: OpenGroupAPI.BatchRequest.Child! + var request: OpenGroupAPI.BatchRequest! context("when initializing") { it("sets the headers to nil if there aren't any") { - subRequest = OpenGroupAPI.BatchRequest.Child( - request: Request( - server: "testServer", - endpoint: .batch - ) + request = OpenGroupAPI.BatchRequest( + requests: [ + OpenGroupAPI.BatchRequest.Info( + request: Request( + server: "testServer", + endpoint: .batch + ) + ) + ] ) - expect(subRequest.headers).to(beNil()) + expect(request.requests.first?.headers).to(beNil()) } it("converts the headers to HTTP headers") { - subRequest = OpenGroupAPI.BatchRequest.Child( - request: Request( - method: .get, - server: "testServer", - endpoint: .batch, - queryParameters: [:], - headers: [.authorization: "testAuth"], - body: nil - ) + request = OpenGroupAPI.BatchRequest( + requests: [ + OpenGroupAPI.BatchRequest.Info( + request: Request( + method: .get, + server: "testServer", + endpoint: .batch, + queryParameters: [:], + headers: [.authorization: "testAuth"], + body: nil + ) + ) + ] ) - expect(subRequest.headers).to(equal(["Authorization": "testAuth"])) + expect(request.requests.first?.headers).to(equal(["Authorization": "testAuth"])) } } context("when encoding") { it("successfully encodes a string body") { - subRequest = OpenGroupAPI.BatchRequest.Child( - request: Request( - method: .get, - server: "testServer", - endpoint: .batch, - queryParameters: [:], - headers: [:], - body: "testBody" - ) + request = OpenGroupAPI.BatchRequest( + requests: [ + OpenGroupAPI.BatchRequest.Info( + request: Request( + method: .get, + server: "testServer", + endpoint: .batch, + queryParameters: [:], + headers: [:], + body: "testBody" + ) + ) + ] ) - let subRequestData: Data = try! JSONEncoder().encode(subRequest) - let subRequestString: String? = String(data: subRequestData, encoding: .utf8) + let childRequestData: Data = try! JSONEncoder().encode(request.requests[0]) + let childRequestString: String? = String(data: childRequestData, encoding: .utf8) - expect(subRequestString) + expect(childRequestString) .to(equal("{\"path\":\"\\/batch\",\"method\":\"GET\",\"b64\":\"testBody\"}")) } it("successfully encodes a byte body") { - subRequest = OpenGroupAPI.BatchRequest.Child( - request: Request<[UInt8], OpenGroupAPI.Endpoint>( - method: .get, - server: "testServer", - endpoint: .batch, - queryParameters: [:], - headers: [:], - body: [1, 2, 3] - ) + request = OpenGroupAPI.BatchRequest( + requests: [ + OpenGroupAPI.BatchRequest.Info( + request: Request<[UInt8], OpenGroupAPI.Endpoint>( + method: .get, + server: "testServer", + endpoint: .batch, + queryParameters: [:], + headers: [:], + body: [1, 2, 3] + ) + ) + ] ) - let subRequestData: Data = try! JSONEncoder().encode(subRequest) - let subRequestString: String? = String(data: subRequestData, encoding: .utf8) + let childRequestData: Data = try! JSONEncoder().encode(request.requests[0]) + let childRequestString: String? = String(data: childRequestData, encoding: .utf8) - expect(subRequestString) + expect(childRequestString) .to(equal("{\"path\":\"\\/batch\",\"method\":\"GET\",\"bytes\":[1,2,3]}")) } it("successfully encodes a JSON body") { - subRequest = OpenGroupAPI.BatchRequest.Child( - request: Request( - method: .get, - server: "testServer", - endpoint: .batch, - queryParameters: [:], - headers: [:], - body: TestType(stringValue: "testValue") - ) + request = OpenGroupAPI.BatchRequest( + requests: [ + OpenGroupAPI.BatchRequest.Info( + request: Request( + method: .get, + server: "testServer", + endpoint: .batch, + queryParameters: [:], + headers: [:], + body: TestType(stringValue: "testValue") + ) + ) + ] ) - let subRequestData: Data = try! JSONEncoder().encode(subRequest) - let subRequestString: String? = String(data: subRequestData, encoding: .utf8) + let childRequestData: Data = try! JSONEncoder().encode(request.requests[0]) + let childRequestString: String? = String(data: childRequestData, encoding: .utf8) - expect(subRequestString) + expect(childRequestString) .to(equal("{\"path\":\"\\/batch\",\"method\":\"GET\",\"json\":{\"stringValue\":\"testValue\"}}")) } } } - // MARK: - BatchRequestInfo + // MARK: - BatchRequest.Info describe("a BatchRequest.Info") { var request: Request! @@ -143,27 +163,17 @@ class BatchRequestInfoSpec: QuickSpec { expect(requestInfo.endpoint.path).to(equal(request.endpoint.path)) expect(requestInfo.responseType == HTTP.BatchSubResponse.self).to(beTrue()) } - - it("exposes the endpoint correctly") { - let requestInfo: OpenGroupAPI.BatchRequest.Info = OpenGroupAPI.BatchRequest.Info( - request: request - ) + } + + // MARK: - Convenience + // MARK: --Decodable + + describe("a Decodable") { + it("decodes correctly") { + let jsonData: Data = "{\"stringValue\":\"testValue\"}".data(using: .utf8)! + let result: TestType? = try? TestType.decoded(from: jsonData) - expect(requestInfo.endpoint.path).to(equal(request.endpoint.path)) - } - - it("generates a sub request correctly") { - let batchRequest: OpenGroupAPI.BatchRequest = OpenGroupAPI.BatchRequest( - requests: [ - OpenGroupAPI.BatchRequest.Info( - request: request - ) - ] - ) - - expect(batchRequest.requests[0].method).to(equal(request.method)) - expect(batchRequest.requests[0].path).to(equal(request.urlPathAndParamsString)) - expect(batchRequest.requests[0].headers).to(beNil()) + expect(result).to(equal(TestType(stringValue: "testValue"))) } } } diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift b/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift index e0f46abc9..288ac22ea 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift +++ b/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. -import PromiseKit +import Foundation +import Combine import GRDB import Sodium import SessionSnodeKit @@ -181,7 +182,7 @@ class OpenGroupAPISpec: QuickSpec { it("generates the correct request") { mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI.poll( db, server: "testserver", @@ -190,9 +191,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in pollResponse = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(pollResponse) .toEventuallyNot( @@ -217,7 +220,7 @@ class OpenGroupAPISpec: QuickSpec { it("retrieves recent messages if there was no last message") { mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI.poll( db, server: "testserver", @@ -226,9 +229,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in pollResponse = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(pollResponse) .toEventuallyNot( @@ -246,7 +251,7 @@ class OpenGroupAPISpec: QuickSpec { } mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI.poll( db, server: "testserver", @@ -255,9 +260,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in pollResponse = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(pollResponse) .toEventuallyNot( @@ -275,7 +282,7 @@ class OpenGroupAPISpec: QuickSpec { } mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI.poll( db, server: "testserver", @@ -284,9 +291,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in pollResponse = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(pollResponse) .toEventuallyNot( @@ -304,7 +313,7 @@ class OpenGroupAPISpec: QuickSpec { } mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI.poll( db, server: "testserver", @@ -313,9 +322,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in pollResponse = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(pollResponse) .toEventuallyNot( @@ -336,7 +347,7 @@ class OpenGroupAPISpec: QuickSpec { it("does not call the inbox and outbox endpoints") { mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI.poll( db, server: "testserver", @@ -345,9 +356,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in pollResponse = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(pollResponse) .toEventuallyNot( @@ -435,7 +448,7 @@ class OpenGroupAPISpec: QuickSpec { it("includes the inbox and outbox endpoints") { mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI.poll( db, server: "testserver", @@ -444,9 +457,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in pollResponse = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(pollResponse) .toEventuallyNot( @@ -462,7 +477,7 @@ class OpenGroupAPISpec: QuickSpec { it("retrieves recent inbox messages if there was no last message") { mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI.poll( db, server: "testserver", @@ -471,9 +486,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in pollResponse = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(pollResponse) .toEventuallyNot( @@ -491,7 +508,7 @@ class OpenGroupAPISpec: QuickSpec { } mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI.poll( db, server: "testserver", @@ -500,9 +517,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in pollResponse = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(pollResponse) .toEventuallyNot( @@ -515,7 +534,7 @@ class OpenGroupAPISpec: QuickSpec { it("retrieves recent outbox messages if there was no last message") { mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI.poll( db, server: "testserver", @@ -524,9 +543,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in pollResponse = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(pollResponse) .toEventuallyNot( @@ -544,7 +565,7 @@ class OpenGroupAPISpec: QuickSpec { } mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI.poll( db, server: "testserver", @@ -553,9 +574,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in pollResponse = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(pollResponse) .toEventuallyNot( @@ -605,7 +628,7 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI.poll( db, server: "testserver", @@ -614,9 +637,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in pollResponse = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(pollResponse) .toEventuallyNot( @@ -635,7 +660,7 @@ class OpenGroupAPISpec: QuickSpec { it("errors when no data is returned") { mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI.poll( db, server: "testserver", @@ -644,9 +669,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in pollResponse = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(error?.localizedDescription) .toEventually( @@ -664,7 +691,7 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI.poll( db, server: "testserver", @@ -673,9 +700,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in pollResponse = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(error?.localizedDescription) .toEventually( @@ -693,7 +722,7 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI.poll( db, server: "testserver", @@ -702,9 +731,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in pollResponse = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(error?.localizedDescription) .toEventually( @@ -722,7 +753,7 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI.poll( db, server: "testserver", @@ -731,9 +762,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in pollResponse = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(error?.localizedDescription) .toEventually( @@ -783,7 +816,7 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI.poll( db, server: "testserver", @@ -792,9 +825,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in pollResponse = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(error?.localizedDescription) .toEventually( @@ -821,16 +856,18 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Capabilities)? mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI.capabilities( db, server: "testserver", using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(response) .toEventuallyNot( @@ -891,16 +928,18 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: [OpenGroupAPI.Room])? mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI.rooms( db, server: "testserver", using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(response) .toEventuallyNot( @@ -982,7 +1021,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (capabilities: (info: ResponseInfoType, data: OpenGroupAPI.Capabilities?), room: (info: ResponseInfoType, data: OpenGroupAPI.Room?))? mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI.capabilitiesAndRoom( db, for: "testRoom", @@ -990,9 +1029,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(response) .toEventuallyNot( @@ -1037,7 +1078,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (capabilities: (info: ResponseInfoType, data: OpenGroupAPI.Capabilities?), room: (info: ResponseInfoType, data: OpenGroupAPI.Room?))? mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .capabilitiesAndRoom( db, @@ -1046,9 +1087,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(error?.localizedDescription) .toEventually( @@ -1109,7 +1152,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (capabilities: (info: ResponseInfoType, data: OpenGroupAPI.Capabilities?), room: (info: ResponseInfoType, data: OpenGroupAPI.Room?))? mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .capabilitiesAndRoom( db, @@ -1118,9 +1161,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(error?.localizedDescription) .toEventually( @@ -1198,7 +1243,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (capabilities: (info: ResponseInfoType, data: OpenGroupAPI.Capabilities?), room: (info: ResponseInfoType, data: OpenGroupAPI.Room?))? mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI.capabilitiesAndRoom( db, for: "testRoom", @@ -1206,9 +1251,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(error?.localizedDescription) .toEventually( @@ -1257,7 +1304,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .send( db, @@ -1270,9 +1317,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(response) .toEventuallyNot( @@ -1302,7 +1351,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .send( db, @@ -1315,9 +1364,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(response) .toEventuallyNot( @@ -1342,7 +1393,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .send( db, @@ -1355,9 +1406,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(error?.localizedDescription) .toEventually( @@ -1377,7 +1430,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .send( db, @@ -1390,9 +1443,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(error?.localizedDescription) .toEventually( @@ -1410,7 +1465,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .send( db, @@ -1423,9 +1478,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(error?.localizedDescription) .toEventually( @@ -1450,7 +1507,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .send( db, @@ -1463,9 +1520,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(response) .toEventuallyNot( @@ -1490,7 +1549,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .send( db, @@ -1503,9 +1562,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(error?.localizedDescription) .toEventually( @@ -1525,7 +1586,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .send( db, @@ -1538,9 +1599,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(error?.localizedDescription) .toEventually( @@ -1566,7 +1629,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .send( db, @@ -1579,9 +1642,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(error?.localizedDescription) .toEventually( @@ -1619,7 +1684,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .message( db, @@ -1629,9 +1694,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(response) .toEventuallyNot( @@ -1671,7 +1738,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: Data?)? mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .messageUpdate( db, @@ -1683,9 +1750,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(response) .toEventuallyNot( @@ -1712,7 +1781,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: Data?)? mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .messageUpdate( db, @@ -1724,9 +1793,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(response) .toEventuallyNot( @@ -1751,7 +1822,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: Data?)? mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .messageUpdate( db, @@ -1763,9 +1834,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(error?.localizedDescription) .toEventually( @@ -1785,7 +1858,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: Data?)? mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .messageUpdate( db, @@ -1797,9 +1870,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(error?.localizedDescription) .toEventually( @@ -1817,7 +1892,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: Data?)? mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .messageUpdate( db, @@ -1829,9 +1904,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(error?.localizedDescription) .toEventually( @@ -1856,7 +1933,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: Data?)? mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .messageUpdate( db, @@ -1868,9 +1945,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(response) .toEventuallyNot( @@ -1895,7 +1974,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: Data?)? mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .messageUpdate( db, @@ -1907,9 +1986,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(error?.localizedDescription) .toEventually( @@ -1929,7 +2010,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: Data?)? mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .messageUpdate( db, @@ -1941,9 +2022,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(error?.localizedDescription) .toEventually( @@ -1969,7 +2052,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: Data?)? mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .messageUpdate( db, @@ -1981,9 +2064,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(error?.localizedDescription) .toEventually( @@ -2006,7 +2091,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: Data?)? mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .messageDelete( db, @@ -2016,9 +2101,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(response) .toEventuallyNot( @@ -2050,7 +2137,7 @@ class OpenGroupAPISpec: QuickSpec { it("generates the request and handles the response correctly") { mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .messagesDeleteAll( db, @@ -2060,9 +2147,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(response) .toEventuallyNot( @@ -2090,7 +2179,7 @@ class OpenGroupAPISpec: QuickSpec { var response: ResponseInfoType? mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .pinMessage( db, @@ -2100,9 +2189,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(response) .toEventuallyNot( @@ -2128,7 +2219,7 @@ class OpenGroupAPISpec: QuickSpec { var response: ResponseInfoType? mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .unpinMessage( db, @@ -2138,9 +2229,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(response) .toEventuallyNot( @@ -2166,7 +2259,7 @@ class OpenGroupAPISpec: QuickSpec { var response: ResponseInfoType? mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .unpinAll( db, @@ -2175,9 +2268,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(response) .toEventuallyNot( @@ -2205,7 +2300,7 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .uploadFile( db, @@ -2215,9 +2310,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(response) .toEventuallyNot( @@ -2241,7 +2338,7 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .uploadFile( db, @@ -2251,9 +2348,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(response) .toEventuallyNot( @@ -2277,7 +2376,7 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .uploadFile( db, @@ -2288,9 +2387,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(response) .toEventuallyNot( @@ -2315,7 +2416,7 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .downloadFile( db, @@ -2325,9 +2426,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(response) .toEventuallyNot( @@ -2372,7 +2475,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.SendDirectMessageResponse)? mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .send( db, @@ -2382,9 +2485,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(response) .toEventuallyNot( @@ -2421,7 +2526,7 @@ class OpenGroupAPISpec: QuickSpec { it("generates the request and handles the response correctly") { mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .userBan( db, @@ -2432,9 +2537,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(response) .toEventuallyNot( @@ -2451,7 +2558,7 @@ class OpenGroupAPISpec: QuickSpec { it("does a global ban if no room tokens are provided") { mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .userBan( db, @@ -2462,9 +2569,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(response) .toEventuallyNot( @@ -2483,7 +2592,7 @@ class OpenGroupAPISpec: QuickSpec { it("does room specific bans if room tokens are provided") { mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .userBan( db, @@ -2494,9 +2603,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(response) .toEventuallyNot( @@ -2530,7 +2641,7 @@ class OpenGroupAPISpec: QuickSpec { it("generates the request and handles the response correctly") { mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .userUnban( db, @@ -2540,9 +2651,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(response) .toEventuallyNot( @@ -2559,7 +2672,7 @@ class OpenGroupAPISpec: QuickSpec { it("does a global ban if no room tokens are provided") { mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .userUnban( db, @@ -2569,9 +2682,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(response) .toEventuallyNot( @@ -2590,7 +2705,7 @@ class OpenGroupAPISpec: QuickSpec { it("does room specific bans if room tokens are provided") { mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .userUnban( db, @@ -2600,9 +2715,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(response) .toEventuallyNot( @@ -2636,7 +2753,7 @@ class OpenGroupAPISpec: QuickSpec { it("generates the request and handles the response correctly") { mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .userModeratorUpdate( db, @@ -2649,9 +2766,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(response) .toEventuallyNot( @@ -2668,7 +2787,7 @@ class OpenGroupAPISpec: QuickSpec { it("does a global update if no room tokens are provided") { mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .userModeratorUpdate( db, @@ -2681,9 +2800,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(response) .toEventuallyNot( @@ -2702,7 +2823,7 @@ class OpenGroupAPISpec: QuickSpec { it("does room specific updates if room tokens are provided") { mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .userModeratorUpdate( db, @@ -2715,9 +2836,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(response) .toEventuallyNot( @@ -2736,7 +2859,7 @@ class OpenGroupAPISpec: QuickSpec { it("fails if neither moderator or admin are set") { mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .userModeratorUpdate( db, @@ -2749,9 +2872,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(error?.localizedDescription) .toEventually( @@ -2800,7 +2925,7 @@ class OpenGroupAPISpec: QuickSpec { it("generates the request and handles the response correctly") { mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .userBanAndDeleteAllMessages( db, @@ -2810,9 +2935,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(response) .toEventuallyNot( @@ -2829,7 +2956,7 @@ class OpenGroupAPISpec: QuickSpec { it("bans the user from the specified room rather than globally") { mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .userBanAndDeleteAllMessages( db, @@ -2839,9 +2966,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(response) .toEventuallyNot( @@ -2886,7 +3015,7 @@ class OpenGroupAPISpec: QuickSpec { } mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .rooms( db, @@ -2894,9 +3023,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(error?.localizedDescription) .toEventually( @@ -2913,7 +3044,7 @@ class OpenGroupAPISpec: QuickSpec { } mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .rooms( db, @@ -2921,9 +3052,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(error?.localizedDescription) .toEventually( @@ -2940,7 +3073,7 @@ class OpenGroupAPISpec: QuickSpec { } mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .rooms( db, @@ -2948,9 +3081,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(error?.localizedDescription) .toEventually( @@ -2971,7 +3106,7 @@ class OpenGroupAPISpec: QuickSpec { it("signs correctly") { mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .rooms( db, @@ -2979,9 +3114,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(response) .toEventuallyNot( @@ -3007,7 +3144,7 @@ class OpenGroupAPISpec: QuickSpec { mockSign.when { $0.signature(message: anyArray(), secretKey: anyArray()) }.thenReturn(nil) mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .rooms( db, @@ -3015,9 +3152,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(error?.localizedDescription) .toEventually( @@ -3040,7 +3179,7 @@ class OpenGroupAPISpec: QuickSpec { it("signs correctly") { mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .rooms( db, @@ -3048,9 +3187,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(response) .toEventuallyNot( @@ -3077,7 +3218,7 @@ class OpenGroupAPISpec: QuickSpec { .thenReturn(nil) mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .rooms( db, @@ -3085,9 +3226,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(error?.localizedDescription) .toEventually( @@ -3104,7 +3247,7 @@ class OpenGroupAPISpec: QuickSpec { .thenReturn(nil) mockStorage - .read { db in + .readPublisherFlatMap { db in OpenGroupAPI .rooms( db, @@ -3112,9 +3255,11 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .handleEvents(receiveOutput: { result in pollResponse = result }) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(error?.localizedDescription) .toEventually( diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift index ac36d002e..cce00741c 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift +++ b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. -import PromiseKit +import Foundation +import Combine import GRDB import Sodium import SessionSnodeKit @@ -779,7 +780,7 @@ class OpenGroupManagerSpec: QuickSpec { var didComplete: Bool = false // Prevent multi-threading test bugs mockStorage - .writeAsync { db in + .writePublisherFlatMap { db in openGroupManager .add( db, @@ -790,8 +791,9 @@ class OpenGroupManagerSpec: QuickSpec { dependencies: dependencies ) } - .map { _ -> Void in didComplete = true } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .sinkUntilComplete(receiveCompletion: { _ in didComplete = true }) expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50)) expect( @@ -810,7 +812,7 @@ class OpenGroupManagerSpec: QuickSpec { var didComplete: Bool = false // Prevent multi-threading test bugs mockStorage - .writeAsync { db in + .writePublisherFlatMap { db in openGroupManager .add( db, @@ -821,8 +823,9 @@ class OpenGroupManagerSpec: QuickSpec { dependencies: dependencies ) } - .map { _ -> Void in didComplete = true } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .sinkUntilComplete(receiveCompletion: { _ in didComplete = true }) expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50)) expect(mockOGMCache) @@ -847,7 +850,7 @@ class OpenGroupManagerSpec: QuickSpec { var didComplete: Bool = false // Prevent multi-threading test bugs mockStorage - .writeAsync { db in + .writePublisherFlatMap { db in openGroupManager .add( db, @@ -860,8 +863,9 @@ class OpenGroupManagerSpec: QuickSpec { dependencies: dependencies ) } - .map { _ -> Void in didComplete = true } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .sinkUntilComplete(receiveCompletion: { _ in didComplete = true }) expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50)) expect( @@ -901,7 +905,7 @@ class OpenGroupManagerSpec: QuickSpec { var error: Error? mockStorage - .writeAsync { db in + .writePublisherFlatMap { db in openGroupManager .add( db, @@ -912,8 +916,10 @@ class OpenGroupManagerSpec: QuickSpec { dependencies: dependencies ) } - .catch { error = $0 } - .retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() expect(error?.localizedDescription) .toEventually( @@ -1242,8 +1248,10 @@ class OpenGroupManagerSpec: QuickSpec { ).insert(db) } - mockOGMCache.when { $0.groupImagePromises } - .thenReturn([OpenGroup.idFor(roomToken: "testRoom", server: "testServer"): Promise.value(Data())]) + mockOGMCache.when { $0.groupImagePublishers } + .thenReturn([ + OpenGroup.idFor(roomToken: "testRoom", server: "testServer"): Just(Data()).setFailureType(to: Error.self).eraseToAnyPublisher() + ]) mockStorage.write { db in try OpenGroupManager.handlePollInfo( @@ -1679,8 +1687,10 @@ class OpenGroupManagerSpec: QuickSpec { .updateAll(db, OpenGroup.Columns.imageData.set(to: nil)) } - mockOGMCache.when { $0.groupImagePromises } - .thenReturn([OpenGroup.idFor(roomToken: "testRoom", server: "testServer"): Promise.value(imageData)]) + mockOGMCache.when { $0.groupImagePublishers } + .thenReturn([ + OpenGroup.idFor(roomToken: "testRoom", server: "testServer"): Just(imageData).setFailureType(to: Error.self).eraseToAnyPublisher() + ]) } it("uses the provided room image id if available") { @@ -1952,8 +1962,10 @@ class OpenGroupManagerSpec: QuickSpec { it("does nothing if it fails to retrieve the room image") { var didComplete: Bool = false // Prevent multi-threading test bugs - mockOGMCache.when { $0.groupImagePromises } - .thenReturn([OpenGroup.idFor(roomToken: "testRoom", server: "testServer"): Promise(error: HTTPError.generic)]) + mockOGMCache.when { $0.groupImagePublishers } + .thenReturn([ + OpenGroup.idFor(roomToken: "testRoom", server: "testServer"): Fail(error: HTTPError.generic).eraseToAnyPublisher() + ]) testPollInfo = OpenGroupAPI.RoomPollInfo( token: "testRoom", @@ -3248,11 +3260,11 @@ class OpenGroupManagerSpec: QuickSpec { } it("returns the cached promise if there is one") { - let (promise, _) = Promise<[OpenGroupAPI.Room]>.pending() - mockOGMCache.when { $0.defaultRoomsPromise }.thenReturn(promise) + let publisher = Future { _ in }.eraseToAnyPublisher() + mockOGMCache.when { $0.defaultRoomsPublisher }.thenReturn(publisher) expect(OpenGroupManager.getDefaultRoomsIfNeeded(using: dependencies)) - .to(equal(promise)) + .to(equal(publisher)) } it("stores the open group information") { @@ -3494,12 +3506,12 @@ class OpenGroupManagerSpec: QuickSpec { } it("retrieves the image retrieval promise from the cache if it exists") { - let (promise, _) = Promise.pending() + let publisher = Future { _ in }.eraseToAnyPublisher() mockOGMCache - .when { $0.groupImagePromises } - .thenReturn([OpenGroup.idFor(roomToken: "testRoom", server: "testServer"): promise]) + .when { $0.groupImagePublishers } + .thenReturn([OpenGroup.idFor(roomToken: "testRoom", server: "testServer"): publisher]) - let promise2 = mockStorage.read { db in + let publisher2 = mockStorage.read { db in OpenGroupManager .roomImage( db, @@ -3509,7 +3521,7 @@ class OpenGroupManagerSpec: QuickSpec { using: dependencies ) } - expect(promise2).to(equal(promise)) + expect(publisher2).to(equal(publisher)) } it("does not save the fetched image to storage") { diff --git a/SessionMessagingKitTests/_TestUtilities/MockAeadXChaCha20Poly1305Ietf.swift b/SessionMessagingKitTests/_TestUtilities/MockAeadXChaCha20Poly1305Ietf.swift index 09b0f9ce1..cb3888b59 100644 --- a/SessionMessagingKitTests/_TestUtilities/MockAeadXChaCha20Poly1305Ietf.swift +++ b/SessionMessagingKitTests/_TestUtilities/MockAeadXChaCha20Poly1305Ietf.swift @@ -1,7 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation -import PromiseKit import Sodium @testable import SessionMessagingKit diff --git a/SessionMessagingKitTests/_TestUtilities/MockBox.swift b/SessionMessagingKitTests/_TestUtilities/MockBox.swift index ff8756977..3a991eec9 100644 --- a/SessionMessagingKitTests/_TestUtilities/MockBox.swift +++ b/SessionMessagingKitTests/_TestUtilities/MockBox.swift @@ -1,7 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation -import PromiseKit import Sodium @testable import SessionMessagingKit diff --git a/SessionMessagingKitTests/_TestUtilities/MockEd25519.swift b/SessionMessagingKitTests/_TestUtilities/MockEd25519.swift index d92250663..ee23e7250 100644 --- a/SessionMessagingKitTests/_TestUtilities/MockEd25519.swift +++ b/SessionMessagingKitTests/_TestUtilities/MockEd25519.swift @@ -1,7 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation -import PromiseKit import Sodium @testable import SessionMessagingKit diff --git a/SessionMessagingKitTests/_TestUtilities/MockGenericHash.swift b/SessionMessagingKitTests/_TestUtilities/MockGenericHash.swift index 3a97611bf..f3eccdbc1 100644 --- a/SessionMessagingKitTests/_TestUtilities/MockGenericHash.swift +++ b/SessionMessagingKitTests/_TestUtilities/MockGenericHash.swift @@ -1,7 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation -import PromiseKit import Sodium @testable import SessionMessagingKit diff --git a/SessionMessagingKitTests/_TestUtilities/MockOGMCache.swift b/SessionMessagingKitTests/_TestUtilities/MockOGMCache.swift index 02caa5e85..cef200b39 100644 --- a/SessionMessagingKitTests/_TestUtilities/MockOGMCache.swift +++ b/SessionMessagingKitTests/_TestUtilities/MockOGMCache.swift @@ -1,19 +1,19 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation -import PromiseKit +import Combine import SessionUtilitiesKit @testable import SessionMessagingKit class MockOGMCache: Mock, OGMCacheType { - var defaultRoomsPromise: Promise<[OpenGroupAPI.Room]>? { - get { return accept() as? Promise<[OpenGroupAPI.Room]> } + var defaultRoomsPublisher: AnyPublisher<[OpenGroupAPI.Room], Error>? { + get { return accept() as? AnyPublisher<[OpenGroupAPI.Room], Error> } set { accept(args: [newValue]) } } - var groupImagePromises: [String: Promise] { - get { return accept() as! [String: Promise] } + var groupImagePublishers: [String: AnyPublisher] { + get { return accept() as! [String: AnyPublisher] } set { accept(args: [newValue]) } } diff --git a/SessionMessagingKitTests/_TestUtilities/MockSign.swift b/SessionMessagingKitTests/_TestUtilities/MockSign.swift index 98f2887db..67a4ebe7f 100644 --- a/SessionMessagingKitTests/_TestUtilities/MockSign.swift +++ b/SessionMessagingKitTests/_TestUtilities/MockSign.swift @@ -1,7 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation -import PromiseKit import Sodium @testable import SessionMessagingKit diff --git a/SessionMessagingKitTests/_TestUtilities/MockSodium.swift b/SessionMessagingKitTests/_TestUtilities/MockSodium.swift index 4ed5bb75f..80f1877d1 100644 --- a/SessionMessagingKitTests/_TestUtilities/MockSodium.swift +++ b/SessionMessagingKitTests/_TestUtilities/MockSodium.swift @@ -1,7 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation -import PromiseKit import Sodium @testable import SessionMessagingKit diff --git a/SessionMessagingKitTests/_TestUtilities/TestOnionRequestAPI.swift b/SessionMessagingKitTests/_TestUtilities/TestOnionRequestAPI.swift index ba6e47349..bb272d204 100644 --- a/SessionMessagingKitTests/_TestUtilities/TestOnionRequestAPI.swift +++ b/SessionMessagingKitTests/_TestUtilities/TestOnionRequestAPI.swift @@ -1,7 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation -import PromiseKit +import Combine import SessionSnodeKit import SessionUtilitiesKit @@ -23,7 +23,7 @@ class TestOnionRequestAPI: OnionRequestAPIType { } } } - + class ResponseInfo: ResponseInfoType { let requestData: RequestData let code: Int @@ -38,41 +38,45 @@ class TestOnionRequestAPI: OnionRequestAPIType { class var mockResponse: Data? { return nil } - static func sendOnionRequest(_ request: URLRequest, to server: String, with x25519PublicKey: String) -> Promise<(ResponseInfoType, Data?)> { + static func sendOnionRequest(_ request: URLRequest, to server: String, with x25519PublicKey: String) -> AnyPublisher<(ResponseInfoType, Data?), Error> { let responseInfo: ResponseInfo = ResponseInfo( requestData: RequestData( urlString: request.url?.absoluteString, httpMethod: (request.httpMethod ?? "GET"), headers: (request.allHTTPHeaderFields ?? [:]), body: request.httpBody, - destination: OnionRequestAPIDestination.server( - host: request.url!.host!, - target: OnionRequestAPIVersion.v4.rawValue, - x25519PublicKey: x25519PublicKey, - scheme: request.url!.scheme, - port: request.url!.port.map { UInt16($0) } - ) + server: server, + version: .v4, + publicKey: x25519PublicKey ), code: 200, headers: [:] ) - return Promise.value((responseInfo, mockResponse)) + return Just((responseInfo, mockResponse)) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } - static func sendOnionRequest(_ payload: Data, to snode: Snode) -> Promise<(ResponseInfoType, Data?)> { + static func sendOnionRequest(_ payload: Data, to snode: Snode) -> AnyPublisher<(ResponseInfoType, Data?), Error> { let responseInfo: ResponseInfo = ResponseInfo( requestData: RequestData( - urlString: "\(snode.address):\(snode.port)/onion_req/v2", + urlString: nil, httpMethod: "POST", headers: [:], + snodeMethod: nil, body: payload, - destination: OnionRequestAPIDestination.snode(snode) + + server: "", + version: .v3, + publicKey: snode.x25519PublicKey ), code: 200, headers: [:] ) - return Promise.value((responseInfo, mockResponse)) + return Just((responseInfo, mockResponse)) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } } diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index 3ae2f1921..c83962cec 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -48,7 +48,6 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension AppReadiness.runNowOrWhenAppDidBecomeReady { let openGroupPollingPublishers: [AnyPublisher] = self.pollForOpenGroups() defer { - // TODO: Test this Publishers .MergeMany(openGroupPollingPublishers) .sinkUntilComplete( diff --git a/SessionSnodeKit/OnionRequestAPI.swift b/SessionSnodeKit/OnionRequestAPI.swift index 73a06d741..997de4088 100644 --- a/SessionSnodeKit/OnionRequestAPI.swift +++ b/SessionSnodeKit/OnionRequestAPI.swift @@ -14,30 +14,24 @@ public protocol OnionRequestAPIType { /// See the "Onion Requests" section of [The Session Whitepaper](https://arxiv.org/pdf/2002.04609.pdf) for more information. public enum OnionRequestAPI: OnionRequestAPIType { private static var buildPathsPublisher: Atomic?> = Atomic(nil) - - /// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions. - private static var pathFailureCount: [[Snode]: UInt] = [:] - - /// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions. - private static var snodeFailureCount: [Snode: UInt] = [:] - - /// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions. - public static var guardSnodes: Set = [] + private static var pathFailureCount: Atomic<[[Snode]: UInt]> = Atomic([:]) + private static var snodeFailureCount: Atomic<[Snode: UInt]> = Atomic([:]) + public static var guardSnodes: Atomic> = Atomic([]) // Not a set to ensure we consistently show the same path to the user - private static var _paths: [[Snode]]? + private static var _paths: Atomic<[[Snode]]?> = Atomic(nil) public static var paths: [[Snode]] { get { - if let paths: [[Snode]] = _paths { return paths } + if let paths: [[Snode]] = _paths.wrappedValue { return paths } let results: [[Snode]]? = Storage.shared.read { db in try? Snode.fetchAllOnionRequestPaths(db) } - if results?.isEmpty == false { _paths = results } + if results?.isEmpty == false { _paths.mutate { $0 = results } } return (results ?? []) } - set { _paths = newValue } + set { _paths.mutate { $0 = newValue } } } // MARK: - Settings @@ -94,8 +88,8 @@ public enum OnionRequestAPI: OnionRequestAPIType { /// Finds `targetGuardSnodeCount` guard snodes to use for path building. The returned promise errors out with /// `Error.insufficientSnodes` if not enough (reliable) snodes are available. private static func getGuardSnodes(reusing reusableGuardSnodes: [Snode]) -> AnyPublisher, Error> { - guard guardSnodes.count < targetGuardSnodeCount else { - return Just(guardSnodes) + guard guardSnodes.wrappedValue.count < targetGuardSnodeCount else { + return Just(guardSnodes.wrappedValue) .setFailureType(to: Error.self) .eraseToAnyPublisher() } @@ -141,7 +135,7 @@ public enum OnionRequestAPI: OnionRequestAPIType { .map { output in Set(output) } .handleEvents( receiveOutput: { output in - OnionRequestAPI.guardSnodes = output + OnionRequestAPI.guardSnodes.mutate { $0 = output } } ) .eraseToAnyPublisher() @@ -222,10 +216,12 @@ public enum OnionRequestAPI: OnionRequestAPIType { var cancellable: [AnyCancellable] = [] if !paths.isEmpty { - guardSnodes.formUnion([ paths[0][0] ]) - - if paths.count >= 2 { - guardSnodes.formUnion([ paths[1][0] ]) + guardSnodes.mutate { + $0.formUnion([ paths[0][0] ]) + + if paths.count >= 2 { + $0.formUnion([ paths[1][0] ]) + } } } @@ -309,20 +305,14 @@ public enum OnionRequestAPI: OnionRequestAPIType { } private static func dropGuardSnode(_ snode: Snode) { - #if DEBUG - dispatchPrecondition(condition: .onQueue(Threading.workQueue)) - #endif - guardSnodes = guardSnodes.filter { $0 != snode } + guardSnodes.mutate { snodes in snodes = snodes.filter { $0 != snode } } } private static func drop(_ snode: Snode) throws { - #if DEBUG - dispatchPrecondition(condition: .onQueue(Threading.workQueue)) - #endif // We repair the path here because we can do it sync. In the case where we drop a whole // path we leave the re-building up to getPath(excluding:) because re-building the path // in that case is async. - OnionRequestAPI.snodeFailureCount[snode] = 0 + OnionRequestAPI.snodeFailureCount.mutate { $0[snode] = 0 } var oldPaths = paths guard let pathIndex = oldPaths.firstIndex(where: { $0.contains(snode) }) else { return } var path = oldPaths[pathIndex] @@ -344,10 +334,7 @@ public enum OnionRequestAPI: OnionRequestAPIType { } private static func drop(_ path: [Snode]) { - #if DEBUG - dispatchPrecondition(condition: .onQueue(Threading.workQueue)) - #endif - OnionRequestAPI.pathFailureCount[path] = 0 + OnionRequestAPI.pathFailureCount.mutate { $0[path] = 0 } var paths = OnionRequestAPI.paths guard let pathIndex = paths.firstIndex(of: path) else { return } paths.remove(at: pathIndex) @@ -533,7 +520,7 @@ public enum OnionRequestAPI: OnionRequestAPIType { func handleUnspecificError() { guard let path = path else { return } - var pathFailureCount = OnionRequestAPI.pathFailureCount[path] ?? 0 + var pathFailureCount: UInt = (OnionRequestAPI.pathFailureCount.wrappedValue[path] ?? 0) pathFailureCount += 1 if pathFailureCount >= pathFailureThreshold { @@ -545,7 +532,7 @@ public enum OnionRequestAPI: OnionRequestAPIType { drop(path) } else { - OnionRequestAPI.pathFailureCount[path] = pathFailureCount + OnionRequestAPI.pathFailureCount.mutate { $0[path] = pathFailureCount } } } @@ -566,7 +553,7 @@ public enum OnionRequestAPI: OnionRequestAPIType { let ed25519PublicKey = message[message.index(message.startIndex, offsetBy: prefix.count)..= snodeFailureThreshold { @@ -579,7 +566,8 @@ public enum OnionRequestAPI: OnionRequestAPIType { } } else { - OnionRequestAPI.snodeFailureCount[snode] = snodeFailureCount + OnionRequestAPI.snodeFailureCount + .mutate { $0[snode] = snodeFailureCount } } } else { // Do nothing diff --git a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift index 4069525c8..ec1433086 100644 --- a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift +++ b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift @@ -209,10 +209,6 @@ class ThreadSettingsViewModelSpec: QuickSpec { beforeEach { viewModel.rightNavItems.firstValue()??.first?.action?() viewModel.textChanged("TestNew", for: .nickname) - // TODO: Enter edit mode by pressing on the first item -// viewModel.tableData.first? -// .elements.first? -// .onTap?() } it("enters the editing state") { @@ -337,12 +333,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { context("when entering edit mode") { beforeEach { viewModel.rightNavItems.firstValue()??.first?.action?() - viewModel.textChanged("TestUserNew", for: .nickname) - - // TODO: Enter edit mode by pressing on the first item -// viewModel.tableData.first? -// .elements.first? -// .onTap?() + viewModel.textChanged("TestNew", for: .nickname) } it("enters the editing state") { diff --git a/SessionUtilitiesKit/Utilities/Optional+Utilities.swift b/SessionUtilitiesKit/Utilities/Optional+Utilities.swift index ede1f4e36..b78cb6679 100644 --- a/SessionUtilitiesKit/Utilities/Optional+Utilities.swift +++ b/SessionUtilitiesKit/Utilities/Optional+Utilities.swift @@ -20,6 +20,11 @@ extension Optional { public func defaulting(to value: Wrapped) -> Wrapped { return (self ?? value) } + + public mutating func setting(to value: Wrapped) -> Wrapped { + self = value + return value + } } extension Optional where Wrapped == String { diff --git a/SessionUtilitiesKitTests/Networking/BatchResponseSpec.swift b/SessionUtilitiesKitTests/Networking/BatchResponseSpec.swift index 2919fcf0f..a794b5d3d 100644 --- a/SessionUtilitiesKitTests/Networking/BatchResponseSpec.swift +++ b/SessionUtilitiesKitTests/Networking/BatchResponseSpec.swift @@ -1,18 +1,17 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation -import PromiseKit +import Combine import Quick import Nimble @testable import SessionUtilitiesKit -class BatchRequestInfoSpec: QuickSpec { +class BatchResponseSpec: QuickSpec { struct TestType: Codable, Equatable { let stringValue: String } - struct TestType2: Codable, Equatable { let intValue: Int let stringValue2: String @@ -136,9 +135,9 @@ class BatchRequestInfoSpec: QuickSpec { } } - // MARK: - --Promise + // MARK: - --Combine - describe("a (ResponseInfoType, Data?) Promise") { + describe("a (ResponseInfoType, Data?) Publisher") { var responseInfo: ResponseInfoType! var testType: TestType! var testType2: TestType2! @@ -146,8 +145,8 @@ class BatchRequestInfoSpec: QuickSpec { beforeEach { responseInfo = HTTP.ResponseInfo(code: 200, headers: [:]) - testType = TestType(stringValue: "Test") - testType2 = TestType2(intValue: 1, stringValue2: "Test2") + testType = TestType(stringValue: "test1") + testType2 = TestType2(intValue: 123, stringValue2: "test2") data = """ [\([ try! JSONEncoder().encode( @@ -173,46 +172,79 @@ class BatchRequestInfoSpec: QuickSpec { } it("decodes valid data correctly") { - let result = Promise.value((responseInfo, data)) + var result: HTTP.BatchResponse? + Just((responseInfo, data)) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() .decoded(as: [ HTTP.BatchSubResponse.self, HTTP.BatchSubResponse.self ]) + .sinkUntilComplete( + receiveValue: { result = $0 } + ) - expect(result.value).toNot(beNil()) - expect((result.value?[0].1 as? HTTP.BatchSubResponse)?.body) + expect(result).toNot(beNil()) + expect((result?[0].1 as? HTTP.BatchSubResponse)?.body) .to(equal(testType)) - expect((result.value?[1].1 as? HTTP.BatchSubResponse)?.body) + expect((result?[1].1 as? HTTP.BatchSubResponse)?.body) .to(equal(testType2)) } it("fails if there is no data") { - let result = Promise.value((responseInfo, nil)).decoded(as: []) + var error: Error? + Just((responseInfo, nil)) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + .decoded(as: []) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() - expect(result.error?.localizedDescription).to(equal(HTTPError.parsingFailed.localizedDescription)) + expect(error?.localizedDescription) + .to(equal(HTTPError.parsingFailed.localizedDescription)) } it("fails if the data is not JSON") { - let result = Promise.value((responseInfo, Data([1, 2, 3]))).decoded(as: []) + var error: Error? + Just((responseInfo, Data([1, 2, 3]))) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + .decoded(as: []) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() - expect(result.error?.localizedDescription).to(equal(HTTPError.parsingFailed.localizedDescription)) + expect(error?.localizedDescription) + .to(equal(HTTPError.parsingFailed.localizedDescription)) } it("fails if the data is not a JSON array") { - let result = Promise.value((responseInfo, "{}".data(using: .utf8))).decoded(as: []) + var error: Error? + Just((responseInfo, "{}".data(using: .utf8))) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + .decoded(as: []) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() - expect(result.error?.localizedDescription).to(equal(HTTPError.parsingFailed.localizedDescription)) + expect(error?.localizedDescription) + .to(equal(HTTPError.parsingFailed.localizedDescription)) } it("fails if the JSON array does not have the same number of items as the expected types") { - let result = Promise.value((responseInfo, data)) + var error: Error? + Just((responseInfo, data)) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() .decoded(as: [ HTTP.BatchSubResponse.self, HTTP.BatchSubResponse.self, HTTP.BatchSubResponse.self ]) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() - expect(result.error?.localizedDescription).to(equal(HTTPError.parsingFailed.localizedDescription)) + expect(error?.localizedDescription) + .to(equal(HTTPError.parsingFailed.localizedDescription)) } it("fails if one of the JSON array values fails to decode") { @@ -230,13 +262,20 @@ class BatchRequestInfoSpec: QuickSpec { .map { String(data: $0, encoding: .utf8)! } .joined(separator: ",")),{"test": "test"}] """.data(using: .utf8)! - let result = Promise.value((responseInfo, data)) + + var error: Error? + Just((responseInfo, data)) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() .decoded(as: [ HTTP.BatchSubResponse.self, HTTP.BatchSubResponse.self ]) + .mapError { error.setting(to: $0) } + .sinkUntilComplete() - expect(result.error?.localizedDescription).to(equal(HTTPError.parsingFailed.localizedDescription)) + expect(error?.localizedDescription) + .to(equal(HTTPError.parsingFailed.localizedDescription)) } } } diff --git a/SessionUtilitiesKitTests/Networking/HeaderSpec.swift b/SessionUtilitiesKitTests/Networking/HeaderSpec.swift index 535e30c6d..49d8fcc34 100644 --- a/SessionUtilitiesKitTests/Networking/HeaderSpec.swift +++ b/SessionUtilitiesKitTests/Networking/HeaderSpec.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import SessionUtilitiesKit import Quick import Nimble diff --git a/_SharedTestUtilities/SynchronousStorage.swift b/_SharedTestUtilities/SynchronousStorage.swift index fdda5a983..e5aa4bcc1 100644 --- a/_SharedTestUtilities/SynchronousStorage.swift +++ b/_SharedTestUtilities/SynchronousStorage.swift @@ -1,28 +1,19 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import Combine import GRDB -import PromiseKit import SessionUtilitiesKit class SynchronousStorage: Storage { - override func writeAsync(updates: @escaping (Database) throws -> T) { - super.write(updates: updates) - } - - override func writeAsync(updates: @escaping (Database) throws -> T, completion: @escaping (Database, Swift.Result) throws -> Void) { - super.write { db in - do { - var result: T? - try db.inTransaction { - result = try updates(db) - return .commit - } - try? completion(db, .success(result!)) - } - catch { - try? completion(db, .failure(error)) - } + override func writePublisher(updates: @escaping (Database) throws -> T) -> AnyPublisher { + guard let result: T = super.write(updates: updates) else { + return Fail(error: StorageError.generic) + .eraseToAnyPublisher() } + + return Just(result) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } } From 70ff2b49f053206bc8583ca456fec32b11a0356f Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 6 Dec 2022 16:59:14 +1100 Subject: [PATCH 015/135] Fixed the broken tests and resolved some warnings Stopped migration logs from appearing in unit tests # Conflicts: # Session/Settings/RemoveUsersModal.swift # SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift # SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift # SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift --- Session.xcodeproj/project.pbxproj | 3 + .../Emoji Picker/EmojiPickerSheet.swift | 3 - .../Settings/ThreadSettingsViewModel.swift | 1 + .../GIFs/GifPickerCell.swift | 2 +- .../Shared/SessionTableViewController.swift | 6 +- Session/Shared/SessionTableViewModel.swift | 1 - .../Database/LegacyDatabase/SMKLegacy.swift | 16 +- .../Migrations/_003_YDBToGRDBMigration.swift | 68 +-- .../Open Groups/OpenGroupManager.swift | 4 + .../Open Groups/OpenGroupAPISpec.swift | 110 ++-- .../Open Groups/OpenGroupManagerSpec.swift | 504 +++++++++++------- .../Migrations/_003_YDBToGRDBMigration.swift | 2 +- ...eadDisappearingMessagesViewModelSpec.swift | 8 +- .../ThreadSettingsViewModelSpec.swift | 28 +- .../NotificationContentViewModelSpec.swift | 6 + .../Migrations/_003_YDBToGRDBMigration.swift | 2 +- .../Database/Types/Migration.swift | 4 +- SessionUtilitiesKit/General/Logging.swift | 9 + .../PersistableRecordUtilitiesSpec.swift | 8 +- .../DirectionalPanGestureRecognizer.swift | 4 +- _SharedTestUtilities/CombineExtensions.swift | 11 + 21 files changed, 474 insertions(+), 326 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index de3c188b6..2de43e709 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -7462,6 +7462,7 @@ MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS $(inherited) -D SQLITE_HAS_CODEC -D GRDBCIPHER -D SQLITE_ENABLE_FTS5 -Xfrontend -warn-long-expression-type-checking=100"; PRODUCT_BUNDLE_IDENTIFIER = io.oxen.SessionTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; @@ -7570,6 +7571,7 @@ MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS $(inherited) -D SQLITE_HAS_CODEC -D GRDBCIPHER -D SQLITE_ENABLE_FTS5 -Xfrontend -warn-long-expression-type-checking=100"; PRODUCT_BUNDLE_IDENTIFIER = io.oxen.SessionUtilitiesKitTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; @@ -7676,6 +7678,7 @@ MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS $(inherited) -D SQLITE_HAS_CODEC -D GRDBCIPHER -D SQLITE_ENABLE_FTS5 -Xfrontend -warn-long-expression-type-checking=100"; PRODUCT_BUNDLE_IDENTIFIER = io.oxen.SessionMessagingKitTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; diff --git a/Session/Conversations/Emoji Picker/EmojiPickerSheet.swift b/Session/Conversations/Emoji Picker/EmojiPickerSheet.swift index ecf7ae71b..89cdebba5 100644 --- a/Session/Conversations/Emoji Picker/EmojiPickerSheet.swift +++ b/Session/Conversations/Emoji Picker/EmojiPickerSheet.swift @@ -177,9 +177,6 @@ class EmojiPickerSheet: BaseVC { let curveValue: Int = ((userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? Int) ?? Int(UIView.AnimationOptions.curveEaseInOut.rawValue)) let options: UIView.AnimationOptions = UIView.AnimationOptions(rawValue: UInt(curveValue << 16)) - let keyboardRect: CGRect = ((userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect) ?? CGRect.zero) - let keyboardTop = (UIScreen.main.bounds.height - keyboardRect.minY) - UIView.animate( withDuration: duration, delay: 0, diff --git a/Session/Conversations/Settings/ThreadSettingsViewModel.swift b/Session/Conversations/Settings/ThreadSettingsViewModel.swift index 760e639e9..7e1a205e2 100644 --- a/Session/Conversations/Settings/ThreadSettingsViewModel.swift +++ b/Session/Conversations/Settings/ThreadSettingsViewModel.swift @@ -84,6 +84,7 @@ class ThreadSettingsViewModel: SessionTableViewModel = PassthroughSubject() private let _isEditing: CurrentValueSubject = CurrentValueSubject(false) lazy var isEditing: AnyPublisher = _isEditing .removeDuplicates() diff --git a/SessionMessagingKit/Database/LegacyDatabase/SMKLegacy.swift b/SessionMessagingKit/Database/LegacyDatabase/SMKLegacy.swift index 65d49217c..7c92fe17e 100644 --- a/SessionMessagingKit/Database/LegacyDatabase/SMKLegacy.swift +++ b/SessionMessagingKit/Database/LegacyDatabase/SMKLegacy.swift @@ -496,7 +496,7 @@ public enum SMKLegacy { let members: [Data] = self.members, let admins: [Data] = self.admins else { - SNLog("[Migration Error] Unable to decode Legacy ClosedGroupControlMessage") + SNLogNotTests("[Migration Error] Unable to decode Legacy ClosedGroupControlMessage") throw StorageError.migrationFailed } @@ -514,7 +514,7 @@ public enum SMKLegacy { case "encryptionKeyPair": guard let wrappers: [_KeyPairWrapper] = self.wrappers else { - SNLog("[Migration Error] Unable to decode Legacy ClosedGroupControlMessage") + SNLogNotTests("[Migration Error] Unable to decode Legacy ClosedGroupControlMessage") throw StorageError.migrationFailed } @@ -525,7 +525,7 @@ public enum SMKLegacy { let publicKey: String = wrapper.publicKey, let encryptedKeyPair: Data = wrapper.encryptedKeyPair else { - SNLog("[Migration Error] Unable to decode Legacy ClosedGroupControlMessage") + SNLogNotTests("[Migration Error] Unable to decode Legacy ClosedGroupControlMessage") throw StorageError.migrationFailed } @@ -538,7 +538,7 @@ public enum SMKLegacy { case "nameChange": guard let name: String = self.name else { - SNLog("[Migration Error] Unable to decode Legacy ClosedGroupControlMessage") + SNLogNotTests("[Migration Error] Unable to decode Legacy ClosedGroupControlMessage") throw StorageError.migrationFailed } @@ -548,7 +548,7 @@ public enum SMKLegacy { case "membersAdded": guard let members: [Data] = self.members else { - SNLog("[Migration Error] Unable to decode Legacy ClosedGroupControlMessage") + SNLogNotTests("[Migration Error] Unable to decode Legacy ClosedGroupControlMessage") throw StorageError.migrationFailed } @@ -556,7 +556,7 @@ public enum SMKLegacy { case "membersRemoved": guard let members: [Data] = self.members else { - SNLog("[Migration Error] Unable to decode Legacy ClosedGroupControlMessage") + SNLogNotTests("[Migration Error] Unable to decode Legacy ClosedGroupControlMessage") throw StorageError.migrationFailed } @@ -602,7 +602,7 @@ public enum SMKLegacy { case "screenshot": return .screenshot case "mediaSaved": guard let timestamp: UInt64 = self.timestamp else { - SNLog("[Migration Error] Unable to decode Legacy DataExtractionNotification") + SNLogNotTests("[Migration Error] Unable to decode Legacy DataExtractionNotification") throw StorageError.migrationFailed } @@ -1646,7 +1646,7 @@ public enum SMKLegacy { } else if _MessageSendJob.process(rawDestination, type: "openGroup") != nil { // We can no longer support sending messages to legacy open groups - SNLog("[Migration Warning] Ignoring pending messageSend job for V1 OpenGroup") + SNLogNotTests("[Migration Warning] Ignoring pending messageSend job for V1 OpenGroup") return nil } else if let destString: String = _MessageSendJob.process(rawDestination, type: "openGroupV2") { diff --git a/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift b/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift index 00f08a2f5..b428c4713 100644 --- a/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift +++ b/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift @@ -21,7 +21,7 @@ enum _003_YDBToGRDBMigration: Migration { // We want this setting to be on by default (even if there isn't a legacy database) db[.trimOpenGroupMessagesOlderThanSixMonths] = true - SNLog("[Migration Warning] No legacy database, skipping \(target.key(with: self))") + SNLogNotTests("[Migration Warning] No legacy database, skipping \(target.key(with: self))") return } @@ -76,7 +76,7 @@ enum _003_YDBToGRDBMigration: Migration { // same changes if the migration hasn't already run) transaction.enumerateKeys(inCollection: SMKLegacy.databaseMigrationCollection) { key, _ in guard let legacyMigration: SMKLegacy._DBMigration = SMKLegacy._DBMigration(rawValue: key) else { - SNLog("[Migration Error] Found unknown migration") + SNLogNotTests("[Migration Error] Found unknown migration") shouldFailMigration = true return } @@ -87,7 +87,7 @@ enum _003_YDBToGRDBMigration: Migration { // MARK: --Contacts - SNLog("[Migration Info] \(target.key(with: self)) - Processing Contacts") + SNLogNotTests("[Migration Info] \(target.key(with: self)) - Processing Contacts") transaction.enumerateRows(inCollection: SMKLegacy.contactCollection) { _, object, _, _ in guard let contact = object as? SMKLegacy._Contact else { return } @@ -106,7 +106,7 @@ enum _003_YDBToGRDBMigration: Migration { // MARK: --Threads - SNLog("[Migration Info] \(target.key(with: self)) - Processing Threads") + SNLogNotTests("[Migration Info] \(target.key(with: self)) - Processing Threads") transaction.enumerateKeysAndObjects(inCollection: SMKLegacy.threadCollection) { key, object, _ in guard let thread: SMKLegacy._Thread = object as? SMKLegacy._Thread else { return } @@ -143,7 +143,7 @@ enum _003_YDBToGRDBMigration: Migration { let groupId: String = String(data: groupIdData, encoding: .utf8), let publicKey: String = groupId.split(separator: "!").last.map({ String($0) }) else { - SNLog("[Migration Error] Unable to decode Closed Group") + SNLogNotTests("[Migration Error] Unable to decode Closed Group") shouldFailMigration = true return } @@ -173,7 +173,7 @@ enum _003_YDBToGRDBMigration: Migration { } else if groupThread.isOpenGroup { guard let openGroup: SMKLegacy._OpenGroup = transaction.object(forKey: thread.uniqueId, inCollection: SMKLegacy.openGroupCollection) as? SMKLegacy._OpenGroup else { - SNLog("[Migration Error] Unable to find open group info") + SNLogNotTests("[Migration Error] Unable to find open group info") shouldFailMigration = true return } @@ -204,7 +204,7 @@ enum _003_YDBToGRDBMigration: Migration { // MARK: --Interactions - SNLog("[Migration Info] \(target.key(with: self)) - Processing Interactions") + SNLogNotTests("[Migration Info] \(target.key(with: self)) - Processing Interactions") /// **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, due to this, if the database is very large, importing thecollections can be @@ -222,7 +222,7 @@ enum _003_YDBToGRDBMigration: Migration { transaction.enumerateKeysAndObjects(inCollection: SMKLegacy.interactionCollection) { _, object, _ in guard let interaction: SMKLegacy._DBInteraction = object as? SMKLegacy._DBInteraction else { - SNLog("[Migration Error] Unable to process interaction") + SNLogNotTests("[Migration Error] Unable to process interaction") shouldFailMigration = true return } @@ -262,11 +262,11 @@ enum _003_YDBToGRDBMigration: Migration { // MARK: --Attachments - SNLog("[Migration Info] \(target.key(with: self)) - Processing Attachments") + SNLogNotTests("[Migration Info] \(target.key(with: self)) - Processing Attachments") 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") + SNLogNotTests("[Migration Error] Unable to process attachment") shouldFailMigration = true return } @@ -306,7 +306,7 @@ enum _003_YDBToGRDBMigration: Migration { // MARK: --Jobs - SNLog("[Migration Info] \(target.key(with: self)) - Processing Jobs") + SNLogNotTests("[Migration Info] \(target.key(with: self)) - Processing Jobs") transaction.enumerateRows(inCollection: SMKLegacy.notifyPushServerJobCollection) { _, object, _, _ in guard let job = object as? SMKLegacy._NotifyPNServerJob else { return } @@ -336,7 +336,7 @@ enum _003_YDBToGRDBMigration: Migration { // MARK: --Preferences - SNLog("[Migration Info] \(target.key(with: self)) - Processing Preferences") + SNLogNotTests("[Migration Info] \(target.key(with: self)) - Processing Preferences") transaction.enumerateKeysAndObjects(inCollection: SMKLegacy.preferencesCollection) { key, object, _ in legacyPreferences[key] = object @@ -403,7 +403,7 @@ enum _003_YDBToGRDBMigration: Migration { // MARK: - Insert Contacts - SNLog("[Migration Info] \(target.key(with: self)) - Inserting Contacts") + SNLogNotTests("[Migration Info] \(target.key(with: self)) - Inserting Contacts") try autoreleasepool { // Values for contact progress @@ -509,7 +509,7 @@ enum _003_YDBToGRDBMigration: Migration { // MARK: - Insert Threads - SNLog("[Migration Info] \(target.key(with: self)) - Inserting Threads & Interactions") + SNLogNotTests("[Migration Info] \(target.key(with: self)) - Inserting Threads & Interactions") var legacyInteractionToIdMap: [String: Int64] = [:] var legacyInteractionIdentifierToIdMap: [String: Int64] = [:] @@ -557,7 +557,7 @@ enum _003_YDBToGRDBMigration: Migration { // 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 { - SNLog("[Migration Error] Unable to migrate thread with no id mapping") + SNLogNotTests("[Migration Error] Unable to migrate thread with no id mapping") throw StorageError.migrationFailed } @@ -610,7 +610,7 @@ enum _003_YDBToGRDBMigration: Migration { let groupModel: SMKLegacy._GroupModel = closedGroupModel[legacyThread.uniqueId], let formationTimestamp: UInt64 = closedGroupFormation[legacyThread.uniqueId] else { - SNLog("[Migration Error] Closed group missing required data") + SNLogNotTests("[Migration Error] Closed group missing required data") throw StorageError.migrationFailed } @@ -635,7 +635,7 @@ enum _003_YDBToGRDBMigration: Migration { // Create the 'GroupMember' models for the group (even if the current user is no longer // a member as these objects are used to generate the group avatar icon) func createDummyProfile(profileId: String) { - SNLog("[Migration Warning] Closed group member with unknown user found - Creating empty profile") + SNLogNotTests("[Migration Warning] Closed group member with unknown user found - Creating empty profile") // Note: Need to upsert here because it's possible multiple quotes // will use the same invalid 'authorId' value resulting in a unique @@ -692,7 +692,7 @@ enum _003_YDBToGRDBMigration: Migration { let openGroup: SMKLegacy._OpenGroup = openGroupInfo[legacyThread.uniqueId], let targetOpenGroupServer: String = openGroupServer[legacyThread.uniqueId] else { - SNLog("[Migration Error] Open group missing required data") + SNLogNotTests("[Migration Error] Open group missing required data") throw StorageError.migrationFailed } @@ -877,7 +877,7 @@ enum _003_YDBToGRDBMigration: Migration { } default: - SNLog("[Migration Error] Unsupported interaction type") + SNLogNotTests("[Migration Error] Unsupported interaction type") throw StorageError.migrationFailed } @@ -940,11 +940,11 @@ enum _003_YDBToGRDBMigration: Migration { switch error { // Ignore duplicate interactions case DatabaseError.SQLITE_CONSTRAINT_UNIQUE: - SNLog("[Migration Warning] Found duplicate message of variant: \(variant); skipping") + SNLogNotTests("[Migration Warning] Found duplicate message of variant: \(variant); skipping") return default: - SNLog("[Migration Error] Failed to insert interaction") + SNLogNotTests("[Migration Error] Failed to insert interaction") throw StorageError.migrationFailed } } @@ -961,7 +961,7 @@ enum _003_YDBToGRDBMigration: Migration { receivedMessageTimestamps.remove(legacyInteraction.timestamp) guard let interactionId: Int64 = interaction.id else { - SNLog("[Migration Error] Failed to insert interaction") + SNLogNotTests("[Migration Error] Failed to insert interaction") throw StorageError.migrationFailed } @@ -1047,7 +1047,7 @@ enum _003_YDBToGRDBMigration: Migration { // an older message not cached to the device) - this will cause a foreign // key constraint violation so in these cases just create an empty profile if !validProfileIds.contains(quotedMessage.authorId) { - SNLog("[Migration Warning] Quote with unknown author found - Creating empty profile") + SNLogNotTests("[Migration Warning] Quote with unknown author found - Creating empty profile") // Note: Need to upsert here because it's possible multiple quotes // will use the same invalid 'authorId' value resulting in a unique @@ -1078,7 +1078,7 @@ enum _003_YDBToGRDBMigration: Migration { .attachmentIds .first - SNLog([ + SNLogNotTests([ "[Migration Warning] Quote with invalid attachmentId found", (quoteAttachmentId == nil ? "Unable to reconcile, leaving attachment blank" : @@ -1157,7 +1157,7 @@ enum _003_YDBToGRDBMigration: Migration { ) guard let attachmentId: String = maybeAttachmentId else { - SNLog("[Migration Warning] Failed to create invalid attachment for missing attachment") + SNLogNotTests("[Migration Warning] Failed to create invalid attachment for missing attachment") return } @@ -1214,7 +1214,7 @@ enum _003_YDBToGRDBMigration: Migration { // MARK: - Insert Jobs - SNLog("[Migration Info] \(target.key(with: self)) - Inserting Jobs") + SNLogNotTests("[Migration Info] \(target.key(with: self)) - Inserting Jobs") // MARK: --notifyPushServer @@ -1333,7 +1333,7 @@ enum _003_YDBToGRDBMigration: Migration { switch legacyJob.message { case is SMKLegacy._VisibleMessage: guard interactionId != nil else { - SNLog("[Migration Warning] Unable to find associated interaction to messageSend job, ignoring.") + SNLogNotTests("[Migration Warning] Unable to find associated interaction to messageSend job, ignoring.") return } @@ -1369,7 +1369,7 @@ enum _003_YDBToGRDBMigration: Migration { try autoreleasepool { try attachmentUploadJobs.forEach { legacyJob in guard let sendJob: Job = messageSendJobLegacyMap[legacyJob.messageSendJobID], let sendJobId: Int64 = sendJob.id else { - SNLog("[Migration Error] attachmentUpload job missing associated MessageSendJob") + SNLogNotTests("[Migration Error] attachmentUpload job missing associated MessageSendJob") throw StorageError.migrationFailed } @@ -1387,7 +1387,7 @@ enum _003_YDBToGRDBMigration: Migration { // Add the dependency to the relevant MessageSendJob guard let uploadJobId: Int64 = uploadJob?.id else { - SNLog("[Migration Error] attachmentUpload job was not created") + SNLogNotTests("[Migration Error] attachmentUpload job was not created") throw StorageError.migrationFailed } @@ -1404,12 +1404,12 @@ enum _003_YDBToGRDBMigration: Migration { try attachmentDownloadJobs.forEach { legacyJob in guard let interactionId: Int64 = legacyInteractionToIdMap[legacyJob.tsMessageID] else { // This can happen if an UnsendRequest came before an AttachmentDownloadJob completed - SNLog("[Migration Warning] attachmentDownload job with no interaction found - ignoring") + SNLogNotTests("[Migration Warning] attachmentDownload job with no interaction found - ignoring") return } guard processedAttachmentIds.contains(legacyJob.attachmentID) else { // Unsure how this case can occur but it seemed to happen when testing internally - SNLog("[Migration Warning] attachmentDownload job unable to find attachment - ignoring") + SNLogNotTests("[Migration Warning] attachmentDownload job unable to find attachment - ignoring") return } @@ -1446,7 +1446,7 @@ enum _003_YDBToGRDBMigration: Migration { // MARK: - Preferences - SNLog("[Migration Info] \(target.key(with: self)) - Inserting Preferences") + SNLogNotTests("[Migration Info] \(target.key(with: self)) - Inserting Preferences") db[.defaultNotificationSound] = Preferences.Sound(rawValue: legacyPreferences[SMKLegacy.soundsGlobalNotificationKey] as? Int ?? -1) .defaulting(to: Preferences.Sound.defaultNotificationSound) @@ -1502,7 +1502,7 @@ enum _003_YDBToGRDBMigration: Migration { guard let legacyAttachmentId: String = legacyAttachmentId else { return nil } guard !processedAttachmentIds.contains(legacyAttachmentId) else { guard isQuotedMessage else { - SNLog("[Migration Error] Attempted to process duplicate attachment") + SNLogNotTests("[Migration Error] Attempted to process duplicate attachment") throw StorageError.migrationFailed } @@ -1510,7 +1510,7 @@ enum _003_YDBToGRDBMigration: Migration { } guard let legacyAttachment: SMKLegacy._Attachment = attachments[legacyAttachmentId] else { - SNLog("[Migration Warning] Missing attachment - interaction will show a \"failed\" attachment") + SNLogNotTests("[Migration Warning] Missing attachment - interaction will show a \"failed\" attachment") return nil } diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index 06a81f6e4..845f461a5 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -509,6 +509,10 @@ public final class OpenGroupManager { on: server, using: dependencies ) + // Note: We need to subscribe and receive on different threads to ensure the + // logic in 'receiveValue' doesn't result in a reentrancy database issue + .subscribe(on: OpenGroupAPI.workQueue) + .receive(on: DispatchQueue.global(qos: .default)) .sinkUntilComplete( receiveCompletion: { _ in if waitForImageToComplete { diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift b/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift index 288ac22ea..d0bee4fff 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift +++ b/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift @@ -865,7 +865,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -937,7 +937,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -1031,7 +1031,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -1089,7 +1089,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -1163,7 +1163,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -1253,7 +1253,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -1319,7 +1319,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -1366,7 +1366,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -1408,7 +1408,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -1445,7 +1445,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -1480,7 +1480,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -1522,7 +1522,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -1564,7 +1564,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -1601,7 +1601,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -1644,7 +1644,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -1696,7 +1696,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -1752,7 +1752,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -1795,7 +1795,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -1836,7 +1836,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -1872,7 +1872,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -1906,7 +1906,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -1947,7 +1947,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -1988,7 +1988,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -2024,7 +2024,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -2066,7 +2066,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -2103,7 +2103,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -2149,7 +2149,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -2191,7 +2191,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -2231,7 +2231,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -2270,7 +2270,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -2312,7 +2312,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -2350,7 +2350,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -2389,7 +2389,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -2428,7 +2428,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -2487,7 +2487,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -2539,7 +2539,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -2571,7 +2571,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -2605,7 +2605,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -2653,7 +2653,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -2684,7 +2684,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -2717,7 +2717,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -2768,7 +2768,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -2802,7 +2802,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -2838,7 +2838,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -2874,7 +2874,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -2937,7 +2937,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -2968,7 +2968,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -3025,7 +3025,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -3054,7 +3054,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -3083,7 +3083,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -3116,7 +3116,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -3154,7 +3154,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -3189,7 +3189,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -3228,7 +3228,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() @@ -3257,7 +3257,7 @@ class OpenGroupAPISpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .handleEvents(receiveOutput: { result in pollResponse = result }) + .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkUntilComplete() diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift index cce00741c..5a9b19206 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift +++ b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift @@ -83,6 +83,7 @@ class OpenGroupManagerSpec: QuickSpec { var mockNonce24Generator: MockNonce24Generator! var mockUserDefaults: MockUserDefaults! var dependencies: OpenGroupManager.OGMDependencies! + var disposables: [AnyCancellable] = [] var testInteraction1: Interaction! var testGroupThread: SessionThread! @@ -235,7 +236,13 @@ class OpenGroupManagerSpec: QuickSpec { mockGeneralCache.when { $0.encodedPublicKey }.thenReturn("05\(TestConstants.publicKey)") mockGenericHash.when { $0.hash(message: anyArray(), outputLength: any()) }.thenReturn([]) mockSodium - .when { $0.blindedKeyPair(serverPublicKey: any(), edKeyPair: any(), genericHash: mockGenericHash) } + .when { [mockGenericHash = mockGenericHash!] sodium in + sodium.blindedKeyPair( + serverPublicKey: any(), + edKeyPair: any(), + genericHash: mockGenericHash + ) + } .thenReturn( Box.KeyPair( publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes, @@ -266,6 +273,8 @@ class OpenGroupManagerSpec: QuickSpec { } afterEach { + disposables.forEach { $0.cancel() } + OpenGroupManager.shared.stopPolling() // Need to stop any pollers which get created during tests openGroupManager.stopPolling() // Assuming it's different from the above @@ -277,6 +286,7 @@ class OpenGroupManagerSpec: QuickSpec { mockSign = nil mockUserDefaults = nil dependencies = nil + disposables = [] testInteraction1 = nil testGroupThread = nil @@ -290,7 +300,9 @@ class OpenGroupManagerSpec: QuickSpec { context("cache data") { it("defaults the time since last open to greatestFiniteMagnitude") { mockUserDefaults - .when { $0.object(forKey: SNUserDefaults.Date.lastOpen.rawValue) } + .when { (defaults: inout any UserDefaultsType) -> Any? in + defaults.object(forKey: SNUserDefaults.Date.lastOpen.rawValue) + } .thenReturn(nil) expect(cache.getTimeSinceLastOpen(using: dependencies)) @@ -299,7 +311,9 @@ class OpenGroupManagerSpec: QuickSpec { it("returns the time since the last open") { mockUserDefaults - .when { $0.object(forKey: SNUserDefaults.Date.lastOpen.rawValue) } + .when { (defaults: inout any UserDefaultsType) -> Any? in + defaults.object(forKey: SNUserDefaults.Date.lastOpen.rawValue) + } .thenReturn(Date(timeIntervalSince1970: 1234567880)) dependencies = dependencies.with(date: Date(timeIntervalSince1970: 1234567890)) @@ -309,7 +323,9 @@ class OpenGroupManagerSpec: QuickSpec { it("caches the time since the last open") { mockUserDefaults - .when { $0.object(forKey: SNUserDefaults.Date.lastOpen.rawValue) } + .when { (defaults: inout any UserDefaultsType) -> Any? in + defaults.object(forKey: SNUserDefaults.Date.lastOpen.rawValue) + } .thenReturn(Date(timeIntervalSince1970: 1234567770)) dependencies = dependencies.with(date: Date(timeIntervalSince1970: 1234567780)) @@ -317,7 +333,9 @@ class OpenGroupManagerSpec: QuickSpec { .to(beCloseTo(10)) mockUserDefaults - .when { $0.object(forKey: SNUserDefaults.Date.lastOpen.rawValue) } + .when { (defaults: inout any UserDefaultsType) -> Any? in + defaults.object(forKey: SNUserDefaults.Date.lastOpen.rawValue) + } .thenReturn(Date(timeIntervalSince1970: 1234567890)) // Cached value shouldn't have been updated @@ -352,7 +370,9 @@ class OpenGroupManagerSpec: QuickSpec { mockOGMCache.when { $0.pollers }.thenReturn([:]) mockUserDefaults - .when { $0.object(forKey: SNUserDefaults.Date.lastOpen.rawValue) } + .when { (defaults: inout any UserDefaultsType) -> Any? in + defaults.object(forKey: SNUserDefaults.Date.lastOpen.rawValue) + } .thenReturn(Date(timeIntervalSince1970: 1234567890)) } @@ -404,7 +424,9 @@ class OpenGroupManagerSpec: QuickSpec { mockOGMCache.when { $0.pollers }.thenReturn(["testserver": OpenGroupAPI.Poller(for: "testserver")]) mockUserDefaults - .when { $0.object(forKey: SNUserDefaults.Date.lastOpen.rawValue) } + .when { (defaults: inout any UserDefaultsType) -> Any? in + defaults.object(forKey: SNUserDefaults.Date.lastOpen.rawValue) + } .thenReturn(Date(timeIntervalSince1970: 1234567890)) openGroupManager.startPolling(using: dependencies) @@ -495,7 +517,7 @@ class OpenGroupManagerSpec: QuickSpec { it("returns true when no scheme is provided") { expect( - mockStorage.read { db in + mockStorage.read { db -> Bool in openGroupManager .hasExistingOpenGroup( db, @@ -510,7 +532,7 @@ class OpenGroupManagerSpec: QuickSpec { it("returns true when a http scheme is provided") { expect( - mockStorage.read { db in + mockStorage.read { db -> Bool in openGroupManager .hasExistingOpenGroup( db, @@ -525,7 +547,7 @@ class OpenGroupManagerSpec: QuickSpec { it("returns true when a https scheme is provided") { expect( - mockStorage.read { db in + mockStorage.read { db -> Bool in openGroupManager .hasExistingOpenGroup( db, @@ -546,7 +568,7 @@ class OpenGroupManagerSpec: QuickSpec { it("returns true when no scheme is provided") { expect( - mockStorage.read { db in + mockStorage.read { db -> Bool in openGroupManager .hasExistingOpenGroup( db, @@ -561,7 +583,7 @@ class OpenGroupManagerSpec: QuickSpec { it("returns true when a http scheme is provided") { expect( - mockStorage.read { db in + mockStorage.read { db -> Bool in openGroupManager .hasExistingOpenGroup( db, @@ -576,7 +598,7 @@ class OpenGroupManagerSpec: QuickSpec { it("returns true when a https scheme is provided") { expect( - mockStorage.read { db in + mockStorage.read { db -> Bool in openGroupManager .hasExistingOpenGroup( db, @@ -597,7 +619,7 @@ class OpenGroupManagerSpec: QuickSpec { it("returns true when no scheme is provided") { expect( - mockStorage.read { db in + mockStorage.read { db -> Bool in openGroupManager .hasExistingOpenGroup( db, @@ -612,7 +634,7 @@ class OpenGroupManagerSpec: QuickSpec { it("returns true when a http scheme is provided") { expect( - mockStorage.read { db in + mockStorage.read { db -> Bool in openGroupManager .hasExistingOpenGroup( db, @@ -627,7 +649,7 @@ class OpenGroupManagerSpec: QuickSpec { it("returns true when a https scheme is provided") { expect( - mockStorage.read { db in + mockStorage.read { db -> Bool in openGroupManager .hasExistingOpenGroup( db, @@ -660,7 +682,7 @@ class OpenGroupManagerSpec: QuickSpec { } expect( - mockStorage.read { db in + mockStorage.read { db -> Bool in openGroupManager .hasExistingOpenGroup( db, @@ -692,7 +714,7 @@ class OpenGroupManagerSpec: QuickSpec { } expect( - mockStorage.read { db in + mockStorage.read { db -> Bool in openGroupManager .hasExistingOpenGroup( db, @@ -710,7 +732,7 @@ class OpenGroupManagerSpec: QuickSpec { mockOGMCache.when { $0.pollers }.thenReturn(["testserver": OpenGroupAPI.Poller(for: "testserver")]) expect( - mockStorage.read { db in + mockStorage.read { db -> Bool in openGroupManager .hasExistingOpenGroup( db, @@ -727,7 +749,7 @@ class OpenGroupManagerSpec: QuickSpec { mockOGMCache.when { $0.pollers }.thenReturn([:]) expect( - mockStorage.read { db in + mockStorage.read { db -> Bool in openGroupManager .hasExistingOpenGroup( db, @@ -747,7 +769,7 @@ class OpenGroupManagerSpec: QuickSpec { } expect( - mockStorage.read { db in + mockStorage.read { db -> Bool in openGroupManager .hasExistingOpenGroup( db, @@ -772,7 +794,9 @@ class OpenGroupManagerSpec: QuickSpec { mockOGMCache.when { $0.pollers }.thenReturn([:]) mockUserDefaults - .when { $0.object(forKey: SNUserDefaults.Date.lastOpen.rawValue) } + .when { (defaults: inout any UserDefaultsType) -> Any? in + defaults.object(forKey: SNUserDefaults.Date.lastOpen.rawValue) + } .thenReturn(Date(timeIntervalSince1970: 1234567890)) } @@ -780,7 +804,7 @@ class OpenGroupManagerSpec: QuickSpec { var didComplete: Bool = false // Prevent multi-threading test bugs mockStorage - .writePublisherFlatMap { db in + .writePublisherFlatMap { (db: Database) -> AnyPublisher in openGroupManager .add( db, @@ -798,7 +822,7 @@ class OpenGroupManagerSpec: QuickSpec { expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50)) expect( mockStorage - .read { db in + .read { (db: Database) in try OpenGroup .select(.threadId) .asRequest(of: String.self) @@ -812,7 +836,7 @@ class OpenGroupManagerSpec: QuickSpec { var didComplete: Bool = false // Prevent multi-threading test bugs mockStorage - .writePublisherFlatMap { db in + .writePublisherFlatMap { (db: Database) -> AnyPublisher in openGroupManager .add( db, @@ -850,7 +874,7 @@ class OpenGroupManagerSpec: QuickSpec { var didComplete: Bool = false // Prevent multi-threading test bugs mockStorage - .writePublisherFlatMap { db in + .writePublisherFlatMap { (db: Database) -> AnyPublisher in openGroupManager .add( db, @@ -897,7 +921,9 @@ class OpenGroupManagerSpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockUserDefaults - .when { $0.object(forKey: SNUserDefaults.Date.lastOpen.rawValue) } + .when { (defaults: inout any UserDefaultsType) -> Any? in + defaults.object(forKey: SNUserDefaults.Date.lastOpen.rawValue) + } .thenReturn(Date(timeIntervalSince1970: 1234567890)) } @@ -905,7 +931,7 @@ class OpenGroupManagerSpec: QuickSpec { var error: Error? mockStorage - .writePublisherFlatMap { db in + .writePublisherFlatMap { (db: Database) -> AnyPublisher in openGroupManager .add( db, @@ -918,7 +944,7 @@ class OpenGroupManagerSpec: QuickSpec { } .subscribe(on: DispatchQueue.main) .receiveOnMain(immediately: true) - .mapError { error.setting(to: $0) } + .mapError { result -> Error in error.setting(to: result) } .sinkUntilComplete() expect(error?.localizedDescription) @@ -976,7 +1002,7 @@ class OpenGroupManagerSpec: QuickSpec { ) } - expect(mockStorage.read { db in try SessionThread.fetchCount(db) }) + expect(mockStorage.read { db -> Int in try SessionThread.fetchCount(db) }) .to(equal(0)) } @@ -1158,7 +1184,9 @@ class OpenGroupManagerSpec: QuickSpec { mockOGMCache.when { $0.pollers }.thenReturn([:]) mockUserDefaults - .when { $0.object(forKey: SNUserDefaults.Date.lastOpen.rawValue) } + .when { (defaults: inout any UserDefaultsType) -> Any? in + defaults.object(forKey: SNUserDefaults.Date.lastOpen.rawValue) + } .thenReturn(nil) } @@ -1311,7 +1339,7 @@ class OpenGroupManagerSpec: QuickSpec { expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50)) expect( - mockStorage.read { db in + mockStorage.read { db -> GroupMember? in try GroupMember .filter(GroupMember.Columns.groupId == OpenGroup.idFor( roomToken: "testRoom", @@ -1370,7 +1398,7 @@ class OpenGroupManagerSpec: QuickSpec { expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50)) expect( - mockStorage.read { db in + mockStorage.read { db -> GroupMember? in try GroupMember .filter(GroupMember.Columns.groupId == OpenGroup.idFor( roomToken: "testRoom", @@ -1423,7 +1451,7 @@ class OpenGroupManagerSpec: QuickSpec { } expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50)) - expect(mockStorage.read { db in try GroupMember.fetchCount(db) }) + expect(mockStorage.read { db -> Int in try GroupMember.fetchCount(db) }) .to(equal(0)) } } @@ -1467,7 +1495,7 @@ class OpenGroupManagerSpec: QuickSpec { expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50)) expect( - mockStorage.read { db in + mockStorage.read { db -> GroupMember? in try GroupMember .filter(GroupMember.Columns.groupId == OpenGroup.idFor( roomToken: "testRoom", @@ -1526,7 +1554,7 @@ class OpenGroupManagerSpec: QuickSpec { expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50)) expect( - mockStorage.read { db in + mockStorage.read { db -> GroupMember? in try GroupMember .filter(GroupMember.Columns.groupId == OpenGroup.idFor( roomToken: "testRoom", @@ -1579,7 +1607,7 @@ class OpenGroupManagerSpec: QuickSpec { } expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50)) - expect(mockStorage.read { db in try GroupMember.fetchCount(db) }) + expect(mockStorage.read { db -> Int in try GroupMember.fetchCount(db) }) .to(equal(0)) } } @@ -1601,7 +1629,7 @@ class OpenGroupManagerSpec: QuickSpec { ) } - expect(mockStorage.read { db in try OpenGroup.fetchCount(db) }).to(equal(0)) + expect(mockStorage.read { db -> Int in try OpenGroup.fetchCount(db) }).to(equal(0)) } } @@ -1622,7 +1650,7 @@ class OpenGroupManagerSpec: QuickSpec { expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50)) expect( - mockStorage.read { db in + mockStorage.read { db -> String? in try OpenGroup .select(.publicKey) .asRequest(of: String.self) @@ -1753,7 +1781,7 @@ class OpenGroupManagerSpec: QuickSpec { expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50)) expect( - mockStorage.read { db in + mockStorage.read { db -> String? in try OpenGroup .select(.imageId) .asRequest(of: String.self) @@ -1761,7 +1789,7 @@ class OpenGroupManagerSpec: QuickSpec { } ).to(equal("10")) expect( - mockStorage.read { db in + mockStorage.read { db -> Data? in try OpenGroup .select(.imageData) .asRequest(of: Data.self) @@ -1819,7 +1847,7 @@ class OpenGroupManagerSpec: QuickSpec { expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50)) expect( - mockStorage.read { db in + mockStorage.read { db -> String? in try OpenGroup .select(.imageId) .asRequest(of: String.self) @@ -1827,7 +1855,7 @@ class OpenGroupManagerSpec: QuickSpec { } ).to(equal("12")) expect( - mockStorage.read { db in + mockStorage.read { db -> Data? in try OpenGroup .select(.imageData) .asRequest(of: Data.self) @@ -1911,7 +1939,7 @@ class OpenGroupManagerSpec: QuickSpec { expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50)) expect( - mockStorage.read { db in + mockStorage.read { db -> String? in try OpenGroup .select(.imageId) .asRequest(of: String.self) @@ -1919,7 +1947,7 @@ class OpenGroupManagerSpec: QuickSpec { } ).to(equal("10")) expect( - mockStorage.read { db in + mockStorage.read { db -> Data? in try OpenGroup .select(.imageData) .asRequest(of: Data.self) @@ -1928,7 +1956,7 @@ class OpenGroupManagerSpec: QuickSpec { ).toNot(beNil()) expect(mockOGMCache) .toEventually( - call(.exactly(times: 1)) { $0.groupImagePromises }, + call(.exactly(times: 1)) { $0.groupImagePublishers }, timeout: .milliseconds(50) ) } @@ -1950,7 +1978,7 @@ class OpenGroupManagerSpec: QuickSpec { expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50)) expect( - mockStorage.read { db in + mockStorage.read { db -> Data? in try OpenGroup .select(.imageData) .asRequest(of: Data.self) @@ -2024,7 +2052,7 @@ class OpenGroupManagerSpec: QuickSpec { expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50)) expect( - mockStorage.read { db in + mockStorage.read { db -> Data? in try OpenGroup .select(.imageData) .asRequest(of: Data.self) @@ -2078,7 +2106,6 @@ class OpenGroupManagerSpec: QuickSpec { defaultUpload: nil ) ) - mockStorage.write { db in try OpenGroupManager.handlePollInfo( db, @@ -2093,7 +2120,7 @@ class OpenGroupManagerSpec: QuickSpec { expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50)) expect( - mockStorage.read { db in + mockStorage.read { db -> Data? in try OpenGroup .select(.imageData) .asRequest(of: Data.self) @@ -2142,7 +2169,7 @@ class OpenGroupManagerSpec: QuickSpec { } expect( - mockStorage.read { db in + mockStorage.read { db -> Int64? in try OpenGroup .select(.sequenceNumber) .asRequest(of: Int64.self) @@ -2163,7 +2190,7 @@ class OpenGroupManagerSpec: QuickSpec { } expect( - mockStorage.read { db in + mockStorage.read { db -> Int64? in try OpenGroup .select(.sequenceNumber) .asRequest(of: Int64.self) @@ -2202,7 +2229,7 @@ class OpenGroupManagerSpec: QuickSpec { ) } - expect(mockStorage.read { db in try Interaction.fetchCount(db) }).to(equal(0)) + expect(mockStorage.read { db -> Int in try Interaction.fetchCount(db) }).to(equal(0)) } it("ignores a message with invalid data") { @@ -2235,7 +2262,7 @@ class OpenGroupManagerSpec: QuickSpec { ) } - expect(mockStorage.read { db in try Interaction.fetchCount(db) }).to(equal(0)) + expect(mockStorage.read { db -> Int in try Interaction.fetchCount(db) }).to(equal(0)) } it("processes a message with valid data") { @@ -2249,7 +2276,7 @@ class OpenGroupManagerSpec: QuickSpec { ) } - expect(mockStorage.read { db in try Interaction.fetchCount(db) }).to(equal(1)) + expect(mockStorage.read { db -> Int in try Interaction.fetchCount(db) }).to(equal(1)) } it("processes valid messages when combined with invalid ones") { @@ -2279,7 +2306,7 @@ class OpenGroupManagerSpec: QuickSpec { ) } - expect(mockStorage.read { db in try Interaction.fetchCount(db) }).to(equal(1)) + expect(mockStorage.read { db -> Int in try Interaction.fetchCount(db) }).to(equal(1)) } context("with no data") { @@ -2317,7 +2344,7 @@ class OpenGroupManagerSpec: QuickSpec { ) } - expect(mockStorage.read { db in try Interaction.fetchCount(db) }).to(equal(0)) + expect(mockStorage.read { db -> Int in try Interaction.fetchCount(db) }).to(equal(0)) } it("does nothing if we do not have the message") { @@ -2346,7 +2373,7 @@ class OpenGroupManagerSpec: QuickSpec { ) } - expect(mockStorage.read { db in try Interaction.fetchCount(db) }).to(equal(0)) + expect(mockStorage.read { db -> Int in try Interaction.fetchCount(db) }).to(equal(0)) } } } @@ -2398,7 +2425,7 @@ class OpenGroupManagerSpec: QuickSpec { } expect( - mockStorage.read { db in + mockStorage.read { db -> Int64? in try OpenGroup .select(.inboxLatestMessageId) .asRequest(of: Int64.self) @@ -2406,7 +2433,7 @@ class OpenGroupManagerSpec: QuickSpec { } ).to(equal(0)) expect( - mockStorage.read { db in + mockStorage.read { db -> Int64? in try OpenGroup .select(.outboxLatestMessageId) .asRequest(of: Int64.self) @@ -2431,7 +2458,7 @@ class OpenGroupManagerSpec: QuickSpec { } expect( - mockStorage.read { db in + mockStorage.read { db -> Int64? in try OpenGroup .select(.inboxLatestMessageId) .asRequest(of: Int64.self) @@ -2439,7 +2466,7 @@ class OpenGroupManagerSpec: QuickSpec { } ).to(beNil()) expect( - mockStorage.read { db in + mockStorage.read { db -> Int64? in try OpenGroup .select(.outboxLatestMessageId) .asRequest(of: Int64.self) @@ -2468,7 +2495,7 @@ class OpenGroupManagerSpec: QuickSpec { ) } - expect(mockStorage.read { db in try Interaction.fetchCount(db) }).to(equal(0)) + expect(mockStorage.read { db -> Int in try Interaction.fetchCount(db) }).to(equal(0)) } context("for the inbox") { @@ -2494,7 +2521,7 @@ class OpenGroupManagerSpec: QuickSpec { } expect( - mockStorage.read { db in + mockStorage.read { db -> Int64? in try OpenGroup .select(.inboxLatestMessageId) .asRequest(of: Int64.self) @@ -2523,7 +2550,7 @@ class OpenGroupManagerSpec: QuickSpec { ) } - expect(mockStorage.read { db in try Interaction.fetchCount(db) }).to(equal(0)) + expect(mockStorage.read { db -> Int in try Interaction.fetchCount(db) }).to(equal(0)) } it("processes a message with valid data") { @@ -2537,7 +2564,7 @@ class OpenGroupManagerSpec: QuickSpec { ) } - expect(mockStorage.read { db in try Interaction.fetchCount(db) }).to(equal(1)) + expect(mockStorage.read { db -> Int in try Interaction.fetchCount(db) }).to(equal(1)) } it("processes valid messages when combined with invalid ones") { @@ -2561,7 +2588,7 @@ class OpenGroupManagerSpec: QuickSpec { ) } - expect(mockStorage.read { db in try Interaction.fetchCount(db) }).to(equal(1)) + expect(mockStorage.read { db -> Int in try Interaction.fetchCount(db) }).to(equal(1)) } } @@ -2588,7 +2615,7 @@ class OpenGroupManagerSpec: QuickSpec { } expect( - mockStorage.read { db in + mockStorage.read { db -> Int64? in try OpenGroup .select(.outboxLatestMessageId) .asRequest(of: Int64.self) @@ -2617,8 +2644,8 @@ class OpenGroupManagerSpec: QuickSpec { ) } - expect(mockStorage.read { db in try BlindedIdLookup.fetchCount(db) }).to(equal(1)) - expect(mockStorage.read { db in try SessionThread.fetchCount(db) }).to(equal(2)) + expect(mockStorage.read { db -> Int in try BlindedIdLookup.fetchCount(db) }).to(equal(1)) + expect(mockStorage.read { db -> Int in try SessionThread.fetchCount(db) }).to(equal(2)) } it("falls back to using the blinded id if no lookup is found") { @@ -2632,18 +2659,20 @@ class OpenGroupManagerSpec: QuickSpec { ) } - expect(mockStorage.read { db in try BlindedIdLookup.fetchCount(db) }).to(equal(1)) + expect(mockStorage.read { db -> Int in try BlindedIdLookup.fetchCount(db) }).to(equal(1)) expect(mockStorage - .read { db in + .read { db -> String? in try BlindedIdLookup .select(.sessionId) .asRequest(of: String.self) .fetchOne(db) } ).to(beNil()) - expect(mockStorage.read { db in try SessionThread.fetchCount(db) }).to(equal(2)) + expect(mockStorage.read { db -> Int in try SessionThread.fetchCount(db) }).to(equal(2)) expect( - mockStorage.read { db in try SessionThread.fetchOne(db, id: "15\(TestConstants.publicKey)") } + mockStorage.read { db -> SessionThread? in + try SessionThread.fetchOne(db, id: "15\(TestConstants.publicKey)") + } ).toNot(beNil()) } @@ -2667,7 +2696,7 @@ class OpenGroupManagerSpec: QuickSpec { ) } - expect(mockStorage.read { db in try SessionThread.fetchCount(db) }).to(equal(1)) + expect(mockStorage.read { db -> Int in try SessionThread.fetchCount(db) }).to(equal(1)) } it("processes a message with valid data") { @@ -2681,7 +2710,7 @@ class OpenGroupManagerSpec: QuickSpec { ) } - expect(mockStorage.read { db in try SessionThread.fetchCount(db) }).to(equal(2)) + expect(mockStorage.read { db -> Int in try SessionThread.fetchCount(db) }).to(equal(2)) } it("processes valid messages when combined with invalid ones") { @@ -2705,7 +2734,7 @@ class OpenGroupManagerSpec: QuickSpec { ) } - expect(mockStorage.read { db in try SessionThread.fetchCount(db) }).to(equal(2)) + expect(mockStorage.read { db -> Int in try SessionThread.fetchCount(db) }).to(equal(2)) } } } @@ -3244,35 +3273,70 @@ class OpenGroupManagerSpec: QuickSpec { .insert(db) } - mockOGMCache.when { $0.defaultRoomsPromise }.thenReturn(nil) - mockOGMCache.when { $0.groupImagePromises }.thenReturn([:]) - mockUserDefaults.when { $0.object(forKey: any()) }.thenReturn(nil) - mockUserDefaults.when { $0.set(anyAny(), forKey: any()) }.thenReturn(()) + mockOGMCache.when { $0.defaultRoomsPublisher }.thenReturn(nil) + mockOGMCache.when { $0.groupImagePublishers }.thenReturn([:]) + mockUserDefaults.when { (defaults: inout any UserDefaultsType) -> Any? in + defaults.object(forKey: any()) + }.thenReturn(nil) + mockUserDefaults.when { (defaults: inout any UserDefaultsType) -> Any? in + defaults.set(anyAny(), forKey: any()) + }.thenReturn(()) } it("caches the promise if there is no cached promise") { - let promise = OpenGroupManager.getDefaultRoomsIfNeeded(using: dependencies) + let publisher = OpenGroupManager.getDefaultRoomsIfNeeded(using: dependencies) expect(mockOGMCache) .to(call(matchingParameters: true) { - $0.defaultRoomsPromise = promise + $0.defaultRoomsPublisher = publisher }) } it("returns the cached promise if there is one") { - let publisher = Future { _ in }.eraseToAnyPublisher() + let uniqueRoomInstance: OpenGroupAPI.Room = OpenGroupAPI.Room( + token: "UniqueId", + name: "", + roomDescription: nil, + infoUpdates: 0, + messageSequence: 0, + created: 0, + activeUsers: 0, + activeUsersCutoff: 0, + imageId: nil, + pinnedMessages: nil, + admin: false, + globalAdmin: false, + admins: [], + hiddenAdmins: nil, + moderator: false, + globalModerator: false, + moderators: [], + hiddenModerators: nil, + read: false, + defaultRead: nil, + defaultAccessible: nil, + write: false, + defaultWrite: nil, + upload: false, + defaultUpload: nil + ) + let publisher = Future<[OpenGroupAPI.Room], Error> { resolver in + resolver(Result.success([uniqueRoomInstance])) + } + .shareReplay(1) + .eraseToAnyPublisher() mockOGMCache.when { $0.defaultRoomsPublisher }.thenReturn(publisher) + let publisher2 = OpenGroupManager.getDefaultRoomsIfNeeded(using: dependencies) - expect(OpenGroupManager.getDefaultRoomsIfNeeded(using: dependencies)) - .to(equal(publisher)) + expect(publisher2.firstValue()).to(equal(publisher.firstValue())) } it("stores the open group information") { OpenGroupManager.getDefaultRoomsIfNeeded(using: dependencies) - expect(mockStorage.read { db in try OpenGroup.fetchCount(db) }).to(equal(1)) + expect(mockStorage.read { db -> Int in try OpenGroup.fetchCount(db) }).to(equal(1)) expect( - mockStorage.read { db in + mockStorage.read { db -> String? in try OpenGroup .select(.server) .asRequest(of: String.self) @@ -3280,7 +3344,7 @@ class OpenGroupManagerSpec: QuickSpec { } ).to(equal("https://open.getsession.org")) expect( - mockStorage.read { db in + mockStorage.read { db -> String? in try OpenGroup .select(.publicKey) .asRequest(of: String.self) @@ -3288,7 +3352,7 @@ class OpenGroupManagerSpec: QuickSpec { } ).to(equal("a03c383cf63c3c4efe67acc52112a6dd734b3a946b9545f488aaa93da7991238")) expect( - mockStorage.read { db in + mockStorage.read { db -> Bool? in try OpenGroup .select(.isActive) .asRequest(of: Bool.self) @@ -3301,8 +3365,9 @@ class OpenGroupManagerSpec: QuickSpec { var response: [OpenGroupAPI.Room]? OpenGroupManager.getDefaultRoomsIfNeeded(using: dependencies) - .done { response = $0 } - .retainUntilComplete() + .sinkUntilComplete(receiveValue: { (data: [OpenGroupAPI.Room]) in + response = data + }) expect(response) .toEventually( @@ -3356,8 +3421,8 @@ class OpenGroupManagerSpec: QuickSpec { var error: Error? OpenGroupManager.getDefaultRoomsIfNeeded(using: dependencies) - .catch { error = $0 } - .retainUntilComplete() + .mapError { result -> Error in error.setting(to: result) } + .sinkUntilComplete() expect(error?.localizedDescription) .toEventually( @@ -3376,8 +3441,8 @@ class OpenGroupManagerSpec: QuickSpec { var error: Error? OpenGroupManager.getDefaultRoomsIfNeeded(using: dependencies) - .catch { error = $0 } - .retainUntilComplete() + .mapError { result -> Error in error.setting(to: result) } + .sinkUntilComplete() expect(error?.localizedDescription) .toEventually( @@ -3386,7 +3451,7 @@ class OpenGroupManagerSpec: QuickSpec { ) expect(mockOGMCache) .to(call(matchingParameters: true) { - $0.defaultRoomsPromise = nil + $0.defaultRoomsPublisher = nil }) } @@ -3454,7 +3519,7 @@ class OpenGroupManagerSpec: QuickSpec { OpenGroupManager .getDefaultRoomsIfNeeded(using: dependencies) - .retainUntilComplete() + .sinkUntilComplete() expect(mockUserDefaults) .toEventually( @@ -3467,7 +3532,7 @@ class OpenGroupManagerSpec: QuickSpec { timeout: .milliseconds(50) ) expect( - mockStorage.read { db in + mockStorage.read { db -> Data? in try OpenGroup .select(.imageData) .filter(id: OpenGroup.idFor(roomToken: "test2", server: OpenGroupAPI.defaultServer)) @@ -3487,9 +3552,13 @@ class OpenGroupManagerSpec: QuickSpec { } dependencies = dependencies.with(onionApi: TestImageApi.self) - mockUserDefaults.when { $0.object(forKey: any()) }.thenReturn(nil) - mockUserDefaults.when { $0.set(anyAny(), forKey: any()) }.thenReturn(()) - mockOGMCache.when { $0.groupImagePromises }.thenReturn([:]) + mockUserDefaults.when { (defaults: inout any UserDefaultsType) -> Any? in + defaults.object(forKey: any()) + }.thenReturn(nil) + mockUserDefaults.when { (defaults: inout any UserDefaultsType) -> Any? in + defaults.set(anyAny(), forKey: any()) + }.thenReturn(()) + mockOGMCache.when { $0.groupImagePublishers }.thenReturn([:]) mockStorage.write { db in _ = try OpenGroup( @@ -3506,40 +3575,50 @@ class OpenGroupManagerSpec: QuickSpec { } it("retrieves the image retrieval promise from the cache if it exists") { - let publisher = Future { _ in }.eraseToAnyPublisher() + let publisher = Future { resolver in + resolver(Result.success(Data([5, 4, 3, 2, 1]))) + } + .shareReplay(1) + .eraseToAnyPublisher() mockOGMCache .when { $0.groupImagePublishers } .thenReturn([OpenGroup.idFor(roomToken: "testRoom", server: "testServer"): publisher]) - let publisher2 = mockStorage.read { db in - OpenGroupManager - .roomImage( - db, - fileId: "1", - for: "testRoom", - on: "testServer", - using: dependencies - ) - } - expect(publisher2).to(equal(publisher)) + var result: Data? + mockStorage + .readPublisherFlatMap { (db: Database) -> AnyPublisher in + OpenGroupManager + .roomImage( + db, + fileId: "1", + for: "testRoom", + on: "testServer", + using: dependencies + ) + } + .sinkUntilComplete(receiveValue: { result = $0 }) + + expect(result).toEventually(equal(publisher.firstValue()), timeout: .milliseconds(50)) } it("does not save the fetched image to storage") { - let promise = mockStorage.read { db in - OpenGroupManager - .roomImage( - db, - fileId: "1", - for: "testRoom", - on: "testServer", - using: dependencies - ) - } - promise.retainUntilComplete() + var didComplete: Bool = false + mockStorage + .readPublisherFlatMap { (db: Database) -> AnyPublisher in + OpenGroupManager + .roomImage( + db, + fileId: "1", + for: "testRoom", + on: "testServer", + using: dependencies + ) + } + .sinkUntilComplete(receiveCompletion: { _ in didComplete = true }) - expect(promise.isFulfilled).toEventually(beTrue(), timeout: .milliseconds(50)) + expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50)) expect( - mockStorage.read { db in + mockStorage.read { db -> Data? in try OpenGroup .select(.imageData) .filter(id: OpenGroup.idFor(roomToken: "testRoom", server: "testServer")) @@ -3553,19 +3632,21 @@ class OpenGroupManagerSpec: QuickSpec { } it("does not update the image update timestamp") { - let promise = mockStorage.read { db in - OpenGroupManager - .roomImage( - db, - fileId: "1", - for: "testRoom", - on: "testServer", - using: dependencies - ) - } - promise.retainUntilComplete() + var didComplete: Bool = false + mockStorage + .readPublisherFlatMap { (db: Database) -> AnyPublisher in + OpenGroupManager + .roomImage( + db, + fileId: "1", + for: "testRoom", + on: "testServer", + using: dependencies + ) + } + .sinkUntilComplete(receiveCompletion: { _ in didComplete = true }) - expect(promise.isFulfilled).toEventually(beTrue(), timeout: .milliseconds(50)) + expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50)) expect(mockUserDefaults) .toEventuallyNot( call(matchingParameters: true) { @@ -3580,30 +3661,35 @@ class OpenGroupManagerSpec: QuickSpec { it("adds the image retrieval promise to the cache") { class TestNeverReturningApi: OnionRequestAPIType { - static func sendOnionRequest(_ request: URLRequest, to server: String, with x25519PublicKey: String) -> Promise<(ResponseInfoType, Data?)> { - return Promise<(ResponseInfoType, Data?)>.pending().promise + static func sendOnionRequest(_ request: URLRequest, to server: String, with x25519PublicKey: String) -> AnyPublisher<(ResponseInfoType, Data?), Error> { + return Future<(ResponseInfoType, Data?), Error> { _ in }.eraseToAnyPublisher() } - static func sendOnionRequest(_ payload: Data, to snode: Snode) -> Promise<(ResponseInfoType, Data?)> { - return Promise.value((HTTP.ResponseInfo(code: 200, headers: [:]), Data())) + static func sendOnionRequest(_ payload: Data, to snode: Snode) -> AnyPublisher<(ResponseInfoType, Data?), Error> { + return Just(Data()) + .setFailureType(to: Error.self) + .map { data in (HTTP.ResponseInfo(code: 0, headers: [:]), data) } + .eraseToAnyPublisher() } } dependencies = dependencies.with(onionApi: TestNeverReturningApi.self) - let promise = mockStorage.read { db in - OpenGroupManager.roomImage( - db, - fileId: "1", - for: "testRoom", - on: "testServer", - using: dependencies - ) - } - + let publisher = mockStorage + .readPublisherFlatMap { (db: Database) -> AnyPublisher in + OpenGroupManager.roomImage( + db, + fileId: "1", + for: "testRoom", + on: "testServer", + using: dependencies + ) + } + publisher.sinkAndStore(in: &disposables) + expect(mockOGMCache) .toEventually( call(matchingParameters: true) { - $0.groupImagePromises = [OpenGroup.idFor(roomToken: "testRoom", server: "testServer"): promise] + $0.groupImagePublishers = [OpenGroup.idFor(roomToken: "testRoom", server: "testServer"): publisher] }, timeout: .milliseconds(50) ) @@ -3613,8 +3699,8 @@ class OpenGroupManagerSpec: QuickSpec { it("fetches a new image if there is no cached one") { var result: Data? - let promise = mockStorage - .read { db in + mockStorage + .readPublisherFlatMap { (db: Database) -> AnyPublisher in OpenGroupManager .roomImage( db, @@ -3624,29 +3710,34 @@ class OpenGroupManagerSpec: QuickSpec { using: dependencies ) } - .done { result = $0 } - promise.retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .sinkUntilComplete(receiveValue: { (data: Data) in result = data }) - expect(promise.isFulfilled).toEventually(beTrue(), timeout: .milliseconds(50)) expect(result).toEventually(equal(Data([1, 2, 3])), timeout: .milliseconds(50)) } it("saves the fetched image to storage") { - let promise = mockStorage.read { db in - OpenGroupManager - .roomImage( - db, - fileId: "1", - for: "testRoom", - on: OpenGroupAPI.defaultServer, - using: dependencies - ) - } - promise.retainUntilComplete() + var didComplete: Bool = false - expect(promise.isFulfilled).toEventually(beTrue(), timeout: .milliseconds(50)) + mockStorage + .readPublisherFlatMap { (db: Database) -> AnyPublisher in + OpenGroupManager + .roomImage( + db, + fileId: "1", + for: "testRoom", + on: OpenGroupAPI.defaultServer, + using: dependencies + ) + } + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .sinkUntilComplete(receiveCompletion: { _ in didComplete = true }) + + expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50)) expect( - mockStorage.read { db in + mockStorage.read { db -> Data? in try OpenGroup .select(.imageData) .filter(id: OpenGroup.idFor(roomToken: "testRoom", server: OpenGroupAPI.defaultServer)) @@ -3660,19 +3751,24 @@ class OpenGroupManagerSpec: QuickSpec { } it("updates the image update timestamp") { - let promise = mockStorage.read { db in - OpenGroupManager - .roomImage( - db, - fileId: "1", - for: "testRoom", - on: OpenGroupAPI.defaultServer, - using: dependencies - ) - } - promise.retainUntilComplete() + var didComplete: Bool = false - expect(promise.isFulfilled).toEventually(beTrue(), timeout: .milliseconds(50)) + mockStorage + .readPublisherFlatMap { (db: Database) -> AnyPublisher in + OpenGroupManager + .roomImage( + db, + fileId: "1", + for: "testRoom", + on: OpenGroupAPI.defaultServer, + using: dependencies + ) + } + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .sinkUntilComplete(receiveCompletion: { _ in didComplete = true }) + + expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50)) expect(mockUserDefaults) .toEventually( call(matchingParameters: true) { @@ -3688,7 +3784,11 @@ class OpenGroupManagerSpec: QuickSpec { context("and there is a cached image") { beforeEach { dependencies = dependencies.with(date: Date(timeIntervalSince1970: 1234567890)) - mockUserDefaults.when { $0.object(forKey: any()) }.thenReturn(dependencies.date) + mockUserDefaults + .when { (defaults: inout any UserDefaultsType) -> Any? in + defaults.object(forKey: any()) + } + .thenReturn(dependencies.date) mockStorage.write(updates: { db in try OpenGroup .filter(id: OpenGroup.idFor(roomToken: "testRoom", server: OpenGroupAPI.defaultServer)) @@ -3702,8 +3802,8 @@ class OpenGroupManagerSpec: QuickSpec { it("retrieves the cached image") { var result: Data? - let promise = mockStorage - .read { db in + mockStorage + .readPublisherFlatMap { (db: Database) -> AnyPublisher in OpenGroupManager .roomImage( db, @@ -3713,26 +3813,28 @@ class OpenGroupManagerSpec: QuickSpec { using: dependencies ) } - .done { result = $0 } - promise.retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .sinkUntilComplete(receiveValue: { (data: Data) in result = data }) - expect(promise.isFulfilled).toEventually(beTrue(), timeout: .milliseconds(50)) expect(result).toEventually(equal(Data([2, 3, 4])), timeout: .milliseconds(50)) } it("fetches a new image if the cached on is older than a week") { + let weekInSeconds: TimeInterval = (7 * 24 * 60 * 60) + let targetTimestamp: TimeInterval = ( + dependencies.date.timeIntervalSince1970 - weekInSeconds - 1 + ) mockUserDefaults - .when { $0.object(forKey: any()) } - .thenReturn( - Date(timeIntervalSince1970: - (dependencies.date.timeIntervalSince1970 - (7 * 24 * 60 * 60) - 1) - ) - ) + .when { (defaults: inout any UserDefaultsType) -> Any? in + defaults.object(forKey: any()) + } + .thenReturn(Date(timeIntervalSince1970: targetTimestamp)) var result: Data? - let promise = mockStorage - .read { db in + mockStorage + .readPublisherFlatMap { (db: Database) -> AnyPublisher in OpenGroupManager .roomImage( db, @@ -3742,10 +3844,10 @@ class OpenGroupManagerSpec: QuickSpec { using: dependencies ) } - .done { result = $0 } - promise.retainUntilComplete() + .subscribe(on: DispatchQueue.main) + .receiveOnMain(immediately: true) + .sinkUntilComplete(receiveValue: { (data: Data) in result = data }) - expect(promise.isFulfilled).toEventually(beTrue(), timeout: .milliseconds(50)) expect(result).toEventually(equal(Data([1, 2, 3])), timeout: .milliseconds(50)) } } diff --git a/SessionSnodeKit/Database/Migrations/_003_YDBToGRDBMigration.swift b/SessionSnodeKit/Database/Migrations/_003_YDBToGRDBMigration.swift index b44201841..de155ba1f 100644 --- a/SessionSnodeKit/Database/Migrations/_003_YDBToGRDBMigration.swift +++ b/SessionSnodeKit/Database/Migrations/_003_YDBToGRDBMigration.swift @@ -17,7 +17,7 @@ enum _003_YDBToGRDBMigration: Migration { static func migrate(_ db: Database) throws { guard let dbConnection: YapDatabaseConnection = SUKLegacy.newDatabaseConnection() else { - SNLog("[Migration Warning] No legacy database, skipping \(target.key(with: self))") + SNLogNotTests("[Migration Warning] No legacy database, skipping \(target.key(with: self))") return } diff --git a/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift b/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift index abae1c949..062114944 100644 --- a/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift +++ b/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift @@ -86,6 +86,7 @@ class ThreadDisappearingMessagesViewModelSpec: QuickSpec { id: ThreadDisappearingMessagesViewModel.Item( title: "DISAPPEARING_MESSAGES_OFF".localized() ), + position: .top, title: "DISAPPEARING_MESSAGES_OFF".localized(), rightAccessory: .radio( isSelected: { true } @@ -101,7 +102,8 @@ class ThreadDisappearingMessagesViewModelSpec: QuickSpec { .to( equal( SessionCell.Info( - id: ThreadDisappearingMessagesViewModel.Item(title: title), + id: ThreadDisappearingMessagesSettingsViewModel.Item(title: title), + position: .bottom, title: title, rightAccessory: .radio( isSelected: { false } @@ -142,6 +144,7 @@ class ThreadDisappearingMessagesViewModelSpec: QuickSpec { id: ThreadDisappearingMessagesViewModel.Item( title: "DISAPPEARING_MESSAGES_OFF".localized() ), + position: .top, title: "DISAPPEARING_MESSAGES_OFF".localized(), rightAccessory: .radio( isSelected: { false } @@ -157,7 +160,8 @@ class ThreadDisappearingMessagesViewModelSpec: QuickSpec { .to( equal( SessionCell.Info( - id: ThreadDisappearingMessagesViewModel.Item(title: title), + id: ThreadDisappearingMessagesSettingsViewModel.Item(title: title), + position: .bottom, title: title, rightAccessory: .radio( isSelected: { true } diff --git a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift index ec1433086..645fe5ac4 100644 --- a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift +++ b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift @@ -15,7 +15,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { override func spec() { var mockStorage: Storage! var mockGeneralCache: MockGeneralCache! - var cancellables: [AnyCancellable] = [] + var disposables: [AnyCancellable] = [] var dependencies: Dependencies! var viewModel: ThreadSettingsViewModel! var didTriggerSearchCallbackTriggered: Bool = false @@ -69,21 +69,33 @@ class ThreadSettingsViewModelSpec: QuickSpec { didTriggerSearchCallbackTriggered = true } ) - cancellables.append( - viewModel.observableSettingsData + setupStandardBinding() + } + + func setupStandardBinding() { + disposables.append( + viewModel.observableTableData .receiveOnMain(immediately: true) .sink( receiveCompletion: { _ in }, - receiveValue: { viewModel.updateSettings($0) } + receiveValue: { viewModel.updateTableData($0.0) } + ) + ) + disposables.append( + viewModel.transitionToScreen + .receiveOnMain(immediately: true) + .sink( + receiveCompletion: { _ in }, + receiveValue: { transitionInfo = $0 } ) ) } afterEach { - cancellables.forEach { $0.cancel() } + disposables.forEach { $0.cancel() } mockStorage = nil - cancellables = [] + disposables = [] dependencies = nil viewModel = nil didTriggerSearchCallbackTriggered = false @@ -207,6 +219,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { context("when entering edit mode") { beforeEach { + viewModel.navState.sinkAndStore(in: &disposables) viewModel.rightNavItems.firstValue()??.first?.action?() viewModel.textChanged("TestNew", for: .nickname) } @@ -332,8 +345,9 @@ class ThreadSettingsViewModelSpec: QuickSpec { context("when entering edit mode") { beforeEach { + viewModel.navState.sinkAndStore(in: &disposables) viewModel.rightNavItems.firstValue()??.first?.action?() - viewModel.textChanged("TestNew", for: .nickname) + viewModel.textChanged("TestUserNew", for: .nickname) } it("enters the editing state") { diff --git a/SessionTests/Settings/NotificationContentViewModelSpec.swift b/SessionTests/Settings/NotificationContentViewModelSpec.swift index 6b9b4ef39..91166959f 100644 --- a/SessionTests/Settings/NotificationContentViewModelSpec.swift +++ b/SessionTests/Settings/NotificationContentViewModelSpec.swift @@ -67,6 +67,7 @@ class NotificationContentViewModelSpec: QuickSpec { equal([ SessionCell.Info( id: Preferences.NotificationPreviewType.nameAndPreview, + position: .top, title: "NOTIFICATIONS_STYLE_CONTENT_OPTION_NAME_AND_CONTENT".localized(), rightAccessory: .radio( isSelected: { true } @@ -74,6 +75,7 @@ class NotificationContentViewModelSpec: QuickSpec { ), SessionCell.Info( id: Preferences.NotificationPreviewType.nameNoPreview, + position: .middle, title: "NOTIFICATIONS_STYLE_CONTENT_OPTION_NAME_ONLY".localized(), rightAccessory: .radio( isSelected: { false } @@ -81,6 +83,7 @@ class NotificationContentViewModelSpec: QuickSpec { ), SessionCell.Info( id: Preferences.NotificationPreviewType.noNameNoPreview, + position: .bottom, title: "NOTIFICATIONS_STYLE_CONTENT_OPTION_NO_NAME_OR_CONTENT".localized(), rightAccessory: .radio( isSelected: { false } @@ -107,6 +110,7 @@ class NotificationContentViewModelSpec: QuickSpec { equal([ SessionCell.Info( id: Preferences.NotificationPreviewType.nameAndPreview, + position: .top, title: "NOTIFICATIONS_STYLE_CONTENT_OPTION_NAME_AND_CONTENT".localized(), rightAccessory: .radio( isSelected: { false } @@ -114,6 +118,7 @@ class NotificationContentViewModelSpec: QuickSpec { ), SessionCell.Info( id: Preferences.NotificationPreviewType.nameNoPreview, + position: .middle, title: "NOTIFICATIONS_STYLE_CONTENT_OPTION_NAME_ONLY".localized(), rightAccessory: .radio( isSelected: { true } @@ -121,6 +126,7 @@ class NotificationContentViewModelSpec: QuickSpec { ), SessionCell.Info( id: Preferences.NotificationPreviewType.noNameNoPreview, + position: .bottom, title: "NOTIFICATIONS_STYLE_CONTENT_OPTION_NO_NAME_OR_CONTENT".localized(), rightAccessory: .radio( isSelected: { false } diff --git a/SessionUtilitiesKit/Database/Migrations/_003_YDBToGRDBMigration.swift b/SessionUtilitiesKit/Database/Migrations/_003_YDBToGRDBMigration.swift index fca128622..af10c2651 100644 --- a/SessionUtilitiesKit/Database/Migrations/_003_YDBToGRDBMigration.swift +++ b/SessionUtilitiesKit/Database/Migrations/_003_YDBToGRDBMigration.swift @@ -12,7 +12,7 @@ enum _003_YDBToGRDBMigration: Migration { static func migrate(_ db: Database) throws { guard let dbConnection: YapDatabaseConnection = SUKLegacy.newDatabaseConnection() else { - SNLog("[Migration Warning] No legacy database, skipping \(target.key(with: self))") + SNLogNotTests("[Migration Warning] No legacy database, skipping \(target.key(with: self))") return } diff --git a/SessionUtilitiesKit/Database/Types/Migration.swift b/SessionUtilitiesKit/Database/Types/Migration.swift index b26bf1b97..761525ea0 100644 --- a/SessionUtilitiesKit/Database/Types/Migration.swift +++ b/SessionUtilitiesKit/Database/Types/Migration.swift @@ -15,9 +15,9 @@ public protocol Migration { public extension Migration { static func loggedMigrate(_ targetIdentifier: TargetMigrations.Identifier) -> ((_ db: Database) throws -> ()) { return { (db: Database) in - SNLog("[Migration Info] Starting \(targetIdentifier.key(with: self))") + SNLogNotTests("[Migration Info] Starting \(targetIdentifier.key(with: self))") try migrate(db) - SNLog("[Migration Info] Completed \(targetIdentifier.key(with: self))") + SNLogNotTests("[Migration Info] Completed \(targetIdentifier.key(with: self))") } } } diff --git a/SessionUtilitiesKit/General/Logging.swift b/SessionUtilitiesKit/General/Logging.swift index e0c73a335..7bd18d8f7 100644 --- a/SessionUtilitiesKit/General/Logging.swift +++ b/SessionUtilitiesKit/General/Logging.swift @@ -1,3 +1,6 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation import SignalCoreKit public func SNLog(_ message: String) { @@ -6,3 +9,9 @@ public func SNLog(_ message: String) { #endif OWSLogger.info("[Session] \(message)") } + +public func SNLogNotTests(_ message: String) { + guard ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] == nil else { return } + + SNLog(message) +} diff --git a/SessionUtilitiesKitTests/Database/Utilities/PersistableRecordUtilitiesSpec.swift b/SessionUtilitiesKitTests/Database/Utilities/PersistableRecordUtilitiesSpec.swift index fdb0d8069..8e43f8afd 100644 --- a/SessionUtilitiesKitTests/Database/Utilities/PersistableRecordUtilitiesSpec.swift +++ b/SessionUtilitiesKitTests/Database/Utilities/PersistableRecordUtilitiesSpec.swift @@ -274,7 +274,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec { it("succeeds when using the migration safe mutable upsert and the item does not already exist") { mockStorage.write { db in expect { - var result = MutableTestType(columnA: "Test14", columnB: "Test14B") + let result = MutableTestType(columnA: "Test14", columnB: "Test14B") try result.migrationSafeUpsert(db) return result } @@ -620,7 +620,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec { it("succeeds when using the migration safe mutable upsert and the item does not already exist") { mockStorage.write { db in expect { - var result = MutableTestType(columnA: "Test21", columnB: "Test21B") + let result = MutableTestType(columnA: "Test21", columnB: "Test21B") try result.migrationSafeUpsert(db) return result } @@ -650,7 +650,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec { sql: "INSERT INTO MutableTestType (columnA) VALUES (?)", arguments: StatementArguments(["Test23"]) ) - var result = MutableTestType(id: 1, columnA: "Test23", columnB: "Test23B") + let result = MutableTestType(id: 1, columnA: "Test23", columnB: "Test23B") try result.migrationSafeUpsert(db) return result } @@ -661,7 +661,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec { sql: "INSERT INTO MutableTestType (columnA) VALUES (?)", arguments: StatementArguments(["Test24"]) ) - var result = MutableTestType(id: 2, columnA: "Test24", columnB: "Test24B") + let result = MutableTestType(id: 2, columnA: "Test24", columnB: "Test24B") try result.migrationSafeUpsert(db) return result.id } diff --git a/SignalUtilitiesKit/Utilities/DirectionalPanGestureRecognizer.swift b/SignalUtilitiesKit/Utilities/DirectionalPanGestureRecognizer.swift index 012457ce6..f73681411 100644 --- a/SignalUtilitiesKit/Utilities/DirectionalPanGestureRecognizer.swift +++ b/SignalUtilitiesKit/Utilities/DirectionalPanGestureRecognizer.swift @@ -76,11 +76,11 @@ public class DirectionalPanGestureRecognizer: UIPanGestureRecognizer { let vel = velocity(in: view) switch direction { case .left, .right: - if fabs(vel.y) > fabs(vel.x) { + if abs(vel.y) > abs(vel.x) { state = .cancelled } case .up, .down: - if fabs(vel.x) > fabs(vel.y) { + if abs(vel.x) > abs(vel.y) { state = .cancelled } default: diff --git a/_SharedTestUtilities/CombineExtensions.swift b/_SharedTestUtilities/CombineExtensions.swift index 4a914cff8..cb59fd653 100644 --- a/_SharedTestUtilities/CombineExtensions.swift +++ b/_SharedTestUtilities/CombineExtensions.swift @@ -2,8 +2,19 @@ import Foundation import Combine +import SessionUtilitiesKit public extension AnyPublisher { + func sinkAndStore(in storage: inout C) where C : RangeReplaceableCollection, C.Element == AnyCancellable { + self + .receiveOnMain(immediately: true) + .sink( + receiveCompletion: { _ in }, + receiveValue: { _ in } + ) + .store(in: &storage) + } + func firstValue() -> Output? { var value: Output? From f1e9412c7aec451a345bff5d12598bcd069cc3d6 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 8 Dec 2022 14:21:38 +1100 Subject: [PATCH 016/135] Added in missing code changes unrelated to closed groups rebuild --- Session.xcodeproj/project.pbxproj | 1315 +++++++++-------- .../Views & Modals/IncomingCallBanner.swift | 4 +- Session/Closed Groups/EditClosedGroupVC.swift | 37 +- Session/Closed Groups/NewClosedGroupVC.swift | 12 +- .../ConversationVC+Interaction.swift | 101 +- Session/Conversations/ConversationVC.swift | 193 ++- .../Input View/MentionSelectionView.swift | 4 +- .../Content Views/DocumentView.swift | 2 +- .../Content Views/MediaAlbumView.swift | 3 +- .../Message Cells/VisibleMessageCell.swift | 4 +- .../ThreadDisappearingMessagesViewModel.swift | 12 +- .../Settings/ThreadSettingsViewModel.swift | 192 ++- .../Views & Modals/ReactionListSheet.swift | 8 +- Session/Home/HomeVC.swift | 4 +- .../New Conversation/NewConversationVC.swift | 14 +- .../DocumentTitleViewController.swift | 2 +- .../PhotoCollectionPickerViewModel.swift | 22 +- Session/Meta/AppDelegate.swift | 6 +- Session/Meta/Signal-Bridging-Header.h | 1 - .../Translations/de.lproj/Localizable.strings | 2 + .../Translations/en.lproj/Localizable.strings | 2 + .../Translations/es.lproj/Localizable.strings | 2 + .../Translations/fa.lproj/Localizable.strings | 2 + .../Translations/fi.lproj/Localizable.strings | 2 + .../Translations/fr.lproj/Localizable.strings | 2 + .../Translations/hi.lproj/Localizable.strings | 2 + .../Translations/hr.lproj/Localizable.strings | 2 + .../id-ID.lproj/Localizable.strings | 2 + .../Translations/it.lproj/Localizable.strings | 2 + .../Translations/ja.lproj/Localizable.strings | 2 + .../Translations/nl.lproj/Localizable.strings | 2 + .../Translations/pl.lproj/Localizable.strings | 2 + .../pt_BR.lproj/Localizable.strings | 2 + .../Translations/ru.lproj/Localizable.strings | 2 + .../Translations/si.lproj/Localizable.strings | 2 + .../Translations/sk.lproj/Localizable.strings | 2 + .../Translations/sv.lproj/Localizable.strings | 2 + .../Translations/th.lproj/Localizable.strings | 2 + .../vi-VN.lproj/Localizable.strings | 2 + .../zh-Hant.lproj/Localizable.strings | 2 + .../zh_CN.lproj/Localizable.strings | 2 + Session/Notifications/AppNotifications.swift | 7 +- .../BlockedContactsViewController.swift | 515 ------- .../Settings/BlockedContactsViewModel.swift | 127 +- .../ConversationSettingsViewModel.swift | 26 +- Session/Settings/HelpViewModel.swift | 14 +- Session/Settings/ImagePickerHandler.swift | 57 + .../NotificationContentViewModel.swift | 14 +- .../NotificationSettingsViewModel.swift | 36 +- .../Settings/NotificationSoundViewModel.swift | 12 +- .../Settings/PrivacySettingsViewModel.swift | 82 +- Session/Settings/SettingsViewModel.swift | 214 ++- .../Settings/Views/BlockedContactCell.swift | 4 +- Session/Shared/FullConversationCell.swift | 25 +- .../Shared/SessionTableViewController.swift | 342 +++-- Session/Shared/SessionTableViewModel.swift | 77 +- Session/Shared/Types/DismissType.swift | 3 + .../Types/SessionCell+Accessibility.swift | 18 + .../Shared/Types/SessionCell+Accessory.swift | 326 ++-- .../Types/SessionCell+ExtraAction.swift | 20 - Session/Shared/Types/SessionCell+Info.swift | 265 +++- .../Shared/Types/SessionCell+Styling.swift | 170 +++ .../Shared/Types/SessionTableSection.swift | 33 +- Session/Shared/UserSelectionVC.swift | 10 +- Session/Shared/Views/SessionAvatarCell.swift | 308 ---- .../Views/SessionCell+AccessoryView.swift | 262 +++- Session/Shared/Views/SessionCell.swift | 466 ++++-- Session/Shared/Views/SessionHeaderView.swift | 118 +- Session/Utilities/BackgroundPoller.swift | 24 +- .../Common Networking/Header.swift | 22 - .../Common Networking/QueryParam.swift | 15 - .../Database/Models/ClosedGroup.swift | 10 - .../File Server/Types/FSEndpoint.swift | 3 +- .../ClosedGroupControlMessage.swift | 2 - .../ExpirationTimerUpdate.swift | 7 - .../Messages/Message+Destination.swift | 7 + SessionMessagingKit/Messages/Message.swift | 12 - .../Visible Messages/VisibleMessage.swift | 10 +- .../Open Groups/Models/BatchRequestInfo.swift | 174 --- .../Open Groups/Models/SOGSMessage.swift | 10 +- .../Open Groups/OpenGroupAPI.swift | 62 +- .../Types/HTTPHeader+OpenGroup.swift | 11 + .../Types/HTTPQueryParam+OpenGroup.swift | 21 + .../Open Groups/Types/SOGSEndpoint.swift | 3 +- SessionMessagingKit/SMKDependencies.swift | 93 ++ .../MessageReceiver+ClosedGroups.swift | 2 +- .../MessageSender+ClosedGroups.swift | 14 +- .../MessageSender+Convenience.swift | 57 +- .../Sending & Receiving/MessageSender.swift | 44 +- .../Notifications/PushNotificationAPI.swift | 8 +- .../Pollers/OpenGroupPoller.swift | 26 +- .../Sending & Receiving/Pollers/Poller.swift | 70 +- .../Utilities/Data+Utilities.swift | 16 - .../Utilities/ProfileManager.swift | 53 +- ...ShareVC.swift => ShareNavController.swift} | 8 +- .../SimplifiedConversationCell.swift | 8 +- SessionShareExtension/ThreadPickerVC.swift | 6 +- SessionSnodeKit/Database/Models/Snode.swift | 2 +- .../Models/SnodeReceivedMessageInfo.swift | 30 +- .../{ => Jobs}/GetSnodePoolJob.swift | 1 + .../Models/DeleteAllBeforeRequest.swift | 81 + .../Models/DeleteAllBeforeResponse.swift | 45 + .../Models/DeleteAllMessagesRequest.swift | 2 +- .../Models/DeleteAllMessagesResponse.swift | 80 + .../Models/DeleteMessagesRequest.swift | 70 + .../Models/DeleteMessagesResponse.swift | 65 + .../Models/GetMessagesRequest.swift | 2 +- .../Models/GetMessagesResponse.swift | 38 + .../Models/GetNetworkTimestampResponse.swift | 15 + .../Models/GetServiceNodesRequest.swift | 31 + SessionSnodeKit/Models/GetSwarmRequest.swift | 13 + .../Models/LegacyGetMessagesRequest.swift | 28 + .../Models/LegacySendMessageRequest.swift | 24 + .../Models/ONSResolveRequest.swift | 15 + .../Models/ONSResolveResponse.swift | 84 ++ .../Models/OxenDaemonRPCRequest.swift | 21 + SessionSnodeKit/Models/RequestInfo.swift | 11 - .../Models/RevokeSubkeyRequest.swift | 60 + .../Models/RevokeSubkeyResponse.swift | 43 + .../Models/SendMessageRequest.swift | 4 +- .../Models/SendMessageResponse.swift | 54 + SessionSnodeKit/Models/SnodeAPIEndpoint.swift | 16 - .../SnodeAuthenticatedRequestBody.swift | 56 + .../{ => Models}/SnodeMessage.swift | 2 +- .../Models/SnodeReceivedMessage.swift | 19 +- .../Models/SnodeRecursiveResponse.swift | 21 + SessionSnodeKit/Models/SnodeRequest.swift | 33 + SessionSnodeKit/Models/SnodeResponse.swift | 13 + SessionSnodeKit/Models/SnodeSwarmItem.swift | 51 + SessionSnodeKit/Models/SwarmSnode.swift | 2 +- .../Models/UpdateExpiryAllRequest.swift | 85 ++ .../Models/UpdateExpiryAllResponse.swift | 85 ++ .../Models/UpdateExpiryRequest.swift | 69 + .../Models/UpdateExpiryResponse.swift | 76 + .../Notification+OnionRequestAPI.swift | 0 .../OnionRequestAPI+Encryption.swift | 0 .../{ => Networking}/OnionRequestAPI.swift | 120 +- SessionSnodeKit/SnodeAPI.swift | 1104 -------------- .../OnionRequestAPIDestination.swift | 2 +- .../OnionRequestAPIError.swift | 0 .../OnionRequestAPIVersion.swift | 0 SessionSnodeKit/Types/SnodeAPIEndpoint.swift | 33 + .../{Models => Types}/SnodeAPIError.swift | 0 SessionUIKit/Components/Separator.swift | 3 +- SessionUIKit/Components/SessionButton.swift | 49 +- SessionUIKit/Style Guide/Format.swift | 32 + .../Themes/Theme+ClassicDark.swift | 1 + .../Themes/Theme+ClassicLight.swift | 1 + .../Style Guide/Themes/Theme+OceanDark.swift | 1 + .../Style Guide/Themes/Theme+OceanLight.swift | 1 + SessionUIKit/Style Guide/Themes/Theme.swift | 1 + SessionUIKit/Types/IconSize.swift | 6 +- .../Types/PagedDatabaseObserver.swift | 56 +- .../QueryInterfaceRequest+Utilities.swift | 2 +- .../General/Data+Utilities.swift | 15 + SessionUtilitiesKit/General/SessionId.swift | 2 + SessionUtilitiesKit/Networking/HTTP.swift | 14 +- .../Networking/HTTPError.swift | 26 + .../Networking/HTTPHeader.swift | 19 + .../Networking/HTTPMethod.swift | 10 + .../Networking/HTTPQueryParam.swift | 5 + .../Networking}/Request.swift | 43 +- .../Networking/RequestInfo.swift | 21 + .../Networking}/ResponseInfo.swift | 7 +- .../MediaMessageView.swift | 2 +- SignalUtilitiesKit/Meta/SignalUtilitiesKit.h | 1 - .../Profile Pictures/ProfilePictureView.swift | 260 ++-- SignalUtilitiesKit/Utilities/OWSFormat.h | 17 - SignalUtilitiesKit/Utilities/OWSFormat.m | 47 - 169 files changed, 5005 insertions(+), 4572 deletions(-) delete mode 100644 Session/Settings/BlockedContactsViewController.swift create mode 100644 Session/Settings/ImagePickerHandler.swift create mode 100644 Session/Shared/Types/SessionCell+Accessibility.swift delete mode 100644 Session/Shared/Types/SessionCell+ExtraAction.swift create mode 100644 Session/Shared/Types/SessionCell+Styling.swift delete mode 100644 Session/Shared/Views/SessionAvatarCell.swift delete mode 100644 SessionMessagingKit/Common Networking/Header.swift delete mode 100644 SessionMessagingKit/Common Networking/QueryParam.swift delete mode 100644 SessionMessagingKit/Open Groups/Models/BatchRequestInfo.swift create mode 100644 SessionMessagingKit/Open Groups/Types/HTTPHeader+OpenGroup.swift create mode 100644 SessionMessagingKit/Open Groups/Types/HTTPQueryParam+OpenGroup.swift create mode 100644 SessionMessagingKit/SMKDependencies.swift rename SessionShareExtension/{ShareVC.swift => ShareNavController.swift} (98%) rename SessionSnodeKit/{ => Jobs}/GetSnodePoolJob.swift (98%) create mode 100644 SessionSnodeKit/Models/DeleteAllBeforeRequest.swift create mode 100644 SessionSnodeKit/Models/DeleteAllBeforeResponse.swift create mode 100644 SessionSnodeKit/Models/DeleteAllMessagesResponse.swift create mode 100644 SessionSnodeKit/Models/DeleteMessagesRequest.swift create mode 100644 SessionSnodeKit/Models/DeleteMessagesResponse.swift create mode 100644 SessionSnodeKit/Models/GetMessagesResponse.swift create mode 100644 SessionSnodeKit/Models/GetNetworkTimestampResponse.swift create mode 100644 SessionSnodeKit/Models/GetServiceNodesRequest.swift create mode 100644 SessionSnodeKit/Models/GetSwarmRequest.swift create mode 100644 SessionSnodeKit/Models/LegacyGetMessagesRequest.swift create mode 100644 SessionSnodeKit/Models/LegacySendMessageRequest.swift create mode 100644 SessionSnodeKit/Models/ONSResolveRequest.swift create mode 100644 SessionSnodeKit/Models/ONSResolveResponse.swift create mode 100644 SessionSnodeKit/Models/OxenDaemonRPCRequest.swift delete mode 100644 SessionSnodeKit/Models/RequestInfo.swift create mode 100644 SessionSnodeKit/Models/RevokeSubkeyRequest.swift create mode 100644 SessionSnodeKit/Models/RevokeSubkeyResponse.swift create mode 100644 SessionSnodeKit/Models/SendMessageResponse.swift delete mode 100644 SessionSnodeKit/Models/SnodeAPIEndpoint.swift create mode 100644 SessionSnodeKit/Models/SnodeAuthenticatedRequestBody.swift rename SessionSnodeKit/{ => Models}/SnodeMessage.swift (98%) create mode 100644 SessionSnodeKit/Models/SnodeRecursiveResponse.swift create mode 100644 SessionSnodeKit/Models/SnodeRequest.swift create mode 100644 SessionSnodeKit/Models/SnodeResponse.swift create mode 100644 SessionSnodeKit/Models/SnodeSwarmItem.swift create mode 100644 SessionSnodeKit/Models/UpdateExpiryAllRequest.swift create mode 100644 SessionSnodeKit/Models/UpdateExpiryAllResponse.swift create mode 100644 SessionSnodeKit/Models/UpdateExpiryRequest.swift create mode 100644 SessionSnodeKit/Models/UpdateExpiryResponse.swift rename SessionSnodeKit/{ => Networking}/Notification+OnionRequestAPI.swift (100%) rename SessionSnodeKit/{ => Networking}/OnionRequestAPI+Encryption.swift (100%) rename SessionSnodeKit/{ => Networking}/OnionRequestAPI.swift (89%) delete mode 100644 SessionSnodeKit/SnodeAPI.swift rename SessionSnodeKit/{Models => Types}/OnionRequestAPIDestination.swift (85%) rename SessionSnodeKit/{Models => Types}/OnionRequestAPIError.swift (100%) rename SessionSnodeKit/{Models => Types}/OnionRequestAPIVersion.swift (100%) create mode 100644 SessionSnodeKit/Types/SnodeAPIEndpoint.swift rename SessionSnodeKit/{Models => Types}/SnodeAPIError.swift (100%) create mode 100644 SessionUIKit/Style Guide/Format.swift create mode 100644 SessionUtilitiesKit/Networking/HTTPError.swift create mode 100644 SessionUtilitiesKit/Networking/HTTPHeader.swift create mode 100644 SessionUtilitiesKit/Networking/HTTPMethod.swift create mode 100644 SessionUtilitiesKit/Networking/HTTPQueryParam.swift rename {SessionMessagingKit/Common Networking => SessionUtilitiesKit/Networking}/Request.swift (70%) create mode 100644 SessionUtilitiesKit/Networking/RequestInfo.swift rename {SessionSnodeKit/Models => SessionUtilitiesKit/Networking}/ResponseInfo.swift (72%) delete mode 100644 SignalUtilitiesKit/Utilities/OWSFormat.h delete mode 100644 SignalUtilitiesKit/Utilities/OWSFormat.m diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 2de43e709..a1d6257db 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -7,8 +7,7 @@ objects = { /* Begin PBXBuildFile section */ - 1FFD68A448D5A1439F2F02FD /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DBA125424EDD2417B515C63A /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework */; }; - 3289CA2E9E89DA9D4D52A90C /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0BF4561630A52BE96F164CF6 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework */; }; + 056ED47155A04437A1EF58C2 /* Pods_GlobalDependencies_Session_SessionTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 32C10A2A536B58FC42C46C3C /* Pods_GlobalDependencies_Session_SessionTests.framework */; }; 3427C64320F500E000EEC730 /* OWSMessageTimerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3427C64220F500DF00EEC730 /* OWSMessageTimerView.m */; }; 3430FE181F7751D4000EC51B /* GiphyAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3430FE171F7751D4000EC51B /* GiphyAPI.swift */; }; 34661FB820C1C0D60056EDD6 /* message_sent.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 34661FB720C1C0D60056EDD6 /* message_sent.aiff */; }; @@ -34,6 +33,8 @@ 34D1F0521F7E8EA30066283D /* GiphyDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0511F7E8EA30066283D /* GiphyDownloader.swift */; }; 34D99CE4217509C2000AFB39 /* AppEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D99CE3217509C1000AFB39 /* AppEnvironment.swift */; }; 34F308A21ECB469700BB7697 /* OWSBezierPathView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F308A11ECB469700BB7697 /* OWSBezierPathView.m */; }; + 41BA6B5C1C693C3A86070C15 /* Pods_GlobalDependencies_Session.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CC8D3B6504946442A0CF775C /* Pods_GlobalDependencies_Session.framework */; }; + 42C48489AFF26BC9034C736C /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 86A3D36084020C9118DBCEE3 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit.framework */; }; 4503F1BE20470A5B00CEE724 /* classic-quiet.aifc in Resources */ = {isa = PBXBuildFile; fileRef = 4503F1BB20470A5B00CEE724 /* classic-quiet.aifc */; }; 4503F1BF20470A5B00CEE724 /* classic.aifc in Resources */ = {isa = PBXBuildFile; fileRef = 4503F1BC20470A5B00CEE724 /* classic.aifc */; }; 450DF2091E0DD2C6003D14BE /* UserNotificationsAdaptee.swift in Sources */ = {isa = PBXBuildFile; fileRef = 450DF2081E0DD2C6003D14BE /* UserNotificationsAdaptee.swift */; }; @@ -84,6 +85,7 @@ 4C1885D2218F8E1C00B67051 /* PhotoGridViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1885D1218F8E1C00B67051 /* PhotoGridViewCell.swift */; }; 4C21D5D8223AC60F00EF8A77 /* PhotoCapture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C21D5D7223AC60F00EF8A77 /* PhotoCapture.swift */; }; 4C4AE6A1224AF35700D4AF6F /* SendMediaNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4AE69F224AF21900D4AF6F /* SendMediaNavigationController.swift */; }; + 4C4FE46740136D591D04261F /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E0B0F4C34363FE679EE3F203 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework */; }; 4C586926224FAB83003FD070 /* AVAudioSession+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C586925224FAB83003FD070 /* AVAudioSession+OWS.m */; }; 4C63CC00210A620B003AE45C /* SignalTSan.supp in Resources */ = {isa = PBXBuildFile; fileRef = 4C63CBFF210A620B003AE45C /* SignalTSan.supp */; }; 4C6F527C20FFE8400097DEEE /* SignalUBSan.supp in Resources */ = {isa = PBXBuildFile; fileRef = 4C6F527B20FFE8400097DEEE /* SignalUBSan.supp */; }; @@ -91,9 +93,8 @@ 4CA46F4C219CCC630038ABDE /* CaptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA46F4B219CCC630038ABDE /* CaptionView.swift */; }; 4CA485BB2232339F004B9E7D /* PhotoCaptureViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA485BA2232339F004B9E7D /* PhotoCaptureViewController.swift */; }; 4CC613362227A00400E21A3A /* ConversationSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC613352227A00400E21A3A /* ConversationSearch.swift */; }; - 5163CBC4F53274C88D1F88F8 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 782B65234A707D762FEAFD3B /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit.framework */; }; + 6C1ADD1127CED42854542F78 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D3BE3061D535DBC1DF0C94D4 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework */; }; 70377AAB1918450100CAF501 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 70377AAA1918450100CAF501 /* MobileCoreServices.framework */; }; - 71B1D8AF3ADE2BD191256496 /* Pods_GlobalDependencies_Session.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 48AD214D67ABED845101E795 /* Pods_GlobalDependencies_Session.framework */; }; 768A1A2B17FC9CD300E00ED8 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 768A1A2A17FC9CD300E00ED8 /* libz.dylib */; }; 76C87F19181EFCE600C4ACAB /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76C87F18181EFCE600C4ACAB /* MediaPlayer.framework */; }; 7B0EFDEE274F598600FFAAE7 /* TimestampUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0EFDED274F598600FFAAE7 /* TimestampUtils.swift */; }; @@ -159,14 +160,12 @@ 7BFD1A8A2745C4F000FB91B9 /* Permissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD1A892745C4F000FB91B9 /* Permissions.swift */; }; 7BFD1A8C2747150E00FB91B9 /* TurnServerInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD1A8B2747150E00FB91B9 /* TurnServerInfo.swift */; }; 7BFD1A972747689000FB91B9 /* Session-Turn-Server in Resources */ = {isa = PBXBuildFile; fileRef = 7BFD1A962747689000FB91B9 /* Session-Turn-Server */; }; - 821EFD1644285AC2D3733D27 /* Pods_GlobalDependencies_SessionUIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9C58C3ADF46C718488458C2 /* Pods_GlobalDependencies_SessionUIKit.framework */; }; - 92EB2776D36B22D2E0552A05 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2691123A7F231EDD8226C4B5 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework */; }; - 98547545DAF8E7916DF9F0BF /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6F84A214B9A1C0CCF6DB09C8 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework */; }; + 9A88F90C33C394513CB4C18A /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit_SessionUtilitiesKitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 07B7038C849F53378CD36B83 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit_SessionUtilitiesKitTests.framework */; }; + A0A69C9CB213DDDD85BF2207 /* Pods_GlobalDependencies_SessionUIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 678CFB04E76F7E388AFCFA86 /* Pods_GlobalDependencies_SessionUIKit.framework */; }; A11CD70D17FA230600A2D1B1 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A11CD70C17FA230600A2D1B1 /* QuartzCore.framework */; }; A163E8AB16F3F6AA0094D68B /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A163E8AA16F3F6A90094D68B /* Security.framework */; }; A1C32D5017A06538000A904E /* AddressBookUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1C32D4F17A06537000A904E /* AddressBookUI.framework */; }; A1C32D5117A06544000A904E /* AddressBook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1C32D4D17A0652C000A904E /* AddressBook.framework */; }; - A49760F37A9AE09D57ECE415 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit_SessionUtilitiesKitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5442DF945D862CEDF7F8AC49 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit_SessionUtilitiesKitTests.framework */; }; B66DBF4A19D5BBC8006EA940 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B66DBF4919D5BBC8006EA940 /* Images.xcassets */; }; B67EBF5D19194AC60084CCFD /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = B67EBF5C19194AC60084CCFD /* Settings.bundle */; }; B6B226971BE4B7D200860F4D /* ContactsUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6B226961BE4B7D200860F4D /* ContactsUI.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; @@ -262,7 +261,7 @@ B8FF8E7425C10FC3004D1F22 /* GeoLite2-Country-Locations-English in Resources */ = {isa = PBXBuildFile; fileRef = B8FF8E7325C10FC3004D1F22 /* GeoLite2-Country-Locations-English */; }; B8FF8EA625C11FEF004D1F22 /* IPv4.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8FF8EA525C11FEF004D1F22 /* IPv4.swift */; }; B9EB5ABD1884C002007CBB57 /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B9EB5ABC1884C002007CBB57 /* MessageUI.framework */; }; - C2CAA4A9737D865B34560B8C /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionSnodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6737124ECBC2DFEE2DD716D3 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionSnodeKit.framework */; }; + BE25D9230CA2C3A40A9216EF /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05E68C7F291EC08B8A43A534 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework */; }; C300A5D32554B05A00555489 /* TypingIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C300A5D22554B05A00555489 /* TypingIndicator.swift */; }; C300A5F22554B09800555489 /* MessageSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = C300A5F12554B09800555489 /* MessageSender.swift */; }; C300A60D2554B31900555489 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5CE2553860700C340D1 /* Logging.swift */; }; @@ -379,9 +378,7 @@ C38EF30C255B6DBF007E1867 /* ScreenLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2E2255B6DB9007E1867 /* ScreenLock.swift */; }; C38EF31C255B6DBF007E1867 /* Searcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2F2255B6DBC007E1867 /* Searcher.swift */; }; C38EF324255B6DBF007E1867 /* Bench.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2FA255B6DBD007E1867 /* Bench.swift */; }; - C38EF32B255B6DBF007E1867 /* OWSFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF301255B6DBD007E1867 /* OWSFormat.h */; settings = {ATTRIBUTES = (Public, ); }; }; C38EF32E255B6DBF007E1867 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF304255B6DBE007E1867 /* ImageCache.swift */; }; - C38EF32F255B6DBF007E1867 /* OWSFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF305255B6DBE007E1867 /* OWSFormat.m */; }; C38EF331255B6DBF007E1867 /* UIGestureRecognizer+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF307255B6DBE007E1867 /* UIGestureRecognizer+OWS.swift */; }; C38EF35E255B6DCC007E1867 /* OWSViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF344255B6DC5007E1867 /* OWSViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; C38EF363255B6DCC007E1867 /* ModalActivityIndicatorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF349255B6DC7007E1867 /* ModalActivityIndicatorViewController.swift */; }; @@ -440,13 +437,8 @@ C3BBE0C72554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3BBE0C62554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift */; }; C3C2A5A3255385C100C340D1 /* SessionSnodeKit.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A5A1255385C100C340D1 /* SessionSnodeKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; C3C2A5A7255385C100C340D1 /* SessionSnodeKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - C3C2A5BF255385EE00C340D1 /* SnodeMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5B6255385EC00C340D1 /* SnodeMessage.swift */; }; C3C2A5C0255385EE00C340D1 /* Snode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5B7255385EC00C340D1 /* Snode.swift */; }; C3C2A5C2255385EE00C340D1 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5B9255385ED00C340D1 /* Configuration.swift */; }; - C3C2A5C3255385EE00C340D1 /* OnionRequestAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5BA255385ED00C340D1 /* OnionRequestAPI.swift */; }; - C3C2A5C4255385EE00C340D1 /* OnionRequestAPI+Encryption.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5BB255385ED00C340D1 /* OnionRequestAPI+Encryption.swift */; }; - C3C2A5C6255385EE00C340D1 /* Notification+OnionRequestAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5BD255385EE00C340D1 /* Notification+OnionRequestAPI.swift */; }; - C3C2A5C7255385EE00C340D1 /* SnodeAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5BE255385EE00C340D1 /* SnodeAPI.swift */; }; C3C2A5DE2553860B00C340D1 /* String+Trimming.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D22553860900C340D1 /* String+Trimming.swift */; }; C3C2A5E02553860B00C340D1 /* Threading.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D42553860A00C340D1 /* Threading.swift */; }; C3C2A5E42553860B00C340D1 /* Data+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D82553860B00C340D1 /* Data+Utilities.swift */; }; @@ -487,7 +479,7 @@ C3DB66C3260ACCE6001EFC55 /* OpenGroupPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DB66C2260ACCE6001EFC55 /* OpenGroupPoller.swift */; }; C3E5C2FA251DBABB0040DFFC /* EditClosedGroupVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E5C2F9251DBABB0040DFFC /* EditClosedGroupVC.swift */; }; C3F0A530255C80BC007BE2A3 /* NoopNotificationsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3F0A52F255C80BC007BE2A3 /* NoopNotificationsManager.swift */; }; - CEE449BA3596483519120D91 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A8A44E3F8AC9282AC5E6E5A /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework */; }; + CB54B7E519F525FF27A7DAD3 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C5828D9A55CD76B75E8FA367 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework */; }; D2179CFC16BB0B3A0006F3AB /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2179CFB16BB0B3A0006F3AB /* CoreTelephony.framework */; }; D2179CFE16BB0B480006F3AB /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2179CFD16BB0B480006F3AB /* SystemConfiguration.framework */; }; D221A08E169C9E5E00537ABF /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D221A08D169C9E5E00537ABF /* UIKit.framework */; }; @@ -495,7 +487,7 @@ D221A0E8169DFFC500537ABF /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D221A0E7169DFFC500537ABF /* AVFoundation.framework */; }; D24B5BD5169F568C00681372 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D24B5BD4169F568C00681372 /* AudioToolbox.framework */; }; D2AEACDC16C426DA00C364C0 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2AEACDB16C426DA00C364C0 /* CFNetwork.framework */; }; - DA2AE22FA77136442EF669E9 /* Pods_GlobalDependencies_Session_SessionTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B4C92F6ADBECCD47A6B6008E /* Pods_GlobalDependencies_Session_SessionTests.framework */; }; + DE0001574AC103562A7CF31F /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionSnodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9E358E7590EB145A5047F885 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionSnodeKit.framework */; }; FC3BD9881A30A790005B96BB /* Social.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC3BD9871A30A790005B96BB /* Social.framework */; }; FCB11D8C1A129A76002F93FB /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FCB11D8B1A129A76002F93FB /* CoreMedia.framework */; }; FD078E4827E02561000769AF /* CommonMockedExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD078E4727E02561000769AF /* CommonMockedExtensions.swift */; }; @@ -506,7 +498,6 @@ FD078E5427E197CA000769AF /* OpenGroupManagerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909D27D85751005DAE71 /* OpenGroupManagerSpec.swift */; }; FD078E5A27E29F09000769AF /* MockNonce16Generator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD078E5927E29F09000769AF /* MockNonce16Generator.swift */; }; FD078E5C27E29F78000769AF /* MockNonce24Generator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD078E5B27E29F78000769AF /* MockNonce24Generator.swift */; }; - FD09796927F6BEA700936362 /* SwarmSnode.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09796827F6BEA700936362 /* SwarmSnode.swift */; }; FD09796B27F6C67500936362 /* Failable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09796A27F6C67500936362 /* Failable.swift */; }; FD09796E27FA6D0000936362 /* Contact.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09796D27FA6D0000936362 /* Contact.swift */; }; FD09797027FA6FF300936362 /* Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09796F27FA6FF300936362 /* Profile.swift */; }; @@ -554,10 +545,6 @@ FD17D7C727F5207C00122BE0 /* DatabaseMigrator+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7C627F5207C00122BE0 /* DatabaseMigrator+Utilities.swift */; }; FD17D7CA27F546D900122BE0 /* _001_InitialSetupMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7C927F546D900122BE0 /* _001_InitialSetupMigration.swift */; }; FD17D7CD27F546FF00122BE0 /* Setting.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7CC27F546FF00122BE0 /* Setting.swift */; }; - FD17D7D227F5797A00122BE0 /* SnodeAPIEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7D127F5797A00122BE0 /* SnodeAPIEndpoint.swift */; }; - FD17D7D427F6584600122BE0 /* OnionRequestAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7D327F6584600122BE0 /* OnionRequestAPIError.swift */; }; - FD17D7D827F658E200122BE0 /* OnionRequestAPIDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7D727F658E200122BE0 /* OnionRequestAPIDestination.swift */; }; - FD17D7E127F67BD400122BE0 /* SnodeReceivedMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7E027F67BD400122BE0 /* SnodeReceivedMessage.swift */; }; FD17D7E527F6A09900122BE0 /* Identity.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7E427F6A09900122BE0 /* Identity.swift */; }; FD17D7E727F6A16700122BE0 /* _003_YDBToGRDBMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7E627F6A16700122BE0 /* _003_YDBToGRDBMigration.swift */; }; FD17D7EA27F6A1C600122BE0 /* SUKLegacy.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7E927F6A1C600122BE0 /* SUKLegacy.swift */; }; @@ -597,28 +584,10 @@ FD245C6B2850667400B966DD /* VisibleMessage+Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C300A5B12554AF9800555489 /* VisibleMessage+Profile.swift */; }; FD245C6C2850669200B966DD /* MessageReceiveJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = C352A31225574F5200338F3E /* MessageReceiveJob.swift */; }; FD245C6D285066A400B966DD /* NotifyPushServerJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = C352A32E2557549C00338F3E /* NotifyPushServerJob.swift */; }; - FD26FA512919F9CE005801D8 /* GroupDeleteMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26FA502919F9CE005801D8 /* GroupDeleteMessage.swift */; }; - FD26FA53291CACA9005801D8 /* BatchResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4386A27B4E88F00C60D73 /* BatchResponse.swift */; }; - FD26FA54291CAD31005801D8 /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9CD27D17A04005E1583 /* Request.swift */; }; - FD26FA55291CAD44005801D8 /* HTTPQueryParam.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4385027B4807400C60D73 /* HTTPQueryParam.swift */; }; - FD26FA57291CADAE005801D8 /* HTTPQueryParam+OpenGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26FA56291CADAE005801D8 /* HTTPQueryParam+OpenGroup.swift */; }; - FD26FA58291CAE38005801D8 /* HTTPHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4384E27B4804F00C60D73 /* HTTPHeader.swift */; }; - FD26FA5A291CAE9B005801D8 /* HTTPHeader+OpenGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26FA59291CAE9B005801D8 /* HTTPHeader+OpenGroup.swift */; }; - FD26FA5E291CAFF9005801D8 /* Data+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26FA5D291CAFF9005801D8 /* Data+Utilities.swift */; }; - FD26FA60291CB098005801D8 /* ResponseInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26FA5F291CB098005801D8 /* ResponseInfo.swift */; }; - FD26FA62291CB46D005801D8 /* RequestInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438B027BB159600C60D73 /* RequestInfo.swift */; }; - FD26FA66291CC981005801D8 /* HTTPError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26FA65291CC981005801D8 /* HTTPError.swift */; }; - FD26FA68291CC99E005801D8 /* HTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26FA67291CC99E005801D8 /* HTTPMethod.swift */; }; - FD26FA6B291DA6BC005801D8 /* SSKDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26FA6A291DA6BC005801D8 /* SSKDependencies.swift */; }; - FD26FA6D291DADAE005801D8 /* SnodeRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26FA6C291DADAE005801D8 /* SnodeRequest.swift */; }; - FD26FA6F291DB171005801D8 /* ONSResolveRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26FA6E291DB171005801D8 /* ONSResolveRequest.swift */; }; - FD26FA71291DB253005801D8 /* OxenDaemonRPCRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26FA70291DB253005801D8 /* OxenDaemonRPCRequest.swift */; }; - FD26FA73291DB5F3005801D8 /* SnodeBatchRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26FA72291DB5F3005801D8 /* SnodeBatchRequest.swift */; }; - FD26FA75291DBC8B005801D8 /* ONSResolveResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26FA74291DBC8B005801D8 /* ONSResolveResponse.swift */; }; - FD26FA77291DE2C7005801D8 /* SnodeResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26FA76291DE2C7005801D8 /* SnodeResponse.swift */; }; - FD26FA79291DEDD7005801D8 /* GetMessagesRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26FA78291DEDD7005801D8 /* GetMessagesRequest.swift */; }; - FD26FA7B291DF8F3005801D8 /* SnodeAPINamespace.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26FA7A291DF8F3005801D8 /* SnodeAPINamespace.swift */; }; - FD26FA7D291E0B10005801D8 /* GetMessagesResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD26FA7C291E0B10005801D8 /* GetMessagesResponse.swift */; }; + FD26FA512919F9CE005801D8 /* (null) in Sources */ = {isa = PBXBuildFile; }; + FD26FA5E291CAFF9005801D8 /* (null) in Sources */ = {isa = PBXBuildFile; }; + FD26FA6D291DADAE005801D8 /* (null) in Sources */ = {isa = PBXBuildFile; }; + FD26FA7B291DF8F3005801D8 /* (null) in Sources */ = {isa = PBXBuildFile; }; FD2AAAED28ED3E1000A49611 /* MockGeneralCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */; }; FD2AAAEE28ED3E1100A49611 /* MockGeneralCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */; }; FD2AAAF028ED57B500A49611 /* SynchronousStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2AAAEF28ED57B500A49611 /* SynchronousStorage.swift */; }; @@ -707,7 +676,6 @@ FD71163228E2C42A00B47552 /* IconSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD71163128E2C42A00B47552 /* IconSize.swift */; }; FD71163728E2C50700B47552 /* SessionTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37EA0628AA2CCA003AE748 /* SessionTableViewController.swift */; }; FD71163828E2C50700B47552 /* SessionTableViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37EA0828AA2D27003AE748 /* SessionTableViewModel.swift */; }; - FD71163A28E2C53700B47552 /* SessionAvatarCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7115ED28C5D79B00B47552 /* SessionAvatarCell.swift */; }; FD71163E28E2C82900B47552 /* SessionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37EA0A28AB12E2003AE748 /* SessionCell.swift */; }; FD71163F28E2C82C00B47552 /* SessionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7115EF28C5D7DE00B47552 /* SessionHeaderView.swift */; }; FD71164228E2C85A00B47552 /* TransitionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD71163328E2C48400B47552 /* TransitionType.swift */; }; @@ -715,7 +683,7 @@ FD71164628E2CC1300B47552 /* SessionHighlightingBackgroundLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD71164528E2CC1300B47552 /* SessionHighlightingBackgroundLabel.swift */; }; FD71164828E2CE8700B47552 /* SessionCell+AccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD71164728E2CE8700B47552 /* SessionCell+AccessoryView.swift */; }; FD71164A28E3EA5B00B47552 /* DismissType.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD71164928E3EA5B00B47552 /* DismissType.swift */; }; - FD71164C28E3F5AA00B47552 /* SessionCell+ExtraAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD71164B28E3F5AA00B47552 /* SessionCell+ExtraAction.swift */; }; + FD71164C28E3F5AA00B47552 /* SessionCell+Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD71164B28E3F5AA00B47552 /* SessionCell+Accessibility.swift */; }; FD71164E28E3F8CC00B47552 /* SessionCell+Info.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD71164D28E3F8CC00B47552 /* SessionCell+Info.swift */; }; FD71165028E3F9FA00B47552 /* SessionTableViewModel+NavItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD71164F28E3F9FA00B47552 /* SessionTableViewModel+NavItem.swift */; }; FD71165228E410BE00B47552 /* SessionTableSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD71165128E410BE00B47552 /* SessionTableSection.swift */; }; @@ -733,9 +701,7 @@ FD7728962849E7E90018502F /* String+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7728952849E7E90018502F /* String+Utilities.swift */; }; FD7728982849E8110018502F /* UITableView+ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7728972849E8110018502F /* UITableView+ReusableView.swift */; }; FD77289A284AF1BD0018502F /* Sodium+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD772899284AF1BD0018502F /* Sodium+Utilities.swift */; }; - FD77289C284DDCE10018502F /* SnodePoolResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD77289B284DDCE10018502F /* SnodePoolResponse.swift */; }; FD77289E284EF1C50018502F /* Sodium+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD77289D284EF1C50018502F /* Sodium+Utilities.swift */; }; - FD7728A0284EF5810018502F /* SnodeAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD77289F284EF5810018502F /* SnodeAPIError.swift */; }; FD83B9B327CF200A005E1583 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; platformFilter = ios; }; FD83B9BB27CF20AF005E1583 /* SessionIdSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9BA27CF20AF005E1583 /* SessionIdSpec.swift */; }; FD83B9BF27CF2294005E1583 /* TestConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9BD27CF2243005E1583 /* TestConstants.swift */; }; @@ -744,7 +710,6 @@ FD83B9C727CF3F10005E1583 /* CapabilitiesSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9C627CF3F10005E1583 /* CapabilitiesSpec.swift */; }; FD83B9C927D0487A005E1583 /* SendDirectMessageResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9C827D0487A005E1583 /* SendDirectMessageResponse.swift */; }; FD83B9CC27D179BC005E1583 /* FSEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9CB27D179BC005E1583 /* FSEndpoint.swift */; }; - FD83B9CE27D17A04005E1583 /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9CD27D17A04005E1583 /* Request.swift */; }; FD83B9D227D59495005E1583 /* MockUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9D127D59495005E1583 /* MockUserDefaults.swift */; }; FD848B8B283DC509000E298B /* PagedDatabaseObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD848B8A283DC509000E298B /* PagedDatabaseObserver.swift */; }; FD848B8D283E0B26000E298B /* MessageInputTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD848B8C283E0B26000E298B /* MessageInputTypes.swift */; }; @@ -760,51 +725,19 @@ FD859EFA27C2F5C500510D0C /* MockGenericHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EF927C2F5C500510D0C /* MockGenericHash.swift */; }; FD859EFC27C2F60700510D0C /* MockEd25519.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EFB27C2F60700510D0C /* MockEd25519.swift */; }; FD87DCFA28B74DB300AF0F98 /* ConversationSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD87DCF928B74DB300AF0F98 /* ConversationSettingsViewModel.swift */; }; - FD87DCFC28B755B800AF0F98 /* BlockedContactsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD87DCFB28B755B800AF0F98 /* BlockedContactsViewController.swift */; }; FD87DCFE28B7582C00AF0F98 /* BlockedContactsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD87DCFD28B7582C00AF0F98 /* BlockedContactsViewModel.swift */; }; FD87DD0028B820F200AF0F98 /* BlockedContactCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD87DCFF28B820F200AF0F98 /* BlockedContactCell.swift */; }; FD87DD0428B8727D00AF0F98 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD87DD0328B8727D00AF0F98 /* Configuration.swift */; }; - FD8ECF40292AF07900C0D1BB /* Poller.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF3F292AF07900C0D1BB /* Poller.swift */; }; - FD8ECF42292B340D00C0D1BB /* SOGSBatchRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF41292B340D00C0D1BB /* SOGSBatchRequest.swift */; }; - FD8ECF44292B397E00C0D1BB /* SendMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF43292B397E00C0D1BB /* SendMessageRequest.swift */; }; - FD8ECF46292B4BD500C0D1BB /* SendMessageResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF45292B4BD500C0D1BB /* SendMessageResponse.swift */; }; - FD8ECF48292C287500C0D1BB /* UpdateExpiryRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF47292C287500C0D1BB /* UpdateExpiryRequest.swift */; }; - FD8ECF4A292C2A7300C0D1BB /* DeleteMessagesRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF49292C2A7300C0D1BB /* DeleteMessagesRequest.swift */; }; - FD8ECF4C292C2AC200C0D1BB /* RevokeSubkeyRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF4B292C2AC200C0D1BB /* RevokeSubkeyRequest.swift */; }; - FD8ECF4E292C2AF800C0D1BB /* DeleteAllMessagesRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF4D292C2AF800C0D1BB /* DeleteAllMessagesRequest.swift */; }; - FD8ECF50292C2B2B00C0D1BB /* DeleteAllBeforeRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF4F292C2B2B00C0D1BB /* DeleteAllBeforeRequest.swift */; }; - FD8ECF52292C2CAE00C0D1BB /* UpdateExpiryAllRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF51292C2CAE00C0D1BB /* UpdateExpiryAllRequest.swift */; }; - FD8ECF54292C2DB000C0D1BB /* SnodeAuthenticatedRequestBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF53292C2DB000C0D1BB /* SnodeAuthenticatedRequestBody.swift */; }; - FD8ECF56292C327700C0D1BB /* LegacyGetMessagesRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF55292C327700C0D1BB /* LegacyGetMessagesRequest.swift */; }; - FD8ECF58292C350500C0D1BB /* LegacySendMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF57292C350500C0D1BB /* LegacySendMessageRequest.swift */; }; - FD8ECF5A292C431B00C0D1BB /* GetSwarmRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF59292C431B00C0D1BB /* GetSwarmRequest.swift */; }; - FD8ECF5C292C469100C0D1BB /* UpdateExpiryResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF5B292C469100C0D1BB /* UpdateExpiryResponse.swift */; }; - FD8ECF5E292C478900C0D1BB /* SnodeRecursiveResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF5D292C478900C0D1BB /* SnodeRecursiveResponse.swift */; }; - FD8ECF60292C4B2400C0D1BB /* SnodeSwarmItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF5F292C4B2400C0D1BB /* SnodeSwarmItem.swift */; }; - FD8ECF62292C4C8200C0D1BB /* DeleteMessagesResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF61292C4C8200C0D1BB /* DeleteMessagesResponse.swift */; }; - FD8ECF64292C4D6600C0D1BB /* DeleteAllMessagesResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF63292C4D6600C0D1BB /* DeleteAllMessagesResponse.swift */; }; - FD8ECF66292C6F8200C0D1BB /* DeleteAllBeforeResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF65292C6F8200C0D1BB /* DeleteAllBeforeResponse.swift */; }; - FD8ECF68292C72BA00C0D1BB /* UpdateExpiryAllResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF67292C72B900C0D1BB /* UpdateExpiryAllResponse.swift */; }; - FD8ECF6A292C74A000C0D1BB /* RevokeSubkeyResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF69292C74A000C0D1BB /* RevokeSubkeyResponse.swift */; }; - FD8ECF6C292C9B6400C0D1BB /* GetNetworkTimestampResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF6B292C9B6400C0D1BB /* GetNetworkTimestampResponse.swift */; }; - FD8ECF6E292C9EA100C0D1BB /* GetServiceNodesRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF6D292C9EA100C0D1BB /* GetServiceNodesRequest.swift */; }; - FD8ECF72292DCD1A00C0D1BB /* _013_AutoDownloadAttachments.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF71292DCD1A00C0D1BB /* _013_AutoDownloadAttachments.swift */; }; - FD8ECF74292DDB4A00C0D1BB /* Format.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF73292DDB4A00C0D1BB /* Format.swift */; }; FD8ECF7929340F7200C0D1BB /* libsession-util.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FD8ECF7829340F7100C0D1BB /* libsession-util.xcframework */; }; FD8ECF7B29340FFD00C0D1BB /* SessionUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7A29340FFD00C0D1BB /* SessionUtil.swift */; }; FD8ECF7D2934293A00C0D1BB /* _011_SharedUtilChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7C2934293A00C0D1BB /* _011_SharedUtilChanges.swift */; }; FD8ECF7F2934298100C0D1BB /* SharedConfigDump.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7E2934298100C0D1BB /* SharedConfigDump.swift */; }; FD8ECF822934387A00C0D1BB /* ConfigUserProfileSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF812934387A00C0D1BB /* ConfigUserProfileSpec.swift */; }; - FD8ECF852934508B00C0D1BB /* BatchResponseSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF842934508B00C0D1BB /* BatchResponseSpec.swift */; }; - FD8ECF8629346DA100C0D1BB /* HeaderSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C906127E411AF00CD579F /* HeaderSpec.swift */; }; - FD8ECF8729346DB500C0D1BB /* RequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C906327E4122F00CD579F /* RequestSpec.swift */; }; FD8ECF892935AB7200C0D1BB /* SessionUtilError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF882935AB7200C0D1BB /* SessionUtilError.swift */; }; FD8ECF8B2935DB4B00C0D1BB /* SharedConfigMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF8A2935DB4B00C0D1BB /* SharedConfigMessage.swift */; }; FD8ECF9029381FC200C0D1BB /* SessionUtil+UserProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF8F29381FC200C0D1BB /* SessionUtil+UserProfile.swift */; }; FD8ECF922938552800C0D1BB /* Threading.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF912938552800C0D1BB /* Threading.swift */; }; FD8ECF94293856AF00C0D1BB /* Randomness.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF93293856AF00C0D1BB /* Randomness.swift */; }; - FD8ECFA1293D8FDD00C0D1BB /* URLResponse+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECFA0293D8FDD00C0D1BB /* URLResponse+Utilities.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 */; }; FD9004152818B46300ABAAF6 /* JobRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B7432804EF1B004C14C5 /* JobRunner.swift */; }; @@ -839,27 +772,21 @@ FDC4382027B36ADC00C60D73 /* SOGSEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381F27B36ADC00C60D73 /* SOGSEndpoint.swift */; }; FDC4382F27B383AF00C60D73 /* PushServerResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4382E27B383AF00C60D73 /* PushServerResponse.swift */; }; FDC4383827B3863200C60D73 /* VersionResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4383727B3863200C60D73 /* VersionResponse.swift */; }; - FDC4384F27B4804F00C60D73 /* Header.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4384E27B4804F00C60D73 /* Header.swift */; }; - FDC4385127B4807400C60D73 /* QueryParam.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4385027B4807400C60D73 /* QueryParam.swift */; }; FDC4385D27B4C18900C60D73 /* Room.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4385C27B4C18900C60D73 /* Room.swift */; }; FDC4385F27B4C4A200C60D73 /* PinnedMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4385E27B4C4A200C60D73 /* PinnedMessage.swift */; }; FDC4386327B4D94E00C60D73 /* SOGSMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4386227B4D94E00C60D73 /* SOGSMessage.swift */; }; FDC4386527B4DE7600C60D73 /* RoomPollInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4386427B4DE7600C60D73 /* RoomPollInfo.swift */; }; FDC4386727B4E10E00C60D73 /* Capabilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4386627B4E10E00C60D73 /* Capabilities.swift */; }; FDC4386927B4E6B800C60D73 /* String+Utlities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4386827B4E6B700C60D73 /* String+Utlities.swift */; }; - FDC4386B27B4E88F00C60D73 /* BatchRequestInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4386A27B4E88F00C60D73 /* BatchRequestInfo.swift */; }; FDC4386C27B4E90300C60D73 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; }; FDC4387227B5BB3B00C60D73 /* FileUploadResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4387127B5BB3B00C60D73 /* FileUploadResponse.swift */; }; - FDC4387427B5BB9B00C60D73 /* Promise+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4387327B5BB9B00C60D73 /* Promise+Utilities.swift */; }; + FDC4387427B5BB9B00C60D73 /* (null) in Sources */ = {isa = PBXBuildFile; }; FDC4387827B5C35400C60D73 /* SendMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4387727B5C35400C60D73 /* SendMessageRequest.swift */; }; FDC4389227B9FFC700C60D73 /* SessionMessagingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A6F025539DE700C340D1 /* SessionMessagingKit.framework */; platformFilter = ios; }; FDC4389A27BA002500C60D73 /* OpenGroupAPISpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4389927BA002500C60D73 /* OpenGroupAPISpec.swift */; }; FDC438A427BB107F00C60D73 /* UserBanRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438A327BB107F00C60D73 /* UserBanRequest.swift */; }; FDC438A627BB113A00C60D73 /* UserUnbanRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438A527BB113A00C60D73 /* UserUnbanRequest.swift */; }; FDC438AA27BB12BB00C60D73 /* UserModeratorRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438A927BB12BB00C60D73 /* UserModeratorRequest.swift */; }; - FDC438B127BB159600C60D73 /* RequestInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438B027BB159600C60D73 /* RequestInfo.swift */; }; - FDC438B327BB15B400C60D73 /* ResponseInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438B227BB15B400C60D73 /* ResponseInfo.swift */; }; - FDC438B927BB161E00C60D73 /* OnionRequestAPIVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438B827BB161E00C60D73 /* OnionRequestAPIVersion.swift */; }; FDC438BD27BB2AB400C60D73 /* Mockable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438BC27BB2AB400C60D73 /* Mockable.swift */; }; FDC438C127BB4E6800C60D73 /* SMKDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438C027BB4E6800C60D73 /* SMKDependencies.swift */; }; FDC438C327BB512200C60D73 /* SodiumProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438C227BB512200C60D73 /* SodiumProtocols.swift */; }; @@ -887,21 +814,82 @@ FDF0B75A2807F3A3004C14C5 /* MessageSenderError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B7592807F3A3004C14C5 /* MessageSenderError.swift */; }; FDF0B75C2807F41D004C14C5 /* MessageSender+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B75B2807F41D004C14C5 /* MessageSender+Convenience.swift */; }; FDF0B75E280AAF35004C14C5 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B75D280AAF35004C14C5 /* Preferences.swift */; }; - FDF1AD5F28FF5F930080A701 /* EditGroupViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF1AD5E28FF5F930080A701 /* EditGroupViewModel.swift */; }; - FDF1AD6128FF61110080A701 /* _012_AddClosedGroupInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF1AD6028FF61110080A701 /* _012_AddClosedGroupInfo.swift */; }; FDF222072818CECF000A4995 /* ConversationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF222062818CECF000A4995 /* ConversationViewModel.swift */; }; FDF222092818D2B0000A4995 /* NSAttributedString+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF222082818D2B0000A4995 /* NSAttributedString+Utilities.swift */; }; FDF2220B2818F38D000A4995 /* SessionApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF2220A2818F38D000A4995 /* SessionApp.swift */; }; FDF2220F281B55E6000A4995 /* QueryInterfaceRequest+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF2220E281B55E6000A4995 /* QueryInterfaceRequest+Utilities.swift */; }; FDF22211281B5E0B000A4995 /* TableRecord+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF22210281B5E0B000A4995 /* TableRecord+Utilities.swift */; }; FDF40CDE2897A1BC006A0CC4 /* _004_RemoveLegacyYDB.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF40CDD2897A1BC006A0CC4 /* _004_RemoveLegacyYDB.swift */; }; + FDF8487929405906007DCAE5 /* HTTPQueryParam.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8487529405906007DCAE5 /* HTTPQueryParam.swift */; }; + FDF8487A29405906007DCAE5 /* HTTPError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8487629405906007DCAE5 /* HTTPError.swift */; }; + FDF8487B29405906007DCAE5 /* HTTPHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8487729405906007DCAE5 /* HTTPHeader.swift */; }; + FDF8487C29405906007DCAE5 /* HTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8487829405906007DCAE5 /* HTTPMethod.swift */; }; + FDF8487F29405994007DCAE5 /* HTTPHeader+OpenGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8487D29405993007DCAE5 /* HTTPHeader+OpenGroup.swift */; }; + FDF8488029405994007DCAE5 /* HTTPQueryParam+OpenGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8487E29405994007DCAE5 /* HTTPQueryParam+OpenGroup.swift */; }; + FDF84881294059F5007DCAE5 /* ResponseInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438B227BB15B400C60D73 /* ResponseInfo.swift */; }; + FDF8488329405A12007DCAE5 /* BatchResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8488229405A12007DCAE5 /* BatchResponse.swift */; }; + FDF8488429405A2B007DCAE5 /* RequestInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438B027BB159600C60D73 /* RequestInfo.swift */; }; + FDF8488629405A61007DCAE5 /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8488529405A60007DCAE5 /* Request.swift */; }; + FDF8488829405A9A007DCAE5 /* SOGSBatchRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8488729405A9A007DCAE5 /* SOGSBatchRequest.swift */; }; + FDF8488929405B27007DCAE5 /* Data+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFD645A27F26D4600808CA1 /* Data+Utilities.swift */; }; + FDF8488B29405BF2007DCAE5 /* SSKDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8488A29405BF2007DCAE5 /* SSKDependencies.swift */; }; + FDF8488E29405C04007DCAE5 /* GetSnodePoolJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8488D29405C04007DCAE5 /* GetSnodePoolJob.swift */; }; + FDF8489129405C13007DCAE5 /* SnodeAPINamespace.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8489029405C13007DCAE5 /* SnodeAPINamespace.swift */; }; + FDF8489429405C1B007DCAE5 /* SnodeAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8489329405C1B007DCAE5 /* SnodeAPI.swift */; }; + FDF848BC29405C5A007DCAE5 /* SnodeRecursiveResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8489A29405C5A007DCAE5 /* SnodeRecursiveResponse.swift */; }; + FDF848BD29405C5A007DCAE5 /* GetMessagesRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8489B29405C5A007DCAE5 /* GetMessagesRequest.swift */; }; + FDF848BE29405C5A007DCAE5 /* GetServiceNodesRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8489C29405C5A007DCAE5 /* GetServiceNodesRequest.swift */; }; + FDF848BF29405C5A007DCAE5 /* SnodeResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8489D29405C5A007DCAE5 /* SnodeResponse.swift */; }; + FDF848C029405C5A007DCAE5 /* ONSResolveResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8489E29405C5A007DCAE5 /* ONSResolveResponse.swift */; }; + FDF848C129405C5A007DCAE5 /* UpdateExpiryRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8489F29405C5A007DCAE5 /* UpdateExpiryRequest.swift */; }; + FDF848C229405C5A007DCAE5 /* OxenDaemonRPCRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848A029405C5A007DCAE5 /* OxenDaemonRPCRequest.swift */; }; + FDF848C329405C5A007DCAE5 /* DeleteMessagesRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848A129405C5A007DCAE5 /* DeleteMessagesRequest.swift */; }; + FDF848C429405C5A007DCAE5 /* RevokeSubkeyResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848A229405C5A007DCAE5 /* RevokeSubkeyResponse.swift */; }; + FDF848C529405C5B007DCAE5 /* GetSwarmRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848A329405C5A007DCAE5 /* GetSwarmRequest.swift */; }; + FDF848C629405C5B007DCAE5 /* DeleteAllMessagesRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848A429405C5A007DCAE5 /* DeleteAllMessagesRequest.swift */; }; + FDF848C729405C5B007DCAE5 /* SendMessageResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848A529405C5A007DCAE5 /* SendMessageResponse.swift */; }; + FDF848C829405C5B007DCAE5 /* ONSResolveRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848A629405C5A007DCAE5 /* ONSResolveRequest.swift */; }; + FDF848C929405C5B007DCAE5 /* SnodeRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848A729405C5A007DCAE5 /* SnodeRequest.swift */; }; + FDF848CA29405C5B007DCAE5 /* DeleteAllBeforeRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848A829405C5A007DCAE5 /* DeleteAllBeforeRequest.swift */; }; + FDF848CB29405C5B007DCAE5 /* SnodePoolResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848A929405C5A007DCAE5 /* SnodePoolResponse.swift */; }; + FDF848CC29405C5B007DCAE5 /* SnodeReceivedMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848AA29405C5A007DCAE5 /* SnodeReceivedMessage.swift */; }; + FDF848CD29405C5B007DCAE5 /* GetNetworkTimestampResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848AB29405C5A007DCAE5 /* GetNetworkTimestampResponse.swift */; }; + FDF848CE29405C5B007DCAE5 /* UpdateExpiryAllRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848AC29405C5A007DCAE5 /* UpdateExpiryAllRequest.swift */; }; + FDF848CF29405C5B007DCAE5 /* SendMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848AD29405C5A007DCAE5 /* SendMessageRequest.swift */; }; + FDF848D029405C5B007DCAE5 /* UpdateExpiryResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848AE29405C5A007DCAE5 /* UpdateExpiryResponse.swift */; }; + FDF848D129405C5B007DCAE5 /* SnodeSwarmItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848AF29405C5A007DCAE5 /* SnodeSwarmItem.swift */; }; + FDF848D229405C5B007DCAE5 /* LegacyGetMessagesRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848B029405C5A007DCAE5 /* LegacyGetMessagesRequest.swift */; }; + FDF848D329405C5B007DCAE5 /* UpdateExpiryAllResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848B129405C5A007DCAE5 /* UpdateExpiryAllResponse.swift */; }; + FDF848D429405C5B007DCAE5 /* DeleteAllBeforeResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848B229405C5A007DCAE5 /* DeleteAllBeforeResponse.swift */; }; + FDF848D529405C5B007DCAE5 /* DeleteAllMessagesResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848B329405C5A007DCAE5 /* DeleteAllMessagesResponse.swift */; }; + FDF848D629405C5B007DCAE5 /* SnodeMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848B429405C5A007DCAE5 /* SnodeMessage.swift */; }; + FDF848D729405C5B007DCAE5 /* SnodeBatchRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848B529405C5A007DCAE5 /* SnodeBatchRequest.swift */; }; + FDF848D829405C5B007DCAE5 /* SwarmSnode.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848B629405C5A007DCAE5 /* SwarmSnode.swift */; }; + FDF848D929405C5B007DCAE5 /* SnodeAuthenticatedRequestBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848B729405C5A007DCAE5 /* SnodeAuthenticatedRequestBody.swift */; }; + FDF848DA29405C5B007DCAE5 /* GetMessagesResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848B829405C5A007DCAE5 /* GetMessagesResponse.swift */; }; + FDF848DB29405C5B007DCAE5 /* DeleteMessagesResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848B929405C5A007DCAE5 /* DeleteMessagesResponse.swift */; }; + FDF848DC29405C5B007DCAE5 /* RevokeSubkeyRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848BA29405C5A007DCAE5 /* RevokeSubkeyRequest.swift */; }; + FDF848DD29405C5B007DCAE5 /* LegacySendMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848BB29405C5A007DCAE5 /* LegacySendMessageRequest.swift */; }; + FDF848E329405D6E007DCAE5 /* OnionRequestAPIVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848DE29405D6E007DCAE5 /* OnionRequestAPIVersion.swift */; }; + FDF848E429405D6E007DCAE5 /* SnodeAPIEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848DF29405D6E007DCAE5 /* SnodeAPIEndpoint.swift */; }; + FDF848E529405D6E007DCAE5 /* SnodeAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848E029405D6E007DCAE5 /* SnodeAPIError.swift */; }; + FDF848E629405D6E007DCAE5 /* OnionRequestAPIDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848E129405D6E007DCAE5 /* OnionRequestAPIDestination.swift */; }; + FDF848E729405D6E007DCAE5 /* OnionRequestAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848E229405D6E007DCAE5 /* OnionRequestAPIError.swift */; }; + FDF848EB29405E4F007DCAE5 /* OnionRequestAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848E829405E4E007DCAE5 /* OnionRequestAPI.swift */; }; + FDF848EC29405E4F007DCAE5 /* OnionRequestAPI+Encryption.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848E929405E4E007DCAE5 /* OnionRequestAPI+Encryption.swift */; }; + FDF848ED29405E4F007DCAE5 /* Notification+OnionRequestAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848EA29405E4E007DCAE5 /* Notification+OnionRequestAPI.swift */; }; + FDF848EF294067E4007DCAE5 /* URLResponse+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848EE294067E4007DCAE5 /* URLResponse+Utilities.swift */; }; + FDF848F129406A30007DCAE5 /* Format.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848F029406A30007DCAE5 /* Format.swift */; }; + FDF848F329413DB0007DCAE5 /* ImagePickerHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848F229413DB0007DCAE5 /* ImagePickerHandler.swift */; }; + FDF848F529413EEC007DCAE5 /* SessionCell+Styling.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848F429413EEC007DCAE5 /* SessionCell+Styling.swift */; }; + FDF848F729414477007DCAE5 /* CurrentUserPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848F629414477007DCAE5 /* CurrentUserPoller.swift */; }; FDFD645927F26C6800808CA1 /* Array+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D12553860800C340D1 /* Array+Utilities.swift */; }; - FDFD645B27F26D4600808CA1 /* Data+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFD645A27F26D4600808CA1 /* Data+Utilities.swift */; }; FDFD645D27F273F300808CA1 /* MockGeneralCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */; }; FDFDE124282D04F20098B17F /* MediaDismissAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE123282D04F20098B17F /* MediaDismissAnimationController.swift */; }; FDFDE126282D05380098B17F /* MediaInteractiveDismiss.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE125282D05380098B17F /* MediaInteractiveDismiss.swift */; }; FDFDE128282D05530098B17F /* MediaPresentationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE127282D05530098B17F /* MediaPresentationContext.swift */; }; FDFDE12A282D056B0098B17F /* MediaZoomAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE129282D056B0098B17F /* MediaZoomAnimationController.swift */; }; + FE43694493EC2E1E438EBEB3 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 13D1714FDC4DAB121DA2C73A /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -1099,16 +1087,14 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 06160ECE3FE5A06A916FF8C5 /* Pods-GlobalDependencies-Session-SessionTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-Session-SessionTests.debug.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-Session-SessionTests/Pods-GlobalDependencies-Session-SessionTests.debug.xcconfig"; sourceTree = ""; }; - 0BF4561630A52BE96F164CF6 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 0E836037CC97CE5A47735596 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.app store release.xcconfig"; sourceTree = ""; }; - 0E8564674E3201E218939AFB /* Pods-GlobalDependencies-Session-SessionTests.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-Session-SessionTests.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-Session-SessionTests/Pods-GlobalDependencies-Session-SessionTests.app store release.xcconfig"; sourceTree = ""; }; - 18EAE958B8C12503F2C294DF /* Pods-GlobalDependencies-SessionUIKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-SessionUIKit.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-SessionUIKit/Pods-GlobalDependencies-SessionUIKit.app store release.xcconfig"; sourceTree = ""; }; - 1A0882BF820F5B44969F91F1 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig"; sourceTree = ""; }; - 245BF74EF6348E2D4125033F /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.app store release.xcconfig"; sourceTree = ""; }; - 2581AFACDDDC1404866D7B8C /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.app store release.xcconfig"; sourceTree = ""; }; - 2691123A7F231EDD8226C4B5 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 29CF8C79F41BF00B1C2E59A0 /* Pods-SessionUIKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionUIKit.app store release.xcconfig"; path = "Target Support Files/Pods-SessionUIKit/Pods-SessionUIKit.app store release.xcconfig"; sourceTree = ""; }; + 05C76EFA593DD507061C50B2 /* Pods-GlobalDependencies-SessionUIKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-SessionUIKit.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-SessionUIKit/Pods-GlobalDependencies-SessionUIKit.app store release.xcconfig"; sourceTree = ""; }; + 05E68C7F291EC08B8A43A534 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 0772459E7D5F6747EDC889F3 /* Pods-GlobalDependencies-Session-SessionTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-Session-SessionTests.debug.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-Session-SessionTests/Pods-GlobalDependencies-Session-SessionTests.debug.xcconfig"; sourceTree = ""; }; + 07B7038C849F53378CD36B83 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit_SessionUtilitiesKitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit_SessionUtilitiesKitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 13D1714FDC4DAB121DA2C73A /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 285705D20F792E174C8A9BBA /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig"; sourceTree = ""; }; + 32C10A2A536B58FC42C46C3C /* Pods_GlobalDependencies_Session_SessionTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_Session_SessionTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 34040971CC7AF9C8A6C1E838 /* Pods-GlobalDependencies-Session.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-Session.debug.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-Session/Pods-GlobalDependencies-Session.debug.xcconfig"; sourceTree = ""; }; 3427C64120F500DE00EEC730 /* OWSMessageTimerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageTimerView.h; sourceTree = ""; }; 3427C64220F500DF00EEC730 /* OWSMessageTimerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSMessageTimerView.m; sourceTree = ""; }; 3430FE171F7751D4000EC51B /* GiphyAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GiphyAPI.swift; sourceTree = ""; }; @@ -1186,7 +1172,6 @@ 45CB2FA71CB7146C00E1B343 /* Launch Screen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = "Launch Screen.storyboard"; path = "Session/Meta/Launch Screen.storyboard"; sourceTree = SOURCE_ROOT; }; 45CD81EE1DC030E7004C9430 /* SyncPushTokensJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyncPushTokensJob.swift; sourceTree = ""; }; 45F32C1D205718B000A300D5 /* MediaPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MediaPageViewController.swift; path = "Session/Media Viewing & Editing/MediaPageViewController.swift"; sourceTree = SOURCE_ROOT; }; - 48AD214D67ABED845101E795 /* Pods_GlobalDependencies_Session.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_Session.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4C090A1A210FD9C7001FD7F9 /* HapticFeedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HapticFeedback.swift; sourceTree = ""; }; 4C1885D1218F8E1C00B67051 /* PhotoGridViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoGridViewCell.swift; sourceTree = ""; }; 4C1D2337218B6BA000A0598F /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; @@ -1200,20 +1185,16 @@ 4CA46F4B219CCC630038ABDE /* CaptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaptionView.swift; sourceTree = ""; }; 4CA485BA2232339F004B9E7D /* PhotoCaptureViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoCaptureViewController.swift; sourceTree = ""; }; 4CC613352227A00400E21A3A /* ConversationSearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationSearch.swift; sourceTree = ""; }; - 506FA2159653FF9F446D97D1 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.debug.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.debug.xcconfig"; sourceTree = ""; }; - 510955DC99A0FD84F2D1C159 /* Pods-GlobalDependencies-SessionUIKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-SessionUIKit.debug.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-SessionUIKit/Pods-GlobalDependencies-SessionUIKit.debug.xcconfig"; sourceTree = ""; }; - 5442DF945D862CEDF7F8AC49 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit_SessionUtilitiesKitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit_SessionUtilitiesKitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 5626DC0D5F62C1C2C64E4AFC /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.app store release.xcconfig"; sourceTree = ""; }; - 56F41C56FC7B2F381E440FB0 /* Pods-GlobalDependencies-Session.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-Session.debug.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-Session/Pods-GlobalDependencies-Session.debug.xcconfig"; sourceTree = ""; }; - 58A6BA91F634756FA0BEC9E5 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.app store release.xcconfig"; sourceTree = ""; }; - 6737124ECBC2DFEE2DD716D3 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionSnodeKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionSnodeKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 6BE8FBF62464A7177034A0AB /* Pods-GlobalDependencies-Session.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-Session.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-Session/Pods-GlobalDependencies-Session.app store release.xcconfig"; sourceTree = ""; }; - 6F84A214B9A1C0CCF6DB09C8 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 55C13C7B4B700846E49C0E25 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.app store release.xcconfig"; sourceTree = ""; }; + 5DA3BDDFFB9E937A49C35FCC /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.app store release.xcconfig"; sourceTree = ""; }; + 621B42AC592F3456ACD82F8B /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.app store release.xcconfig"; sourceTree = ""; }; + 62B512CEB14BD4A4A53CF532 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.app store release.xcconfig"; sourceTree = ""; }; + 678CFB04E76F7E388AFCFA86 /* Pods_GlobalDependencies_SessionUIKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_SessionUIKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 6A71AD9BEAFF0C9E8016BC23 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.app store release.xcconfig"; sourceTree = ""; }; + 6DA09080DD9779C860023A60 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.debug.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.debug.xcconfig"; sourceTree = ""; }; 70377AAA1918450100CAF501 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; 768A1A2A17FC9CD300E00ED8 /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; }; 76C87F18181EFCE600C4ACAB /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = System/Library/Frameworks/MediaPlayer.framework; sourceTree = SDKROOT; }; - 782B65234A707D762FEAFD3B /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 7A8A44E3F8AC9282AC5E6E5A /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7B0EFDED274F598600FFAAE7 /* TimestampUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimestampUtils.swift; sourceTree = ""; }; 7B0EFDEF275084AA00FFAAE7 /* CallMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallMessageCell.swift; sourceTree = ""; }; 7B0EFDF3275490EA00FFAAE7 /* ringing.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = ringing.mp3; sourceTree = ""; }; @@ -1281,16 +1262,20 @@ 7BFD1A892745C4F000FB91B9 /* Permissions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Permissions.swift; sourceTree = ""; }; 7BFD1A8B2747150E00FB91B9 /* TurnServerInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TurnServerInfo.swift; sourceTree = ""; }; 7BFD1A962747689000FB91B9 /* Session-Turn-Server */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "Session-Turn-Server"; sourceTree = ""; }; - 82099864FD91C9126A750313 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.app store release.xcconfig"; sourceTree = ""; }; - 8E029A324780A800DE6B70B3 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.app store release.xcconfig"; sourceTree = ""; }; - 96ED0C9B69379BE6FF4E9DA6 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.debug.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.debug.xcconfig"; sourceTree = ""; }; + 8448EFF76CD3CA5B2283B8A0 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.debug.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.debug.xcconfig"; sourceTree = ""; }; + 847091A12D82E41B1EBB8FB3 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.debug.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.debug.xcconfig"; sourceTree = ""; }; + 8603226ED1C6F61F1F2D3734 /* Pods-GlobalDependencies-Session.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-Session.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-Session/Pods-GlobalDependencies-Session.app store release.xcconfig"; sourceTree = ""; }; + 86A3D36084020C9118DBCEE3 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 8727C47348B6EFA767EE583A /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.debug.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.debug.xcconfig"; sourceTree = ""; }; + 8E946CB54A221018E23599DE /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig"; sourceTree = ""; }; + 9E358E7590EB145A5047F885 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionSnodeKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionSnodeKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A11CD70C17FA230600A2D1B1 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; A163E8AA16F3F6A90094D68B /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; A1C32D4D17A0652C000A904E /* AddressBook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBook.framework; path = System/Library/Frameworks/AddressBook.framework; sourceTree = SDKROOT; }; A1C32D4F17A06537000A904E /* AddressBookUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBookUI.framework; path = System/Library/Frameworks/AddressBookUI.framework; sourceTree = SDKROOT; }; A1FDCBEE16DAA6C300868894 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; - A9C58C3ADF46C718488458C2 /* Pods_GlobalDependencies_SessionUIKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_SessionUIKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - B4C92F6ADBECCD47A6B6008E /* Pods_GlobalDependencies_Session_SessionTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_Session_SessionTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + B1910A32EB2AD01913629646 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.debug.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.debug.xcconfig"; sourceTree = ""; }; + B4F9FCBDA07F07CB48220D4C /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.debug.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.debug.xcconfig"; sourceTree = ""; }; B60EDE031A05A01700D73516 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; B646D10E1AA5461A004133BA /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; B657DDC91911A40500F45B0C /* Signal.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Signal.entitlements; sourceTree = ""; }; @@ -1396,7 +1381,6 @@ B8FF8E7325C10FC3004D1F22 /* GeoLite2-Country-Locations-English */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; name = "GeoLite2-Country-Locations-English"; path = "Countries/GeoLite2-Country-Locations-English"; sourceTree = ""; }; B8FF8EA525C11FEF004D1F22 /* IPv4.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPv4.swift; sourceTree = ""; }; B9EB5ABC1884C002007CBB57 /* MessageUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessageUI.framework; path = System/Library/Frameworks/MessageUI.framework; sourceTree = SDKROOT; }; - BE11AFA6FD8CAE894CABC28D /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.debug.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.debug.xcconfig"; sourceTree = ""; }; C300A5B12554AF9800555489 /* VisibleMessage+Profile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VisibleMessage+Profile.swift"; sourceTree = ""; }; C300A5BC2554B00D00555489 /* ReadReceipt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadReceipt.swift; sourceTree = ""; }; C300A5D22554B05A00555489 /* TypingIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypingIndicator.swift; sourceTree = ""; }; @@ -1535,9 +1519,7 @@ C38EF2F7255B6DBC007E1867 /* OWSAudioPlayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSAudioPlayer.m; path = SessionMessagingKit/Utilities/OWSAudioPlayer.m; sourceTree = SOURCE_ROOT; }; C38EF2FA255B6DBD007E1867 /* Bench.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Bench.swift; path = SignalUtilitiesKit/Utilities/Bench.swift; sourceTree = SOURCE_ROOT; }; C38EF2FB255B6DBD007E1867 /* OWSWindowManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSWindowManager.h; path = SessionMessagingKit/Utilities/OWSWindowManager.h; 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; }; 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; }; C38EF307255B6DBE007E1867 /* UIGestureRecognizer+OWS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIGestureRecognizer+OWS.swift"; path = "SignalUtilitiesKit/Utilities/UIGestureRecognizer+OWS.swift"; sourceTree = SOURCE_ROOT; }; C38EF309255B6DBE007E1867 /* DeviceSleepManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DeviceSleepManager.swift; path = SessionMessagingKit/Utilities/DeviceSleepManager.swift; sourceTree = SOURCE_ROOT; }; @@ -1596,20 +1578,15 @@ C3A8AF752665B03900A467FE /* hi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hi; path = hi.lproj/Localizable.strings; sourceTree = ""; }; C3A8AF762665F97A00A467FE /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = ""; }; C3AAFFF125AE99710089E6DD /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - C3ADC66026426688005F1414 /* ShareVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareVC.swift; sourceTree = ""; }; + C3ADC66026426688005F1414 /* ShareNavController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareNavController.swift; sourceTree = ""; }; C3AECBEA24EF5244005743DE /* fa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fa; path = fa.lproj/Localizable.strings; sourceTree = ""; }; C3BBE0C62554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FixedWidthInteger+BigEndian.swift"; sourceTree = ""; }; C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SessionSnodeKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C3C2A5A1255385C100C340D1 /* SessionSnodeKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SessionSnodeKit.h; sourceTree = ""; }; C3C2A5A2255385C100C340D1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - C3C2A5B6255385EC00C340D1 /* SnodeMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnodeMessage.swift; sourceTree = ""; }; C3C2A5B7255385EC00C340D1 /* Snode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Snode.swift; sourceTree = ""; }; C3C2A5B9255385ED00C340D1 /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; - C3C2A5BA255385ED00C340D1 /* OnionRequestAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnionRequestAPI.swift; sourceTree = ""; }; - C3C2A5BB255385ED00C340D1 /* OnionRequestAPI+Encryption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OnionRequestAPI+Encryption.swift"; sourceTree = ""; }; C3C2A5BC255385EE00C340D1 /* HTTP.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTP.swift; sourceTree = ""; }; - C3C2A5BD255385EE00C340D1 /* Notification+OnionRequestAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Notification+OnionRequestAPI.swift"; sourceTree = ""; }; - C3C2A5BE255385EE00C340D1 /* SnodeAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnodeAPI.swift; sourceTree = ""; }; C3C2A5CE2553860700C340D1 /* Logging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; C3C2A5D12553860800C340D1 /* Array+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array+Utilities.swift"; sourceTree = ""; }; C3C2A5D22553860900C340D1 /* String+Trimming.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Trimming.swift"; sourceTree = ""; }; @@ -1647,7 +1624,8 @@ C3ECBF7A257056B700EA7FCE /* Threading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Threading.swift; sourceTree = ""; }; C3F0A52F255C80BC007BE2A3 /* NoopNotificationsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoopNotificationsManager.swift; sourceTree = ""; }; C3F0A5B2255C915C007BE2A3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; - D0CE0424239A1574F683D2D7 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.debug.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.debug.xcconfig"; sourceTree = ""; }; + C5828D9A55CD76B75E8FA367 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + CC8D3B6504946442A0CF775C /* Pods_GlobalDependencies_Session.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_Session.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D2179CFB16BB0B3A0006F3AB /* CoreTelephony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreTelephony.framework; path = System/Library/Frameworks/CoreTelephony.framework; sourceTree = SDKROOT; }; D2179CFD16BB0B480006F3AB /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; D221A089169C9E5E00537ABF /* Session.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Session.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1658,12 +1636,14 @@ D221A0E7169DFFC500537ABF /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = ../../../../../../System/Library/Frameworks/AVFoundation.framework; sourceTree = ""; }; D24B5BD4169F568C00681372 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = ../../../../../../System/Library/Frameworks/AudioToolbox.framework; sourceTree = ""; }; D2AEACDB16C426DA00C364C0 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; - DAF57FAAF30631D0E99DA361 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.debug.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.debug.xcconfig"; sourceTree = ""; }; - DBA125424EDD2417B515C63A /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D3BE3061D535DBC1DF0C94D4 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E0B0F4C34363FE679EE3F203 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E1A0AD8B16E13FDD0071E604 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; - E23C1E6B7E0C12BF4ACD9CBE /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.debug.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.debug.xcconfig"; sourceTree = ""; }; - EC5C23F9D234F558BE5E41DE /* Pods-SessionUIKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionUIKit.debug.xcconfig"; path = "Target Support Files/Pods-SessionUIKit/Pods-SessionUIKit.debug.xcconfig"; sourceTree = ""; }; - F705826F79C4A591AB35D68F /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig"; sourceTree = ""; }; + EB5B8ACA4C6F512FA3E21859 /* Pods-GlobalDependencies-SessionUIKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-SessionUIKit.debug.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-SessionUIKit/Pods-GlobalDependencies-SessionUIKit.debug.xcconfig"; sourceTree = ""; }; + EED1CF82CAB23FE3345564F9 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.app store release.xcconfig"; sourceTree = ""; }; + F154A10CE1ADA33C16B45357 /* Pods-GlobalDependencies-Session-SessionTests.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-Session-SessionTests.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-Session-SessionTests/Pods-GlobalDependencies-Session-SessionTests.app store release.xcconfig"; sourceTree = ""; }; + F390F8E34CA76B3F7D3B1826 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.app store release.xcconfig"; sourceTree = ""; }; + F60C5B6CD14329816B0E8CC0 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.app store release.xcconfig"; sourceTree = ""; }; FC3BD9871A30A790005B96BB /* Social.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Social.framework; path = System/Library/Frameworks/Social.framework; sourceTree = SDKROOT; }; FCB11D8B1A129A76002F93FB /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; }; FD078E4727E02561000769AF /* CommonMockedExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonMockedExtensions.swift; sourceTree = ""; }; @@ -1672,7 +1652,6 @@ FD078E5127E1760A000769AF /* OGMDependencyExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OGMDependencyExtensions.swift; sourceTree = ""; }; FD078E5927E29F09000769AF /* MockNonce16Generator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockNonce16Generator.swift; sourceTree = ""; }; FD078E5B27E29F78000769AF /* MockNonce24Generator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockNonce24Generator.swift; sourceTree = ""; }; - FD09796827F6BEA700936362 /* SwarmSnode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwarmSnode.swift; sourceTree = ""; }; FD09796A27F6C67500936362 /* Failable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Failable.swift; sourceTree = ""; }; FD09796D27FA6D0000936362 /* Contact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Contact.swift; sourceTree = ""; }; FD09796F27FA6FF300936362 /* Profile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Profile.swift; sourceTree = ""; }; @@ -1720,10 +1699,6 @@ FD17D7C627F5207C00122BE0 /* DatabaseMigrator+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DatabaseMigrator+Utilities.swift"; sourceTree = ""; }; FD17D7C927F546D900122BE0 /* _001_InitialSetupMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _001_InitialSetupMigration.swift; sourceTree = ""; }; FD17D7CC27F546FF00122BE0 /* Setting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Setting.swift; sourceTree = ""; }; - FD17D7D127F5797A00122BE0 /* SnodeAPIEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnodeAPIEndpoint.swift; sourceTree = ""; }; - FD17D7D327F6584600122BE0 /* OnionRequestAPIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnionRequestAPIError.swift; sourceTree = ""; }; - FD17D7D727F658E200122BE0 /* OnionRequestAPIDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnionRequestAPIDestination.swift; sourceTree = ""; }; - FD17D7E027F67BD400122BE0 /* SnodeReceivedMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnodeReceivedMessage.swift; sourceTree = ""; }; FD17D7E427F6A09900122BE0 /* Identity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Identity.swift; sourceTree = ""; }; FD17D7E627F6A16700122BE0 /* _003_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _003_YDBToGRDBMigration.swift; sourceTree = ""; }; FD17D7E927F6A1C600122BE0 /* SUKLegacy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SUKLegacy.swift; sourceTree = ""; }; @@ -1762,20 +1737,11 @@ FD37EA1428AB42CB003AE748 /* IdentitySpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentitySpec.swift; sourceTree = ""; }; FD37EA1628AC5605003AE748 /* NotificationContentViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationContentViewModel.swift; sourceTree = ""; }; FD37EA1828AC5CCA003AE748 /* NotificationSoundViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSoundViewModel.swift; sourceTree = ""; }; - FD37F5572908F5C3005A5E92 /* RemoveUsersModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveUsersModal.swift; sourceTree = ""; }; - FD37F559290F9E9A005A5E92 /* GroupMembersViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMembersViewModel.swift; sourceTree = ""; }; - FD37F55F291867A8005A5E92 /* ImagePickerHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePickerHandler.swift; sourceTree = ""; }; - FD37F5612918C471005A5E92 /* GroupMemberLeftMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMemberLeftMessage.swift; sourceTree = ""; }; - FD37F5632918C58D005A5E92 /* GroupInviteMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupInviteMessage.swift; sourceTree = ""; }; - FD37F5652918C697005A5E92 /* GroupPromoteMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupPromoteMessage.swift; sourceTree = ""; }; - FD37F5672918D458005A5E92 /* MessageReceiver+ClosedGroups.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+ClosedGroups.swift"; sourceTree = ""; }; FD39352B28F382920084DADA /* VersionFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionFooterView.swift; sourceTree = ""; }; FD39353528F7C3390084DADA /* _004_FlagMessageHashAsDeletedOrInvalid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _004_FlagMessageHashAsDeletedOrInvalid.swift; sourceTree = ""; }; FD3AABE828306BBD00E5099A /* ThreadPickerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadPickerViewModel.swift; sourceTree = ""; }; FD3C905B27E3FBEF00CD579F /* BatchRequestInfoSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchRequestInfoSpec.swift; sourceTree = ""; }; FD3C905F27E410F700CD579F /* FileUploadResponseSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileUploadResponseSpec.swift; sourceTree = ""; }; - FD3C906127E411AF00CD579F /* HeaderSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderSpec.swift; sourceTree = ""; }; - FD3C906327E4122F00CD579F /* RequestSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestSpec.swift; sourceTree = ""; }; FD3C906627E416AF00CD579F /* BlindedIdLookupSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlindedIdLookupSpec.swift; sourceTree = ""; }; FD3C906927E417CE00CD579F /* SodiumUtilitiesSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SodiumUtilitiesSpec.swift; sourceTree = ""; }; FD3C906C27E43C4B00CD579F /* MessageSenderEncryptionSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSenderEncryptionSpec.swift; sourceTree = ""; }; @@ -1805,7 +1771,6 @@ FD6A7A6C2818C61500035AC1 /* _002_SetupStandardJobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_SetupStandardJobs.swift; sourceTree = ""; }; FD705A91278D051200F16121 /* ReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReusableView.swift; sourceTree = ""; }; FD7115EA28C5D78E00B47552 /* ThreadSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadSettingsViewModel.swift; sourceTree = ""; }; - FD7115ED28C5D79B00B47552 /* SessionAvatarCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionAvatarCell.swift; sourceTree = ""; }; FD7115EF28C5D7DE00B47552 /* SessionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionHeaderView.swift; sourceTree = ""; }; FD7115F128C6CB3900B47552 /* _010_AddThreadIdToFTS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _010_AddThreadIdToFTS.swift; sourceTree = ""; }; FD7115F328C71EB200B47552 /* ThreadDisappearingMessagesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadDisappearingMessagesViewModel.swift; sourceTree = ""; }; @@ -1832,7 +1797,7 @@ FD71164528E2CC1300B47552 /* SessionHighlightingBackgroundLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionHighlightingBackgroundLabel.swift; sourceTree = ""; }; FD71164728E2CE8700B47552 /* SessionCell+AccessoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionCell+AccessoryView.swift"; sourceTree = ""; }; FD71164928E3EA5B00B47552 /* DismissType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DismissType.swift; sourceTree = ""; }; - FD71164B28E3F5AA00B47552 /* SessionCell+ExtraAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionCell+ExtraAction.swift"; sourceTree = ""; }; + FD71164B28E3F5AA00B47552 /* SessionCell+Accessibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionCell+Accessibility.swift"; sourceTree = ""; }; FD71164D28E3F8CC00B47552 /* SessionCell+Info.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionCell+Info.swift"; sourceTree = ""; }; FD71164F28E3F9FA00B47552 /* SessionTableViewModel+NavItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionTableViewModel+NavItem.swift"; sourceTree = ""; }; FD71165128E410BE00B47552 /* SessionTableSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionTableSection.swift; sourceTree = ""; }; @@ -1847,9 +1812,7 @@ FD7728952849E7E90018502F /* String+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Utilities.swift"; sourceTree = ""; }; FD7728972849E8110018502F /* UITableView+ReusableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITableView+ReusableView.swift"; sourceTree = ""; }; FD772899284AF1BD0018502F /* Sodium+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Sodium+Utilities.swift"; sourceTree = ""; }; - FD77289B284DDCE10018502F /* SnodePoolResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnodePoolResponse.swift; sourceTree = ""; }; FD77289D284EF1C50018502F /* Sodium+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Sodium+Utilities.swift"; sourceTree = ""; }; - FD77289F284EF5810018502F /* SnodeAPIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnodeAPIError.swift; sourceTree = ""; }; FD83B9AF27CF200A005E1583 /* SessionUtilitiesKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SessionUtilitiesKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; FD83B9BA27CF20AF005E1583 /* SessionIdSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionIdSpec.swift; sourceTree = ""; }; FD83B9BD27CF2243005E1583 /* TestConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestConstants.swift; sourceTree = ""; }; @@ -1857,7 +1820,6 @@ FD83B9C627CF3F10005E1583 /* CapabilitiesSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapabilitiesSpec.swift; sourceTree = ""; }; FD83B9C827D0487A005E1583 /* SendDirectMessageResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendDirectMessageResponse.swift; sourceTree = ""; }; FD83B9CB27D179BC005E1583 /* FSEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FSEndpoint.swift; sourceTree = ""; }; - FD83B9CD27D17A04005E1583 /* Request.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Request.swift; sourceTree = ""; }; FD83B9D127D59495005E1583 /* MockUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUserDefaults.swift; sourceTree = ""; }; FD848B86283B844B000E298B /* MessageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageViewModel.swift; sourceTree = ""; }; FD848B8A283DC509000E298B /* PagedDatabaseObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagedDatabaseObserver.swift; sourceTree = ""; }; @@ -1876,49 +1838,19 @@ FD859EF927C2F5C500510D0C /* MockGenericHash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockGenericHash.swift; sourceTree = ""; }; FD859EFB27C2F60700510D0C /* MockEd25519.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockEd25519.swift; sourceTree = ""; }; FD87DCF928B74DB300AF0F98 /* ConversationSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationSettingsViewModel.swift; sourceTree = ""; }; - FD87DCFB28B755B800AF0F98 /* BlockedContactsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedContactsViewController.swift; sourceTree = ""; }; FD87DCFD28B7582C00AF0F98 /* BlockedContactsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedContactsViewModel.swift; sourceTree = ""; }; FD87DCFF28B820F200AF0F98 /* BlockedContactCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedContactCell.swift; sourceTree = ""; }; FD87DD0328B8727D00AF0F98 /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; - FD8ECF3F292AF07900C0D1BB /* Poller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Poller.swift; sourceTree = ""; }; - FD8ECF41292B340D00C0D1BB /* SOGSBatchRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOGSBatchRequest.swift; sourceTree = ""; }; - FD8ECF43292B397E00C0D1BB /* SendMessageRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMessageRequest.swift; sourceTree = ""; }; - FD8ECF45292B4BD500C0D1BB /* SendMessageResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMessageResponse.swift; sourceTree = ""; }; - FD8ECF47292C287500C0D1BB /* UpdateExpiryRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateExpiryRequest.swift; sourceTree = ""; }; - FD8ECF49292C2A7300C0D1BB /* DeleteMessagesRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteMessagesRequest.swift; sourceTree = ""; }; - FD8ECF4B292C2AC200C0D1BB /* RevokeSubkeyRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RevokeSubkeyRequest.swift; sourceTree = ""; }; - FD8ECF4D292C2AF800C0D1BB /* DeleteAllMessagesRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteAllMessagesRequest.swift; sourceTree = ""; }; - FD8ECF4F292C2B2B00C0D1BB /* DeleteAllBeforeRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteAllBeforeRequest.swift; sourceTree = ""; }; - FD8ECF51292C2CAE00C0D1BB /* UpdateExpiryAllRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateExpiryAllRequest.swift; sourceTree = ""; }; - FD8ECF53292C2DB000C0D1BB /* SnodeAuthenticatedRequestBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnodeAuthenticatedRequestBody.swift; sourceTree = ""; }; - FD8ECF55292C327700C0D1BB /* LegacyGetMessagesRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyGetMessagesRequest.swift; sourceTree = ""; }; - FD8ECF57292C350500C0D1BB /* LegacySendMessageRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacySendMessageRequest.swift; sourceTree = ""; }; - FD8ECF59292C431B00C0D1BB /* GetSwarmRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetSwarmRequest.swift; sourceTree = ""; }; - FD8ECF5B292C469100C0D1BB /* UpdateExpiryResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateExpiryResponse.swift; sourceTree = ""; }; - FD8ECF5D292C478900C0D1BB /* SnodeRecursiveResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnodeRecursiveResponse.swift; sourceTree = ""; }; - FD8ECF5F292C4B2400C0D1BB /* SnodeSwarmItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnodeSwarmItem.swift; sourceTree = ""; }; - FD8ECF61292C4C8200C0D1BB /* DeleteMessagesResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteMessagesResponse.swift; sourceTree = ""; }; - FD8ECF63292C4D6600C0D1BB /* DeleteAllMessagesResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteAllMessagesResponse.swift; sourceTree = ""; }; - FD8ECF65292C6F8200C0D1BB /* DeleteAllBeforeResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteAllBeforeResponse.swift; sourceTree = ""; }; - FD8ECF67292C72B900C0D1BB /* UpdateExpiryAllResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateExpiryAllResponse.swift; sourceTree = ""; }; - FD8ECF69292C74A000C0D1BB /* RevokeSubkeyResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RevokeSubkeyResponse.swift; sourceTree = ""; }; - FD8ECF6B292C9B6400C0D1BB /* GetNetworkTimestampResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetNetworkTimestampResponse.swift; sourceTree = ""; }; - FD8ECF6D292C9EA100C0D1BB /* GetServiceNodesRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetServiceNodesRequest.swift; sourceTree = ""; }; - FD8ECF71292DCD1A00C0D1BB /* _013_AutoDownloadAttachments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _013_AutoDownloadAttachments.swift; sourceTree = ""; }; - FD8ECF73292DDB4A00C0D1BB /* Format.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Format.swift; sourceTree = ""; }; FD8ECF7829340F7100C0D1BB /* libsession-util.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = "libsession-util.xcframework"; sourceTree = ""; }; FD8ECF7A29340FFD00C0D1BB /* SessionUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionUtil.swift; sourceTree = ""; }; FD8ECF7C2934293A00C0D1BB /* _011_SharedUtilChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _011_SharedUtilChanges.swift; sourceTree = ""; }; FD8ECF7E2934298100C0D1BB /* SharedConfigDump.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedConfigDump.swift; sourceTree = ""; }; FD8ECF812934387A00C0D1BB /* ConfigUserProfileSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigUserProfileSpec.swift; sourceTree = ""; }; - FD8ECF842934508B00C0D1BB /* BatchResponseSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchResponseSpec.swift; sourceTree = ""; }; FD8ECF882935AB7200C0D1BB /* SessionUtilError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionUtilError.swift; sourceTree = ""; }; FD8ECF8A2935DB4B00C0D1BB /* SharedConfigMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedConfigMessage.swift; sourceTree = ""; }; FD8ECF8F29381FC200C0D1BB /* SessionUtil+UserProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionUtil+UserProfile.swift"; sourceTree = ""; }; FD8ECF912938552800C0D1BB /* Threading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Threading.swift; sourceTree = ""; }; FD8ECF93293856AF00C0D1BB /* Randomness.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Randomness.swift; sourceTree = ""; }; - FD8ECFA0293D8FDD00C0D1BB /* URLResponse+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLResponse+Utilities.swift"; sourceTree = ""; }; - FD90040E2818AB6D00ABAAF6 /* GetSnodePoolJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetSnodePoolJob.swift; sourceTree = ""; }; FD9004132818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_SetupStandardJobs.swift; sourceTree = ""; }; FD9B30F2293EA0BF008DEE3E /* BatchResponseSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchResponseSpec.swift; sourceTree = ""; }; FDA8EAFD280E8B78002B68E5 /* FailedMessageSendsJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailedMessageSendsJob.swift; sourceTree = ""; }; @@ -1950,15 +1882,12 @@ FDC4382E27B383AF00C60D73 /* PushServerResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushServerResponse.swift; sourceTree = ""; }; FDC4383727B3863200C60D73 /* VersionResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionResponse.swift; sourceTree = ""; }; FDC4383D27B4708600C60D73 /* Atomic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Atomic.swift; sourceTree = ""; }; - FDC4384E27B4804F00C60D73 /* Header.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Header.swift; sourceTree = ""; }; - FDC4385027B4807400C60D73 /* QueryParam.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryParam.swift; sourceTree = ""; }; FDC4385C27B4C18900C60D73 /* Room.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Room.swift; sourceTree = ""; }; FDC4385E27B4C4A200C60D73 /* PinnedMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedMessage.swift; sourceTree = ""; }; FDC4386227B4D94E00C60D73 /* SOGSMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOGSMessage.swift; sourceTree = ""; }; FDC4386427B4DE7600C60D73 /* RoomPollInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPollInfo.swift; sourceTree = ""; }; FDC4386627B4E10E00C60D73 /* Capabilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Capabilities.swift; sourceTree = ""; }; FDC4386827B4E6B700C60D73 /* String+Utlities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Utlities.swift"; sourceTree = ""; }; - FDC4386A27B4E88F00C60D73 /* BatchRequestInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchRequestInfo.swift; sourceTree = ""; }; FDC4387127B5BB3B00C60D73 /* FileUploadResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileUploadResponse.swift; sourceTree = ""; }; FDC4387727B5C35400C60D73 /* SendMessageRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMessageRequest.swift; sourceTree = ""; }; FDC4388E27B9FFC700C60D73 /* SessionMessagingKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SessionMessagingKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1968,7 +1897,6 @@ FDC438A927BB12BB00C60D73 /* UserModeratorRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserModeratorRequest.swift; sourceTree = ""; }; FDC438B027BB159600C60D73 /* RequestInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestInfo.swift; sourceTree = ""; }; FDC438B227BB15B400C60D73 /* ResponseInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResponseInfo.swift; sourceTree = ""; }; - FDC438B827BB161E00C60D73 /* OnionRequestAPIVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnionRequestAPIVersion.swift; sourceTree = ""; }; FDC438BC27BB2AB400C60D73 /* Mockable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mockable.swift; sourceTree = ""; }; FDC438C027BB4E6800C60D73 /* SMKDependencies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMKDependencies.swift; sourceTree = ""; }; FDC438C227BB512200C60D73 /* SodiumProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SodiumProtocols.swift; sourceTree = ""; }; @@ -2001,21 +1929,78 @@ FDF0B7592807F3A3004C14C5 /* MessageSenderError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSenderError.swift; sourceTree = ""; }; FDF0B75B2807F41D004C14C5 /* MessageSender+Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MessageSender+Convenience.swift"; sourceTree = ""; }; FDF0B75D280AAF35004C14C5 /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = ""; }; - FDF1AD5E28FF5F930080A701 /* EditGroupViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditGroupViewModel.swift; sourceTree = ""; }; - FDF1AD6028FF61110080A701 /* _012_AddClosedGroupInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _012_AddClosedGroupInfo.swift; sourceTree = ""; }; FDF222062818CECF000A4995 /* ConversationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationViewModel.swift; sourceTree = ""; }; FDF222082818D2B0000A4995 /* NSAttributedString+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Utilities.swift"; sourceTree = ""; }; FDF2220A2818F38D000A4995 /* SessionApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionApp.swift; sourceTree = ""; }; FDF2220E281B55E6000A4995 /* QueryInterfaceRequest+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QueryInterfaceRequest+Utilities.swift"; sourceTree = ""; }; FDF22210281B5E0B000A4995 /* TableRecord+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TableRecord+Utilities.swift"; sourceTree = ""; }; FDF40CDD2897A1BC006A0CC4 /* _004_RemoveLegacyYDB.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _004_RemoveLegacyYDB.swift; sourceTree = ""; }; + FDF8487529405906007DCAE5 /* HTTPQueryParam.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPQueryParam.swift; sourceTree = ""; }; + FDF8487629405906007DCAE5 /* HTTPError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPError.swift; sourceTree = ""; }; + FDF8487729405906007DCAE5 /* HTTPHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPHeader.swift; sourceTree = ""; }; + FDF8487829405906007DCAE5 /* HTTPMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPMethod.swift; sourceTree = ""; }; + FDF8487D29405993007DCAE5 /* HTTPHeader+OpenGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HTTPHeader+OpenGroup.swift"; sourceTree = ""; }; + FDF8487E29405994007DCAE5 /* HTTPQueryParam+OpenGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HTTPQueryParam+OpenGroup.swift"; sourceTree = ""; }; + FDF8488229405A12007DCAE5 /* BatchResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BatchResponse.swift; sourceTree = ""; }; + FDF8488529405A60007DCAE5 /* Request.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Request.swift; sourceTree = ""; }; + FDF8488729405A9A007DCAE5 /* SOGSBatchRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SOGSBatchRequest.swift; sourceTree = ""; }; + FDF8488A29405BF2007DCAE5 /* SSKDependencies.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SSKDependencies.swift; sourceTree = ""; }; + FDF8488D29405C04007DCAE5 /* GetSnodePoolJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetSnodePoolJob.swift; sourceTree = ""; }; + FDF8489029405C13007DCAE5 /* SnodeAPINamespace.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnodeAPINamespace.swift; sourceTree = ""; }; + FDF8489329405C1B007DCAE5 /* SnodeAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnodeAPI.swift; sourceTree = ""; }; + FDF8489A29405C5A007DCAE5 /* SnodeRecursiveResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnodeRecursiveResponse.swift; sourceTree = ""; }; + FDF8489B29405C5A007DCAE5 /* GetMessagesRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetMessagesRequest.swift; sourceTree = ""; }; + FDF8489C29405C5A007DCAE5 /* GetServiceNodesRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetServiceNodesRequest.swift; sourceTree = ""; }; + FDF8489D29405C5A007DCAE5 /* SnodeResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnodeResponse.swift; sourceTree = ""; }; + FDF8489E29405C5A007DCAE5 /* ONSResolveResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ONSResolveResponse.swift; sourceTree = ""; }; + FDF8489F29405C5A007DCAE5 /* UpdateExpiryRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateExpiryRequest.swift; sourceTree = ""; }; + FDF848A029405C5A007DCAE5 /* OxenDaemonRPCRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OxenDaemonRPCRequest.swift; sourceTree = ""; }; + FDF848A129405C5A007DCAE5 /* DeleteMessagesRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeleteMessagesRequest.swift; sourceTree = ""; }; + FDF848A229405C5A007DCAE5 /* RevokeSubkeyResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RevokeSubkeyResponse.swift; sourceTree = ""; }; + FDF848A329405C5A007DCAE5 /* GetSwarmRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetSwarmRequest.swift; sourceTree = ""; }; + FDF848A429405C5A007DCAE5 /* DeleteAllMessagesRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeleteAllMessagesRequest.swift; sourceTree = ""; }; + FDF848A529405C5A007DCAE5 /* SendMessageResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendMessageResponse.swift; sourceTree = ""; }; + FDF848A629405C5A007DCAE5 /* ONSResolveRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ONSResolveRequest.swift; sourceTree = ""; }; + FDF848A729405C5A007DCAE5 /* SnodeRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnodeRequest.swift; sourceTree = ""; }; + FDF848A829405C5A007DCAE5 /* DeleteAllBeforeRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeleteAllBeforeRequest.swift; sourceTree = ""; }; + FDF848A929405C5A007DCAE5 /* SnodePoolResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnodePoolResponse.swift; sourceTree = ""; }; + FDF848AA29405C5A007DCAE5 /* SnodeReceivedMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnodeReceivedMessage.swift; sourceTree = ""; }; + FDF848AB29405C5A007DCAE5 /* GetNetworkTimestampResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetNetworkTimestampResponse.swift; sourceTree = ""; }; + FDF848AC29405C5A007DCAE5 /* UpdateExpiryAllRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateExpiryAllRequest.swift; sourceTree = ""; }; + FDF848AD29405C5A007DCAE5 /* SendMessageRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendMessageRequest.swift; sourceTree = ""; }; + FDF848AE29405C5A007DCAE5 /* UpdateExpiryResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateExpiryResponse.swift; sourceTree = ""; }; + FDF848AF29405C5A007DCAE5 /* SnodeSwarmItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnodeSwarmItem.swift; sourceTree = ""; }; + FDF848B029405C5A007DCAE5 /* LegacyGetMessagesRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyGetMessagesRequest.swift; sourceTree = ""; }; + FDF848B129405C5A007DCAE5 /* UpdateExpiryAllResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateExpiryAllResponse.swift; sourceTree = ""; }; + FDF848B229405C5A007DCAE5 /* DeleteAllBeforeResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeleteAllBeforeResponse.swift; sourceTree = ""; }; + FDF848B329405C5A007DCAE5 /* DeleteAllMessagesResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeleteAllMessagesResponse.swift; sourceTree = ""; }; + FDF848B429405C5A007DCAE5 /* SnodeMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnodeMessage.swift; sourceTree = ""; }; + FDF848B529405C5A007DCAE5 /* SnodeBatchRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnodeBatchRequest.swift; sourceTree = ""; }; + FDF848B629405C5A007DCAE5 /* SwarmSnode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwarmSnode.swift; sourceTree = ""; }; + FDF848B729405C5A007DCAE5 /* SnodeAuthenticatedRequestBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnodeAuthenticatedRequestBody.swift; sourceTree = ""; }; + FDF848B829405C5A007DCAE5 /* GetMessagesResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetMessagesResponse.swift; sourceTree = ""; }; + FDF848B929405C5A007DCAE5 /* DeleteMessagesResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeleteMessagesResponse.swift; sourceTree = ""; }; + FDF848BA29405C5A007DCAE5 /* RevokeSubkeyRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RevokeSubkeyRequest.swift; sourceTree = ""; }; + FDF848BB29405C5A007DCAE5 /* LegacySendMessageRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacySendMessageRequest.swift; sourceTree = ""; }; + FDF848DE29405D6E007DCAE5 /* OnionRequestAPIVersion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnionRequestAPIVersion.swift; sourceTree = ""; }; + FDF848DF29405D6E007DCAE5 /* SnodeAPIEndpoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnodeAPIEndpoint.swift; sourceTree = ""; }; + FDF848E029405D6E007DCAE5 /* SnodeAPIError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnodeAPIError.swift; sourceTree = ""; }; + FDF848E129405D6E007DCAE5 /* OnionRequestAPIDestination.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnionRequestAPIDestination.swift; sourceTree = ""; }; + FDF848E229405D6E007DCAE5 /* OnionRequestAPIError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnionRequestAPIError.swift; sourceTree = ""; }; + FDF848E829405E4E007DCAE5 /* OnionRequestAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnionRequestAPI.swift; sourceTree = ""; }; + FDF848E929405E4E007DCAE5 /* OnionRequestAPI+Encryption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OnionRequestAPI+Encryption.swift"; sourceTree = ""; }; + FDF848EA29405E4E007DCAE5 /* Notification+OnionRequestAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Notification+OnionRequestAPI.swift"; sourceTree = ""; }; + FDF848EE294067E4007DCAE5 /* URLResponse+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URLResponse+Utilities.swift"; sourceTree = ""; }; + FDF848F029406A30007DCAE5 /* Format.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Format.swift; path = "SessionUIKit/Style Guide/Format.swift"; sourceTree = SOURCE_ROOT; }; + FDF848F229413DB0007DCAE5 /* ImagePickerHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImagePickerHandler.swift; sourceTree = ""; }; + FDF848F429413EEC007DCAE5 /* SessionCell+Styling.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SessionCell+Styling.swift"; sourceTree = ""; }; + FDF848F629414477007DCAE5 /* CurrentUserPoller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CurrentUserPoller.swift; sourceTree = ""; }; FDFD645A27F26D4600808CA1 /* Data+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+Utilities.swift"; sourceTree = ""; }; FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockGeneralCache.swift; sourceTree = ""; }; FDFDE123282D04F20098B17F /* MediaDismissAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaDismissAnimationController.swift; sourceTree = ""; }; FDFDE125282D05380098B17F /* MediaInteractiveDismiss.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaInteractiveDismiss.swift; sourceTree = ""; }; FDFDE127282D05530098B17F /* MediaPresentationContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPresentationContext.swift; sourceTree = ""; }; FDFDE129282D056B0098B17F /* MediaZoomAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaZoomAnimationController.swift; sourceTree = ""; }; - FF694C71BE4B41B6AFD252A0 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.app store release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -2028,7 +2013,7 @@ C3D90A5C25773A25002C9DF5 /* SessionUtilitiesKit.framework in Frameworks */, C3402FE52559036600EA6424 /* SessionUIKit.framework in Frameworks */, B8D64FCB25BA78A90029CFC0 /* SignalUtilitiesKit.framework in Frameworks */, - 1FFD68A448D5A1439F2F02FD /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework in Frameworks */, + 4C4FE46740136D591D04261F /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2040,7 +2025,7 @@ B8D64FBD25BA78310029CFC0 /* SessionSnodeKit.framework in Frameworks */, B8D64FBE25BA78310029CFC0 /* SessionUtilitiesKit.framework in Frameworks */, C38EF00C255B61CC007E1867 /* SignalUtilitiesKit.framework in Frameworks */, - 98547545DAF8E7916DF9F0BF /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework in Frameworks */, + CB54B7E519F525FF27A7DAD3 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2049,7 +2034,7 @@ buildActionMask = 2147483647; files = ( FD37E9EF28A5ED70003AE748 /* SessionUtilitiesKit.framework in Frameworks */, - 821EFD1644285AC2D3733D27 /* Pods_GlobalDependencies_SessionUIKit.framework in Frameworks */, + A0A69C9CB213DDDD85BF2207 /* Pods_GlobalDependencies_SessionUIKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2061,7 +2046,7 @@ C33FD9C2255A54EF00E217F9 /* SessionMessagingKit.framework in Frameworks */, C33FD9C4255A54EF00E217F9 /* SessionSnodeKit.framework in Frameworks */, C33FD9C5255A54EF00E217F9 /* SessionUtilitiesKit.framework in Frameworks */, - 3289CA2E9E89DA9D4D52A90C /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework in Frameworks */, + 6C1ADD1127CED42854542F78 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2070,7 +2055,7 @@ buildActionMask = 2147483647; files = ( C3C2A6C62553896A00C340D1 /* SessionUtilitiesKit.framework in Frameworks */, - C2CAA4A9737D865B34560B8C /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionSnodeKit.framework in Frameworks */, + DE0001574AC103562A7CF31F /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionSnodeKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2078,7 +2063,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 5163CBC4F53274C88D1F88F8 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit.framework in Frameworks */, + 42C48489AFF26BC9034C736C /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2089,7 +2074,7 @@ FD8ECF7929340F7200C0D1BB /* libsession-util.xcframework in Frameworks */, FDC4386C27B4E90300C60D73 /* SessionUtilitiesKit.framework in Frameworks */, C3C2A70B25539E1E00C340D1 /* SessionSnodeKit.framework in Frameworks */, - CEE449BA3596483519120D91 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework in Frameworks */, + BE25D9230CA2C3A40A9216EF /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2128,7 +2113,7 @@ D221A090169C9E5E00537ABF /* Foundation.framework in Frameworks */, D221A0E8169DFFC500537ABF /* AVFoundation.framework in Frameworks */, D24B5BD5169F568C00681372 /* AudioToolbox.framework in Frameworks */, - 71B1D8AF3ADE2BD191256496 /* Pods_GlobalDependencies_Session.framework in Frameworks */, + 41BA6B5C1C693C3A86070C15 /* Pods_GlobalDependencies_Session.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2136,7 +2121,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DA2AE22FA77136442EF669E9 /* Pods_GlobalDependencies_Session_SessionTests.framework in Frameworks */, + 056ED47155A04437A1EF58C2 /* Pods_GlobalDependencies_Session_SessionTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2145,7 +2130,7 @@ buildActionMask = 2147483647; files = ( FD83B9B327CF200A005E1583 /* SessionUtilitiesKit.framework in Frameworks */, - A49760F37A9AE09D57ECE415 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit_SessionUtilitiesKitTests.framework in Frameworks */, + 9A88F90C33C394513CB4C18A /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit_SessionUtilitiesKitTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2154,7 +2139,7 @@ buildActionMask = 2147483647; files = ( FDC4389227B9FFC700C60D73 /* SessionMessagingKit.framework in Frameworks */, - 92EB2776D36B22D2E0552A05 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework in Frameworks */, + FE43694493EC2E1E438EBEB3 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2164,30 +2149,28 @@ 2BADBA206E0B8D297E313FBA /* Pods */ = { isa = PBXGroup; children = ( - 506FA2159653FF9F446D97D1 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.debug.xcconfig */, - FF694C71BE4B41B6AFD252A0 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.app store release.xcconfig */, - D0CE0424239A1574F683D2D7 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.debug.xcconfig */, - 8E029A324780A800DE6B70B3 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.app store release.xcconfig */, - 96ED0C9B69379BE6FF4E9DA6 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.debug.xcconfig */, - 5626DC0D5F62C1C2C64E4AFC /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.app store release.xcconfig */, - F705826F79C4A591AB35D68F /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig */, - 2581AFACDDDC1404866D7B8C /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.app store release.xcconfig */, - BE11AFA6FD8CAE894CABC28D /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.debug.xcconfig */, - 58A6BA91F634756FA0BEC9E5 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.app store release.xcconfig */, - DAF57FAAF30631D0E99DA361 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.debug.xcconfig */, - 82099864FD91C9126A750313 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.app store release.xcconfig */, - 1A0882BF820F5B44969F91F1 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig */, - 245BF74EF6348E2D4125033F /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.app store release.xcconfig */, - E23C1E6B7E0C12BF4ACD9CBE /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.debug.xcconfig */, - 0E836037CC97CE5A47735596 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.app store release.xcconfig */, - 56F41C56FC7B2F381E440FB0 /* Pods-GlobalDependencies-Session.debug.xcconfig */, - 6BE8FBF62464A7177034A0AB /* Pods-GlobalDependencies-Session.app store release.xcconfig */, - EC5C23F9D234F558BE5E41DE /* Pods-SessionUIKit.debug.xcconfig */, - 29CF8C79F41BF00B1C2E59A0 /* Pods-SessionUIKit.app store release.xcconfig */, - 510955DC99A0FD84F2D1C159 /* Pods-GlobalDependencies-SessionUIKit.debug.xcconfig */, - 18EAE958B8C12503F2C294DF /* Pods-GlobalDependencies-SessionUIKit.app store release.xcconfig */, - 06160ECE3FE5A06A916FF8C5 /* Pods-GlobalDependencies-Session-SessionTests.debug.xcconfig */, - 0E8564674E3201E218939AFB /* Pods-GlobalDependencies-Session-SessionTests.app store release.xcconfig */, + 6DA09080DD9779C860023A60 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.debug.xcconfig */, + F390F8E34CA76B3F7D3B1826 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.app store release.xcconfig */, + 8727C47348B6EFA767EE583A /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.debug.xcconfig */, + 621B42AC592F3456ACD82F8B /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.app store release.xcconfig */, + B1910A32EB2AD01913629646 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.debug.xcconfig */, + 6A71AD9BEAFF0C9E8016BC23 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.app store release.xcconfig */, + 8E946CB54A221018E23599DE /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig */, + F60C5B6CD14329816B0E8CC0 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.app store release.xcconfig */, + 8448EFF76CD3CA5B2283B8A0 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.debug.xcconfig */, + 5DA3BDDFFB9E937A49C35FCC /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.app store release.xcconfig */, + B4F9FCBDA07F07CB48220D4C /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.debug.xcconfig */, + 55C13C7B4B700846E49C0E25 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.app store release.xcconfig */, + 285705D20F792E174C8A9BBA /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig */, + 62B512CEB14BD4A4A53CF532 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.app store release.xcconfig */, + 847091A12D82E41B1EBB8FB3 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.debug.xcconfig */, + EED1CF82CAB23FE3345564F9 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.app store release.xcconfig */, + 34040971CC7AF9C8A6C1E838 /* Pods-GlobalDependencies-Session.debug.xcconfig */, + 8603226ED1C6F61F1F2D3734 /* Pods-GlobalDependencies-Session.app store release.xcconfig */, + 0772459E7D5F6747EDC889F3 /* Pods-GlobalDependencies-Session-SessionTests.debug.xcconfig */, + F154A10CE1ADA33C16B45357 /* Pods-GlobalDependencies-Session-SessionTests.app store release.xcconfig */, + EB5B8ACA4C6F512FA3E21859 /* Pods-GlobalDependencies-SessionUIKit.debug.xcconfig */, + 05C76EFA593DD507061C50B2 /* Pods-GlobalDependencies-SessionUIKit.app store release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -2270,7 +2253,7 @@ 7BAF54D227ACCF01003D12F8 /* SAEScreenLockViewController.swift */, 7BAF54D127ACCF01003D12F8 /* ShareAppExtensionContext.swift */, 4535186C1FC635DD00210559 /* MainInterface.storyboard */, - C3ADC66026426688005F1414 /* ShareVC.swift */, + C3ADC66026426688005F1414 /* ShareNavController.swift */, FD3AABE828306BBD00E5099A /* ThreadPickerViewModel.swift */, B817AD9B26436F73009DF825 /* ThreadPickerVC.swift */, B817AD9926436593009DF825 /* SimplifiedConversationCell.swift */, @@ -2537,12 +2520,19 @@ isa = PBXGroup; children = ( C33FDB68255A580F00E217F9 /* ContentProxy.swift */, - C3C2A5BC255385EE00C340D1 /* HTTP.swift */, + FDF8487629405906007DCAE5 /* HTTPError.swift */, + FDF8487729405906007DCAE5 /* HTTPHeader.swift */, + FDF8487829405906007DCAE5 /* HTTPMethod.swift */, + FDF8487529405906007DCAE5 /* HTTPQueryParam.swift */, B8FF8EA525C11FEF004D1F22 /* IPv4.swift */, C3C2A5D92553860B00C340D1 /* JSON.swift */, + FDF8488529405A60007DCAE5 /* Request.swift */, + FDC438B027BB159600C60D73 /* RequestInfo.swift */, + FDF8488229405A12007DCAE5 /* BatchResponse.swift */, + FDC438B227BB15B400C60D73 /* ResponseInfo.swift */, C33FDAF2255A580500E217F9 /* ProxiedContentDownloader.swift */, C3C2A5BC255385EE00C340D1 /* HTTP.swift */, - FD8ECFA0293D8FDD00C0D1BB /* URLResponse+Utilities.swift */, + FDF848EE294067E4007DCAE5 /* URLResponse+Utilities.swift */, ); path = Networking; sourceTree = ""; @@ -2577,10 +2567,9 @@ FDC4383D27B4708600C60D73 /* Atomic.swift */, FDC438CC27BC641200C60D73 /* Set+Utilities.swift */, C33FDAA8255A57FF00E217F9 /* BuildConfiguration.swift */, - B8F5F58225EC94A6003BF8D4 /* Collection+Subscripting.swift */, + B8F5F58225EC94A6003BF8D4 /* Collection+Utilities.swift */, FDFD645A27F26D4600808CA1 /* Data+Utilities.swift */, FDC6D75F2862B3F600B04575 /* Dependencies.swift */, - FD26FA5D291CAFF9005801D8 /* Data+Utilities.swift */, C3C2A5D52553860A00C340D1 /* Dictionary+Utilities.swift */, B87EF18026377A1D00124B3C /* Features.swift */, B8BC00BF257D90E30032E807 /* General.swift */, @@ -2711,7 +2700,6 @@ C3C2A7702553A41E00C340D1 /* ControlMessage.swift */, B8DE1FB526C22FCB0079C9CE /* CallMessage.swift */, C34A977325A3E34A00852C71 /* ClosedGroupControlMessage.swift */, - C34A977325A3E34A00852C71 /* LegacyClosedGroupControlMessage.swift */, FD8ECF8A2935DB4B00C0D1BB /* SharedConfigMessage.swift */, C3DA9C0625AE7396008F7C7E /* ConfigurationMessage.swift */, 7B93D06E27CF194000811CB6 /* ConfigurationMessage+Convenience.swift */, @@ -2808,9 +2796,10 @@ C32C59F8256DB5A6003C73A2 /* Pollers */ = { isa = PBXGroup; children = ( + C33FDB3A255A580B00E217F9 /* Poller.swift */, C33FDB34255A580B00E217F9 /* ClosedGroupPoller.swift */, C3DB66C2260ACCE6001EFC55 /* OpenGroupPoller.swift */, - C33FDB3A255A580B00E217F9 /* Poller.swift */, + FDF848F629414477007DCAE5 /* CurrentUserPoller.swift */, ); path = Pollers; sourceTree = ""; @@ -2873,6 +2862,7 @@ FD37E9C228A1C6F3003AE748 /* ThemeManager.swift */, B8BB82BD2394D4CE00BA5194 /* Fonts.swift */, B8BB82A1238F356100BA5194 /* Values.swift */, + FDF848F029406A30007DCAE5 /* Format.swift */, ); path = "Style Guide"; sourceTree = ""; @@ -2983,11 +2973,11 @@ FD37EA1628AC5605003AE748 /* NotificationContentViewModel.swift */, FD87DCF928B74DB300AF0F98 /* ConversationSettingsViewModel.swift */, FD87DCFD28B7582C00AF0F98 /* BlockedContactsViewModel.swift */, - FD87DCFB28B755B800AF0F98 /* BlockedContactsViewController.swift */, FD37E9CB28A1E578003AE748 /* AppearanceViewController.swift */, FD37EA0228A9FDCC003AE748 /* HelpViewModel.swift */, B86BD08523399CEF000F5AE3 /* SeedModal.swift */, B894D0742339EDCF00B4D94D /* NukeDataModal.swift */, + FDF848F229413DB0007DCAE5 /* ImagePickerHandler.swift */, ); path = Settings; sourceTree = ""; @@ -3199,7 +3189,6 @@ C33FDB75255A581000E217F9 /* AppReadiness.m */, FDF0B7542807C4BB004C14C5 /* Environment.swift */, FD859EF127BF6BA200510D0C /* Data+Utilities.swift */, - FDC438C027BB4E6800C60D73 /* SMKDependencies.swift */, C38EF309255B6DBE007E1867 /* DeviceSleepManager.swift */, C3BBE0C62554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift */, C3A71D0A2558989C0043A11F /* MessageWrapper.swift */, @@ -3214,7 +3203,6 @@ C38EF2EC255B6DBA007E1867 /* ProximityMonitoringManager.swift */, C38EEF09255B49A8007E1867 /* SNProtoEnvelope+Conversion.swift */, FDC4386827B4E6B700C60D73 /* String+Utlities.swift */, - FDC4387327B5BB9B00C60D73 /* Promise+Utilities.swift */, FD772899284AF1BD0018502F /* Sodium+Utilities.swift */, C3A3A170256E1D25004D228D /* SSKReachabilityManager.swift */, C3ECBF7A257056B700EA7FCE /* Threading.swift */, @@ -3227,15 +3215,13 @@ children = ( C3C2A5B0255385C700C340D1 /* Meta */, FD17D79D27F40CAA00122BE0 /* Database */, - FDC438AF27BB158500C60D73 /* Models */, + FDF8489929405C5A007DCAE5 /* Models */, + FDF8488F29405C13007DCAE5 /* Types */, + FDF8488C29405C04007DCAE5 /* Jobs */, + FDF8489229405C1B007DCAE5 /* Networking */, C3C2A5CD255385F300C340D1 /* Utilities */, + FDF8488A29405BF2007DCAE5 /* SSKDependencies.swift */, C3C2A5B9255385ED00C340D1 /* Configuration.swift */, - C3C2A5BD255385EE00C340D1 /* Notification+OnionRequestAPI.swift */, - C3C2A5BA255385ED00C340D1 /* OnionRequestAPI.swift */, - C3C2A5BB255385ED00C340D1 /* OnionRequestAPI+Encryption.swift */, - C3C2A5BE255385EE00C340D1 /* SnodeAPI.swift */, - FD90040E2818AB6D00ABAAF6 /* GetSnodePoolJob.swift */, - C3C2A5B6255385EC00C340D1 /* SnodeMessage.swift */, ); path = SessionSnodeKit; sourceTree = ""; @@ -3394,8 +3380,6 @@ C38EF3DD255B6DF1007E1867 /* UIAlertController+OWS.swift */, C38EF241255B6D67007E1867 /* Collection+OWS.swift */, C38EF3AE255B6DE5007E1867 /* OrderedDictionary.swift */, - C38EF301255B6DBD007E1867 /* OWSFormat.h */, - C38EF305255B6DBE007E1867 /* OWSFormat.m */, C38EF226255B6D5D007E1867 /* ShareViewDelegate.swift */, C38EF2FA255B6DBD007E1867 /* Bench.swift */, C38EF23F255B6D67007E1867 /* NSAttributedString+OWS.h */, @@ -3518,17 +3502,17 @@ D221A08D169C9E5E00537ABF /* UIKit.framework */, D221A08F169C9E5E00537ABF /* Foundation.framework */, D221A091169C9E5E00537ABF /* CoreGraphics.framework */, - 7A8A44E3F8AC9282AC5E6E5A /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework */, - 2691123A7F231EDD8226C4B5 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework */, - DBA125424EDD2417B515C63A /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework */, - 782B65234A707D762FEAFD3B /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit.framework */, - 5442DF945D862CEDF7F8AC49 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit_SessionUtilitiesKitTests.framework */, - 0BF4561630A52BE96F164CF6 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework */, - 6F84A214B9A1C0CCF6DB09C8 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework */, - 6737124ECBC2DFEE2DD716D3 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionSnodeKit.framework */, - 48AD214D67ABED845101E795 /* Pods_GlobalDependencies_Session.framework */, - A9C58C3ADF46C718488458C2 /* Pods_GlobalDependencies_SessionUIKit.framework */, - B4C92F6ADBECCD47A6B6008E /* Pods_GlobalDependencies_Session_SessionTests.framework */, + 05E68C7F291EC08B8A43A534 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework */, + 13D1714FDC4DAB121DA2C73A /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework */, + E0B0F4C34363FE679EE3F203 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework */, + 86A3D36084020C9118DBCEE3 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit.framework */, + 07B7038C849F53378CD36B83 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit_SessionUtilitiesKitTests.framework */, + D3BE3061D535DBC1DF0C94D4 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework */, + C5828D9A55CD76B75E8FA367 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework */, + 9E358E7590EB145A5047F885 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionSnodeKit.framework */, + CC8D3B6504946442A0CF775C /* Pods_GlobalDependencies_Session.framework */, + 32C10A2A536B58FC42C46C3C /* Pods_GlobalDependencies_Session_SessionTests.framework */, + 678CFB04E76F7E388AFCFA86 /* Pods_GlobalDependencies_SessionUIKit.framework */, ); name = Frameworks; sourceTree = ""; @@ -3612,8 +3596,6 @@ 7BAA7B6528D2DE4700AE1489 /* _009_OpenGroupPermission.swift */, FD7115F128C6CB3900B47552 /* _010_AddThreadIdToFTS.swift */, FD8ECF7C2934293A00C0D1BB /* _011_SharedUtilChanges.swift */, - FDF1AD6028FF61110080A701 /* _012_AddClosedGroupInfo.swift */, - FD8ECF71292DCD1A00C0D1BB /* _013_AutoDownloadAttachments.swift */, ); path = Migrations; sourceTree = ""; @@ -3917,7 +3899,6 @@ isa = PBXGroup; children = ( FD7115EF28C5D7DE00B47552 /* SessionHeaderView.swift */, - FD7115ED28C5D79B00B47552 /* SessionAvatarCell.swift */, FD37EA0A28AB12E2003AE748 /* SessionCell.swift */, FD71164728E2CE8700B47552 /* SessionCell+AccessoryView.swift */, FD71164528E2CC1300B47552 /* SessionHighlightingBackgroundLabel.swift */, @@ -3932,7 +3913,8 @@ FD71163328E2C48400B47552 /* TransitionType.swift */, FD71164D28E3F8CC00B47552 /* SessionCell+Info.swift */, FD71164328E2CB8A00B47552 /* SessionCell+Accessory.swift */, - FD71164B28E3F5AA00B47552 /* SessionCell+ExtraAction.swift */, + FD71164B28E3F5AA00B47552 /* SessionCell+Accessibility.swift */, + FDF848F429413EEC007DCAE5 /* SessionCell+Styling.swift */, FD71165128E410BE00B47552 /* SessionTableSection.swift */, FD71164F28E3F9FA00B47552 /* SessionTableViewModel+NavItem.swift */, ); @@ -4040,16 +4022,6 @@ path = LibSessionUtil; sourceTree = ""; }; - FD8ECF832934507500C0D1BB /* Networking */ = { - isa = PBXGroup; - children = ( - FD3C906127E411AF00CD579F /* HeaderSpec.swift */, - FD3C906327E4122F00CD579F /* RequestSpec.swift */, - FD8ECF842934508B00C0D1BB /* BatchResponseSpec.swift */, - ); - path = Networking; - sourceTree = ""; - }; FD8ECF8E29381FB200C0D1BB /* Config Handling */ = { isa = PBXGroup; children = ( @@ -4090,6 +4062,8 @@ FDC4380727B31D3A00C60D73 /* Types */ = { isa = PBXGroup; children = ( + FDF8487D29405993007DCAE5 /* HTTPHeader+OpenGroup.swift */, + FDF8487E29405994007DCAE5 /* HTTPQueryParam+OpenGroup.swift */, FDC4381F27B36ADC00C60D73 /* SOGSEndpoint.swift */, FDC4380827B31D4E00C60D73 /* OpenGroupAPIError.swift */, FDC4381627B32EC700C60D73 /* Personalization.swift */, @@ -4102,7 +4076,7 @@ FDC4381827B34EAD00C60D73 /* Models */ = { isa = PBXGroup; children = ( - FDC4386A27B4E88F00C60D73 /* BatchRequestInfo.swift */, + FDF8488729405A9A007DCAE5 /* SOGSBatchRequest.swift */, FDC4386627B4E10E00C60D73 /* Capabilities.swift */, FDC4385C27B4C18900C60D73 /* Room.swift */, FDC4386427B4DE7600C60D73 /* RoomPollInfo.swift */, @@ -4142,9 +4116,6 @@ isa = PBXGroup; children = ( FDC4385527B484AE00C60D73 /* Models */, - FDC4384E27B4804F00C60D73 /* Header.swift */, - FDC4385027B4807400C60D73 /* QueryParam.swift */, - FD83B9CD27D17A04005E1583 /* Request.swift */, 7B81682228A4C1210069F315 /* UpdateTypes.swift */, ); path = "Common Networking"; @@ -4204,23 +4175,6 @@ path = _TestUtilities; sourceTree = ""; }; - FDC438AF27BB158500C60D73 /* Models */ = { - isa = PBXGroup; - children = ( - FD17D7D127F5797A00122BE0 /* SnodeAPIEndpoint.swift */, - FD77289F284EF5810018502F /* SnodeAPIError.swift */, - FDC438B827BB161E00C60D73 /* OnionRequestAPIVersion.swift */, - FD17D7D727F658E200122BE0 /* OnionRequestAPIDestination.swift */, - FD17D7D327F6584600122BE0 /* OnionRequestAPIError.swift */, - FDC438B027BB159600C60D73 /* RequestInfo.swift */, - FDC438B227BB15B400C60D73 /* ResponseInfo.swift */, - FD09796827F6BEA700936362 /* SwarmSnode.swift */, - FD77289B284DDCE10018502F /* SnodePoolResponse.swift */, - FD17D7E027F67BD400122BE0 /* SnodeReceivedMessage.swift */, - ); - path = Models; - sourceTree = ""; - }; FDE7214E287E50D50093DF33 /* Scripts */ = { isa = PBXGroup; children = ( @@ -4259,6 +4213,79 @@ path = Errors; sourceTree = ""; }; + FDF8488C29405C04007DCAE5 /* Jobs */ = { + isa = PBXGroup; + children = ( + FDF8488D29405C04007DCAE5 /* GetSnodePoolJob.swift */, + ); + path = Jobs; + sourceTree = ""; + }; + FDF8488F29405C13007DCAE5 /* Types */ = { + isa = PBXGroup; + children = ( + FDF848DF29405D6E007DCAE5 /* SnodeAPIEndpoint.swift */, + FDF848E029405D6E007DCAE5 /* SnodeAPIError.swift */, + FDF8489029405C13007DCAE5 /* SnodeAPINamespace.swift */, + FDF848DE29405D6E007DCAE5 /* OnionRequestAPIVersion.swift */, + FDF848E229405D6E007DCAE5 /* OnionRequestAPIError.swift */, + FDF848E129405D6E007DCAE5 /* OnionRequestAPIDestination.swift */, + ); + path = Types; + sourceTree = ""; + }; + FDF8489229405C1B007DCAE5 /* Networking */ = { + isa = PBXGroup; + children = ( + FDF8489329405C1B007DCAE5 /* SnodeAPI.swift */, + FDF848EA29405E4E007DCAE5 /* Notification+OnionRequestAPI.swift */, + FDF848E829405E4E007DCAE5 /* OnionRequestAPI.swift */, + FDF848E929405E4E007DCAE5 /* OnionRequestAPI+Encryption.swift */, + ); + path = Networking; + sourceTree = ""; + }; + FDF8489929405C5A007DCAE5 /* Models */ = { + isa = PBXGroup; + children = ( + FDF848A729405C5A007DCAE5 /* SnodeRequest.swift */, + FDF8489D29405C5A007DCAE5 /* SnodeResponse.swift */, + FDF8489A29405C5A007DCAE5 /* SnodeRecursiveResponse.swift */, + FDF848AF29405C5A007DCAE5 /* SnodeSwarmItem.swift */, + FDF848B729405C5A007DCAE5 /* SnodeAuthenticatedRequestBody.swift */, + FDF848B529405C5A007DCAE5 /* SnodeBatchRequest.swift */, + FDF8489B29405C5A007DCAE5 /* GetMessagesRequest.swift */, + FDF848B029405C5A007DCAE5 /* LegacyGetMessagesRequest.swift */, + FDF848B829405C5A007DCAE5 /* GetMessagesResponse.swift */, + FDF848AD29405C5A007DCAE5 /* SendMessageRequest.swift */, + FDF848BB29405C5A007DCAE5 /* LegacySendMessageRequest.swift */, + FDF848A529405C5A007DCAE5 /* SendMessageResponse.swift */, + FDF848A129405C5A007DCAE5 /* DeleteMessagesRequest.swift */, + FDF848B929405C5A007DCAE5 /* DeleteMessagesResponse.swift */, + FDF848A429405C5A007DCAE5 /* DeleteAllMessagesRequest.swift */, + FDF848B329405C5A007DCAE5 /* DeleteAllMessagesResponse.swift */, + FDF848A829405C5A007DCAE5 /* DeleteAllBeforeRequest.swift */, + FDF848B229405C5A007DCAE5 /* DeleteAllBeforeResponse.swift */, + FDF8489F29405C5A007DCAE5 /* UpdateExpiryRequest.swift */, + FDF848AE29405C5A007DCAE5 /* UpdateExpiryResponse.swift */, + FDF848AC29405C5A007DCAE5 /* UpdateExpiryAllRequest.swift */, + FDF848B129405C5A007DCAE5 /* UpdateExpiryAllResponse.swift */, + FDF848BA29405C5A007DCAE5 /* RevokeSubkeyRequest.swift */, + FDF848A229405C5A007DCAE5 /* RevokeSubkeyResponse.swift */, + FDF848B629405C5A007DCAE5 /* SwarmSnode.swift */, + FDF848A929405C5A007DCAE5 /* SnodePoolResponse.swift */, + FDF848AA29405C5A007DCAE5 /* SnodeReceivedMessage.swift */, + FDF848B429405C5A007DCAE5 /* SnodeMessage.swift */, + FDF848AB29405C5A007DCAE5 /* GetNetworkTimestampResponse.swift */, + FDF848A329405C5A007DCAE5 /* GetSwarmRequest.swift */, + FDF848A029405C5A007DCAE5 /* OxenDaemonRPCRequest.swift */, + FDF848A629405C5A007DCAE5 /* ONSResolveRequest.swift */, + FDF8489E29405C5A007DCAE5 /* ONSResolveResponse.swift */, + FDF8489C29405C5A007DCAE5 /* GetServiceNodesRequest.swift */, + ); + path = Models; + sourceTree = ""; + }; FDFDE122282D04E30098B17F /* Transitions */ = { isa = PBXGroup; children = ( @@ -4290,7 +4317,6 @@ C33FDD5B255A582000E217F9 /* OWSOperation.h in Headers */, C33FDD7C255A582000E217F9 /* SSKAsserts.h in Headers */, C38EF24C255B6D67007E1867 /* NSAttributedString+OWS.h in Headers */, - C38EF32B255B6DBF007E1867 /* OWSFormat.h in Headers */, C33FDDCC255A582000E217F9 /* TSConstants.h in Headers */, C33FDDBD255A582000E217F9 /* ByteParser.h in Headers */, C33FDDB3255A582000E217F9 /* OWSError.h in Headers */, @@ -4349,7 +4375,7 @@ isa = PBXNativeTarget; buildConfigurationList = 453518761FC635DD00210559 /* Build configuration list for PBXNativeTarget "SessionShareExtension" */; buildPhases = ( - BE7147D3A1A96E25B7541FD7 /* [CP] Check Pods Manifest.lock */, + 55CE11E14880742A24ADC127 /* [CP] Check Pods Manifest.lock */, 453518641FC635DD00210559 /* Sources */, 453518651FC635DD00210559 /* Frameworks */, 453518661FC635DD00210559 /* Resources */, @@ -4372,7 +4398,7 @@ isa = PBXNativeTarget; buildConfigurationList = 7BC01A45241F40AB00BC7C55 /* Build configuration list for PBXNativeTarget "SessionNotificationServiceExtension" */; buildPhases = ( - D08C2A7507F29F5B2E8686D0 /* [CP] Check Pods Manifest.lock */, + 18CDA58AE057F8C9AE71F46E /* [CP] Check Pods Manifest.lock */, 7BC01A37241F40AB00BC7C55 /* Sources */, 7BC01A38241F40AB00BC7C55 /* Frameworks */, 7BC01A39241F40AB00BC7C55 /* Resources */, @@ -4394,7 +4420,7 @@ isa = PBXNativeTarget; buildConfigurationList = C331FF262558F9D400070591 /* Build configuration list for PBXNativeTarget "SessionUIKit" */; buildPhases = ( - 3EB40416B01456AA719B686B /* [CP] Check Pods Manifest.lock */, + D5AFDC09857840D2D2631E2D /* [CP] Check Pods Manifest.lock */, C331FF162558F9D300070591 /* Headers */, C331FF172558F9D300070591 /* Sources */, C331FF182558F9D300070591 /* Frameworks */, @@ -4414,7 +4440,7 @@ isa = PBXNativeTarget; buildConfigurationList = C33FD9B6255A548A00E217F9 /* Build configuration list for PBXNativeTarget "SignalUtilitiesKit" */; buildPhases = ( - B3755B8F0046FD78A100ADF5 /* [CP] Check Pods Manifest.lock */, + 5CE8055024B876590AED6DEA /* [CP] Check Pods Manifest.lock */, C33FD9A6255A548A00E217F9 /* Headers */, C33FD9A7255A548A00E217F9 /* Sources */, C33FD9A8255A548A00E217F9 /* Frameworks */, @@ -4433,7 +4459,7 @@ isa = PBXNativeTarget; buildConfigurationList = C3C2A5AA255385C100C340D1 /* Build configuration list for PBXNativeTarget "SessionSnodeKit" */; buildPhases = ( - 0DAEB0CC30945175049E8D88 /* [CP] Check Pods Manifest.lock */, + 77F55C879DAF28750120D343 /* [CP] Check Pods Manifest.lock */, C3C2A59A255385C100C340D1 /* Headers */, C3C2A59B255385C100C340D1 /* Sources */, C3C2A59C255385C100C340D1 /* Frameworks */, @@ -4452,7 +4478,7 @@ isa = PBXNativeTarget; buildConfigurationList = C3C2A684255388CC00C340D1 /* Build configuration list for PBXNativeTarget "SessionUtilitiesKit" */; buildPhases = ( - AF68D547A722E10BF230F662 /* [CP] Check Pods Manifest.lock */, + 446B0E16474DF9F15509BC64 /* [CP] Check Pods Manifest.lock */, C3C2A674255388CC00C340D1 /* Headers */, C3C2A675255388CC00C340D1 /* Sources */, C3C2A676255388CC00C340D1 /* Frameworks */, @@ -4471,7 +4497,7 @@ isa = PBXNativeTarget; buildConfigurationList = C3C2A6F925539DE700C340D1 /* Build configuration list for PBXNativeTarget "SessionMessagingKit" */; buildPhases = ( - 216EE1F2AA1CC3CBD9416785 /* [CP] Check Pods Manifest.lock */, + 2014435DF351DF6C60122751 /* [CP] Check Pods Manifest.lock */, C3C2A6EB25539DE700C340D1 /* Headers */, C3C2A6EC25539DE700C340D1 /* Sources */, C3C2A6ED25539DE700C340D1 /* Frameworks */, @@ -4491,14 +4517,14 @@ isa = PBXNativeTarget; buildConfigurationList = D221A0BC169C9E5F00537ABF /* Build configuration list for PBXNativeTarget "Session" */; buildPhases = ( - 0401967CF3320CC84B175A3B /* [CP] Check Pods Manifest.lock */, + 351E727E03A8F141EA25FBF4 /* [CP] Check Pods Manifest.lock */, FDE7214D287E50820093DF33 /* Lint Localizable.strings */, D221A085169C9E5E00537ABF /* Sources */, D221A086169C9E5E00537ABF /* Frameworks */, D221A087169C9E5E00537ABF /* Resources */, 453518771FC635DD00210559 /* Embed Foundation Extensions */, 4535189F1FC63DBF00210559 /* Embed Frameworks */, - 6E75B456D9C7705F6FD9C9D4 /* [CP] Embed Pods Frameworks */, + 90DF4725BB1271EBA2C66A12 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -4520,11 +4546,11 @@ isa = PBXNativeTarget; buildConfigurationList = FD71160F28D00BAE00B47552 /* Build configuration list for PBXNativeTarget "SessionTests" */; buildPhases = ( - 567FCF8CB93B411EE1FD4BBF /* [CP] Check Pods Manifest.lock */, + 19CD7B4EDC153293FB61CBA1 /* [CP] Check Pods Manifest.lock */, FD71160528D00BAE00B47552 /* Sources */, FD71160628D00BAE00B47552 /* Frameworks */, FD71160728D00BAE00B47552 /* Resources */, - A08B0675BD19884F61FF48D9 /* [CP] Embed Pods Frameworks */, + 23B58F2A2BA9E3295EA451C1 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -4540,11 +4566,11 @@ isa = PBXNativeTarget; buildConfigurationList = FD83B9B627CF200A005E1583 /* Build configuration list for PBXNativeTarget "SessionUtilitiesKitTests" */; buildPhases = ( - 73A908879E7A14832EF8B14A /* [CP] Check Pods Manifest.lock */, + EDDFB3BFBD5E1378BD03AAAB /* [CP] Check Pods Manifest.lock */, FD83B9AB27CF200A005E1583 /* Sources */, FD83B9AC27CF200A005E1583 /* Frameworks */, FD83B9AD27CF200A005E1583 /* Resources */, - 4124998F864C780E396BCF56 /* [CP] Embed Pods Frameworks */, + 49439A35FE57D3C0768A8127 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -4561,11 +4587,11 @@ isa = PBXNativeTarget; buildConfigurationList = FDC4389527B9FFC700C60D73 /* Build configuration list for PBXNativeTarget "SessionMessagingKitTests" */; buildPhases = ( - F30F419364B564D672BA0940 /* [CP] Check Pods Manifest.lock */, + 0E6C1748F41E48ED59563D96 /* [CP] Check Pods Manifest.lock */, FDC4388A27B9FFC700C60D73 /* Sources */, FDC4388B27B9FFC700C60D73 /* Frameworks */, FDC4388C27B9FFC700C60D73 /* Resources */, - BE3DD274CEE45348F1CA328A /* [CP] Embed Pods Frameworks */, + E0D19D723F633D7EE6163A84 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -4879,7 +4905,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 0401967CF3320CC84B175A3B /* [CP] Check Pods Manifest.lock */ = { + 0E6C1748F41E48ED59563D96 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -4894,258 +4920,14 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-GlobalDependencies-Session-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 0DAEB0CC30945175049E8D88 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 216EE1F2AA1CC3CBD9416785 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 3EB40416B01456AA719B686B /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-GlobalDependencies-SessionUIKit-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 4124998F864C780E396BCF56 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 567FCF8CB93B411EE1FD4BBF /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-GlobalDependencies-Session-SessionTests-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 6E75B456D9C7705F6FD9C9D4 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-GlobalDependencies-Session/Pods-GlobalDependencies-Session-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-GlobalDependencies-Session/Pods-GlobalDependencies-Session-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-GlobalDependencies-Session/Pods-GlobalDependencies-Session-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 73A908879E7A14832EF8B14A /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - A08B0675BD19884F61FF48D9 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-GlobalDependencies-Session-SessionTests/Pods-GlobalDependencies-Session-SessionTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-GlobalDependencies-Session-SessionTests/Pods-GlobalDependencies-Session-SessionTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-GlobalDependencies-Session-SessionTests/Pods-GlobalDependencies-Session-SessionTests-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - AF68D547A722E10BF230F662 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - B3755B8F0046FD78A100ADF5 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - BE3DD274CEE45348F1CA328A /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - BE7147D3A1A96E25B7541FD7 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - D08C2A7507F29F5B2E8686D0 /* [CP] Check Pods Manifest.lock */ = { + 18CDA58AE057F8C9AE71F46E /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -5167,7 +4949,7 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - F30F419364B564D672BA0940 /* [CP] Check Pods Manifest.lock */ = { + 19CD7B4EDC153293FB61CBA1 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -5182,7 +4964,251 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-GlobalDependencies-Session-SessionTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 2014435DF351DF6C60122751 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 23B58F2A2BA9E3295EA451C1 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-GlobalDependencies-Session-SessionTests/Pods-GlobalDependencies-Session-SessionTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-GlobalDependencies-Session-SessionTests/Pods-GlobalDependencies-Session-SessionTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-GlobalDependencies-Session-SessionTests/Pods-GlobalDependencies-Session-SessionTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 351E727E03A8F141EA25FBF4 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-GlobalDependencies-Session-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 446B0E16474DF9F15509BC64 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 49439A35FE57D3C0768A8127 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 55CE11E14880742A24ADC127 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 5CE8055024B876590AED6DEA /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 77F55C879DAF28750120D343 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 90DF4725BB1271EBA2C66A12 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-GlobalDependencies-Session/Pods-GlobalDependencies-Session-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-GlobalDependencies-Session/Pods-GlobalDependencies-Session-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-GlobalDependencies-Session/Pods-GlobalDependencies-Session-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + D5AFDC09857840D2D2631E2D /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-GlobalDependencies-SessionUIKit-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + E0D19D723F633D7EE6163A84 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + EDDFB3BFBD5E1378BD03AAAB /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -5217,7 +5243,7 @@ files = ( FD3AABE928306BBD00E5099A /* ThreadPickerViewModel.swift in Sources */, B817AD9A26436593009DF825 /* SimplifiedConversationCell.swift in Sources */, - C3ADC66126426688005F1414 /* ShareVC.swift in Sources */, + C3ADC66126426688005F1414 /* ShareNavController.swift in Sources */, 7BAF54D427ACCF01003D12F8 /* SAEScreenLockViewController.swift in Sources */, B817AD9C26436F73009DF825 /* ThreadPickerVC.swift in Sources */, 7BAF54D327ACCF01003D12F8 /* ShareAppExtensionContext.swift in Sources */, @@ -5249,6 +5275,7 @@ FD71165B28E6DDBC00B47552 /* StyledNavigationController.swift in Sources */, C331FFE32558FB0000070591 /* TabBar.swift in Sources */, FD37E9D528A1FCE8003AE748 /* Theme+OceanLight.swift in Sources */, + FDF848F129406A30007DCAE5 /* Format.swift in Sources */, FD37E9C828A1D73F003AE748 /* Theme+Colors.swift in Sources */, FD37EA0128A60473003AE748 /* UIKit+Theme.swift in Sources */, FD37E9CF28A1EB1B003AE748 /* Theme.swift in Sources */, @@ -5314,7 +5341,6 @@ C38EF3C7255B6DE7007E1867 /* ImageEditorCanvasView.swift in Sources */, C38EF400255B6DF7007E1867 /* GalleryRailView.swift in Sources */, C38EF32E255B6DBF007E1867 /* ImageCache.swift in Sources */, - C38EF32F255B6DBF007E1867 /* OWSFormat.m in Sources */, FD87DD0428B8727D00AF0F98 /* Configuration.swift in Sources */, C38EF3BA255B6DE7007E1867 /* ImageEditorItem.swift in Sources */, C33FDD3A255A582000E217F9 /* Notification+Loki.swift in Sources */, @@ -5362,45 +5388,68 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - FD77289C284DDCE10018502F /* SnodePoolResponse.swift in Sources */, - FD09796927F6BEA700936362 /* SwarmSnode.swift in Sources */, + FDF8488B29405BF2007DCAE5 /* SSKDependencies.swift in Sources */, + FDF8488E29405C04007DCAE5 /* GetSnodePoolJob.swift in Sources */, + FDF848C329405C5A007DCAE5 /* DeleteMessagesRequest.swift in Sources */, + FDF8489129405C13007DCAE5 /* SnodeAPINamespace.swift in Sources */, C3C2A5E02553860B00C340D1 /* Threading.swift in Sources */, - C3C2A5BF255385EE00C340D1 /* SnodeMessage.swift in Sources */, - FD17D7E127F67BD400122BE0 /* SnodeReceivedMessage.swift in Sources */, - FDC438B127BB159600C60D73 /* RequestInfo.swift in Sources */, - FDC438B927BB161E00C60D73 /* OnionRequestAPIVersion.swift in Sources */, - FD7728A0284EF5810018502F /* SnodeAPIError.swift in Sources */, + FDF848C529405C5B007DCAE5 /* GetSwarmRequest.swift in Sources */, + FDF848D729405C5B007DCAE5 /* SnodeBatchRequest.swift in Sources */, C3C2A5C0255385EE00C340D1 /* Snode.swift in Sources */, + FDF848D829405C5B007DCAE5 /* SwarmSnode.swift in Sources */, + FDF848CE29405C5B007DCAE5 /* UpdateExpiryAllRequest.swift in Sources */, + FDF848C229405C5A007DCAE5 /* OxenDaemonRPCRequest.swift in Sources */, + FDF848DC29405C5B007DCAE5 /* RevokeSubkeyRequest.swift in Sources */, FD17D7A027F40CC800122BE0 /* _001_InitialSetupMigration.swift in Sources */, - C3C2A5C7255385EE00C340D1 /* SnodeAPI.swift in Sources */, - C3C2A5C6255385EE00C340D1 /* Notification+OnionRequestAPI.swift in Sources */, + FDF848EC29405E4F007DCAE5 /* OnionRequestAPI+Encryption.swift in Sources */, FD17D7AA27F41BF500122BE0 /* SnodeSet.swift in Sources */, + FDF848D029405C5B007DCAE5 /* UpdateExpiryResponse.swift in Sources */, + FDF848D329405C5B007DCAE5 /* UpdateExpiryAllResponse.swift in Sources */, + FDF848BC29405C5A007DCAE5 /* SnodeRecursiveResponse.swift in Sources */, + FDF848C029405C5A007DCAE5 /* ONSResolveResponse.swift in Sources */, FD17D7A427F40F8100122BE0 /* _003_YDBToGRDBMigration.swift in Sources */, - C3C2A5C4255385EE00C340D1 /* OnionRequestAPI+Encryption.swift in Sources */, - FD17D7D227F5797A00122BE0 /* SnodeAPIEndpoint.swift in Sources */, + FDF848C629405C5B007DCAE5 /* DeleteAllMessagesRequest.swift in Sources */, C3C2A5DE2553860B00C340D1 /* String+Trimming.swift in Sources */, - FD26FA7B291DF8F3005801D8 /* SnodeAPINamespace.swift in Sources */, - FD8ECF6E292C9EA100C0D1BB /* GetServiceNodesRequest.swift in Sources */, - C3C2A5C4255385EE00C340D1 /* OnionRequestAPI+Encryption.swift in Sources */, - FD17D7D227F5797A00122BE0 /* SnodeAPIEndpoint.swift in Sources */, + FDF848D429405C5B007DCAE5 /* DeleteAllBeforeResponse.swift in Sources */, + FDF848D629405C5B007DCAE5 /* SnodeMessage.swift in Sources */, + FDF848D129405C5B007DCAE5 /* SnodeSwarmItem.swift in Sources */, + FDF848DD29405C5B007DCAE5 /* LegacySendMessageRequest.swift in Sources */, + FDF848BD29405C5A007DCAE5 /* GetMessagesRequest.swift in Sources */, + FD26FA7B291DF8F3005801D8 /* (null) in Sources */, + FDF848DB29405C5B007DCAE5 /* DeleteMessagesResponse.swift in Sources */, + FDF848E629405D6E007DCAE5 /* OnionRequestAPIDestination.swift in Sources */, + FDF848CC29405C5B007DCAE5 /* SnodeReceivedMessage.swift in Sources */, + FDF848C129405C5A007DCAE5 /* UpdateExpiryRequest.swift in Sources */, + FDF848C729405C5B007DCAE5 /* SendMessageResponse.swift in Sources */, + FDF848CA29405C5B007DCAE5 /* DeleteAllBeforeRequest.swift in Sources */, + FDF848D229405C5B007DCAE5 /* LegacyGetMessagesRequest.swift in Sources */, + FDF848CB29405C5B007DCAE5 /* SnodePoolResponse.swift in Sources */, + FDF848C429405C5A007DCAE5 /* RevokeSubkeyResponse.swift in Sources */, C3C2A5DE2553860B00C340D1 /* String+Trimming.swift in Sources */, - FD8ECF56292C327700C0D1BB /* LegacyGetMessagesRequest.swift in Sources */, - FD8ECF54292C2DB000C0D1BB /* SnodeAuthenticatedRequestBody.swift in Sources */, - FD26FA6D291DADAE005801D8 /* SnodeRequest.swift in Sources */, - FD8ECF50292C2B2B00C0D1BB /* DeleteAllBeforeRequest.swift in Sources */, - FD8ECF5A292C431B00C0D1BB /* GetSwarmRequest.swift in Sources */, + FD26FA6D291DADAE005801D8 /* (null) in Sources */, + FDF848E529405D6E007DCAE5 /* SnodeAPIError.swift in Sources */, + FDF848D529405C5B007DCAE5 /* DeleteAllMessagesResponse.swift in Sources */, + FDF848E329405D6E007DCAE5 /* OnionRequestAPIVersion.swift in Sources */, + FDF848BF29405C5A007DCAE5 /* SnodeResponse.swift in Sources */, C3C2A5E42553860B00C340D1 /* Data+Utilities.swift in Sources */, + FDF848D929405C5B007DCAE5 /* SnodeAuthenticatedRequestBody.swift in Sources */, + FDF848ED29405E4F007DCAE5 /* Notification+OnionRequestAPI.swift in Sources */, + FDF848CD29405C5B007DCAE5 /* GetNetworkTimestampResponse.swift in Sources */, + FDF848DA29405C5B007DCAE5 /* GetMessagesResponse.swift in Sources */, FD17D7A727F41AF000122BE0 /* SSKLegacy.swift in Sources */, - FDC438B327BB15B400C60D73 /* ResponseInfo.swift in Sources */, FD39353628F7C3390084DADA /* _004_FlagMessageHashAsDeletedOrInvalid.swift in Sources */, + FDF8489429405C1B007DCAE5 /* SnodeAPI.swift in Sources */, + FDF848C829405C5B007DCAE5 /* ONSResolveRequest.swift in Sources */, C3C2A5C2255385EE00C340D1 /* Configuration.swift in Sources */, - FD17D7D827F658E200122BE0 /* OnionRequestAPIDestination.swift in Sources */, + FDF848C929405C5B007DCAE5 /* SnodeRequest.swift in Sources */, + FDF848CF29405C5B007DCAE5 /* SendMessageRequest.swift in Sources */, FD6A7A6D2818C61500035AC1 /* _002_SetupStandardJobs.swift in Sources */, FD17D7B327F51E5B00122BE0 /* SSKSetting.swift in Sources */, + FDF848E429405D6E007DCAE5 /* SnodeAPIEndpoint.swift in Sources */, + FDF848E729405D6E007DCAE5 /* OnionRequestAPIError.swift in Sources */, + FDF848BE29405C5A007DCAE5 /* GetServiceNodesRequest.swift in Sources */, + FDF848EB29405E4F007DCAE5 /* OnionRequestAPI.swift in Sources */, FD17D7AE27F41C4300122BE0 /* SnodeReceivedMessageInfo.swift in Sources */, - C3C2A5C3255385EE00C340D1 /* OnionRequestAPI.swift in Sources */, - FD90040F2818AB6D00ABAAF6 /* GetSnodePoolJob.swift in Sources */, - FD17D7D427F6584600122BE0 /* OnionRequestAPIError.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -5412,6 +5461,7 @@ C32C5A47256DB8F0003C73A2 /* ECKeyPair+Hexadecimal.swift in Sources */, 7B1D74B027C365960030B423 /* Timer+MainThread.swift in Sources */, C32C5D83256DD5B6003C73A2 /* SSKKeychainStorage.swift in Sources */, + FDF8488329405A12007DCAE5 /* BatchResponse.swift in Sources */, C3D9E39B256763C20040E4F3 /* AppContext.m in Sources */, C3BBE0A82554D4DE0050F1E3 /* JSON.swift in Sources */, FD17D7C127F5200100122BE0 /* TypedTableDefinition.swift in Sources */, @@ -5426,6 +5476,7 @@ B8856DEF256F161F001CE70E /* NSString+SSK.m in Sources */, FD09C5E2282212B3000CE219 /* JobDependencies.swift in Sources */, FDED2E3C282E1B5D00B2CD2A /* UICollectionView+ReusableView.swift in Sources */, + FDF8487B29405906007DCAE5 /* HTTPHeader.swift in Sources */, FD9004122818ABDC00ABAAF6 /* Job.swift in Sources */, FD09797927FAB7E800936362 /* ImageFormat.swift in Sources */, FD7115FE28C8202D00B47552 /* ReplaySubject.swift in Sources */, @@ -5434,8 +5485,8 @@ C32C5DD2256DD9E5003C73A2 /* LRUCache.swift in Sources */, FD71160228C8255900B47552 /* UIControl+Combine.swift in Sources */, FD9004152818B46300ABAAF6 /* JobRunner.swift in Sources */, + FDF8487929405906007DCAE5 /* HTTPQueryParam.swift in Sources */, C3A7211A2558BCA10043A11F /* DiffieHellman.swift in Sources */, - FD26FA53291CACA9005801D8 /* BatchResponse.swift in Sources */, FD17D7CA27F546D900122BE0 /* _001_InitialSetupMigration.swift in Sources */, C3D9E4D12567777D0040E4F3 /* OWSMediaUtils.swift in Sources */, C3BBE0AA2554D4DE0050F1E3 /* Dictionary+Utilities.swift in Sources */, @@ -5453,7 +5504,6 @@ FD848B9328420164000E298B /* UnicodeScalar+Utilities.swift in Sources */, FD09796B27F6C67500936362 /* Failable.swift in Sources */, FD7115FA28C8153400B47552 /* UIBarButtonItem+Combine.swift in Sources */, - FD8ECFA1293D8FDD00C0D1BB /* URLResponse+Utilities.swift in Sources */, FD705A92278D051200F16121 /* ReusableView.swift in Sources */, FD17D7BA27F51F2100122BE0 /* TargetMigrations.swift in Sources */, FD17D7C327F5204C00122BE0 /* Database+Utilities.swift in Sources */, @@ -5464,6 +5514,7 @@ B8856DE6256F15F2001CE70E /* String+SSK.swift in Sources */, FDF2220F281B55E6000A4995 /* QueryInterfaceRequest+Utilities.swift in Sources */, C3471ED42555386B00297E91 /* AESGCM.swift in Sources */, + FDF848EF294067E4007DCAE5 /* URLResponse+Utilities.swift in Sources */, FD848B9A28442CE6000E298B /* StorageError.swift in Sources */, FD17D7B827F51ECA00122BE0 /* Migration.swift in Sources */, FD7728982849E8110018502F /* UITableView+ReusableView.swift in Sources */, @@ -5474,6 +5525,7 @@ B8F5F58325EC94A6003BF8D4 /* Collection+Utilities.swift in Sources */, 7BD477A827EC39F5004E2822 /* Atomic.swift in Sources */, B8BC00C0257D90E30032E807 /* General.swift in Sources */, + FDF8488629405A61007DCAE5 /* Request.swift in Sources */, FD17D7A127F40D2500122BE0 /* Storage.swift in Sources */, FD1A94FB2900D1C2000D73D3 /* PersistableRecord+Utilities.swift in Sources */, FD5D201E27B0D87C00FEA984 /* SessionId.swift in Sources */, @@ -5487,24 +5539,29 @@ C3D9E4F4256778AF0040E4F3 /* NSData+Image.m in Sources */, FDF222092818D2B0000A4995 /* NSAttributedString+Utilities.swift in Sources */, C32C5E0C256DDAFA003C73A2 /* NSRegularExpression+SSK.swift in Sources */, + FDF8487C29405906007DCAE5 /* HTTPMethod.swift in Sources */, + FDF8488429405A2B007DCAE5 /* RequestInfo.swift in Sources */, C3BBE0A92554D4DE0050F1E3 /* HTTP.swift in Sources */, FD71160028C8253500B47552 /* UIView+Combine.swift in Sources */, B8856D23256F116B001CE70E /* Weak.swift in Sources */, FD17D7CD27F546FF00122BE0 /* Setting.swift in Sources */, FD7115FC28C8155800B47552 /* Publisher+Utilities.swift in Sources */, C32C5A48256DB8F0003C73A2 /* BuildConfiguration.swift in Sources */, + FDF84881294059F5007DCAE5 /* ResponseInfo.swift in Sources */, FD17D7BF27F51F8200122BE0 /* ColumnExpressible.swift in Sources */, FD17D7E527F6A09900122BE0 /* Identity.swift in Sources */, FD9004142818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift in Sources */, + FDF8487A29405906007DCAE5 /* HTTPError.swift in Sources */, B87EF18126377A1D00124B3C /* Features.swift in Sources */, FD09797727FAB7A600936362 /* Data+Image.swift in Sources */, C300A60D2554B31900555489 /* Logging.swift in Sources */, B8FF8EA625C11FEF004D1F22 /* IPv4.swift in Sources */, C3D9E35525675EE10040E4F3 /* MIMETypeUtil.m in Sources */, FD9004162818B46700ABAAF6 /* JobRunnerError.swift in Sources */, + FDF8488929405B27007DCAE5 /* Data+Utilities.swift in Sources */, FD09797227FAA2F500936362 /* Optional+Utilities.swift in Sources */, FD7162DB281B6C440060647B /* TypedTableAlias.swift in Sources */, - FD26FA5E291CAFF9005801D8 /* Data+Utilities.swift in Sources */, + FD26FA5E291CAFF9005801D8 /* (null) in Sources */, FD7115F828C8151C00B47552 /* DisposableBarButtonItem.swift in Sources */, FD17D7E727F6A16700122BE0 /* _003_YDBToGRDBMigration.swift in Sources */, ); @@ -5532,6 +5589,7 @@ FD71161C28D194FB00B47552 /* MentionInfo.swift in Sources */, 7B4C75CB26B37E0F0000AC89 /* UnsendRequest.swift in Sources */, C300A5F22554B09800555489 /* MessageSender.swift in Sources */, + FDF848F729414477007DCAE5 /* CurrentUserPoller.swift in Sources */, B8B558FF26C4E05E00693325 /* WebRTCSession+MessageHandling.swift in Sources */, C3C2A74D2553A39700C340D1 /* VisibleMessage.swift in Sources */, FD09B7E7288670FD00ED0B66 /* Reaction.swift in Sources */, @@ -5554,7 +5612,7 @@ FD245C6A2850666F00B966DD /* FileServerAPI.swift in Sources */, FDC4386927B4E6B800C60D73 /* String+Utlities.swift in Sources */, FD716E6628502EE200C96BF4 /* CurrentCallProtocol.swift in Sources */, - FD26FA512919F9CE005801D8 /* GroupDeleteMessage.swift in Sources */, + FD26FA512919F9CE005801D8 /* (null) in Sources */, FD8ECF9029381FC200C0D1BB /* SessionUtil+UserProfile.swift in Sources */, FD09B7E5288670BB00ED0B66 /* _008_EmojiReacts.swift in Sources */, FDC4385F27B4C4A200C60D73 /* PinnedMessage.swift in Sources */, @@ -5582,7 +5640,7 @@ FD09C5EC282B8F18000CE219 /* AttachmentError.swift in Sources */, FD17D79927F40AB800122BE0 /* _003_YDBToGRDBMigration.swift in Sources */, FDF0B7512807BA56004C14C5 /* NotificationsProtocol.swift in Sources */, - FDC4387427B5BB9B00C60D73 /* Promise+Utilities.swift in Sources */, + FDC4387427B5BB9B00C60D73 /* (null) in Sources */, B8DE1FB426C22F2F0079C9CE /* WebRTCSession.swift in Sources */, FDC6D6F32860607300B04575 /* Environment.swift in Sources */, C3A3A171256E1D25004D228D /* SSKReachabilityManager.swift in Sources */, @@ -5590,11 +5648,11 @@ B8DE1FB626C22FCB0079C9CE /* CallMessage.swift in Sources */, FD245C50285065C700B966DD /* VisibleMessage+Quote.swift in Sources */, FD8ECF892935AB7200C0D1BB /* SessionUtilError.swift in Sources */, - FD8ECF42292B340D00C0D1BB /* SOGSBatchRequest.swift in Sources */, FD5C7307284F103B0029977D /* MessageReceiver+MessageRequests.swift in Sources */, C3A71D0B2558989C0043A11F /* MessageWrapper.swift in Sources */, FDE77F6B280FEB28002CFC5D /* ControlMessageProcessRecord.swift in Sources */, 7B93D07127CF194000811CB6 /* MessageRequestResponse.swift in Sources */, + FDF8488829405A9A007DCAE5 /* SOGSBatchRequest.swift in Sources */, FD245C662850665900B966DD /* OpenGroupAPI.swift in Sources */, FD245C5B2850660500B966DD /* ReadReceipt.swift in Sources */, B8F5F60325EDE16F003BF8D4 /* DataExtractionNotification.swift in Sources */, @@ -5603,6 +5661,7 @@ FDF0B74F28079E5E004C14C5 /* SendReadReceiptsJob.swift in Sources */, FDF0B7422804EA4F004C14C5 /* _002_SetupStandardJobs.swift in Sources */, B8EB20EE2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift in Sources */, + FDF8487F29405994007DCAE5 /* HTTPHeader+OpenGroup.swift in Sources */, FD8ECF7D2934293A00C0D1BB /* _011_SharedUtilChanges.swift in Sources */, FD17D7A227F40F0500122BE0 /* _001_InitialSetupMigration.swift in Sources */, FD245C5D2850660F00B966DD /* OWSAudioPlayer.m in Sources */, @@ -5619,7 +5678,6 @@ FD7115F228C6CB3900B47552 /* _010_AddThreadIdToFTS.swift in Sources */, FD716E6428502DDD00C96BF4 /* CallManagerProtocol.swift in Sources */, FDC438C727BB6DF000C60D73 /* DirectMessage.swift in Sources */, - FDC4384F27B4804F00C60D73 /* Header.swift in Sources */, FDC4381727B32EC700C60D73 /* Personalization.swift in Sources */, FD245C51285065CC00B966DD /* MessageReceiver.swift in Sources */, FD245C652850665400B966DD /* ClosedGroupControlMessage.swift in Sources */, @@ -5647,27 +5705,25 @@ FD09796E27FA6D0000936362 /* Contact.swift in Sources */, C38D5E8D2575011E00B6A65C /* MessageSender+ClosedGroups.swift in Sources */, FD5C72F7284F0E560029977D /* MessageReceiver+ReadReceipts.swift in Sources */, - FD83B9CE27D17A04005E1583 /* Request.swift in Sources */, C32C598A256D0664003C73A2 /* SNProtoEnvelope+Conversion.swift in Sources */, FDC438CB27BB7DB100C60D73 /* UpdateMessageRequest.swift in Sources */, FD8ECF7F2934298100C0D1BB /* SharedConfigDump.swift in Sources */, C352A2FF25574B6300338F3E /* MessageSendJob.swift in Sources */, FDC438C327BB512200C60D73 /* SodiumProtocols.swift in Sources */, B8856D11256F112A001CE70E /* OWSAudioSession.swift in Sources */, - FD8ECF72292DCD1A00C0D1BB /* _013_AutoDownloadAttachments.swift in Sources */, C3DB66C3260ACCE6001EFC55 /* OpenGroupPoller.swift in Sources */, FD716E722850647600C96BF4 /* Data+Utilities.swift in Sources */, FD245C5C2850660A00B966DD /* ConfigurationMessage.swift in Sources */, FD5C7301284F0F7A0029977D /* MessageReceiver+UnsendRequests.swift in Sources */, C3C2A75F2553A3C500C340D1 /* VisibleMessage+LinkPreview.swift in Sources */, FD245C642850664F00B966DD /* Threading.swift in Sources */, - FDF1AD6128FF61110080A701 /* _012_AddClosedGroupInfo.swift in Sources */, FD848B8D283E0B26000E298B /* MessageInputTypes.swift in Sources */, C32C5C3D256DCBAF003C73A2 /* AppReadiness.m in Sources */, FD09799B27FFC82D00936362 /* Quote.swift in Sources */, FD245C6D285066A400B966DD /* NotifyPushServerJob.swift in Sources */, C3C2A74425539EB700C340D1 /* Message.swift in Sources */, FD245C682850666300B966DD /* Message+Destination.swift in Sources */, + FDF8488029405994007DCAE5 /* HTTPQueryParam+OpenGroup.swift in Sources */, FD09798527FD1A6500936362 /* ClosedGroupKeyPair.swift in Sources */, FD245C632850664600B966DD /* Configuration.swift in Sources */, C32C5DBF256DD743003C73A2 /* ClosedGroupPoller.swift in Sources */, @@ -5687,8 +5743,6 @@ 7BCD116C27016062006330F1 /* WebRTCSession+DataChannel.swift in Sources */, FD5C72F9284F0E880029977D /* MessageReceiver+TypingIndicators.swift in Sources */, FD5C7303284F0FA50029977D /* MessageReceiver+Calls.swift in Sources */, - FDC4386B27B4E88F00C60D73 /* BatchRequestInfo.swift in Sources */, - FDC4385127B4807400C60D73 /* QueryParam.swift in Sources */, FD83B9C927D0487A005E1583 /* SendDirectMessageResponse.swift in Sources */, FDC438AA27BB12BB00C60D73 /* UserModeratorRequest.swift in Sources */, FD245C54285065E000B966DD /* ThumbnailService.swift in Sources */, @@ -5707,7 +5761,7 @@ B8CCF63723961D6D0091D419 /* NewDMVC.swift in Sources */, FDFDE12A282D056B0098B17F /* MediaZoomAnimationController.swift in Sources */, 4C1885D2218F8E1C00B67051 /* PhotoGridViewCell.swift in Sources */, - FD71164C28E3F5AA00B47552 /* SessionCell+ExtraAction.swift in Sources */, + FD71164C28E3F5AA00B47552 /* SessionCell+Accessibility.swift in Sources */, 34D1F0501F7D45A60066283D /* GifPickerCell.swift in Sources */, 7BFA8AE32831D0D4001876F3 /* ContextMenuVC+EmojiReactsView.swift in Sources */, C3E5C2FA251DBABB0040DFFC /* EditClosedGroupVC.swift in Sources */, @@ -5757,7 +5811,6 @@ C3548F0624456447009433A8 /* PNModeVC.swift in Sources */, FD71164828E2CE8700B47552 /* SessionCell+AccessoryView.swift in Sources */, B80A579F23DFF1F300876683 /* NewClosedGroupVC.swift in Sources */, - FD71163A28E2C53700B47552 /* SessionAvatarCell.swift in Sources */, 7BA68909272A27BE00EFC32F /* SessionCall.swift in Sources */, B835247925C38D880089A44F /* MessageCell.swift in Sources */, B86BD08623399CEF000F5AE3 /* SeedModal.swift in Sources */, @@ -5788,6 +5841,7 @@ 7BA6890D27325CCC00EFC32F /* SessionCallManager+CXCallController.swift in Sources */, C374EEEB25DA3CA70073A857 /* ConversationTitleView.swift in Sources */, FD716E7128505E5200C96BF4 /* MessageRequestsCell.swift in Sources */, + FDF848F329413DB0007DCAE5 /* ImagePickerHandler.swift in Sources */, B88FA7F2260C3EB10049422F /* OpenGroupSuggestionGrid.swift in Sources */, FD71160428C95B5600B47552 /* PhotoCollectionPickerViewModel.swift in Sources */, FD37EA1928AC5CCA003AE748 /* NotificationSoundViewModel.swift in Sources */, @@ -5824,7 +5878,6 @@ FD7115F428C71EB200B47552 /* ThreadDisappearingMessagesViewModel.swift in Sources */, B8D84ECF25E3108A005A043E /* ExpandingAttachmentsButton.swift in Sources */, 7B7CB190270FB2150079FF93 /* MiniCallView.swift in Sources */, - FD87DCFC28B755B800AF0F98 /* BlockedContactsViewController.swift in Sources */, 7B13E1E92810F01300BD4F64 /* SessionCallManager+Action.swift in Sources */, FD71163728E2C50700B47552 /* SessionTableViewController.swift in Sources */, C354E75A23FE2A7600CE22E3 /* BaseVC.swift in Sources */, @@ -5866,6 +5919,7 @@ 7B9F71D22852EEE2006DFE7B /* Emoji+SkinTones.swift in Sources */, 7B7CB18E270D066F0079FF93 /* IncomingCallBanner.swift in Sources */, B8569AE325CBB19A00DBA3DB /* DocumentView.swift in Sources */, + FDF848F529413EEC007DCAE5 /* SessionCell+Styling.swift in Sources */, 7BFD1A8A2745C4F000FB91B9 /* Permissions.swift in Sources */, B85357BF23A1AE0800AAF6CD /* SeedReminderView.swift in Sources */, FD716E6C28505E1C00C96BF4 /* MessageRequestsViewModel.swift in Sources */, @@ -5916,15 +5970,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - FD8ECF8729346DB500C0D1BB /* RequestSpec.swift in Sources */, FD2AAAF228ED57B500A49611 /* SynchronousStorage.swift in Sources */, FD078E4927E02576000769AF /* CommonMockedExtensions.swift in Sources */, FD83B9BF27CF2294005E1583 /* TestConstants.swift in Sources */, - FD8ECF8629346DA100C0D1BB /* HeaderSpec.swift in Sources */, FD83B9BB27CF20AF005E1583 /* SessionIdSpec.swift in Sources */, FDC290A927D9B46D005DAE71 /* NimbleExtensions.swift in Sources */, FD23EA6328ED0B260058676E /* CombineExtensions.swift in Sources */, - FD8ECF852934508B00C0D1BB /* BatchResponseSpec.swift in Sources */, FD2AAAEE28ED3E1100A49611 /* MockGeneralCache.swift in Sources */, FD9B30F3293EA0BF008DEE3E /* BatchResponseSpec.swift in Sources */, FD37EA1528AB42CB003AE748 /* IdentitySpec.swift in Sources */, @@ -6149,7 +6200,7 @@ /* Begin XCBuildConfiguration section */ 453518731FC635DD00210559 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 96ED0C9B69379BE6FF4E9DA6 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.debug.xcconfig */; + baseConfigurationReference = B1910A32EB2AD01913629646 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.debug.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -6207,7 +6258,7 @@ }; 453518751FC635DD00210559 /* App Store Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 5626DC0D5F62C1C2C64E4AFC /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.app store release.xcconfig */; + baseConfigurationReference = 6A71AD9BEAFF0C9E8016BC23 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.app store release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -6286,7 +6337,7 @@ }; 7BC01A43241F40AB00BC7C55 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 1A0882BF820F5B44969F91F1 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig */; + baseConfigurationReference = 285705D20F792E174C8A9BBA /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -6343,7 +6394,7 @@ }; 7BC01A44241F40AB00BC7C55 /* App Store Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 245BF74EF6348E2D4125033F /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.app store release.xcconfig */; + baseConfigurationReference = 62B512CEB14BD4A4A53CF532 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.app store release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -6424,7 +6475,7 @@ }; C331FF242558F9D400070591 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 510955DC99A0FD84F2D1C159 /* Pods-GlobalDependencies-SessionUIKit.debug.xcconfig */; + baseConfigurationReference = EB5B8ACA4C6F512FA3E21859 /* Pods-GlobalDependencies-SessionUIKit.debug.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -6480,7 +6531,7 @@ }; C331FF252558F9D400070591 /* App Store Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 18EAE958B8C12503F2C294DF /* Pods-GlobalDependencies-SessionUIKit.app store release.xcconfig */; + baseConfigurationReference = 05C76EFA593DD507061C50B2 /* Pods-GlobalDependencies-SessionUIKit.app store release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; APPLICATION_EXTENSION_API_ONLY = YES; @@ -6560,7 +6611,7 @@ }; C33FD9B4255A548A00E217F9 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DAF57FAAF30631D0E99DA361 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.debug.xcconfig */; + baseConfigurationReference = B4F9FCBDA07F07CB48220D4C /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.debug.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -6624,7 +6675,7 @@ }; C33FD9B5255A548A00E217F9 /* App Store Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 82099864FD91C9126A750313 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.app store release.xcconfig */; + baseConfigurationReference = 55C13C7B4B700846E49C0E25 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit.app store release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; APPLICATION_EXTENSION_API_ONLY = YES; @@ -6711,7 +6762,7 @@ }; C3C2A5A8255385C100C340D1 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = E23C1E6B7E0C12BF4ACD9CBE /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.debug.xcconfig */; + baseConfigurationReference = 847091A12D82E41B1EBB8FB3 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.debug.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -6768,7 +6819,7 @@ }; C3C2A5A9255385C100C340D1 /* App Store Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 0E836037CC97CE5A47735596 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.app store release.xcconfig */; + baseConfigurationReference = EED1CF82CAB23FE3345564F9 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.app store release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; APPLICATION_EXTENSION_API_ONLY = YES; @@ -6848,7 +6899,7 @@ }; C3C2A682255388CC00C340D1 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = F705826F79C4A591AB35D68F /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig */; + baseConfigurationReference = 8E946CB54A221018E23599DE /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -6890,15 +6941,7 @@ ); MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; - OTHER_LDFLAGS = ( - "$(inherited)", - "-framework", - "\"Foundation\"", - "-framework", - "\"PromiseKit\"", - "-framework", - "\"UIKit\"", - ); + OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.SessionUtilitiesKit"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -6914,7 +6957,7 @@ }; C3C2A683255388CC00C340D1 /* App Store Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 2581AFACDDDC1404866D7B8C /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.app store release.xcconfig */; + baseConfigurationReference = F60C5B6CD14329816B0E8CC0 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.app store release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; APPLICATION_EXTENSION_API_ONLY = YES; @@ -6977,15 +7020,7 @@ ); MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; - OTHER_LDFLAGS = ( - "$(inherited)", - "-framework", - "\"Foundation\"", - "-framework", - "\"PromiseKit\"", - "-framework", - "\"UIKit\"", - ); + OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.SessionUtilitiesKit"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -7003,7 +7038,7 @@ }; C3C2A6FA25539DE700C340D1 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 506FA2159653FF9F446D97D1 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.debug.xcconfig */; + baseConfigurationReference = 6DA09080DD9779C860023A60 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.debug.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -7060,7 +7095,7 @@ }; C3C2A6FB25539DE700C340D1 /* App Store Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = FF694C71BE4B41B6AFD252A0 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.app store release.xcconfig */; + baseConfigurationReference = F390F8E34CA76B3F7D3B1826 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.app store release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; APPLICATION_EXTENSION_API_ONLY = YES; @@ -7293,7 +7328,7 @@ }; D221A0BD169C9E5F00537ABF /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 56F41C56FC7B2F381E440FB0 /* Pods-GlobalDependencies-Session.debug.xcconfig */; + baseConfigurationReference = 34040971CC7AF9C8A6C1E838 /* Pods-GlobalDependencies-Session.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -7366,7 +7401,7 @@ }; D221A0BE169C9E5F00537ABF /* App Store Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 6BE8FBF62464A7177034A0AB /* Pods-GlobalDependencies-Session.app store release.xcconfig */; + baseConfigurationReference = 8603226ED1C6F61F1F2D3734 /* Pods-GlobalDependencies-Session.app store release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -7434,7 +7469,7 @@ }; FD71161028D00BAE00B47552 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 06160ECE3FE5A06A916FF8C5 /* Pods-GlobalDependencies-Session-SessionTests.debug.xcconfig */; + baseConfigurationReference = 0772459E7D5F6747EDC889F3 /* Pods-GlobalDependencies-Session-SessionTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NONNULL = YES; @@ -7476,7 +7511,7 @@ }; FD71161128D00BAE00B47552 /* App Store Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 0E8564674E3201E218939AFB /* Pods-GlobalDependencies-Session-SessionTests.app store release.xcconfig */; + baseConfigurationReference = F154A10CE1ADA33C16B45357 /* Pods-GlobalDependencies-Session-SessionTests.app store release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; BUNDLE_LOADER = "$(TEST_HOST)"; @@ -7542,7 +7577,7 @@ }; FD83B9B727CF200A005E1583 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = BE11AFA6FD8CAE894CABC28D /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.debug.xcconfig */; + baseConfigurationReference = 8448EFF76CD3CA5B2283B8A0 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.debug.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -7584,7 +7619,7 @@ }; FD83B9B827CF200A005E1583 /* App Store Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 58A6BA91F634756FA0BEC9E5 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.app store release.xcconfig */; + baseConfigurationReference = 5DA3BDDFFB9E937A49C35FCC /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.app store release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -7649,7 +7684,7 @@ }; FDC4389627B9FFC700C60D73 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = D0CE0424239A1574F683D2D7 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.debug.xcconfig */; + baseConfigurationReference = 8727C47348B6EFA767EE583A /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.debug.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -7691,7 +7726,7 @@ }; FDC4389727B9FFC700C60D73 /* App Store Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 8E029A324780A800DE6B70B3 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.app store release.xcconfig */; + baseConfigurationReference = 621B42AC592F3456ACD82F8B /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.app store release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; diff --git a/Session/Calls/Views & Modals/IncomingCallBanner.swift b/Session/Calls/Views & Modals/IncomingCallBanner.swift index cecb6c01b..75ce31583 100644 --- a/Session/Calls/Views & Modals/IncomingCallBanner.swift +++ b/Session/Calls/Views & Modals/IncomingCallBanner.swift @@ -118,8 +118,10 @@ final class IncomingCallBanner: UIView, UIGestureRecognizerDelegate { profilePictureView.update( publicKey: call.sessionId, + threadVariant: .contact, + customImageData: nil, profile: Profile.fetchOrCreate(id: call.sessionId), - threadVariant: .contact + additionalProfile: nil ) displayNameLabel.text = call.contactName diff --git a/Session/Closed Groups/EditClosedGroupVC.swift b/Session/Closed Groups/EditClosedGroupVC.swift index 1c67c9e25..8a9a74e1f 100644 --- a/Session/Closed Groups/EditClosedGroupVC.swift +++ b/Session/Closed Groups/EditClosedGroupVC.swift @@ -1,9 +1,9 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit +import Combine import GRDB import DifferenceKit -import PromiseKit import SessionUIKit import SessionMessagingKit import SignalUtilitiesKit @@ -220,7 +220,8 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat cell.update( with: SessionCell.Info( id: displayInfo, - leftAccessory: .profile(displayInfo.profileId, displayInfo.profile), + position: Position.with(indexPath.row, count: membersAndZombies.count), + leftAccessory: .profile(id: displayInfo.profileId, profile: displayInfo.profile), title: ( displayInfo.profile?.displayName() ?? Profile.truncated(id: displayInfo.profileId, threadVariant: .contact) @@ -231,10 +232,9 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat .withRenderingMode(.alwaysTemplate), customTint: .textSecondary ) - ) - ), - style: .edgeToEdge, - position: Position.with(indexPath.row, count: membersAndZombies.count) + ), + styling: SessionCell.StyleInfo(backgroundStyle: .edgeToEdge) + ) ) return cell @@ -449,7 +449,7 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat ModalActivityIndicatorViewController.present(fromViewController: navigationController) { _ in Storage.shared - .writeAsync { db in + .writePublisherFlatMap { db -> AnyPublisher in if !updatedMemberIds.contains(userPublicKey) { return try MessageSender.leave(db, groupPublicKey: threadId) } @@ -461,15 +461,20 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat name: updatedName ) } - .done(on: DispatchQueue.main) { [weak self] in - self?.dismiss(animated: true, completion: nil) // Dismiss the loader - popToConversationVC(self) - } - .catch(on: DispatchQueue.main) { [weak self] error in - self?.dismiss(animated: true, completion: nil) // Dismiss the loader - self?.showError(title: "GROUP_UPDATE_ERROR_TITLE".localized(), message: error.localizedDescription) - } - .retainUntilComplete() + .sinkUntilComplete( + receiveCompletion: { [weak self] result in + self?.dismiss(animated: true, completion: nil) // Dismiss the loader + + switch result { + case .finished: popToConversationVC(self) + case .failure(let error): + self?.showError( + title: "GROUP_UPDATE_ERROR_TITLE".localized(), + message: error.localizedDescription + ) + } + } + ) } } diff --git a/Session/Closed Groups/NewClosedGroupVC.swift b/Session/Closed Groups/NewClosedGroupVC.swift index eee544e92..3e799825d 100644 --- a/Session/Closed Groups/NewClosedGroupVC.swift +++ b/Session/Closed Groups/NewClosedGroupVC.swift @@ -205,15 +205,17 @@ final class NewClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegate cell.update( with: SessionCell.Info( id: profile, - leftAccessory: .profile(profile.id, profile), + position: Position.with(indexPath.row, count: data[indexPath.section].elements.count), + leftAccessory: .profile(id: profile.id, profile: profile), title: profile.displayName(), rightAccessory: .radio(isSelected: { [weak self] in self?.selectedContacts.contains(profile.id) == true }), - accessibilityIdentifier: "Contact" - ), - style: .edgeToEdge, - position: Position.with(indexPath.row, count: data[indexPath.section].elements.count) + styling: SessionCell.StyleInfo(backgroundStyle: .edgeToEdge), + accessibility: SessionCell.Accessibility( + identifier: "Contact" + ) + ) ) return cell diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 91d739cf8..766689672 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -1872,7 +1872,7 @@ extension ConversationVC: deleteRemotely( from: self, request: SnodeAPI - .deleteMessage( + .deleteMessages( publicKey: threadId, serverHashes: [serverHash] ) @@ -2328,10 +2328,11 @@ extension ConversationVC { ) .save(db) - // Send a sync message with the details of the contact + + // Update the config with the approved contact try MessageSender .syncConfiguration(db, forceSyncNow: true) - .retainUntilComplete() + .sinkUntilComplete() }, completion: { _, _ in updateNavigationBackStack() } ) @@ -2347,79 +2348,31 @@ extension ConversationVC { } @objc func deleteMessageRequest() { - guard self.viewModel.threadData.threadVariant == .contact else { return } - - let threadId: String = self.viewModel.threadData.threadId - let alertVC: UIAlertController = UIAlertController( - title: "MESSAGE_REQUESTS_DELETE_CONFIRMATION_ACTON".localized(), - message: nil, - preferredStyle: .actionSheet - ) - alertVC.addAction(UIAlertAction(title: "TXT_DELETE_TITLE".localized(), style: .destructive) { _ in - // Delete the request - Storage.shared.writeAsync( - updates: { db in - _ = try SessionThread - .filter(id: threadId) - .deleteAll(db) - }, - completion: { db, _ in - DispatchQueue.main.async { [weak self] in - self?.navigationController?.popViewController(animated: true) - } - } - ) - }) - alertVC.addAction(UIAlertAction(title: "TXT_CANCEL_TITLE".localized(), style: .cancel, handler: nil)) - - self.present(alertVC, animated: true, completion: nil) + MessageRequestsViewModel.deleteMessageRequest( + threadId: self.viewModel.threadData.threadId, + threadVariant: self.viewModel.threadData.threadVariant, + viewController: self + ) { [weak self] in + self?.stopObservingChanges() + + DispatchQueue.main.async { + self?.navigationController?.popViewController(animated: true) + } + } } - @objc func block() { - guard self.viewModel.threadData.threadVariant == .contact else { return } - - let threadId: String = self.viewModel.threadData.threadId - let alertVC: UIAlertController = UIAlertController( - title: "MESSAGE_REQUESTS_BLOCK_CONFIRMATION_ACTON".localized(), - message: nil, - preferredStyle: .actionSheet - ) - alertVC.addAction(UIAlertAction(title: "BLOCK_LIST_BLOCK_BUTTON".localized(), style: .destructive) { _ in - // Delete the request - Storage.shared.writeAsync( - updates: { db in - // Update the contact - _ = try Contact - .fetchOrCreate(db, id: threadId) - .with( - isApproved: false, - isBlocked: true, - - // Note: We set this to true so the current user will be able to send a - // message to the person who originally sent them the message request in - // the future if they unblock them - didApproveMe: true - ) - .saved(db) - - _ = try SessionThread - .filter(id: threadId) - .deleteAll(db) - - try MessageSender - .syncConfiguration(db, forceSyncNow: true) - .retainUntilComplete() - }, - completion: { db, _ in - DispatchQueue.main.async { [weak self] in - self?.navigationController?.popViewController(animated: true) - } - } - ) - }) - alertVC.addAction(UIAlertAction(title: "TXT_CANCEL_TITLE".localized(), style: .cancel, handler: nil)) - - self.present(alertVC, animated: true, completion: nil) + @objc func blockMessageRequest() { + MessageRequestsViewModel.blockMessageRequest( + threadId: self.viewModel.threadData.threadId, + threadVariant: self.viewModel.threadData.threadVariant, + viewController: self + ) { [weak self] in + self?.stopObservingChanges() + + DispatchQueue.main.async { + self?.navigationController?.popViewController(animated: true) + } + } } } diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index 87376ffa3..dbb8c9f7e 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -27,10 +27,6 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl var focusedInteractionId: Int64? var shouldHighlightNextScrollToInteraction: Bool = false - var scrollButtonBottomConstraint: NSLayoutConstraint? - var scrollButtonMessageRequestsBottomConstraint: NSLayoutConstraint? - var scrollButtonPendingMessageRequestInfoBottomConstraint: NSLayoutConstraint? - var messageRequestsViewBotomConstraint: NSLayoutConstraint? // Search var isShowingSearchUI = false @@ -40,8 +36,6 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl var audioRecorder: AVAudioRecorder? var audioTimer: Timer? - private var searchBarWidth: NSLayoutConstraint? - // Context menu var contextMenuWindow: ContextMenuWindow? var contextMenuVC: ContextMenuVC? @@ -129,6 +123,12 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl // MARK: - UI + var scrollButtonBottomConstraint: NSLayoutConstraint? + var scrollButtonMessageRequestsBottomConstraint: NSLayoutConstraint? + var scrollButtonPendingMessageRequestInfoBottomConstraint: NSLayoutConstraint? + var messageRequestsViewBotomConstraint: NSLayoutConstraint? + var messageRequestDescriptionLabelBottomConstraint: NSLayoutConstraint? + lazy var titleView: ConversationTitleView = { let result: ConversationTitleView = ConversationTitleView() let tapGestureRecognizer = UITapGestureRecognizer( @@ -221,11 +221,22 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl }() lazy var scrollButton: ScrollToBottomButton = ScrollToBottomButton(delegate: self) - - lazy var messageRequestView: UIView = { + + lazy var messageRequestBackgroundView: UIView = { let result: UIView = UIView() result.translatesAutoresizingMaskIntoConstraints = false result.themeBackgroundColor = .backgroundPrimary + result.isHidden = messageRequestStackView.isHidden + + return result + }() + + lazy var messageRequestStackView: UIStackView = { + let result: UIStackView = UIStackView() + result.translatesAutoresizingMaskIntoConstraints = false + result.axis = .vertical + result.alignment = .fill + result.distribution = .fill result.isHidden = ( self.viewModel.threadData.threadIsMessageRequest == false || self.viewModel.threadData.threadRequiresApproval == true @@ -233,18 +244,40 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl return result }() + + private lazy var messageRequestDescriptionContainerView: UIView = { + let result: UIView = UIView() + result.translatesAutoresizingMaskIntoConstraints = false + + return result + }() - private let messageRequestDescriptionLabel: UILabel = { + private lazy var messageRequestDescriptionLabel: UILabel = { let result: UILabel = UILabel() result.translatesAutoresizingMaskIntoConstraints = false + result.setContentCompressionResistancePriority(.required, for: .vertical) result.font = UIFont.systemFont(ofSize: 12) - result.text = "MESSAGE_REQUESTS_INFO".localized() + result.text = (self.viewModel.threadData.threadRequiresApproval == false ? + "MESSAGE_REQUESTS_INFO".localized() : + "MESSAGE_REQUEST_PENDING_APPROVAL_INFO".localized() + ) result.themeTextColor = .textSecondary result.textAlignment = .center result.numberOfLines = 0 return result }() + + private lazy var messageRequestActionStackView: UIStackView = { + let result: UIStackView = UIStackView() + result.translatesAutoresizingMaskIntoConstraints = false + result.axis = .horizontal + result.alignment = .fill + result.distribution = .fill + result.spacing = (UIDevice.current.isIPad ? Values.iPadButtonSpacing : 20) + + return result + }() private lazy var messageRequestAcceptButton: UIButton = { let result: SessionButton = SessionButton(style: .bordered, size: .medium) @@ -276,27 +309,11 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl result.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16) result.setTitle("TXT_BLOCK_USER_TITLE".localized(), for: .normal) result.setThemeTitleColor(.danger, for: .normal) - result.addTarget(self, action: #selector(block), for: .touchUpInside) + result.addTarget(self, action: #selector(blockMessageRequest), for: .touchUpInside) + result.isHidden = (self.viewModel.threadData.threadVariant != .contact) return result }() - - private lazy var pendingMessageRequestExplanationLabel: UILabel = { - let result: UILabel = UILabel() - result.translatesAutoresizingMaskIntoConstraints = false - result.setContentCompressionResistancePriority(.required, for: .vertical) - result.font = UIFont.systemFont(ofSize: 12) - result.text = "MESSAGE_REQUEST_PENDING_APPROVAL_INFO".localized() - result.themeTextColor = .textSecondary - result.textAlignment = .center - result.numberOfLines = 0 - result.isHidden = ( - !self.messageRequestStackView.isHidden || - self.viewModel.threadData.threadRequiresApproval == false - ) - - return result - }() // MARK: - Settings @@ -352,46 +369,32 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl view.addSubview(scrollButton) view.addSubview(messageRequestBackgroundView) view.addSubview(messageRequestStackView) - view.addSubview(pendingMessageRequestExplanationLabel) - messageRequestView.addSubview(messageRequestBlockButton) - messageRequestView.addSubview(messageRequestDescriptionLabel) - messageRequestView.addSubview(messageRequestAcceptButton) - messageRequestView.addSubview(messageRequestDeleteButton) + messageRequestStackView.addArrangedSubview(messageRequestBlockButton) + messageRequestStackView.addArrangedSubview(messageRequestDescriptionContainerView) + messageRequestStackView.addArrangedSubview(messageRequestActionStackView) + messageRequestDescriptionContainerView.addSubview(messageRequestDescriptionLabel) + messageRequestActionStackView.addArrangedSubview(messageRequestAcceptButton) + messageRequestActionStackView.addArrangedSubview(messageRequestDeleteButton) - scrollButton.pin(.right, to: .right, of: view, withInset: -20) - messageRequestView.pin(.left, to: .left, of: view) - messageRequestView.pin(.right, to: .right, of: view) - self.messageRequestsViewBotomConstraint = messageRequestView.pin(.bottom, to: .bottom, of: view, withInset: -16) + scrollButton.pin(.trailing, to: .trailing, of: view, withInset: -20) + messageRequestStackView.pin(.leading, to: .leading, of: view, withInset: 16) + messageRequestStackView.pin(.trailing, to: .trailing, of: view, withInset: -16) + self.messageRequestsViewBotomConstraint = messageRequestStackView.pin(.bottom, to: .bottom, of: view, withInset: -16) self.scrollButtonBottomConstraint = scrollButton.pin(.bottom, to: .bottom, of: view, withInset: -16) self.scrollButtonBottomConstraint?.isActive = false // Note: Need to disable this to avoid a conflict with the other bottom constraint - self.scrollButtonMessageRequestsBottomConstraint = scrollButton.pin(.bottom, to: .top, of: messageRequestStackView) - self.scrollButtonPendingMessageRequestInfoBottomConstraint = scrollButton.pin(.bottom, to: .top, of: pendingMessageRequestExplanationLabel, withInset: -16) + self.scrollButtonMessageRequestsBottomConstraint = scrollButton.pin(.bottom, to: .top, of: messageRequestStackView, withInset: -4) - messageRequestBlockButton.pin(.top, to: .top, of: messageRequestView, withInset: 10) - messageRequestBlockButton.center(.horizontal, in: messageRequestView) - - messageRequestDescriptionLabel.pin(.top, to: .bottom, of: messageRequestBlockButton, withInset: 5) - messageRequestDescriptionLabel.pin(.left, to: .left, of: messageRequestView, withInset: 40) - messageRequestDescriptionLabel.pin(.right, to: .right, of: messageRequestView, withInset: -40) - - messageRequestAcceptButton.pin(.top, to: .bottom, of: messageRequestDescriptionLabel, withInset: 20) - messageRequestAcceptButton.pin(.left, to: .left, of: messageRequestView, withInset: 20) - messageRequestAcceptButton.pin(.bottom, to: .bottom, of: messageRequestView) - - messageRequestDeleteButton.pin(.top, to: .bottom, of: messageRequestDescriptionLabel, withInset: 20) - messageRequestDeleteButton.pin(.left, to: .right, of: messageRequestAcceptButton, withInset: UIDevice.current.isIPad ? Values.iPadButtonSpacing : 20) - messageRequestDeleteButton.pin(.right, to: .right, of: messageRequestView, withInset: -20) - messageRequestDeleteButton.pin(.bottom, to: .bottom, of: messageRequestView) + messageRequestDescriptionLabel.pin(.top, to: .top, of: messageRequestDescriptionContainerView, withInset: 4) + messageRequestDescriptionLabel.pin(.leading, to: .leading, of: messageRequestDescriptionContainerView, withInset: 20) + messageRequestDescriptionLabel.pin(.trailing, to: .trailing, of: messageRequestDescriptionContainerView, withInset: -20) + self.messageRequestDescriptionLabelBottomConstraint = messageRequestDescriptionLabel.pin(.bottom, to: .bottom, of: messageRequestDescriptionContainerView, withInset: -20) + messageRequestActionStackView.pin(.top, to: .bottom, of: messageRequestDescriptionContainerView) messageRequestDeleteButton.set(.width, to: .width, of: messageRequestAcceptButton) messageRequestBackgroundView.pin(.top, to: .top, of: messageRequestStackView) messageRequestBackgroundView.pin(.leading, to: .leading, of: view) messageRequestBackgroundView.pin(.trailing, to: .trailing, of: view) messageRequestBackgroundView.pin(.bottom, to: .bottom, of: view) - - pendingMessageRequestExplanationLabel.pin(.left, to: .left, of: messageRequestStackView, withInset: 40) - pendingMessageRequestExplanationLabel.pin(.right, to: .right, of: messageRequestStackView, withInset: -40) - pendingMessageRequestExplanationLabel.pin(.bottom, to: .bottom, of: messageRequestStackView, withInset: -16) // Unread count view view.addSubview(unreadCountView) @@ -505,12 +508,6 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl stopObservingChanges() } - override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { - super.viewWillTransition(to: size, with: coordinator) - searchBarWidth?.constant = size.width - 32 - tableView.reloadData() - } - // MARK: - Updating private func startObservingChanges(didReturnFromBackground: Bool = false) { @@ -571,7 +568,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl ) } - private func stopObservingChanges() { + func stopObservingChanges() { // Stop observing database changes dataChangeObservable?.cancel() self.viewModel.onInteractionChange = nil @@ -619,6 +616,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl if initialLoad || + viewModel.threadData.threadVariant != updatedThreadData.threadVariant || viewModel.threadData.threadRequiresApproval != updatedThreadData.threadRequiresApproval || viewModel.threadData.threadIsMessageRequest != updatedThreadData.threadIsMessageRequest || viewModel.threadData.profile != updatedThreadData.profile @@ -628,47 +626,33 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl let messageRequestsViewWasVisible: Bool = ( messageRequestStackView.isHidden == false ) - let pendingMessageRequestInfoWasVisible: Bool = ( - pendingMessageRequestExplanationLabel.isHidden == false - ) UIView.animate(withDuration: 0.3) { [weak self] in - self?.messageRequestView.isHidden = ( - updatedThreadData.threadIsMessageRequest == false || + self?.messageRequestBlockButton.isHidden = ( + self?.viewModel.threadData.threadVariant != .contact || updatedThreadData.threadRequiresApproval == true ) - self?.messageRequestBackgroundView.isHidden = (self?.messageRequestStackView.isHidden == true) - self?.pendingMessageRequestExplanationLabel.isHidden = ( - self?.messageRequestStackView.isHidden == false || + self?.messageRequestActionStackView.isHidden = ( + updatedThreadData.threadRequiresApproval == true + ) + self?.messageRequestStackView.isHidden = ( + updatedThreadData.threadIsMessageRequest == false && updatedThreadData.threadRequiresApproval == false ) + self?.messageRequestBackgroundView.isHidden = (self?.messageRequestStackView.isHidden == true) + self?.messageRequestDescriptionLabelBottomConstraint?.constant = (updatedThreadData.threadRequiresApproval == true ? -4 : -20) self?.scrollButtonMessageRequestsBottomConstraint?.isActive = ( self?.messageRequestStackView.isHidden == false ) - self?.scrollButtonPendingMessageRequestInfoBottomConstraint?.isActive = ( - self?.scrollButtonPendingMessageRequestInfoBottomConstraint?.isActive == false && - self?.pendingMessageRequestExplanationLabel.isHidden == false - ) self?.scrollButtonBottomConstraint?.isActive = ( - self?.scrollButtonMessageRequestsBottomConstraint?.isActive == false && - self?.scrollButtonPendingMessageRequestInfoBottomConstraint?.isActive == false + self?.scrollButtonMessageRequestsBottomConstraint?.isActive == false ) // Update the table content inset and offset to account for // the dissapearance of the messageRequestsView - if messageRequestsViewWasVisible { - let messageRequestsOffset: CGFloat = ((self?.messageRequestView.bounds.height ?? 0) + 16) - let oldContentInset: UIEdgeInsets = (self?.tableView.contentInset ?? UIEdgeInsets.zero) - self?.tableView.contentInset = UIEdgeInsets( - top: 0, - leading: 0, - bottom: max(oldContentInset.bottom - messageRequestsOffset, 0), - trailing: 0 - ) - } - else if pendingMessageRequestInfoWasVisible { - let messageRequestsOffset: CGFloat = ((self?.pendingMessageRequestExplanationLabel.bounds.height ?? 0) + (16 * 2)) + if messageRequestsViewWasVisible != (self?.messageRequestStackView.isHidden == false) { + let messageRequestsOffset: CGFloat = ((self?.messageRequestStackView.bounds.height ?? 0) + 12) let oldContentInset: UIEdgeInsets = (self?.tableView.contentInset ?? UIEdgeInsets.zero) self?.tableView.contentInset = UIEdgeInsets( top: 0, @@ -1103,9 +1087,12 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl profilePictureView.size = Values.verySmallProfilePictureSize profilePictureView.update( publicKey: threadData.threadId, // Contact thread uses the contactId + threadVariant: threadData.threadVariant, + customImageData: nil, profile: threadData.profile, - threadVariant: threadData.threadVariant + additionalProfile: nil ) + profilePictureView.set(.width, to: (44 - 16)) // Width of the standard back button profilePictureView.set(.height, to: Values.verySmallProfilePictureSize) @@ -1159,7 +1146,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl // needed for proper calculations, so force an initial layout if it doesn't have a size) var hasDoneLayout: Bool = true - if messageRequestView.bounds.height <= CGFloat.leastNonzeroMagnitude { + if messageRequestStackView.bounds.height <= CGFloat.leastNonzeroMagnitude { hasDoneLayout = false UIView.performWithoutAnimation { @@ -1168,19 +1155,18 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl } let keyboardTop = (UIScreen.main.bounds.height - keyboardRect.minY) - let messageRequestsOffset: CGFloat = (messageRequestStackView.isHidden ? 0 : messageRequestStackView.bounds.height + 16) - let pendingMessageRequestsOffset: CGFloat = (pendingMessageRequestExplanationLabel.isHidden ? 0 : (pendingMessageRequestExplanationLabel.bounds.height + (16 * 2))) + let messageRequestsOffset: CGFloat = (messageRequestStackView.isHidden ? 0 : messageRequestStackView.bounds.height + 12) let oldContentInset: UIEdgeInsets = tableView.contentInset let newContentInset: UIEdgeInsets = UIEdgeInsets( top: 0, leading: 0, - bottom: (Values.mediumSpacing + keyboardTop + messageRequestsOffset + pendingMessageRequestsOffset), + bottom: (Values.mediumSpacing + keyboardTop + messageRequestsOffset), trailing: 0 ) let newContentOffsetY: CGFloat = (tableView.contentOffset.y + (newContentInset.bottom - oldContentInset.bottom)) let changes = { [weak self] in - self?.scrollButtonBottomConstraint?.constant = -(keyboardTop + 16) - self?.messageRequestsViewBotomConstraint?.constant = -(keyboardTop + 16) + self?.scrollButtonBottomConstraint?.constant = -(keyboardTop + 12) + self?.messageRequestsViewBotomConstraint?.constant = -(keyboardTop + 12) self?.tableView.contentInset = newContentInset self?.tableView.contentOffset.y = newContentOffsetY @@ -1226,8 +1212,8 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl delay: 0, options: options, animations: { [weak self] in - self?.scrollButtonBottomConstraint?.constant = -(keyboardTop + 16) - self?.messageRequestsViewBotomConstraint?.constant = -(keyboardTop + 16) + self?.scrollButtonBottomConstraint?.constant = -(keyboardTop + 12) + self?.messageRequestsViewBotomConstraint?.constant = -(keyboardTop + 12) let scrollButtonOpacity: CGFloat = (self?.getScrollButtonOpacity() ?? 0) self?.scrollButton.alpha = scrollButtonOpacity @@ -1536,7 +1522,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl searchBar.sizeToFit() searchBar.layoutMargins = UIEdgeInsets.zero searchBarContainer.set(.height, to: 44) - searchBarWidth = searchBarContainer.set(.width, to: UIScreen.main.bounds.width - 32) + searchBarContainer.set(.width, to: UIScreen.main.bounds.width - 32) searchBarContainer.addSubview(searchBar) navigationItem.titleView = searchBarContainer @@ -1676,6 +1662,11 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl animated: (self.didFinishInitialLayout && isAnimated) ) + // Need to explicitly call 'scrollViewDidScroll' here as it won't get triggered + // by 'scrollToRow' if a scroll doesn't occur (eg. if there is less than 1 screen + // of messages) + self.scrollViewDidScroll(self.tableView) + // If we haven't finished the initial layout then we want to delay the highlight slightly // so it doesn't look buggy with the push transition if highlight { diff --git a/Session/Conversations/Input View/MentionSelectionView.swift b/Session/Conversations/Input View/MentionSelectionView.swift index 881059cc3..b89cd884f 100644 --- a/Session/Conversations/Input View/MentionSelectionView.swift +++ b/Session/Conversations/Input View/MentionSelectionView.swift @@ -199,8 +199,10 @@ private extension MentionSelectionView { displayNameLabel.text = profile.displayName(for: threadVariant) profilePictureView.update( publicKey: profile.id, + threadVariant: .contact, + customImageData: nil, profile: profile, - threadVariant: threadVariant + additionalProfile: nil ) moderatorIconImageView.isHidden = !isUserModeratorOrAdmin separator.isHidden = isLast diff --git a/Session/Conversations/Message Cells/Content Views/DocumentView.swift b/Session/Conversations/Message Cells/Content Views/DocumentView.swift index cdcfe5fed..39115cbec 100644 --- a/Session/Conversations/Message Cells/Content Views/DocumentView.swift +++ b/Session/Conversations/Message Cells/Content Views/DocumentView.swift @@ -46,7 +46,7 @@ final class DocumentView: UIView { // Size label let sizeLabel = UILabel() sizeLabel.font = .systemFont(ofSize: Values.verySmallFontSize) - sizeLabel.text = OWSFormat.formatFileSize(UInt(attachment.byteCount)) + sizeLabel.text = Format.fileSize(attachment.byteCount) sizeLabel.themeTextColor = textColor sizeLabel.lineBreakMode = .byTruncatingTail diff --git a/Session/Conversations/Message Cells/Content Views/MediaAlbumView.swift b/Session/Conversations/Message Cells/Content Views/MediaAlbumView.swift index c6ca54b4e..c3f6a1c5b 100644 --- a/Session/Conversations/Message Cells/Content Views/MediaAlbumView.swift +++ b/Session/Conversations/Message Cells/Content Views/MediaAlbumView.swift @@ -111,11 +111,10 @@ public class MediaAlbumView: UIStackView { tintView.autoPinEdgesToSuperviewEdges() let moreCount = max(1, items.count - MediaAlbumView.kMaxItems) - let moreCountText = OWSFormat.formatInt(Int32(moreCount)) let moreText = String( // Format for the 'more items' indicator for media galleries. Embeds {{the number of additional items}}. format: "MEDIA_GALLERY_MORE_ITEMS_FORMAT".localized(), - moreCountText + "\(moreCount)" ) let moreLabel: UILabel = UILabel() moreLabel.font = .systemFont(ofSize: 24) diff --git a/Session/Conversations/Message Cells/VisibleMessageCell.swift b/Session/Conversations/Message Cells/VisibleMessageCell.swift index 45145bb22..7d74b4c0e 100644 --- a/Session/Conversations/Message Cells/VisibleMessageCell.swift +++ b/Session/Conversations/Message Cells/VisibleMessageCell.swift @@ -289,8 +289,10 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { profilePictureView.isHidden = (!cellViewModel.shouldShowProfile || cellViewModel.profile == nil) profilePictureView.update( publicKey: cellViewModel.authorId, + threadVariant: cellViewModel.threadVariant, + customImageData: nil, profile: cellViewModel.profile, - threadVariant: cellViewModel.threadVariant + additionalProfile: nil ) moderatorIconImageView.isHidden = (!cellViewModel.isSenderOpenGroupModerator || !cellViewModel.shouldShowProfile) diff --git a/Session/Conversations/Settings/ThreadDisappearingMessagesViewModel.swift b/Session/Conversations/Settings/ThreadDisappearingMessagesViewModel.swift index aace471aa..8cdfdce1a 100644 --- a/Session/Conversations/Settings/ThreadDisappearingMessagesViewModel.swift +++ b/Session/Conversations/Settings/ThreadDisappearingMessagesViewModel.swift @@ -85,10 +85,7 @@ class ThreadDisappearingMessagesViewModel: SessionTableViewModel [SectionModel] in return [ SectionModel( @@ -131,12 +128,9 @@ class ThreadDisappearingMessagesViewModel: SessionTableViewModel [SectionModel] in let userPublicKey: String = getUserHexEncodedPublicKey(db, dependencies: dependencies) let maybeThreadViewModel: SessionThreadViewModel? = try SessionThreadViewModel @@ -207,25 +207,88 @@ class ThreadSettingsViewModel: SessionTableViewModel #import #import -#import #import #import #import diff --git a/Session/Meta/Translations/de.lproj/Localizable.strings b/Session/Meta/Translations/de.lproj/Localizable.strings index 8d583d798..71f6adef7 100644 --- a/Session/Meta/Translations/de.lproj/Localizable.strings +++ b/Session/Meta/Translations/de.lproj/Localizable.strings @@ -596,3 +596,5 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"REMOVE_AVATAR" = "Remove"; +"CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index 3829aceab..4882c5a3c 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -596,3 +596,5 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"REMOVE_AVATAR" = "Remove"; +"CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; diff --git a/Session/Meta/Translations/es.lproj/Localizable.strings b/Session/Meta/Translations/es.lproj/Localizable.strings index 31d0bc578..8e7e9d651 100644 --- a/Session/Meta/Translations/es.lproj/Localizable.strings +++ b/Session/Meta/Translations/es.lproj/Localizable.strings @@ -596,3 +596,5 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"REMOVE_AVATAR" = "Remove"; +"CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; diff --git a/Session/Meta/Translations/fa.lproj/Localizable.strings b/Session/Meta/Translations/fa.lproj/Localizable.strings index 75cc6f737..27edb43c5 100644 --- a/Session/Meta/Translations/fa.lproj/Localizable.strings +++ b/Session/Meta/Translations/fa.lproj/Localizable.strings @@ -596,3 +596,5 @@ "MESSAGE_STATE_READ" = "خوانده شد"; "MESSAGE_STATE_SENT" = "ارسال شد"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"REMOVE_AVATAR" = "Remove"; +"CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; diff --git a/Session/Meta/Translations/fi.lproj/Localizable.strings b/Session/Meta/Translations/fi.lproj/Localizable.strings index 92e2e6a11..8f6ddcc6b 100644 --- a/Session/Meta/Translations/fi.lproj/Localizable.strings +++ b/Session/Meta/Translations/fi.lproj/Localizable.strings @@ -596,3 +596,5 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"REMOVE_AVATAR" = "Remove"; +"CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; diff --git a/Session/Meta/Translations/fr.lproj/Localizable.strings b/Session/Meta/Translations/fr.lproj/Localizable.strings index 17c7b0dff..e8559c883 100644 --- a/Session/Meta/Translations/fr.lproj/Localizable.strings +++ b/Session/Meta/Translations/fr.lproj/Localizable.strings @@ -596,3 +596,5 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"REMOVE_AVATAR" = "Remove"; +"CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; diff --git a/Session/Meta/Translations/hi.lproj/Localizable.strings b/Session/Meta/Translations/hi.lproj/Localizable.strings index 8bab51375..e2cc9b89e 100644 --- a/Session/Meta/Translations/hi.lproj/Localizable.strings +++ b/Session/Meta/Translations/hi.lproj/Localizable.strings @@ -596,3 +596,5 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"REMOVE_AVATAR" = "Remove"; +"CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; diff --git a/Session/Meta/Translations/hr.lproj/Localizable.strings b/Session/Meta/Translations/hr.lproj/Localizable.strings index 8af92e999..c626beb1f 100644 --- a/Session/Meta/Translations/hr.lproj/Localizable.strings +++ b/Session/Meta/Translations/hr.lproj/Localizable.strings @@ -596,3 +596,5 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"REMOVE_AVATAR" = "Remove"; +"CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; diff --git a/Session/Meta/Translations/id-ID.lproj/Localizable.strings b/Session/Meta/Translations/id-ID.lproj/Localizable.strings index 5ff981ef7..367917f26 100644 --- a/Session/Meta/Translations/id-ID.lproj/Localizable.strings +++ b/Session/Meta/Translations/id-ID.lproj/Localizable.strings @@ -596,3 +596,5 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"REMOVE_AVATAR" = "Remove"; +"CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; diff --git a/Session/Meta/Translations/it.lproj/Localizable.strings b/Session/Meta/Translations/it.lproj/Localizable.strings index f6725544a..7926decf8 100644 --- a/Session/Meta/Translations/it.lproj/Localizable.strings +++ b/Session/Meta/Translations/it.lproj/Localizable.strings @@ -596,3 +596,5 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"REMOVE_AVATAR" = "Remove"; +"CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; diff --git a/Session/Meta/Translations/ja.lproj/Localizable.strings b/Session/Meta/Translations/ja.lproj/Localizable.strings index 6319ef7fd..f820dea7a 100644 --- a/Session/Meta/Translations/ja.lproj/Localizable.strings +++ b/Session/Meta/Translations/ja.lproj/Localizable.strings @@ -596,3 +596,5 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"REMOVE_AVATAR" = "Remove"; +"CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; diff --git a/Session/Meta/Translations/nl.lproj/Localizable.strings b/Session/Meta/Translations/nl.lproj/Localizable.strings index 284441372..696e46828 100644 --- a/Session/Meta/Translations/nl.lproj/Localizable.strings +++ b/Session/Meta/Translations/nl.lproj/Localizable.strings @@ -596,3 +596,5 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"REMOVE_AVATAR" = "Remove"; +"CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; diff --git a/Session/Meta/Translations/pl.lproj/Localizable.strings b/Session/Meta/Translations/pl.lproj/Localizable.strings index ab8b48d0b..a20c51602 100644 --- a/Session/Meta/Translations/pl.lproj/Localizable.strings +++ b/Session/Meta/Translations/pl.lproj/Localizable.strings @@ -596,3 +596,5 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"REMOVE_AVATAR" = "Remove"; +"CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; diff --git a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings index cd772f650..82c9c9d76 100644 --- a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings +++ b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings @@ -596,3 +596,5 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"REMOVE_AVATAR" = "Remove"; +"CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; diff --git a/Session/Meta/Translations/ru.lproj/Localizable.strings b/Session/Meta/Translations/ru.lproj/Localizable.strings index 632e50aa4..52bd23dc3 100644 --- a/Session/Meta/Translations/ru.lproj/Localizable.strings +++ b/Session/Meta/Translations/ru.lproj/Localizable.strings @@ -596,3 +596,5 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"REMOVE_AVATAR" = "Remove"; +"CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; diff --git a/Session/Meta/Translations/si.lproj/Localizable.strings b/Session/Meta/Translations/si.lproj/Localizable.strings index 753687545..96a72c635 100644 --- a/Session/Meta/Translations/si.lproj/Localizable.strings +++ b/Session/Meta/Translations/si.lproj/Localizable.strings @@ -596,3 +596,5 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"REMOVE_AVATAR" = "Remove"; +"CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; diff --git a/Session/Meta/Translations/sk.lproj/Localizable.strings b/Session/Meta/Translations/sk.lproj/Localizable.strings index 1388f0536..b6b217183 100644 --- a/Session/Meta/Translations/sk.lproj/Localizable.strings +++ b/Session/Meta/Translations/sk.lproj/Localizable.strings @@ -596,3 +596,5 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"REMOVE_AVATAR" = "Remove"; +"CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; diff --git a/Session/Meta/Translations/sv.lproj/Localizable.strings b/Session/Meta/Translations/sv.lproj/Localizable.strings index 2af704369..12f155930 100644 --- a/Session/Meta/Translations/sv.lproj/Localizable.strings +++ b/Session/Meta/Translations/sv.lproj/Localizable.strings @@ -596,3 +596,5 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"REMOVE_AVATAR" = "Remove"; +"CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; diff --git a/Session/Meta/Translations/th.lproj/Localizable.strings b/Session/Meta/Translations/th.lproj/Localizable.strings index 24910f380..34d60ce88 100644 --- a/Session/Meta/Translations/th.lproj/Localizable.strings +++ b/Session/Meta/Translations/th.lproj/Localizable.strings @@ -596,3 +596,5 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"REMOVE_AVATAR" = "Remove"; +"CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; diff --git a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings index 6a27510bc..abfce5897 100644 --- a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings +++ b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings @@ -596,3 +596,5 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"REMOVE_AVATAR" = "Remove"; +"CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; diff --git a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings index d408fc648..329caa9a8 100644 --- a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings @@ -596,3 +596,5 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"REMOVE_AVATAR" = "Remove"; +"CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; diff --git a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings index f391b1784..fcca23455 100644 --- a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings @@ -596,3 +596,5 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"REMOVE_AVATAR" = "Remove"; +"CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; diff --git a/Session/Notifications/AppNotifications.swift b/Session/Notifications/AppNotifications.swift index eb1830bec..a767635b4 100644 --- a/Session/Notifications/AppNotifications.swift +++ b/Session/Notifications/AppNotifications.swift @@ -538,12 +538,7 @@ class NotificationActionHandler { variant: .standardOutgoing, body: replyText, timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)), - hasMention: Interaction.isUserMentioned( - db, - threadId: threadId, - threadVariant: thread.variant, - body: replyText - ), + hasMention: Interaction.isUserMentioned(db, threadId: threadId, body: replyText), expiresInSeconds: try? DisappearingMessagesConfiguration .select(.durationSeconds) .filter(id: threadId) diff --git a/Session/Settings/BlockedContactsViewController.swift b/Session/Settings/BlockedContactsViewController.swift deleted file mode 100644 index 7fbdb00af..000000000 --- a/Session/Settings/BlockedContactsViewController.swift +++ /dev/null @@ -1,515 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import UIKit -import GRDB -import DifferenceKit -import SessionUIKit -import SessionMessagingKit -import SignalUtilitiesKit - -class BlockedContactsViewController: BaseVC, UITableViewDelegate, UITableViewDataSource { - private static let loadingHeaderHeight: CGFloat = 40 - - private let viewModel: BlockedContactsViewModel = BlockedContactsViewModel() - private var dataChangeObservable: DatabaseCancellable? - private var hasLoadedInitialContactData: Bool = false - private var isLoadingMore: Bool = false - private var isAutoLoadingNextPage: Bool = false - private var viewHasAppeared: Bool = false - - // MARK: - Intialization - - init() { - Storage.shared.addObserver(viewModel.pagedDataObserver) - - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { - preconditionFailure("Use init() instead.") - } - - deinit { - NotificationCenter.default.removeObserver(self) - } - - // MARK: - UI - - private lazy var tableView: UITableView = { - let result: UITableView = UITableView() - result.translatesAutoresizingMaskIntoConstraints = false - result.clipsToBounds = true - result.separatorStyle = .none - result.themeBackgroundColor = .clear - result.showsVerticalScrollIndicator = false - result.register(view: SessionCell.self) - result.dataSource = self - result.delegate = self - result.layer.cornerRadius = SessionCell.cornerRadius - - if #available(iOS 15.0, *) { - result.sectionHeaderTopPadding = 0 - } - - return result - }() - - private lazy var emptyStateLabel: UILabel = { - let result: UILabel = UILabel() - result.translatesAutoresizingMaskIntoConstraints = false - result.isUserInteractionEnabled = false - result.font = .systemFont(ofSize: Values.smallFontSize) - result.text = "CONVERSATION_SETTINGS_BLOCKED_CONTACTS_EMPTY_STATE".localized() - result.themeTextColor = .textSecondary - result.textAlignment = .center - result.numberOfLines = 0 - result.isHidden = true - - return result - }() - - private lazy var fadeView: GradientView = { - let result: GradientView = GradientView() - result.themeBackgroundGradient = [ - .value(.backgroundPrimary, alpha: 0), // Want this to take up 20% (~25pt) - .backgroundPrimary, - .backgroundPrimary, - .backgroundPrimary, - .backgroundPrimary - ] - result.set(.height, to: Values.footerGradientHeight(window: UIApplication.shared.keyWindow)) - - return result - }() - - private lazy var unblockButton: SessionButton = { - let result: SessionButton = SessionButton(style: .destructive, size: .large) - result.translatesAutoresizingMaskIntoConstraints = false - result.setTitle("CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK".localized(), for: .normal) - result.addTarget(self, action: #selector(unblockTapped), for: .touchUpInside) - - return result - }() - - // MARK: - Lifecycle - - override func viewDidLoad() { - super.viewDidLoad() - - view.themeBackgroundColor = .backgroundPrimary - - ViewControllerUtilities.setUpDefaultSessionStyle( - for: self, - title: "CONVERSATION_SETTINGS_BLOCKED_CONTACTS_TITLE".localized(), - hasCustomBackButton: false - ) - - view.addSubview(tableView) - view.addSubview(emptyStateLabel) - view.addSubview(fadeView) - view.addSubview(unblockButton) - setupLayout() - - // Notifications - NotificationCenter.default.addObserver( - self, - selector: #selector(applicationDidBecomeActive(_:)), - name: UIApplication.didBecomeActiveNotification, - object: nil - ) - NotificationCenter.default.addObserver( - self, - selector: #selector(applicationDidResignActive(_:)), - name: UIApplication.didEnterBackgroundNotification, object: nil - ) - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - startObservingChanges() - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - self.viewHasAppeared = true - self.autoLoadNextPageIfNeeded() - } - - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - - // Stop observing database changes - dataChangeObservable?.cancel() - } - - @objc func applicationDidBecomeActive(_ notification: Notification) { - startObservingChanges(didReturnFromBackground: true) - } - - @objc func applicationDidResignActive(_ notification: Notification) { - // Stop observing database changes - dataChangeObservable?.cancel() - } - - // MARK: - Layout - - private func setupLayout() { - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: view.topAnchor, constant: Values.smallSpacing), - tableView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: Values.largeSpacing), - tableView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -Values.largeSpacing), - tableView.bottomAnchor.constraint( - equalTo: unblockButton.topAnchor, - constant: -Values.largeSpacing - ), - - emptyStateLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: Values.massiveSpacing), - emptyStateLabel.leftAnchor.constraint(equalTo: view.leftAnchor, constant: Values.mediumSpacing), - emptyStateLabel.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -Values.mediumSpacing), - emptyStateLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor), - - fadeView.leftAnchor.constraint(equalTo: view.leftAnchor), - fadeView.rightAnchor.constraint(equalTo: view.rightAnchor), - fadeView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - - unblockButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), - unblockButton.bottomAnchor.constraint( - equalTo: view.safeAreaLayoutGuide.bottomAnchor, - constant: -Values.smallSpacing - ), - unblockButton.widthAnchor.constraint(equalToConstant: Values.iPadButtonWidth) - ]) - } - - // MARK: - Updating - - private func startObservingChanges(didReturnFromBackground: Bool = false) { - self.viewModel.onContactChange = { [weak self] updatedContactData, changeset in - self?.handleContactUpdates(updatedContactData, changeset: changeset) - } - - // Note: When returning from the background we could have received notifications but the - // PagedDatabaseObserver won't have them so we need to force a re-fetch of the current - // data to ensure everything is up to date - if didReturnFromBackground { - self.viewModel.pagedDataObserver?.reload() - } - } - - private func handleContactUpdates( - _ updatedData: [BlockedContactsViewModel.SectionModel], - changeset: StagedChangeset<[BlockedContactsViewModel.SectionModel]>, - initialLoad: Bool = false - ) { - // Ensure the first load runs without animations (if we don't do this the cells will animate - // in from a frame of CGRect.zero) - guard hasLoadedInitialContactData else { - hasLoadedInitialContactData = true - UIView.performWithoutAnimation { - handleContactUpdates(updatedData, changeset: changeset, initialLoad: true) - } - return - } - - // Show the empty state if there is no data - let hasContactsData: Bool = (updatedData - .first(where: { $0.model == .contacts })? - .elements - .isEmpty == false) - unblockButton.isEnabled = !viewModel.selectedContactIds.isEmpty - unblockButton.isHidden = !hasContactsData - emptyStateLabel.isHidden = hasContactsData - - CATransaction.begin() - CATransaction.setCompletionBlock { [weak self] in - // Complete page loading - self?.isLoadingMore = false - self?.autoLoadNextPageIfNeeded() - } - - // Reload the table content (animate changes after the first load) - tableView.reload( - using: changeset, - deleteSectionsAnimation: .none, - insertSectionsAnimation: .none, - reloadSectionsAnimation: .none, - deleteRowsAnimation: .bottom, - insertRowsAnimation: .top, - reloadRowsAnimation: .none, - interrupt: { $0.changeCount > 100 } // Prevent too many changes from causing performance issues - ) { [weak self] updatedData in - self?.viewModel.updateContactData(updatedData) - } - - CATransaction.commit() - } - - private func autoLoadNextPageIfNeeded() { - guard !self.isAutoLoadingNextPage && !self.isLoadingMore else { return } - - self.isAutoLoadingNextPage = true - - DispatchQueue.main.asyncAfter(deadline: .now() + PagedData.autoLoadNextPageDelay) { [weak self] in - self?.isAutoLoadingNextPage = false - - // Note: We sort the headers as we want to prioritise loading newer pages over older ones - let sections: [(BlockedContactsViewModel.Section, CGRect)] = (self?.viewModel.contactData - .enumerated() - .map { index, section in - (section.model, (self?.tableView.rectForHeader(inSection: index) ?? .zero)) - }) - .defaulting(to: []) - let shouldLoadMore: Bool = sections - .contains { section, headerRect in - section == .loadMore && - headerRect != .zero && - (self?.tableView.bounds.contains(headerRect) == true) - } - - guard shouldLoadMore else { return } - - self?.isLoadingMore = true - - DispatchQueue.global(qos: .userInitiated).async { [weak self] in - self?.viewModel.pagedDataObserver?.load(.pageAfter) - } - } - } - - // MARK: - UITableViewDataSource - - func numberOfSections(in tableView: UITableView) -> Int { - return viewModel.contactData.count - } - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - let section: BlockedContactsViewModel.SectionModel = viewModel.contactData[section] - - return section.elements.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let section: BlockedContactsViewModel.SectionModel = viewModel.contactData[indexPath.section] - - switch section.model { - case .contacts: - let info: SessionCell.Info = section.elements[indexPath.row] - let cell: SessionCell = tableView.dequeue(type: SessionCell.self, for: indexPath) - cell.update( - with: info, - style: .roundedEdgeToEdge, - position: Position.with(indexPath.row, count: section.elements.count) - ) - - return cell - - default: preconditionFailure("Other sections should have no content") - } - } - - func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - let section: BlockedContactsViewModel.SectionModel = viewModel.contactData[section] - - switch section.model { - case .loadMore: - let loadingIndicator: UIActivityIndicatorView = UIActivityIndicatorView(style: .medium) - loadingIndicator.themeTintColor = .textPrimary - loadingIndicator.alpha = 0.5 - loadingIndicator.startAnimating() - - let view: UIView = UIView() - view.addSubview(loadingIndicator) - loadingIndicator.center(in: view) - - return view - - default: return nil - } - } - - // MARK: - UITableViewDelegate - - func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { - return UITableView.automaticDimension - } - - func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - return UITableView.automaticDimension - } - - func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - let section: BlockedContactsViewModel.SectionModel = viewModel.contactData[section] - - switch section.model { - case .loadMore: return BlockedContactsViewController.loadingHeaderHeight - default: return 0 - } - } - - func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) { - guard self.hasLoadedInitialContactData && self.viewHasAppeared && !self.isLoadingMore else { return } - - let section: BlockedContactsViewModel.SectionModel = self.viewModel.contactData[section] - - switch section.model { - case .loadMore: - self.isLoadingMore = true - - DispatchQueue.global(qos: .userInitiated).async { [weak self] in - self?.viewModel.pagedDataObserver?.load(.pageAfter) - } - - default: break - } - } - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: true) - - let section: BlockedContactsViewModel.SectionModel = self.viewModel.contactData[indexPath.section] - - switch section.model { - case .contacts: - let info: SessionCell.Info = section.elements[indexPath.row] - - // Do nothing if the item is disabled - guard info.isEnabled else { return } - - // Get the view that was tapped (for presenting on iPad) - let tappedView: UIView? = tableView.cellForRow(at: indexPath) - let maybeOldSelection: (Int, SessionCell.Info)? = section.elements - .enumerated() - .first(where: { index, info in - switch (info.leftAccessory, info.rightAccessory) { - case (_, .radio(_, let isSelected, _)): return isSelected() - case (.radio(_, let isSelected, _), _): return isSelected() - default: return false - } - }) - - info.onTap?(tappedView) - self.manuallyReload(indexPath: indexPath, section: section, info: info) - self.unblockButton.isEnabled = !self.viewModel.selectedContactIds.isEmpty - - // Update the old selection as well - if let oldSelection: (index: Int, info: SessionCell.Info) = maybeOldSelection { - self.manuallyReload( - indexPath: IndexPath( - row: oldSelection.index, - section: indexPath.section - ), - section: section, - info: oldSelection.info - ) - } - - default: break - } - } - - private func manuallyReload( - indexPath: IndexPath, - section: BlockedContactsViewModel.SectionModel, - info: SessionCell.Info - ) { - // Try update the existing cell to have a nice animation instead of reloading the cell - if let existingCell: SessionCell = tableView.cellForRow(at: indexPath) as? SessionCell { - existingCell.update( - with: info, - style: .roundedEdgeToEdge, - position: Position.with(indexPath.row, count: section.elements.count) - ) - } - else { - tableView.reloadRows(at: [indexPath], with: .none) - } - } - - // MARK: - Interaction - - @objc private func unblockTapped() { - guard !viewModel.selectedContactIds.isEmpty else { return } - - let contactIds: Set = viewModel.selectedContactIds - let contactNames: [String] = contactIds - .map { contactId in - guard - let section: BlockedContactsViewModel.SectionModel = self.viewModel.contactData - .first(where: { section in section.model == .contacts }), - let info: SessionCell.Info = section.elements - .first(where: { info in info.id.id == contactId }) - else { return contactId } - - return info.title - } - let confirmationTitle: String = { - guard contactNames.count > 1 else { - // Show a single users name - return String( - format: "CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_SINGLE".localized(), - ( - contactNames.first ?? - "CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_FALLBACK".localized() - ) - ) - } - guard contactNames.count > 3 else { - // Show up to three users names - let initialNames: [String] = Array(contactNames.prefix(upTo: (contactNames.count - 1))) - let lastName: String = contactNames[contactNames.count - 1] - - return [ - String( - format: "CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_MULTIPLE_1".localized(), - initialNames.joined(separator: ", ") - ), - String( - format: "CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_MULTIPLE_2_SINGLE".localized(), - lastName - ) - ] - .reversed(if: CurrentAppContext().isRTL) - .joined(separator: " ") - } - - // If we have exactly 4 users, show the first two names followed by 'and X others', for - // more than 4 users, show the first 3 names followed by 'and X others' - let numNamesToShow: Int = (contactNames.count == 4 ? 2 : 3) - let initialNames: [String] = Array(contactNames.prefix(upTo: numNamesToShow)) - - return [ - String( - format: "CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_MULTIPLE_1".localized(), - initialNames.joined(separator: ", ") - ), - String( - format: "CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_TITLE_MULTIPLE_3".localized(), - (contactNames.count - numNamesToShow) - ) - ] - .reversed(if: CurrentAppContext().isRTL) - .joined(separator: " ") - }() - let confirmationModal: ConfirmationModal = ConfirmationModal( - info: ConfirmationModal.Info( - title: confirmationTitle, - confirmTitle: "CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_ACTON".localized(), - confirmStyle: .danger, - cancelStyle: .alert_text - ) { _ in - // Unblock the contacts - Storage.shared.write { db in - _ = try Contact - .filter(ids: contactIds) - .updateAll(db, Contact.Columns.isBlocked.set(to: false)) - - // Force a config sync - try MessageSender.syncConfiguration(db, forceSyncNow: true).retainUntilComplete() - } - } - ) - self.present(confirmationModal, animated: true, completion: nil) - } -} diff --git a/Session/Settings/BlockedContactsViewModel.swift b/Session/Settings/BlockedContactsViewModel.swift index 18957a07a..6c608c2bd 100644 --- a/Session/Settings/BlockedContactsViewModel.swift +++ b/Session/Settings/BlockedContactsViewModel.swift @@ -1,18 +1,25 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import Combine import GRDB import DifferenceKit +import SessionUIKit import SignalUtilitiesKit -public class BlockedContactsViewModel { - public typealias SectionModel = ArraySection> - +class BlockedContactsViewModel: SessionTableViewModel { // MARK: - Section - public enum Section: Differentiable { + public enum Section: SessionTableSection { case contacts case loadMore + + var style: SessionTableSectionStyle { + switch self { + case .contacts: return .none + case .loadMore: return .loadMore + } + } } // MARK: - Variables @@ -21,14 +28,16 @@ public class BlockedContactsViewModel { // MARK: - Initialization - init() { - self.pagedDataObserver = nil + override init() { + _pagedDataObserver = nil + + super.init() // Note: Since this references self we need to finish initializing before setting it, we // also want to skip the initial query and trigger it async so that the push animation // doesn't stutter (it should load basically immediately but without this there is a // distinct stutter) - self.pagedDataObserver = PagedDatabaseObserver( + _pagedDataObserver = PagedDatabaseObserver( pagedTable: Profile.self, pageSize: BlockedContactsViewModel.pageSize, idColumn: .id, @@ -63,12 +72,13 @@ public class BlockedContactsViewModel { ), onChangeUnsorted: { [weak self] updatedData, updatedPageInfo in PagedData.processAndTriggerUpdates( - updatedData: self?.process(data: updatedData, for: updatedPageInfo), - currentDataRetriever: { self?.contactData }, - onDataChange: self?.onContactChange, - onUnobservedDataChange: { updatedData, changeset in - self?.unobservedContactDataChanges = (updatedData, changeset) - } + updatedData: self?.process(data: updatedData, for: updatedPageInfo) + .mapToSessionTableViewData(for: self), + currentDataRetriever: { self?.tableData }, + onDataChange: { updatedData, changeset in + self?.contactDataSubject.send((updatedData, changeset)) + }, + onUnobservedDataChange: { _, _ in } ) } ) @@ -76,59 +86,80 @@ public class BlockedContactsViewModel { // Run the initial query on a background thread so we don't block the push transition DispatchQueue.global(qos: .userInitiated).async { [weak self] in // The `.pageBefore` will query from a `0` offset loading the first page - self?.pagedDataObserver?.load(.pageBefore) + self?._pagedDataObserver?.load(.pageBefore) } } // MARK: - Contact Data - public private(set) var selectedContactIds: Set = [] - public private(set) var unobservedContactDataChanges: ([SectionModel], StagedChangeset<[SectionModel]>)? - public private(set) var contactData: [SectionModel] = [] - public private(set) var pagedDataObserver: PagedDatabaseObserver? - - public var onContactChange: (([SectionModel], StagedChangeset<[SectionModel]>) -> ())? { - didSet { - // When starting to observe interaction changes we want to trigger a UI update just in case the - // data was changed while we weren't observing - if let unobservedContactDataChanges: ([SectionModel], StagedChangeset<[SectionModel]>) = self.unobservedContactDataChanges { - onContactChange?(unobservedContactDataChanges.0 , unobservedContactDataChanges.1) - self.unobservedContactDataChanges = nil - } - } + override var title: String { "CONVERSATION_SETTINGS_BLOCKED_CONTACTS_TITLE".localized() } + override var emptyStateTextPublisher: AnyPublisher { + Just("CONVERSATION_SETTINGS_BLOCKED_CONTACTS_EMPTY_STATE".localized()) + .eraseToAnyPublisher() } - private func process(data: [DataModel], for pageInfo: PagedData.PageInfo) -> [SectionModel] { - // Update the 'selectedContactIds' to only include selected contacts which are within the - // data (ie. handle profile deletions) - let profileIds: Set = data.map { $0.id }.asSet() - selectedContactIds = selectedContactIds.intersection(profileIds) - + private let contactDataSubject: CurrentValueSubject<([SectionModel], StagedChangeset<[SectionModel]>), Never> = CurrentValueSubject(([], StagedChangeset())) + private let selectedContactIdsSubject: CurrentValueSubject, Never> = CurrentValueSubject([]) + private var _pagedDataObserver: PagedDatabaseObserver? + public override var pagedDataObserver: TransactionObserver? { _pagedDataObserver } + + public override var observableTableData: ObservableData { _observableTableData } + + private lazy var _observableTableData: ObservableData = contactDataSubject + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + + override var footerButtonInfo: AnyPublisher { + selectedContactIdsSubject + .prepend([]) + .map { selectedContactIds in + SessionButton.Info( + style: .destructive, + title: "CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK".localized(), + isEnabled: !selectedContactIds.isEmpty, + onTap: { [weak self] in self?.unblockTapped() } + ) + } + .eraseToAnyPublisher() + } + + // MARK: - Functions + + override func loadPageAfter() { _pagedDataObserver?.load(.pageAfter) } + + private func process( + data: [DataModel], + for pageInfo: PagedData.PageInfo + ) -> [SectionModel] { return [ [ SectionModel( section: .contacts, elements: data .sorted { lhs, rhs -> Bool in - lhs.profile.displayName() > rhs.profile.displayName() + lhs.profile.displayName() < rhs.profile.displayName() } - .map { model -> SessionCell.Info in + .map { [weak self] model -> SessionCell.Info in SessionCell.Info( id: model.profile, - leftAccessory: .profile(model.profile.id, model.profile), + leftAccessory: .profile(id: model.profile.id, profile: model.profile), title: model.profile.displayName(), rightAccessory: .radio( - isSelected: { [weak self] in - self?.selectedContactIds.contains(model.profile.id) == true + isSelected: { + self?.selectedContactIdsSubject.value.contains(model.profile.id) == true } ), - onTap: { [weak self] in - guard self?.selectedContactIds.contains(model.profile.id) == true else { - self?.selectedContactIds.insert(model.profile.id) - return + onTap: { + var updatedSelectedIds: Set = (self?.selectedContactIdsSubject.value ?? []) + + if !updatedSelectedIds.contains(model.profile.id) { + updatedSelectedIds.insert(model.profile.id) + } + else { + updatedSelectedIds.remove(model.profile.id) } - self?.selectedContactIds.remove(model.profile.id) + self?.selectedContactIdsSubject.send(updatedSelectedIds) } ) } @@ -210,7 +241,7 @@ public class BlockedContactsViewModel { confirmTitle: "CONVERSATION_SETTINGS_BLOCKED_CONTACTS_UNBLOCK_CONFIRMATION_ACTON".localized(), confirmStyle: .danger, cancelStyle: .alert_text - ) { _ in + ) { [weak self] _ in // Unblock the contacts Storage.shared.write { db in _ = try Contact @@ -220,6 +251,8 @@ public class BlockedContactsViewModel { // Force a config sync try MessageSender.syncConfiguration(db, forceSyncNow: true).sinkUntilComplete() } + + self?.selectedContactIdsSubject.send([]) } ) self.transitionToScreen(confirmationModal, transitionType: .present) @@ -242,8 +275,8 @@ public class BlockedContactsViewModel { static func query( filterSQL: SQL, orderSQL: SQL - ) -> (([Int64]) -> AdaptedFetchRequest>) { - return { rowIds -> AdaptedFetchRequest> in + ) -> (([Int64]) -> any FetchRequest) { + return { rowIds -> any FetchRequest in let profile: TypedTableAlias = TypedTableAlias() /// **Note:** The `numColumnsBeforeProfile` value **MUST** match the number of fields before diff --git a/Session/Settings/ConversationSettingsViewModel.swift b/Session/Settings/ConversationSettingsViewModel.swift index c8b56ec10..2786de2c0 100644 --- a/Session/Settings/ConversationSettingsViewModel.swift +++ b/Session/Settings/ConversationSettingsViewModel.swift @@ -26,7 +26,7 @@ class ConversationSettingsViewModel: SessionTableViewModel [SectionModel] in return [ SectionModel( @@ -92,10 +89,14 @@ class ConversationSettingsViewModel: SessionTableViewModel [SectionModel] in return [ SectionModel( @@ -50,7 +47,7 @@ class HelpViewModel: SessionTableViewModel Void + private let onImagePicked: (UIImage?, String?) -> Void + + // MARK: - Initialization + + init( + onTransition: @escaping (UIViewController, TransitionType) -> Void, + onImagePicked: @escaping (UIImage?, String?) -> Void + ) { + self.onTransition = onTransition + self.onImagePicked = onImagePicked + } + + // MARK: - UIImagePickerControllerDelegate + + func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { + picker.dismiss(animated: true) + } + + func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { + guard + let imageUrl: URL = info[.imageURL] as? URL, + let rawAvatar: UIImage = info[.originalImage] as? UIImage + else { + picker.presentingViewController?.dismiss(animated: true) + return + } + + picker.presentingViewController?.dismiss(animated: true) { [weak self] in + // Check if the user selected an animated image (if so then don't crop, just + // set the avatar directly + guard + let type: Any = try? imageUrl.resourceValues(forKeys: [.typeIdentifierKey]) + .allValues + .first, + let typeString: String = type as? String, + MIMETypeUtil.supportedAnimatedImageUTITypes().contains(typeString) + else { + let viewController: CropScaleImageViewController = CropScaleImageViewController( + srcImage: rawAvatar, + successCompletion: { resultImage in + self?.onImagePicked(resultImage, nil) + } + ) + self?.onTransition(viewController, .present) + return + } + + self?.onImagePicked(nil, imageUrl.path) + } + } +} diff --git a/Session/Settings/NotificationContentViewModel.swift b/Session/Settings/NotificationContentViewModel.swift index c32e79dad..f4b97fbe1 100644 --- a/Session/Settings/NotificationContentViewModel.swift +++ b/Session/Settings/NotificationContentViewModel.swift @@ -31,10 +31,7 @@ class NotificationContentViewModel: SessionTableViewModel [SectionModel] in let currentSelection: Preferences.NotificationPreviewType? = db[.preferencesNotificationPreviewType] .defaulting(to: .defaultPreviewType) @@ -73,10 +70,5 @@ class NotificationContentViewModel: SessionTableViewModel [SectionModel] in let notificationSound: Preferences.Sound = db[.defaultNotificationSound] .defaulting(to: Preferences.Sound.defaultNotificationSound) @@ -72,9 +70,9 @@ class NotificationSettingsViewModel: SessionTableViewModel [SectionModel] in self?.storedSelection = try { guard let threadId: String = self?.threadId else { @@ -150,12 +147,9 @@ class NotificationSoundViewModel: SessionTableViewModel [SectionModel] in return [ SectionModel( @@ -128,34 +125,40 @@ class PrivacySettingsViewModel: SessionTableViewModel = { - isEditing - .map { isEditing in (isEditing ? .editing : .standard) } + Publishers + .CombineLatest( + isEditing + .map { isEditing in isEditing }, + textChanged + .handleEvents( + receiveOutput: { [weak self] value, _ in + self?.editedDisplayName = value + } + ) + .filter { _ in false } + .prepend((nil, .profileName)) + ) + .map { isEditing, _ -> NavState in (isEditing ? .editing : .standard) } .removeDuplicates() .prepend(.standard) // Initial value .eraseToAnyPublisher() @@ -176,10 +220,7 @@ class SettingsViewModel: SessionTableViewModel [SectionModel] in + private lazy var _observableTableData: ObservableData = ValueObservation + .trackingConstantRegion { [weak self] db -> [SectionModel] in let userPublicKey: String = getUserHexEncodedPublicKey(db) let profile: Profile = Profile.fetchOrCreateCurrentUser(db) @@ -198,38 +239,82 @@ class SettingsViewModel: SessionTableViewModel { Just(VersionFooterView()) @@ -383,26 +469,30 @@ class SettingsViewModel: SessionTableViewModel.SectionModel private let viewModel: SessionTableViewModel - private var hasLoadedInitialSettingsData: Bool = false + private var hasLoadedInitialTableData: Bool = false + private var isLoadingMore: Bool = false + private var isAutoLoadingNextPage: Bool = false + private var viewHasAppeared: Bool = false private var dataStreamJustFailed: Bool = false private var dataChangeCancellable: AnyCancellable? private var disposables: Set = Set() + private var onFooterTap: (() -> ())? public var viewModelType: AnyObject.Type { return type(of: viewModel) } @@ -32,7 +36,6 @@ class SessionTableViewController) { self.viewModel = viewModel + Storage.shared.addObserver(viewModel.pagedDataObserver) + super.init(nibName: nil, bundle: nil) } @@ -74,6 +116,9 @@ class SessionTableViewController, + initialLoad: Bool = false + ) { // Ensure the first load runs without animations (if we don't do this the cells will animate // in from a frame of CGRect.zero) - guard hasLoadedInitialSettingsData else { - hasLoadedInitialSettingsData = true - UIView.performWithoutAnimation { handleSettingsUpdates(updatedData, initialLoad: true) } + guard hasLoadedInitialTableData else { + hasLoadedInitialTableData = true + UIView.performWithoutAnimation { + handleDataUpdates(updatedData, changeset: changeset, initialLoad: true) + } return } + // Show the empty state if there is no data + let itemCount: Int = updatedData + .map { $0.elements.count } + .reduce(0, +) + emptyStateLabel.isHidden = (itemCount > 0) + + CATransaction.begin() + CATransaction.setCompletionBlock { [weak self] in + // Complete page loading + self?.isLoadingMore = false + self?.autoLoadNextPageIfNeeded() + } + // Reload the table content (animate changes after the first load) tableView.reload( - using: StagedChangeset(source: viewModel.settingsData, target: updatedData), + using: changeset, deleteSectionsAnimation: .none, insertSectionsAnimation: .none, reloadSectionsAnimation: .none, - deleteRowsAnimation: .bottom, - insertRowsAnimation: .none, - reloadRowsAnimation: .none, + deleteRowsAnimation: .fade, + insertRowsAnimation: .fade, + reloadRowsAnimation: .fade, interrupt: { $0.changeCount > 100 } // Prevent too many changes from causing performance issues ) { [weak self] updatedData in - self?.viewModel.updateSettings(updatedData) + self?.viewModel.updateTableData(updatedData) + } + + CATransaction.commit() + } + + private func autoLoadNextPageIfNeeded() { + guard !self.isAutoLoadingNextPage && !self.isLoadingMore else { return } + + self.isAutoLoadingNextPage = true + + DispatchQueue.main.asyncAfter(deadline: .now() + PagedData.autoLoadNextPageDelay) { [weak self] in + self?.isAutoLoadingNextPage = false + + // Note: We sort the headers as we want to prioritise loading newer pages over older ones + let sections: [(Section, CGRect)] = (self?.viewModel.tableData + .enumerated() + .map { index, section in + (section.model, (self?.tableView.rectForHeader(inSection: index) ?? .zero)) + }) + .defaulting(to: []) + let shouldLoadMore: Bool = sections + .contains { section, headerRect in + section.style == .loadMore && + headerRect != .zero && + (self?.tableView.bounds.contains(headerRect) == true) + } + + guard shouldLoadMore else { return } + + self?.isLoadingMore = true + + DispatchQueue.global(qos: .userInitiated).async { [weak self] in + self?.viewModel.loadPageAfter() + } } } @@ -188,18 +304,27 @@ class SessionTableViewController Int { - return self.viewModel.settingsData.count + return self.viewModel.tableData.count } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return self.viewModel.settingsData[section].elements.count + return self.viewModel.tableData[section].elements.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let section: SectionModel = viewModel.settingsData[indexPath.section] + let section: SectionModel = viewModel.tableData[indexPath.section] let info: SessionCell.Info = section.elements[indexPath.row] + let cell: SessionCell = tableView.dequeue(type: SessionCell.self, for: indexPath) + cell.update(with: info) + cell.update( + isEditing: (self.isEditing || (info.title?.interaction == .alwaysEditing)), + becomeFirstResponder: false, + animated: false + ) + cell.textPublisher + .sink(receiveValue: { [weak self] text in + self?.viewModel.textChanged(text, for: info.id) + }) + .store(in: &cell.disposables) - switch info.leftAccessory { - case .threadInfo(let threadViewModel, let style, let avatarTapped, let titleTapped, let titleChanged): - let cell: SessionAvatarCell = tableView.dequeue(type: SessionAvatarCell.self, for: indexPath) - cell.update( - threadViewModel: threadViewModel, - style: style, - viewController: self - ) - cell.update(isEditing: self.isEditing, animated: false) - - cell.profilePictureTapPublisher - .filter { _ in threadViewModel.threadVariant == .contact } - .sink(receiveValue: { _ in avatarTapped?() }) - .store(in: &cell.disposables) - - cell.displayNameTapPublisher - .filter { _ in threadViewModel.threadVariant == .contact } - .sink(receiveValue: { _ in titleTapped?() }) - .store(in: &cell.disposables) - - cell.textPublisher - .sink(receiveValue: { text in titleChanged?(text) }) - .store(in: &cell.disposables) - - return cell - - default: - let cell: SessionCell = tableView.dequeue(type: SessionCell.self, for: indexPath) - cell.update( - with: info, - style: .rounded, - position: Position.with(indexPath.row, count: section.elements.count) - ) - cell.update(isEditing: self.isEditing, animated: false) - - return cell - } + return cell } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - let section: SectionModel = viewModel.settingsData[section] + let section: SectionModel = viewModel.tableData[section] + let result: SessionHeaderView = tableView.dequeueHeaderFooterView(type: SessionHeaderView.self) + result.update( + title: section.model.title, + style: section.model.style + ) - switch section.model.style { - case .none: - return UIView() - - case .padding, .title: - let result: SessionHeaderView = tableView.dequeueHeaderFooterView(type: SessionHeaderView.self) - result.update( - title: section.model.title, - hasSeparator: (section.elements.first?.shouldHaveBackground != false) - ) - - return result - } + return result } // MARK: - UITableViewDelegate func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - let section: SectionModel = viewModel.settingsData[section] - - switch section.model.style { - case .none: return 0 - case .padding, .title: return UITableView.automaticDimension - } + return viewModel.tableData[section].model.style.height } func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { @@ -397,11 +525,28 @@ class SessionTableViewController CGFloat { return UITableView.automaticDimension } + + func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) { + guard self.hasLoadedInitialTableData && self.viewHasAppeared && !self.isLoadingMore else { return } + + let section: SectionModel = self.viewModel.tableData[section] + + switch section.model.style { + case .loadMore: + self.isLoadingMore = true + + DispatchQueue.global(qos: .userInitiated).async { [weak self] in + self?.viewModel.loadPageAfter() + } + + default: break + } + } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) - let section: SectionModel = self.viewModel.settingsData[indexPath.section] + let section: SectionModel = self.viewModel.tableData[indexPath.section] let info: SessionCell.Info = section.elements[indexPath.row] // Do nothing if the item is disabled @@ -414,10 +559,10 @@ class SessionTableViewController Void = { [weak self, weak tappedView] in - info.onTap?(tappedView) + info.onTap?() + info.onTapView?(tappedView) self?.manuallyReload(indexPath: indexPath, section: section, info: info) // Update the old selection as well @@ -463,10 +609,6 @@ class SessionTableViewController { typealias SectionModel = ArraySection> - typealias ObservableData = AnyPublisher<[SectionModel], Error> + typealias ObservableData = AnyPublisher<([SectionModel], StagedChangeset<[SectionModel]>), Error> // MARK: - Input @@ -18,6 +18,9 @@ class SessionTableViewModel = _isEditing .removeDuplicates() .shareReplay(1) + private let _textChanged: PassthroughSubject<(text: String?, item: SettingItem), Never> = PassthroughSubject() + lazy var textChanged: AnyPublisher<(text: String?, item: SettingItem), Never> = _textChanged + .eraseToAnyPublisher() // MARK: - Navigation @@ -37,15 +40,25 @@ class SessionTableViewModel { Just(nil).eraseToAnyPublisher() } + open var emptyStateTextPublisher: AnyPublisher { Just(nil).eraseToAnyPublisher() } - func updateSettings(_ updatedSettings: [SectionModel]) { + fileprivate var hasEmittedInitialData: Bool = false + public private(set) var tableData: [SectionModel] = [] + open var observableTableData: ObservableData { preconditionFailure("abstract class - override in subclass") } + open var pagedDataObserver: TransactionObserver? { nil } + open var footerView: AnyPublisher { Just(nil).eraseToAnyPublisher() } + open var footerButtonInfo: AnyPublisher { + Just(nil).eraseToAnyPublisher() + } + + func updateTableData(_ updatedData: [SectionModel]) { + self.tableData = updatedData + } + + func loadPageBefore() { preconditionFailure("abstract class - override in subclass") } + func loadPageAfter() { preconditionFailure("abstract class - override in subclass") } // MARK: - Functions @@ -53,6 +66,10 @@ class SessionTableViewModel( + for viewModel: SessionTableViewModel? + ) -> [ArraySection>] where Element == ArraySection> { + // Update the data to include the proper position for each element + return self.map { section in + ArraySection( + model: section.model, + elements: section.elements.enumerated().map { index, element in + element.updatedPosition(for: index, count: section.elements.count) + } + ) + } + } +} + +extension AnyPublisher { + func mapToSessionTableViewData( + for viewModel: SessionTableViewModel + ) -> AnyPublisher<(Output, StagedChangeset), Failure> where Output == [ArraySection>] { + return self + .map { [weak viewModel] updatedData -> (Output, StagedChangeset) in + let updatedDataWithPositions: Output = updatedData + .mapToSessionTableViewData(for: viewModel) + + // Generate an updated changeset + let changeset = StagedChangeset( + source: (viewModel?.tableData ?? []), + target: updatedDataWithPositions + ) + + return (updatedDataWithPositions, changeset) + } + .filter { [weak viewModel] _, changeset in + viewModel?.hasEmittedInitialData == false || // Always emit at least once + !changeset.isEmpty // Do nothing if there were no changes + } + .handleEvents(receiveOutput: { [weak viewModel] _ in + viewModel?.hasEmittedInitialData = true + }) + .eraseToAnyPublisher() + } +} diff --git a/Session/Shared/Types/DismissType.swift b/Session/Shared/Types/DismissType.swift index 494813082..0f8dbcd1c 100644 --- a/Session/Shared/Types/DismissType.swift +++ b/Session/Shared/Types/DismissType.swift @@ -10,6 +10,9 @@ public enum DismissType { /// This will only trigger a `popViewController` call (if the screen was presented it'll do nothing) case pop + /// This will only trigger a `popToRootViewController` call (if the screen was presented it'll do nothing) + case popToRoot + /// This will only trigger a `dismiss` call (if the screen was pushed to a presented navigation controller it'll dismiss /// the navigation controller, otherwise this will do nothing) case dismiss diff --git a/Session/Shared/Types/SessionCell+Accessibility.swift b/Session/Shared/Types/SessionCell+Accessibility.swift new file mode 100644 index 000000000..a9957c6df --- /dev/null +++ b/Session/Shared/Types/SessionCell+Accessibility.swift @@ -0,0 +1,18 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public extension SessionCell { + struct Accessibility: Hashable, Equatable { + let identifier: String? + let label: String? + + public init( + identifier: String? = nil, + label: String? = nil + ) { + self.identifier = identifier + self.label = label + } + } +} diff --git a/Session/Shared/Types/SessionCell+Accessory.swift b/Session/Shared/Types/SessionCell+Accessory.swift index fb0f08fe8..98a1da94f 100644 --- a/Session/Shared/Types/SessionCell+Accessory.swift +++ b/Session/Shared/Types/SessionCell+Accessory.swift @@ -12,45 +12,76 @@ extension SessionCell { UIImage?, size: IconSize, customTint: ThemeValue?, - shouldFill: Bool + shouldFill: Bool, + accessibility: SessionCell.Accessibility? ) case iconAsync( size: IconSize, customTint: ThemeValue?, shouldFill: Bool, + accessibility: SessionCell.Accessibility?, setter: (UIImageView) -> Void ) - case toggle(DataSource) - case dropDown(DataSource) + case toggle( + DataSource, + accessibility: SessionCell.Accessibility? + ) + case dropDown( + DataSource, + accessibility: SessionCell.Accessibility? + ) case radio( size: RadioSize, isSelected: () -> Bool, - storedSelection: Bool + storedSelection: Bool, + accessibility: SessionCell.Accessibility? ) - case highlightingBackgroundLabel(title: String) - case profile(String, Profile?) - case customView(viewGenerator: () -> UIView) - case threadInfo( - threadViewModel: SessionThreadViewModel, - style: ThreadInfoStyle = ThreadInfoStyle(), - avatarTapped: (() -> Void)? = nil, - titleTapped: (() -> Void)? = nil, - titleChanged: ((String) -> Void)? = nil + case highlightingBackgroundLabel( + title: String, + accessibility: SessionCell.Accessibility? + ) + case profile( + id: String, + size: IconSize, + threadVariant: SessionThread.Variant, + customImageData: Data?, + profile: Profile?, + additionalProfile: Profile?, + cornerIcon: UIImage?, + accessibility: SessionCell.Accessibility? + ) + + case search( + placeholder: String, + accessibility: SessionCell.Accessibility?, + searchTermChanged: (String?) -> Void + ) + case button( + style: SessionButton.Style, + title: String, + accessibility: SessionCell.Accessibility?, + run: (SessionButton?) -> Void + ) + case customView( + hashValue: AnyHashable, + viewGenerator: () -> UIView ) // MARK: - Convenience Vatiables var shouldFitToEdge: Bool { switch self { - case .icon(_, _, _, let shouldFill), .iconAsync(_, _, let shouldFill, _): return shouldFill + case .icon(_, _, _, let shouldFill, _), .iconAsync(_, _, let shouldFill, _, _): + return shouldFill default: return false } } var currentBoolValue: Bool { switch self { - case .toggle(let dataSource), .dropDown(let dataSource): return dataSource.currentBoolValue + case .toggle(let dataSource, _), .dropDown(let dataSource, _): return dataSource.currentBoolValue + case .radio(_, let isSelected, _, _): return isSelected() default: return false } } @@ -59,90 +90,166 @@ extension SessionCell { public func hash(into hasher: inout Hasher) { switch self { - case .icon(let image, let size, let customTint, let shouldFill): + case .icon(let image, let size, let customTint, let shouldFill, let accessibility): image.hash(into: &hasher) size.hash(into: &hasher) customTint.hash(into: &hasher) shouldFill.hash(into: &hasher) + accessibility.hash(into: &hasher) - case .iconAsync(let size, let customTint, let shouldFill, _): + case .iconAsync(let size, let customTint, let shouldFill, let accessibility, _): size.hash(into: &hasher) customTint.hash(into: &hasher) shouldFill.hash(into: &hasher) + accessibility.hash(into: &hasher) - case .toggle(let dataSource): + case .toggle(let dataSource, let accessibility): dataSource.hash(into: &hasher) + accessibility.hash(into: &hasher) - case .dropDown(let dataSource): + case .dropDown(let dataSource, let accessibility): dataSource.hash(into: &hasher) + accessibility.hash(into: &hasher) - case .radio(let size, let isSelected, let storedSelection): + case .radio(let size, let isSelected, let storedSelection, let accessibility): size.hash(into: &hasher) isSelected().hash(into: &hasher) storedSelection.hash(into: &hasher) + accessibility.hash(into: &hasher) - case .highlightingBackgroundLabel(let title): + case .highlightingBackgroundLabel(let title, let accessibility): title.hash(into: &hasher) + accessibility.hash(into: &hasher) - case .profile(let profileId, let profile): + case .profile( + let profileId, + let size, + let threadVariant, + let customImageData, + let profile, + let additionalProfile, + let cornerIcon, + let accessibility + ): profileId.hash(into: &hasher) + size.hash(into: &hasher) + threadVariant.hash(into: &hasher) + customImageData.hash(into: &hasher) profile.hash(into: &hasher) + additionalProfile.hash(into: &hasher) + cornerIcon.hash(into: &hasher) + accessibility.hash(into: &hasher) - case .customView: break - - case .threadInfo(let threadViewModel, let style, _, _, _): - threadViewModel.hash(into: &hasher) + case .search(let placeholder, let accessibility, _): + placeholder.hash(into: &hasher) + accessibility.hash(into: &hasher) + + case .button(let style, let title, let accessibility, _): style.hash(into: &hasher) + title.hash(into: &hasher) + accessibility.hash(into: &hasher) + + case .customView(let hashValue, _): + hashValue.hash(into: &hasher) } } public static func == (lhs: Accessory, rhs: Accessory) -> Bool { switch (lhs, rhs) { - case (.icon(let lhsImage, let lhsSize, let lhsCustomTint, let lhsShouldFill), .icon(let rhsImage, let rhsSize, let rhsCustomTint, let rhsShouldFill)): + case (.icon(let lhsImage, let lhsSize, let lhsCustomTint, let lhsShouldFill, let lhsAccessibility), .icon(let rhsImage, let rhsSize, let rhsCustomTint, let rhsShouldFill, let rhsAccessibility)): return ( lhsImage == rhsImage && lhsSize == rhsSize && lhsCustomTint == rhsCustomTint && - lhsShouldFill == rhsShouldFill + lhsShouldFill == rhsShouldFill && + lhsAccessibility == rhsAccessibility ) - case (.iconAsync(let lhsSize, let lhsCustomTint, let lhsShouldFill, _), .iconAsync(let rhsSize, let rhsCustomTint, let rhsShouldFill, _)): + case (.iconAsync(let lhsSize, let lhsCustomTint, let lhsShouldFill, let lhsAccessibility, _), .iconAsync(let rhsSize, let rhsCustomTint, let rhsShouldFill, let rhsAccessibility, _)): return ( lhsSize == rhsSize && lhsCustomTint == rhsCustomTint && - lhsShouldFill == rhsShouldFill + lhsShouldFill == rhsShouldFill && + lhsAccessibility == rhsAccessibility ) - case (.toggle(let lhsDataSource), .toggle(let rhsDataSource)): - return (lhsDataSource == rhsDataSource) + case (.toggle(let lhsDataSource, let lhsAccessibility), .toggle(let rhsDataSource, let rhsAccessibility)): + return ( + lhsDataSource == rhsDataSource && + lhsAccessibility == rhsAccessibility + ) - case (.dropDown(let lhsDataSource), .dropDown(let rhsDataSource)): - return (lhsDataSource == rhsDataSource) + case (.dropDown(let lhsDataSource, let lhsAccessibility), .dropDown(let rhsDataSource, let rhsAccessibility)): + return ( + lhsDataSource == rhsDataSource && + lhsAccessibility == rhsAccessibility + ) - case (.radio(let lhsSize, let lhsIsSelected, let lhsStoredSelection), .radio(let rhsSize, let rhsIsSelected, let rhsStoredSelection)): + case (.radio(let lhsSize, let lhsIsSelected, let lhsStoredSelection, let lhsAccessibility), .radio(let rhsSize, let rhsIsSelected, let rhsStoredSelection, let rhsAccessibility)): return ( lhsSize == rhsSize && lhsIsSelected() == rhsIsSelected() && - lhsStoredSelection == rhsStoredSelection + lhsStoredSelection == rhsStoredSelection && + lhsAccessibility == rhsAccessibility ) - case (.highlightingBackgroundLabel(let lhsTitle), .highlightingBackgroundLabel(let rhsTitle)): - return (lhsTitle == rhsTitle) + case (.highlightingBackgroundLabel(let lhsTitle, let lhsAccessibility), .highlightingBackgroundLabel(let rhsTitle, let rhsAccessibility)): + return ( + lhsTitle == rhsTitle && + lhsAccessibility == rhsAccessibility + ) - case (.profile(let lhsProfileId, let lhsProfile), .profile(let rhsProfileId, let rhsProfile)): + case ( + .profile( + let lhsProfileId, + let lhsSize, + let lhsThreadVariant, + let lhsProfile, + let lhsAdditionalProfile, + let lhsCustomImageData, + let lhsCornerIcon, + let lhsAccessibility + ), + .profile( + let rhsProfileId, + let rhsSize, + let rhsThreadVariant, + let rhsProfile, + let rhsAdditionalProfile, + let rhsCustomImageData, + let rhsCornerIcon, + let rhsAccessibility + ) + ): return ( lhsProfileId == rhsProfileId && - lhsProfile == rhsProfile + lhsSize == rhsSize && + lhsThreadVariant == rhsThreadVariant && + lhsProfile == rhsProfile && + lhsAdditionalProfile == rhsAdditionalProfile && + lhsCustomImageData == rhsCustomImageData && + lhsCornerIcon == rhsCornerIcon && + lhsAccessibility == rhsAccessibility ) - case (.customView, .customView): return false - - case (.threadInfo(let lhsThreadViewModel, let lhsStyle, _, _, _), .threadInfo(let rhsThreadViewModel, let rhsStyle, _, _, _)): + case (.search(let lhsPlaceholder, let lhsAccessibility, _), .search(let rhsPlaceholder, let rhsAccessibility, _)): return ( - lhsThreadViewModel == rhsThreadViewModel && - lhsStyle == rhsStyle + lhsPlaceholder == rhsPlaceholder && + lhsAccessibility == rhsAccessibility ) - + + case (.button(let lhsStyle, let lhsTitle, let lhsAccessibility, _), .button(let rhsStyle, let rhsTitle, let rhsAccessibility, _)): + return ( + lhsStyle == rhsStyle && + lhsTitle == rhsTitle && + lhsAccessibility == rhsAccessibility + ) + + case (.customView(let lhsHashValue, _), .customView(let rhsHashValue, _)): + return ( + lhsHashValue.hashValue == rhsHashValue.hashValue + ) + default: return false } } @@ -157,59 +264,121 @@ extension SessionCell.Accessory { // MARK: - .icon Variants public static func icon(_ image: UIImage?) -> SessionCell.Accessory { - return .icon(image, size: .medium, customTint: nil, shouldFill: false) + return .icon(image, size: .medium, customTint: nil, shouldFill: false, accessibility: nil) } public static func icon(_ image: UIImage?, customTint: ThemeValue) -> SessionCell.Accessory { - return .icon(image, size: .medium, customTint: customTint, shouldFill: false) + return .icon(image, size: .medium, customTint: customTint, shouldFill: false, accessibility: nil) } public static func icon(_ image: UIImage?, size: IconSize) -> SessionCell.Accessory { - return .icon(image, size: size, customTint: nil, shouldFill: false) + return .icon(image, size: size, customTint: nil, shouldFill: false, accessibility: nil) } public static func icon(_ image: UIImage?, size: IconSize, customTint: ThemeValue) -> SessionCell.Accessory { - return .icon(image, size: size, customTint: customTint, shouldFill: false) + return .icon(image, size: size, customTint: customTint, shouldFill: false, accessibility: nil) } public static func icon(_ image: UIImage?, shouldFill: Bool) -> SessionCell.Accessory { - return .icon(image, size: .medium, customTint: nil, shouldFill: shouldFill) + return .icon(image, size: .medium, customTint: nil, shouldFill: shouldFill, accessibility: nil) + } + + public static func icon(_ image: UIImage?, accessibility: SessionCell.Accessibility) -> SessionCell.Accessory { + return .icon(image, size: .medium, customTint: nil, shouldFill: false, accessibility: accessibility) } // MARK: - .iconAsync Variants public static func iconAsync(_ setter: @escaping (UIImageView) -> Void) -> SessionCell.Accessory { - return .iconAsync(size: .medium, customTint: nil, shouldFill: false, setter: setter) + return .iconAsync(size: .medium, customTint: nil, shouldFill: false, accessibility: nil, setter: setter) } public static func iconAsync(customTint: ThemeValue, _ setter: @escaping (UIImageView) -> Void) -> SessionCell.Accessory { - return .iconAsync(size: .medium, customTint: customTint, shouldFill: false, setter: setter) + return .iconAsync(size: .medium, customTint: customTint, shouldFill: false, accessibility: nil, setter: setter) } public static func iconAsync(size: IconSize, setter: @escaping (UIImageView) -> Void) -> SessionCell.Accessory { - return .iconAsync(size: size, customTint: nil, shouldFill: false, setter: setter) + return .iconAsync(size: size, customTint: nil, shouldFill: false, accessibility: nil, setter: setter) } public static func iconAsync(shouldFill: Bool, setter: @escaping (UIImageView) -> Void) -> SessionCell.Accessory { - return .iconAsync(size: .medium, customTint: nil, shouldFill: shouldFill, setter: setter) + return .iconAsync(size: .medium, customTint: nil, shouldFill: shouldFill, accessibility: nil, setter: setter) } public static func iconAsync(size: IconSize, customTint: ThemeValue, setter: @escaping (UIImageView) -> Void) -> SessionCell.Accessory { - return .iconAsync(size: size, customTint: customTint, shouldFill: false, setter: setter) + return .iconAsync(size: size, customTint: customTint, shouldFill: false, accessibility: nil, setter: setter) } public static func iconAsync(size: IconSize, shouldFill: Bool, setter: @escaping (UIImageView) -> Void) -> SessionCell.Accessory { - return .iconAsync(size: size, customTint: nil, shouldFill: shouldFill, setter: setter) + return .iconAsync(size: size, customTint: nil, shouldFill: shouldFill, accessibility: nil, setter: setter) + } + + // MARK: - .toggle Variants + + public static func toggle(_ dataSource: DataSource) -> SessionCell.Accessory { + return .toggle(dataSource, accessibility: nil) + } + + // MARK: - .dropDown Variants + + public static func dropDown(_ dataSource: DataSource) -> SessionCell.Accessory { + return .dropDown(dataSource, accessibility: nil) } // MARK: - .radio Variants public static func radio(isSelected: @escaping () -> Bool) -> SessionCell.Accessory { - return .radio(size: .medium, isSelected: isSelected, storedSelection: false) + return .radio(size: .medium, isSelected: isSelected, storedSelection: false, accessibility: nil) } public static func radio(isSelected: @escaping () -> Bool, storedSelection: Bool) -> SessionCell.Accessory { - return .radio(size: .medium, isSelected: isSelected, storedSelection: storedSelection) + return .radio(size: .medium, isSelected: isSelected, storedSelection: storedSelection, accessibility: nil) + } + + // MARK: - .highlightingBackgroundLabel Variants + + public static func highlightingBackgroundLabel(title: String) -> SessionCell.Accessory { + return .highlightingBackgroundLabel(title: title, accessibility: nil) + } + + // MARK: - .profile Variants + + public static func profile(id: String, profile: Profile?) -> SessionCell.Accessory { + return .profile( + id: id, + size: .veryLarge, + threadVariant: .contact, + customImageData: nil, + profile: profile, + additionalProfile: nil, + cornerIcon: nil, + accessibility: nil + ) + } + + public static func profile(id: String, size: IconSize, profile: Profile?) -> SessionCell.Accessory { + return .profile( + id: id, + size: size, + threadVariant: .contact, + customImageData: nil, + profile: profile, + additionalProfile: nil, + cornerIcon: nil, + accessibility: nil + ) + } + + // MARK: - .search Variants + + public static func search(placeholder: String, searchTermChanged: @escaping (String?) -> Void) -> SessionCell.Accessory { + return .search(placeholder: placeholder, accessibility: nil, searchTermChanged: searchTermChanged) + } + + // MARK: - .button Variants + + public static func button(style: SessionButton.Style, title: String, run: @escaping (SessionButton?) -> Void) -> SessionCell.Accessory { + return .button(style: style, title: title, accessibility: nil, run: run) } } @@ -293,42 +462,3 @@ extension SessionCell.Accessory { } } } - -// MARK: - SessionCell.Accessory.ThreadInfoStyle - -extension SessionCell.Accessory { - public struct ThreadInfoStyle: Hashable, Equatable { - public enum Style: Hashable, Equatable { - case small - case monoSmall - case monoLarge - } - - public struct Action: Hashable, Equatable { - let title: String - let run: (SessionButton?) -> () - - public func hash(into hasher: inout Hasher) { - title.hash(into: &hasher) - } - - public static func == (lhs: Action, rhs: Action) -> Bool { - return (lhs.title == rhs.title) - } - } - - public let separatorTitle: String? - public let descriptionStyle: Style - public let descriptionActions: [Action] - - public init( - separatorTitle: String? = nil, - descriptionStyle: Style = .monoSmall, - descriptionActions: [Action] = [] - ) { - self.separatorTitle = separatorTitle - self.descriptionStyle = descriptionStyle - self.descriptionActions = descriptionActions - } - } -} diff --git a/Session/Shared/Types/SessionCell+ExtraAction.swift b/Session/Shared/Types/SessionCell+ExtraAction.swift deleted file mode 100644 index 64ee0428c..000000000 --- a/Session/Shared/Types/SessionCell+ExtraAction.swift +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import Foundation - -extension SessionCell { - struct ExtraAction: Hashable, Equatable { - let title: String - let onTap: (() -> Void) - - // MARK: - Conformance - - public func hash(into hasher: inout Hasher) { - title.hash(into: &hasher) - } - - static func == (lhs: SessionCell.ExtraAction, rhs: SessionCell.ExtraAction) -> Bool { - return (lhs.title == rhs.title) - } - } -} diff --git a/Session/Shared/Types/SessionCell+Info.swift b/Session/Shared/Types/SessionCell+Info.swift index e342f7789..35cd764ac 100644 --- a/Session/Shared/Types/SessionCell+Info.swift +++ b/Session/Shared/Types/SessionCell+Info.swift @@ -7,21 +7,17 @@ import SessionUIKit extension SessionCell { public struct Info: Equatable, Hashable, Differentiable { let id: ID + let position: Position let leftAccessory: SessionCell.Accessory? - let title: String - let subtitle: String? - let subtitleExtraViewGenerator: (() -> UIView)? - let tintColor: ThemeValue + let title: TextInfo? + let subtitle: TextInfo? let rightAccessory: SessionCell.Accessory? - let extraAction: SessionCell.ExtraAction? + let styling: StyleInfo let isEnabled: Bool - let shouldHaveBackground: Bool - let accessibilityIdentifier: String? - let accessibilityLabel: String? - let leftAccessoryAccessibilityLabel: String? - let rightAccessoryAccessibilityLabel: String? + let accessibility: SessionCell.Accessibility? let confirmationInfo: ConfirmationModal.Info? - let onTap: ((UIView?) -> Void)? + let onTap: (() -> Void)? + let onTapView: ((UIView?) -> Void)? var currentBoolValue: Bool { return ( @@ -34,74 +30,30 @@ extension SessionCell { init( id: ID, + position: Position = .individual, leftAccessory: SessionCell.Accessory? = nil, - title: String, - subtitle: String? = nil, - subtitleExtraViewGenerator: (() -> UIView)? = nil, - tintColor: ThemeValue = .textPrimary, + title: SessionCell.TextInfo? = nil, + subtitle: SessionCell.TextInfo? = nil, rightAccessory: SessionCell.Accessory? = nil, - extraAction: SessionCell.ExtraAction? = nil, + styling: StyleInfo = StyleInfo(), isEnabled: Bool = true, - shouldHaveBackground: Bool = true, - accessibilityIdentifier: String? = nil, - accessibilityLabel: String? = nil, - leftAccessoryAccessibilityLabel: String? = nil, - rightAccessoryAccessibilityLabel: String? = nil, + accessibility: SessionCell.Accessibility? = nil, confirmationInfo: ConfirmationModal.Info? = nil, - onTap: ((UIView?) -> Void)? + onTap: (() -> Void)? = nil, + onTapView: ((UIView?) -> Void)? = nil ) { self.id = id + self.position = position self.leftAccessory = leftAccessory self.title = title self.subtitle = subtitle - self.subtitleExtraViewGenerator = subtitleExtraViewGenerator - self.tintColor = tintColor self.rightAccessory = rightAccessory - self.extraAction = extraAction + self.styling = styling self.isEnabled = isEnabled - self.shouldHaveBackground = shouldHaveBackground - self.accessibilityIdentifier = accessibilityIdentifier - self.accessibilityLabel = accessibilityLabel - self.leftAccessoryAccessibilityLabel = leftAccessoryAccessibilityLabel - self.rightAccessoryAccessibilityLabel = rightAccessoryAccessibilityLabel + self.accessibility = accessibility self.confirmationInfo = confirmationInfo self.onTap = onTap - } - - init( - id: ID, - leftAccessory: SessionCell.Accessory? = nil, - title: String, - subtitle: String? = nil, - subtitleExtraViewGenerator: (() -> UIView)? = nil, - tintColor: ThemeValue = .textPrimary, - rightAccessory: SessionCell.Accessory? = nil, - extraAction: SessionCell.ExtraAction? = nil, - isEnabled: Bool = true, - shouldHaveBackground: Bool = true, - accessibilityIdentifier: String? = nil, - accessibilityLabel: String? = nil, - leftAccessoryAccessibilityLabel: String? = nil, - rightAccessoryAccessibilityLabel: String? = nil, - confirmationInfo: ConfirmationModal.Info? = nil, - onTap: (() -> Void)? = nil - ) { - self.id = id - self.leftAccessory = leftAccessory - self.title = title - self.subtitle = subtitle - self.subtitleExtraViewGenerator = subtitleExtraViewGenerator - self.tintColor = tintColor - self.rightAccessory = rightAccessory - self.extraAction = extraAction - self.isEnabled = isEnabled - self.shouldHaveBackground = shouldHaveBackground - self.accessibilityIdentifier = accessibilityIdentifier - self.accessibilityLabel = accessibilityLabel - self.leftAccessoryAccessibilityLabel = leftAccessoryAccessibilityLabel - self.rightAccessoryAccessibilityLabel = rightAccessoryAccessibilityLabel - self.confirmationInfo = confirmationInfo - self.onTap = (onTap != nil ? { _ in onTap?() } : nil) + self.onTapView = onTapView } // MARK: - Conformance @@ -110,37 +62,190 @@ extension SessionCell { public func hash(into hasher: inout Hasher) { id.hash(into: &hasher) + position.hash(into: &hasher) leftAccessory.hash(into: &hasher) title.hash(into: &hasher) subtitle.hash(into: &hasher) - tintColor.hash(into: &hasher) rightAccessory.hash(into: &hasher) - extraAction.hash(into: &hasher) + styling.hash(into: &hasher) isEnabled.hash(into: &hasher) - shouldHaveBackground.hash(into: &hasher) - accessibilityIdentifier.hash(into: &hasher) - accessibilityLabel.hash(into: &hasher) - leftAccessoryAccessibilityLabel.hash(into: &hasher) - rightAccessoryAccessibilityLabel.hash(into: &hasher) + accessibility.hash(into: &hasher) confirmationInfo.hash(into: &hasher) } public static func == (lhs: Info, rhs: Info) -> Bool { return ( lhs.id == rhs.id && + lhs.position == rhs.position && lhs.leftAccessory == rhs.leftAccessory && lhs.title == rhs.title && lhs.subtitle == rhs.subtitle && - lhs.tintColor == rhs.tintColor && lhs.rightAccessory == rhs.rightAccessory && - lhs.extraAction == rhs.extraAction && + lhs.styling == rhs.styling && lhs.isEnabled == rhs.isEnabled && - lhs.shouldHaveBackground == rhs.shouldHaveBackground && - lhs.accessibilityIdentifier == rhs.accessibilityIdentifier && - lhs.accessibilityLabel == rhs.accessibilityLabel && - lhs.leftAccessoryAccessibilityLabel == rhs.leftAccessoryAccessibilityLabel && - lhs.rightAccessoryAccessibilityLabel == rhs.rightAccessoryAccessibilityLabel + lhs.accessibility == rhs.accessibility + ) + } + + // MARK: - Convenience + + public func updatedPosition(for index: Int, count: Int) -> Info { + return Info( + id: id, + position: Position.with(index, count: count), + leftAccessory: leftAccessory, + title: title, + subtitle: subtitle, + rightAccessory: rightAccessory, + styling: styling, + isEnabled: isEnabled, + accessibility: accessibility, + confirmationInfo: confirmationInfo, + onTap: onTap, + onTapView: onTapView ) } } } + +// MARK: - Convenience Initializers + +public extension SessionCell.Info { + // Accessory, () -> Void + + init( + id: ID, + position: Position = .individual, + accessory: SessionCell.Accessory, + styling: SessionCell.StyleInfo = SessionCell.StyleInfo(), + isEnabled: Bool = true, + accessibility: SessionCell.Accessibility? = nil, + confirmationInfo: ConfirmationModal.Info? = nil, + onTap: (() -> Void)? = nil + ) { + self.id = id + self.position = position + self.leftAccessory = accessory + self.title = nil + self.subtitle = nil + self.rightAccessory = nil + self.styling = styling + self.isEnabled = isEnabled + self.accessibility = accessibility + self.confirmationInfo = confirmationInfo + self.onTap = onTap + self.onTapView = nil + } + + // leftAccessory, rightAccessory + + init( + id: ID, + position: Position = .individual, + leftAccessory: SessionCell.Accessory, + rightAccessory: SessionCell.Accessory, + styling: SessionCell.StyleInfo = SessionCell.StyleInfo(), + isEnabled: Bool = true, + accessibility: SessionCell.Accessibility? = nil, + confirmationInfo: ConfirmationModal.Info? = nil + ) { + self.id = id + self.position = position + self.leftAccessory = leftAccessory + self.title = nil + self.subtitle = nil + self.rightAccessory = rightAccessory + self.styling = styling + self.isEnabled = isEnabled + self.accessibility = accessibility + self.confirmationInfo = confirmationInfo + self.onTap = nil + self.onTapView = nil + } + + // String, () -> Void + + init( + id: ID, + position: Position = .individual, + leftAccessory: SessionCell.Accessory? = nil, + title: String, + rightAccessory: SessionCell.Accessory? = nil, + styling: SessionCell.StyleInfo = SessionCell.StyleInfo(), + isEnabled: Bool = true, + accessibility: SessionCell.Accessibility? = nil, + confirmationInfo: ConfirmationModal.Info? = nil, + onTap: (() -> Void)? = nil + ) { + self.id = id + self.position = position + self.leftAccessory = leftAccessory + self.title = SessionCell.TextInfo(title, font: .title) + self.subtitle = nil + self.rightAccessory = rightAccessory + self.styling = styling + self.isEnabled = isEnabled + self.accessibility = accessibility + self.confirmationInfo = confirmationInfo + self.onTap = onTap + self.onTapView = nil + } + + // TextInfo, () -> Void + + init( + id: ID, + position: Position = .individual, + leftAccessory: SessionCell.Accessory? = nil, + title: SessionCell.TextInfo, + rightAccessory: SessionCell.Accessory? = nil, + styling: SessionCell.StyleInfo = SessionCell.StyleInfo(), + isEnabled: Bool = true, + accessibility: SessionCell.Accessibility? = nil, + confirmationInfo: ConfirmationModal.Info? = nil, + onTap: (() -> Void)? = nil + ) { + self.id = id + self.position = position + self.leftAccessory = leftAccessory + self.title = title + self.subtitle = nil + self.rightAccessory = rightAccessory + self.styling = styling + self.isEnabled = isEnabled + self.accessibility = accessibility + self.confirmationInfo = confirmationInfo + self.onTap = onTap + self.onTapView = nil + } + + // String, String?, () -> Void + + init( + id: ID, + position: Position = .individual, + leftAccessory: SessionCell.Accessory? = nil, + title: String, + subtitle: String?, + rightAccessory: SessionCell.Accessory? = nil, + styling: SessionCell.StyleInfo = SessionCell.StyleInfo(), + isEnabled: Bool = true, + accessibility: SessionCell.Accessibility? = nil, + confirmationInfo: ConfirmationModal.Info? = nil, + onTap: (() -> Void)? = nil, + onTapView: ((UIView?) -> Void)? = nil + ) { + self.id = id + self.position = position + self.leftAccessory = leftAccessory + self.title = SessionCell.TextInfo(title, font: .title) + self.subtitle = SessionCell.TextInfo(subtitle, font: .subtitle) + self.rightAccessory = rightAccessory + self.styling = styling + self.isEnabled = isEnabled + self.accessibility = accessibility + self.confirmationInfo = confirmationInfo + self.onTap = onTap + self.onTapView = onTapView + } +} diff --git a/Session/Shared/Types/SessionCell+Styling.swift b/Session/Shared/Types/SessionCell+Styling.swift new file mode 100644 index 000000000..948cd1631 --- /dev/null +++ b/Session/Shared/Types/SessionCell+Styling.swift @@ -0,0 +1,170 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import SessionUIKit + +// MARK: - Main Types + +public extension SessionCell { + struct TextInfo: Hashable, Equatable { + public enum Interaction: Hashable, Equatable { + case none + case editable + case copy + case alwaysEditing + } + + let text: String? + let textAlignment: NSTextAlignment + let editingPlaceholder: String? + let interaction: Interaction + let extraViewGenerator: (() -> UIView)? + + private let fontStyle: FontStyle + var font: UIFont { fontStyle.font } + + init( + _ text: String?, + font: FontStyle, + alignment: NSTextAlignment = .left, + editingPlaceholder: String? = nil, + interaction: Interaction = .none, + extraViewGenerator: (() -> UIView)? = nil + ) { + self.text = text + self.fontStyle = font + self.textAlignment = alignment + self.editingPlaceholder = editingPlaceholder + self.interaction = interaction + self.extraViewGenerator = extraViewGenerator + } + + // MARK: - Conformance + + public func hash(into hasher: inout Hasher) { + text.hash(into: &hasher) + fontStyle.hash(into: &hasher) + textAlignment.hash(into: &hasher) + interaction.hash(into: &hasher) + editingPlaceholder.hash(into: &hasher) + } + + public static func == (lhs: TextInfo, rhs: TextInfo) -> Bool { + return ( + lhs.text == rhs.text && + lhs.fontStyle == rhs.fontStyle && + lhs.textAlignment == rhs.textAlignment && + lhs.interaction == rhs.interaction && + lhs.editingPlaceholder == rhs.editingPlaceholder + ) + } + } + + struct StyleInfo: Equatable, Hashable { + let tintColor: ThemeValue + let alignment: SessionCell.Alignment + let allowedSeparators: Separators + let customPadding: Padding? + let backgroundStyle: SessionCell.BackgroundStyle + + public init( + tintColor: ThemeValue = .textPrimary, + alignment: SessionCell.Alignment = .leading, + allowedSeparators: Separators = [.top, .bottom], + customPadding: Padding? = nil, + backgroundStyle: SessionCell.BackgroundStyle = .rounded + ) { + self.tintColor = tintColor + self.alignment = alignment + self.allowedSeparators = allowedSeparators + self.customPadding = customPadding + self.backgroundStyle = backgroundStyle + } + } +} + +// MARK: - Child Types + +public extension SessionCell { + enum FontStyle: Hashable, Equatable { + case title + case titleLarge + + case subtitle + case subtitleBold + + case monoSmall + case monoLarge + + var font: UIFont { + switch self { + case .title: return .boldSystemFont(ofSize: 16) + case .titleLarge: return .systemFont(ofSize: Values.veryLargeFontSize, weight: .medium) + + case .subtitle: return .systemFont(ofSize: 14) + case .subtitleBold: return .boldSystemFont(ofSize: 14) + + case .monoSmall: return Fonts.spaceMono(ofSize: Values.smallFontSize) + case .monoLarge: return Fonts.spaceMono( + ofSize: (isIPhone5OrSmaller ? Values.mediumFontSize : Values.largeFontSize) + ) + } + } + } + + enum Alignment: Equatable, Hashable { + case leading + case centerHugging + } + + enum BackgroundStyle: Equatable, Hashable { + case rounded + case edgeToEdge + case noBackground + } + + struct Separators: OptionSet, Equatable, Hashable { + public let rawValue: Int8 + + public init(rawValue: Int8) { + self.rawValue = rawValue + } + + public static let top: Separators = Separators(rawValue: 1 << 0) + public static let bottom: Separators = Separators(rawValue: 1 << 1) + } + + struct Padding: Equatable, Hashable { + let top: CGFloat? + let leading: CGFloat? + let trailing: CGFloat? + let bottom: CGFloat? + let interItem: CGFloat? + + init( + top: CGFloat? = nil, + leading: CGFloat? = nil, + trailing: CGFloat? = nil, + bottom: CGFloat? = nil, + interItem: CGFloat? = nil + ) { + self.top = top + self.leading = leading + self.trailing = trailing + self.bottom = bottom + self.interItem = interItem + } + } +} + +// MARK: - ExpressibleByStringLiteral + +extension SessionCell.TextInfo: ExpressibleByStringLiteral, ExpressibleByExtendedGraphemeClusterLiteral, ExpressibleByUnicodeScalarLiteral { + public init(stringLiteral value: String) { + self = SessionCell.TextInfo(value, font: .title) + } + + public init(unicodeScalarLiteral value: Character) { + self = SessionCell.TextInfo(String(value), font: .title) + } +} diff --git a/Session/Shared/Types/SessionTableSection.swift b/Session/Shared/Types/SessionTableSection.swift index b0f117269..023e69f5f 100644 --- a/Session/Shared/Types/SessionTableSection.swift +++ b/Session/Shared/Types/SessionTableSection.swift @@ -2,6 +2,7 @@ import Foundation import DifferenceKit +import SessionUIKit protocol SessionTableSection: Differentiable { var title: String? { get } @@ -13,8 +14,36 @@ extension SessionTableSection { var style: SessionTableSectionStyle { .none } } -public enum SessionTableSectionStyle: Differentiable { +public enum SessionTableSectionStyle: Equatable, Hashable, Differentiable { case none - case title + case titleRoundedContent + case titleEdgeToEdgeContent + case titleNoBackgroundContent + case titleSeparator case padding + case loadMore + + var height: CGFloat { + switch self { + case .none: return 0 + case .titleRoundedContent, .titleEdgeToEdgeContent, .titleNoBackgroundContent: + return UITableView.automaticDimension + + case .titleSeparator: return Separator.height + case .padding: return Values.smallSpacing + case .loadMore: return 40 + } + } + + /// These values should always be consistent with the padding in `SessionCell` to ensure the text lines up + var edgePadding: CGFloat { + switch self { + case .titleRoundedContent, .titleNoBackgroundContent: + // Align to the start of the text in the cell + return (Values.largeSpacing + Values.mediumSpacing) + + case .titleEdgeToEdgeContent, .titleSeparator: return Values.largeSpacing + case .none, .padding, .loadMore: return 0 + } + } } diff --git a/Session/Shared/UserSelectionVC.swift b/Session/Shared/UserSelectionVC.swift index 0c920f101..17a5633cf 100644 --- a/Session/Shared/UserSelectionVC.swift +++ b/Session/Shared/UserSelectionVC.swift @@ -68,15 +68,15 @@ final class UserSelectionVC: BaseVC, UITableViewDataSource, UITableViewDelegate cell.update( with: SessionCell.Info( id: profile, - leftAccessory: .profile(profile.id, profile), + position: Position.with(indexPath.row, count: users.count), + leftAccessory: .profile(id: profile.id, profile: profile), title: profile.displayName(), rightAccessory: .radio(isSelected: { [weak self] in self?.selectedUsers.contains(profile.id) == true }), - accessibilityIdentifier: "Contact" - ), - style: .edgeToEdge, - position: Position.with(indexPath.row, count: users.count) + styling: SessionCell.StyleInfo(backgroundStyle: .edgeToEdge), + accessibility: SessionCell.Accessibility(identifier: "Contact") + ) ) return cell diff --git a/Session/Shared/Views/SessionAvatarCell.swift b/Session/Shared/Views/SessionAvatarCell.swift deleted file mode 100644 index c27d16ca7..000000000 --- a/Session/Shared/Views/SessionAvatarCell.swift +++ /dev/null @@ -1,308 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import UIKit -import Combine -import SessionUIKit -import SessionMessagingKit -import SessionUtilitiesKit -import SignalUtilitiesKit - -class SessionAvatarCell: UITableViewCell { - var disposables: Set = Set() - private var originalInputValue: String? - - // MARK: - Initialization - - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - - setupViewHierarchy() - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - - setupViewHierarchy() - } - - // MARK: - UI - - private let stackView: UIStackView = { - let stackView: UIStackView = UIStackView() - stackView.translatesAutoresizingMaskIntoConstraints = false - stackView.axis = .vertical - stackView.spacing = Values.mediumSpacing - stackView.alignment = .center - stackView.distribution = .equalSpacing - - let horizontalSpacing: CGFloat = (UIScreen.main.bounds.size.height < 568 ? - Values.largeSpacing : - Values.veryLargeSpacing - ) - stackView.layoutMargins = UIEdgeInsets( - top: Values.mediumSpacing, - leading: horizontalSpacing, - bottom: Values.largeSpacing, - trailing: horizontalSpacing - ) - stackView.isLayoutMarginsRelativeArrangement = true - - return stackView - }() - - fileprivate let profilePictureView: ProfilePictureView = { - let view: ProfilePictureView = ProfilePictureView() - view.accessibilityLabel = "Profile picture" - view.isAccessibilityElement = true - view.translatesAutoresizingMaskIntoConstraints = false - view.size = Values.largeProfilePictureSize - - return view - }() - - fileprivate let displayNameContainer: UIView = { - let view: UIView = UIView() - view.translatesAutoresizingMaskIntoConstraints = false - view.accessibilityLabel = "Username" - view.isAccessibilityElement = true - - return view - }() - - private lazy var displayNameLabel: UILabel = { - let label: UILabel = UILabel() - label.isAccessibilityElement = true - label.translatesAutoresizingMaskIntoConstraints = false - label.font = .ows_mediumFont(withSize: Values.veryLargeFontSize) - label.themeTextColor = .textPrimary - label.textAlignment = .center - label.lineBreakMode = .byTruncatingTail - label.numberOfLines = 0 - - return label - }() - - fileprivate let displayNameTextField: UITextField = { - let textField: TextField = TextField(placeholder: "Enter a name", usesDefaultHeight: false) - textField.translatesAutoresizingMaskIntoConstraints = false - textField.textAlignment = .center - textField.accessibilityIdentifier = "Nickname" - textField.accessibilityLabel = "Nickname" - textField.isAccessibilityElement = true - textField.alpha = 0 - - return textField - }() - - private let descriptionSeparator: Separator = { - let result: Separator = Separator() - result.isHidden = true - - return result - }() - - private let descriptionLabel: SRCopyableLabel = { - let label: SRCopyableLabel = SRCopyableLabel() - label.accessibilityLabel = "Session ID" - label.translatesAutoresizingMaskIntoConstraints = false - label.themeTextColor = .textPrimary - label.textAlignment = .center - label.lineBreakMode = .byCharWrapping - label.numberOfLines = 0 - - return label - }() - - private let descriptionActionStackView: UIStackView = { - let stackView: UIStackView = UIStackView() - stackView.translatesAutoresizingMaskIntoConstraints = false - stackView.axis = .horizontal - stackView.alignment = .center - stackView.distribution = .fillEqually - stackView.spacing = (UIDevice.current.isIPad ? Values.iPadButtonSpacing : Values.mediumSpacing) - - return stackView - }() - - private func setupViewHierarchy() { - self.themeBackgroundColor = nil - self.selectedBackgroundView = UIView() - - contentView.addSubview(stackView) - - stackView.addArrangedSubview(profilePictureView) - stackView.addArrangedSubview(displayNameContainer) - stackView.addArrangedSubview(descriptionSeparator) - stackView.addArrangedSubview(descriptionLabel) - stackView.addArrangedSubview(descriptionActionStackView) - - displayNameContainer.addSubview(displayNameLabel) - displayNameContainer.addSubview(displayNameTextField) - - setupLayout() - } - - // MARK: - Layout - - private func setupLayout() { - stackView.pin(to: contentView) - - profilePictureView.set(.width, to: profilePictureView.size) - profilePictureView.set(.height, to: profilePictureView.size) - - displayNameLabel.pin(to: displayNameContainer) - displayNameTextField.center(in: displayNameContainer) - displayNameTextField.widthAnchor - .constraint( - lessThanOrEqualTo: stackView.widthAnchor, - constant: -(stackView.layoutMargins.left + stackView.layoutMargins.right) - ) - .isActive = true - - descriptionSeparator.set( - .width, - to: .width, - of: stackView, - withOffset: -(stackView.layoutMargins.left + stackView.layoutMargins.right) - ) - descriptionActionStackView.set( - .width, - to: .width, - of: stackView, - withOffset: -(stackView.layoutMargins.left + stackView.layoutMargins.right) - ) - } - - // MARK: - Content - - override func prepareForReuse() { - super.prepareForReuse() - - self.disposables = Set() - self.originalInputValue = nil - self.displayNameLabel.text = nil - self.displayNameTextField.text = nil - self.descriptionLabel.font = .ows_lightFont(withSize: Values.smallFontSize) - self.descriptionLabel.text = nil - - self.descriptionSeparator.isHidden = true - self.descriptionActionStackView.arrangedSubviews.forEach { $0.removeFromSuperview() } - } - - func update( - threadViewModel: SessionThreadViewModel, - style: SessionCell.Accessory.ThreadInfoStyle, - viewController: UIViewController - ) { - profilePictureView.update( - publicKey: threadViewModel.threadId, - profile: threadViewModel.profile, - additionalProfile: threadViewModel.additionalProfile, - threadVariant: threadViewModel.threadVariant, - openGroupProfilePictureData: threadViewModel.openGroupProfilePictureData, - useFallbackPicture: ( - threadViewModel.threadVariant == .openGroup && - threadViewModel.openGroupProfilePictureData == nil - ), - showMultiAvatarForClosedGroup: true - ) - - originalInputValue = threadViewModel.profile?.nickname - displayNameLabel.text = { - guard !threadViewModel.threadIsNoteToSelf else { - guard let profile: Profile = threadViewModel.profile else { - return Profile.truncated(id: threadViewModel.threadId, truncating: .middle) - } - - return profile.displayName() - } - - return threadViewModel.displayName - }() - descriptionLabel.font = { - switch style.descriptionStyle { - case .small: return .ows_lightFont(withSize: Values.smallFontSize) - case .monoSmall: return Fonts.spaceMono(ofSize: Values.smallFontSize) - case .monoLarge: return Fonts.spaceMono( - ofSize: (isIPhone5OrSmaller ? Values.mediumFontSize : Values.largeFontSize) - ) - } - }() - descriptionLabel.text = threadViewModel.threadId - descriptionLabel.isHidden = (threadViewModel.threadVariant != .contact) - descriptionLabel.isUserInteractionEnabled = ( - threadViewModel.threadVariant == .contact || - threadViewModel.threadVariant == .openGroup - ) - displayNameTextField.text = threadViewModel.profile?.nickname - descriptionSeparator.update(title: style.separatorTitle) - descriptionSeparator.isHidden = (style.separatorTitle == nil) - - if (UIDevice.current.isIPad) { - descriptionActionStackView.addArrangedSubview(UIView.hStretchingSpacer()) - } - - style.descriptionActions.forEach { action in - let result: SessionButton = SessionButton(style: .bordered, size: .medium) - result.setTitle(action.title, for: UIControl.State.normal) - result.tapPublisher - .receive(on: DispatchQueue.main) - .sink(receiveValue: { [weak result] _ in action.run(result) }) - .store(in: &self.disposables) - - descriptionActionStackView.addArrangedSubview(result) - } - - if (UIDevice.current.isIPad) { - descriptionActionStackView.addArrangedSubview(UIView.hStretchingSpacer()) - } - descriptionActionStackView.isHidden = style.descriptionActions.isEmpty - } - - func update(isEditing: Bool, animated: Bool) { - let changes = { [weak self] in - self?.displayNameLabel.alpha = (isEditing ? 0 : 1) - self?.displayNameTextField.alpha = (isEditing ? 1 : 0) - } - let completion: (Bool) -> Void = { [weak self] complete in - self?.displayNameTextField.text = self?.originalInputValue - } - - if animated { - UIView.animate(withDuration: 0.25, animations: changes, completion: completion) - } - else { - changes() - completion(true) - } - - if isEditing { - displayNameTextField.becomeFirstResponder() - } - else { - displayNameTextField.resignFirstResponder() - } - } -} - -// MARK: - Compose - -extension CombineCompatible where Self: SessionAvatarCell { - var textPublisher: AnyPublisher { - return self.displayNameTextField.publisher(for: .editingChanged) - .map { textField -> String in (textField.text ?? "") } - .eraseToAnyPublisher() - } - - var displayNameTapPublisher: AnyPublisher { - return self.displayNameContainer.tapPublisher - .map { _ in () } - .eraseToAnyPublisher() - } - - var profilePictureTapPublisher: AnyPublisher { - return self.profilePictureView.tapPublisher - .map { _ in () } - .eraseToAnyPublisher() - } -} diff --git a/Session/Shared/Views/SessionCell+AccessoryView.swift b/Session/Shared/Views/SessionCell+AccessoryView.swift index dffda5793..c909e6041 100644 --- a/Session/Shared/Views/SessionCell+AccessoryView.swift +++ b/Session/Shared/Views/SessionCell+AccessoryView.swift @@ -7,15 +7,25 @@ import SessionUtilitiesKit import SignalUtilitiesKit extension SessionCell { - public class AccessoryView: UIView { + public class AccessoryView: UIView, UISearchBarDelegate { + // Note: We set a minimum width for the 'AccessoryView' so that the titles line up + // nicely when we have a mix of icons and switches + private static let minWidth: CGFloat = 50 + + private var onTap: ((SessionButton?) -> Void)? + private var searchTermChanged: ((String?) -> Void)? + // MARK: - UI + private lazy var minWidthConstraint: NSLayoutConstraint = self.widthAnchor + .constraint(greaterThanOrEqualToConstant: AccessoryView.minWidth) + private lazy var fixedWidthConstraint: NSLayoutConstraint = self.set(.width, to: AccessoryView.minWidth) private lazy var imageViewConstraints: [NSLayoutConstraint] = [ imageView.pin(.top, to: .top, of: self), - imageView.pin(.leading, to: .leading, of: self), - imageView.pin(.trailing, to: .trailing, of: self), imageView.pin(.bottom, to: .bottom, of: self) ] + private lazy var imageViewLeadingConstraint: NSLayoutConstraint = imageView.pin(.leading, to: .leading, of: self) + private lazy var imageViewTrailingConstraint: NSLayoutConstraint = imageView.pin(.trailing, to: .trailing, of: self) private lazy var imageViewWidthConstraint: NSLayoutConstraint = imageView.set(.width, to: 0) private lazy var imageViewHeightConstraint: NSLayoutConstraint = imageView.set(.height, to: 0) private lazy var toggleSwitchConstraints: [NSLayoutConstraint] = [ @@ -26,8 +36,8 @@ extension SessionCell { ] private lazy var dropDownStackViewConstraints: [NSLayoutConstraint] = [ dropDownStackView.pin(.top, to: .top, of: self), - dropDownStackView.pin(.leading, to: .leading, of: self), - dropDownStackView.pin(.trailing, to: .trailing, of: self), + dropDownStackView.pin(.leading, to: .leading, of: self, withInset: Values.smallSpacing), + dropDownStackView.pin(.trailing, to: .trailing, of: self, withInset: -Values.smallSpacing), dropDownStackView.pin(.bottom, to: .bottom, of: self) ] private lazy var radioViewWidthConstraint: NSLayoutConstraint = radioView.set(.width, to: 0) @@ -36,22 +46,35 @@ extension SessionCell { private lazy var radioBorderViewHeightConstraint: NSLayoutConstraint = radioBorderView.set(.height, to: 0) private lazy var radioBorderViewConstraints: [NSLayoutConstraint] = [ radioBorderView.pin(.top, to: .top, of: self), - radioBorderView.pin(.leading, to: .leading, of: self), - radioBorderView.pin(.trailing, to: .trailing, of: self), + radioBorderView.center(.horizontal, in: self), radioBorderView.pin(.bottom, to: .bottom, of: self) ] private lazy var highlightingBackgroundLabelConstraints: [NSLayoutConstraint] = [ highlightingBackgroundLabel.pin(.top, to: .top, of: self), - highlightingBackgroundLabel.pin(.leading, to: .leading, of: self), - highlightingBackgroundLabel.pin(.trailing, to: .trailing, of: self), + highlightingBackgroundLabel.pin(.leading, to: .leading, of: self, withInset: Values.smallSpacing), + highlightingBackgroundLabel.pin(.trailing, to: .trailing, of: self, withInset: -Values.smallSpacing), highlightingBackgroundLabel.pin(.bottom, to: .bottom, of: self) ] + private lazy var profilePictureViewLeadingConstraint: NSLayoutConstraint = profilePictureView.pin(.leading, to: .leading, of: self) + private lazy var profilePictureViewTrailingConstraint: NSLayoutConstraint = profilePictureView.pin(.trailing, to: .trailing, of: self) private lazy var profilePictureViewConstraints: [NSLayoutConstraint] = [ profilePictureView.pin(.top, to: .top, of: self), - profilePictureView.pin(.leading, to: .leading, of: self), - profilePictureView.pin(.trailing, to: .trailing, of: self), profilePictureView.pin(.bottom, to: .bottom, of: self) ] + private lazy var profilePictureViewWidthConstraint: NSLayoutConstraint = profilePictureView.set(.width, to: 0) + private lazy var profilePictureViewHeightConstraint: NSLayoutConstraint = profilePictureView.set(.height, to: 0) + private lazy var searchBarConstraints: [NSLayoutConstraint] = [ + searchBar.pin(.top, to: .top, of: self), + searchBar.pin(.leading, to: .leading, of: self, withInset: -8), // Removing default inset + searchBar.pin(.trailing, to: .trailing, of: self, withInset: 8), // Removing default inset + searchBar.pin(.bottom, to: .bottom, of: self) + ] + private lazy var buttonConstraints: [NSLayoutConstraint] = [ + button.pin(.top, to: .top, of: self), + button.pin(.leading, to: .leading, of: self), + button.pin(.trailing, to: .trailing, of: self), + button.pin(.bottom, to: .bottom, of: self) + ] private let imageView: UIImageView = { let result: UIImageView = UIImageView() @@ -143,10 +166,45 @@ extension SessionCell { private lazy var profilePictureView: ProfilePictureView = { let result: ProfilePictureView = ProfilePictureView() result.translatesAutoresizingMaskIntoConstraints = false - result.size = Values.smallProfilePictureSize result.isHidden = true - result.set(.width, to: Values.smallProfilePictureSize) - result.set(.height, to: Values.smallProfilePictureSize) + + return result + }() + + private lazy var profileIconContainerView: UIView = { + let result: UIView = UIView() + result.translatesAutoresizingMaskIntoConstraints = false + result.themeBackgroundColor = .primary + result.isHidden = true + result.set(.width, to: 26) + result.set(.height, to: 26) + result.layer.cornerRadius = (26 / 2) + + return result + }() + + private lazy var profileIconImageView: UIImageView = { + let result: UIImageView = UIImageView() + result.translatesAutoresizingMaskIntoConstraints = false + + return result + }() + + private lazy var searchBar: UISearchBar = { + let result: ContactsSearchBar = ContactsSearchBar() + result.themeTintColor = .textPrimary + result.themeBackgroundColor = .clear + result.searchTextField.themeBackgroundColor = .backgroundSecondary + result.delegate = self + + return result + }() + + private lazy var button: SessionButton = { + let result: SessionButton = SessionButton(style: .bordered, size: .medium) + result.translatesAutoresizingMaskIntoConstraints = false + result.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) + result.isHidden = true return result }() @@ -174,18 +232,29 @@ extension SessionCell { addSubview(radioBorderView) addSubview(highlightingBackgroundLabel) addSubview(profilePictureView) + addSubview(profileIconContainerView) + addSubview(button) + addSubview(searchBar) dropDownStackView.addArrangedSubview(dropDownImageView) dropDownStackView.addArrangedSubview(dropDownLabel) radioBorderView.addSubview(radioView) radioView.center(in: radioBorderView) + + profileIconContainerView.addSubview(profileIconImageView) + + profileIconContainerView.pin(.bottom, to: .bottom, of: profilePictureView) + profileIconContainerView.pin(.trailing, to: .trailing, of: profilePictureView) + profileIconImageView.pin(to: profileIconContainerView, withInset: Values.verySmallSpacing) } // MARK: - Content func prepareForReuse() { - self.isHidden = true + isHidden = true + onTap = nil + searchTermChanged = nil imageView.image = nil imageView.themeTintColor = .textPrimary @@ -207,7 +276,16 @@ extension SessionCell { radioView.isHidden = true highlightingBackgroundLabel.isHidden = true profilePictureView.isHidden = true + profileIconContainerView.isHidden = true + button.isHidden = true + searchBar.isHidden = true + minWidthConstraint.constant = AccessoryView.minWidth + minWidthConstraint.isActive = false + fixedWidthConstraint.constant = AccessoryView.minWidth + fixedWidthConstraint.isActive = false + imageViewLeadingConstraint.isActive = false + imageViewTrailingConstraint.isActive = false imageViewWidthConstraint.isActive = false imageViewHeightConstraint.isActive = false imageViewConstraints.forEach { $0.isActive = false } @@ -219,14 +297,19 @@ extension SessionCell { radioBorderViewHeightConstraint.isActive = false radioBorderViewConstraints.forEach { $0.isActive = false } highlightingBackgroundLabelConstraints.forEach { $0.isActive = false } + profilePictureViewLeadingConstraint.isActive = false + profilePictureViewTrailingConstraint.isActive = false + profilePictureViewWidthConstraint.isActive = false + profilePictureViewHeightConstraint.isActive = false profilePictureViewConstraints.forEach { $0.isActive = false } + searchBarConstraints.forEach { $0.isActive = false } + buttonConstraints.forEach { $0.isActive = false } } public func update( with accessory: Accessory?, tintColor: ThemeValue, - isEnabled: Bool, - accessibilityLabel: String? + isEnabled: Bool ) { guard let accessory: Accessory = accessory else { return } @@ -234,8 +317,9 @@ extension SessionCell { self.isHidden = false switch accessory { - case .icon(let image, let iconSize, let customTint, let shouldFill): - imageView.accessibilityLabel = accessibilityLabel + case .icon(let image, let iconSize, let customTint, let shouldFill, let accessibility): + imageView.accessibilityIdentifier = accessibility?.identifier + imageView.accessibilityLabel = accessibility?.label imageView.image = image imageView.themeTintColor = (customTint ?? tintColor) imageView.contentMode = (shouldFill ? .scaleAspectFill : .scaleAspectFit) @@ -244,21 +328,30 @@ extension SessionCell { switch iconSize { case .fit: imageView.sizeToFit() + fixedWidthConstraint.constant = (imageView.bounds.width + (shouldFill ? 0 : (Values.smallSpacing * 2))) + fixedWidthConstraint.isActive = true imageViewWidthConstraint.constant = imageView.bounds.width imageViewHeightConstraint.constant = imageView.bounds.height default: + fixedWidthConstraint.isActive = (iconSize.size <= fixedWidthConstraint.constant) imageViewWidthConstraint.constant = iconSize.size imageViewHeightConstraint.constant = iconSize.size } + minWidthConstraint.isActive = !fixedWidthConstraint.isActive + imageViewLeadingConstraint.constant = (shouldFill ? 0 : Values.smallSpacing) + imageViewTrailingConstraint.constant = (shouldFill ? 0 : -Values.smallSpacing) + imageViewLeadingConstraint.isActive = true + imageViewTrailingConstraint.isActive = true imageViewWidthConstraint.isActive = true imageViewHeightConstraint.isActive = true imageViewConstraints.forEach { $0.isActive = true } - case .iconAsync(let iconSize, let customTint, let shouldFill, let setter): + case .iconAsync(let iconSize, let customTint, let shouldFill, let accessibility, let setter): setter(imageView) - imageView.accessibilityLabel = accessibilityLabel + imageView.accessibilityIdentifier = accessibility?.identifier + imageView.accessibilityLabel = accessibility?.label imageView.themeTintColor = (customTint ?? tintColor) imageView.contentMode = (shouldFill ? .scaleAspectFill : .scaleAspectFit) imageView.isHidden = false @@ -266,22 +359,33 @@ extension SessionCell { switch iconSize { case .fit: imageView.sizeToFit() + fixedWidthConstraint.constant = (imageView.bounds.width + (shouldFill ? 0 : (Values.smallSpacing * 2))) + fixedWidthConstraint.isActive = true imageViewWidthConstraint.constant = imageView.bounds.width imageViewHeightConstraint.constant = imageView.bounds.height default: + fixedWidthConstraint.isActive = (iconSize.size <= fixedWidthConstraint.constant) imageViewWidthConstraint.constant = iconSize.size imageViewHeightConstraint.constant = iconSize.size } + minWidthConstraint.isActive = !fixedWidthConstraint.isActive + imageViewLeadingConstraint.constant = (shouldFill ? 0 : Values.smallSpacing) + imageViewTrailingConstraint.constant = (shouldFill ? 0 : -Values.smallSpacing) + imageViewLeadingConstraint.isActive = true + imageViewTrailingConstraint.isActive = true imageViewWidthConstraint.isActive = true imageViewHeightConstraint.isActive = true imageViewConstraints.forEach { $0.isActive = true } - case .toggle(let dataSource): - toggleSwitch.accessibilityLabel = accessibilityLabel + case .toggle(let dataSource, let accessibility): + toggleSwitch.accessibilityIdentifier = accessibility?.identifier + toggleSwitch.accessibilityLabel = accessibility?.label toggleSwitch.isHidden = false toggleSwitch.isEnabled = isEnabled + + fixedWidthConstraint.isActive = true toggleSwitchConstraints.forEach { $0.isActive = true } let newValue: Bool = dataSource.currentBoolValue @@ -290,13 +394,15 @@ extension SessionCell { toggleSwitch.setOn(newValue, animated: true) } - case .dropDown(let dataSource): - dropDownLabel.accessibilityLabel = accessibilityLabel + case .dropDown(let dataSource, let accessibility): + dropDownLabel.accessibilityIdentifier = accessibility?.identifier + dropDownLabel.accessibilityLabel = accessibility?.label dropDownLabel.text = dataSource.currentStringValue dropDownStackView.isHidden = false dropDownStackViewConstraints.forEach { $0.isActive = true } + minWidthConstraint.isActive = true - case .radio(let size, let isSelectedRetriever, let storedSelection): + case .radio(let size, let isSelectedRetriever, let storedSelection, let accessibility): let isSelected: Bool = isSelectedRetriever() let wasOldSelection: Bool = (!isSelected && storedSelection) @@ -307,7 +413,8 @@ extension SessionCell { ) radioBorderView.layer.cornerRadius = (size.borderSize / 2) - radioView.accessibilityLabel = accessibilityLabel + radioView.accessibilityIdentifier = accessibility?.identifier + radioView.accessibilityLabel = accessibility?.label radioView.alpha = (wasOldSelection ? 0.3 : 1) radioView.isHidden = (!isSelected && !storedSelection) radioView.themeBackgroundColor = (isSelected || wasOldSelection ? @@ -321,32 +428,89 @@ extension SessionCell { radioBorderViewWidthConstraint.constant = size.borderSize radioBorderViewHeightConstraint.constant = size.borderSize + fixedWidthConstraint.isActive = true radioViewWidthConstraint.isActive = true radioViewHeightConstraint.isActive = true radioBorderViewWidthConstraint.isActive = true radioBorderViewHeightConstraint.isActive = true radioBorderViewConstraints.forEach { $0.isActive = true } - case .highlightingBackgroundLabel(let title): - highlightingBackgroundLabel.accessibilityLabel = accessibilityLabel + case .highlightingBackgroundLabel(let title, let accessibility): + highlightingBackgroundLabel.accessibilityIdentifier = accessibility?.identifier + highlightingBackgroundLabel.accessibilityLabel = accessibility?.label highlightingBackgroundLabel.text = title highlightingBackgroundLabel.themeTextColor = tintColor highlightingBackgroundLabel.isHidden = false highlightingBackgroundLabelConstraints.forEach { $0.isActive = true } + minWidthConstraint.isActive = true - case .profile(let profileId, let profile): - profilePictureView.accessibilityLabel = accessibilityLabel + case .profile( + let profileId, + let profileSize, + let threadVariant, + let customImageData, + let profile, + let additionalProfile, + let cornerIcon, + let accessibility + ): + // Note: We MUST set the 'size' property before triggering the 'update' + // function or the profile picture won't layout correctly + switch profileSize { + case .fit: + profilePictureView.size = IconSize.large.size + profilePictureViewWidthConstraint.constant = IconSize.large.size + profilePictureViewHeightConstraint.constant = IconSize.large.size + + default: + profilePictureView.size = profileSize.size + profilePictureViewWidthConstraint.constant = profileSize.size + profilePictureViewHeightConstraint.constant = profileSize.size + } + + profilePictureView.accessibilityIdentifier = accessibility?.identifier + profilePictureView.accessibilityLabel = accessibility?.label profilePictureView.update( publicKey: profileId, + threadVariant: threadVariant, + customImageData: customImageData, profile: profile, - threadVariant: .contact + additionalProfile: additionalProfile ) profilePictureView.isHidden = false + profileIconContainerView.isHidden = (cornerIcon == nil) + profileIconImageView.image = cornerIcon + + fixedWidthConstraint.constant = profilePictureViewWidthConstraint.constant + fixedWidthConstraint.isActive = true + profilePictureViewLeadingConstraint.constant = (profilePictureView.size > AccessoryView.minWidth ? 0 : Values.smallSpacing) + profilePictureViewTrailingConstraint.constant = (profilePictureView.size > AccessoryView.minWidth ? 0 : -Values.smallSpacing) + profilePictureViewLeadingConstraint.isActive = true + profilePictureViewTrailingConstraint.isActive = true + profilePictureViewWidthConstraint.isActive = true + profilePictureViewHeightConstraint.isActive = true profilePictureViewConstraints.forEach { $0.isActive = true } - case .customView(let viewGenerator): + case .search(let placeholder, let accessibility, let searchTermChanged): + self.searchTermChanged = searchTermChanged + searchBar.accessibilityIdentifier = accessibility?.identifier + searchBar.accessibilityLabel = accessibility?.label + searchBar.placeholder = placeholder + searchBar.isHidden = false + searchBarConstraints.forEach { $0.isActive = true } + + case .button(let style, let title, let accessibility, let onTap): + self.onTap = onTap + button.accessibilityIdentifier = accessibility?.identifier + button.accessibilityLabel = accessibility?.label + button.setTitle(title, for: .normal) + button.setStyle(style) + button.isHidden = false + minWidthConstraint.isActive = true + buttonConstraints.forEach { $0.isActive = true } + + case .customView(_, let viewGenerator): let generatedView: UIView = viewGenerator() - generatedView.accessibilityLabel = accessibilityLabel addSubview(generatedView) generatedView.pin(.top, to: .top, of: self) @@ -354,10 +518,9 @@ extension SessionCell { generatedView.pin(.trailing, to: .trailing, of: self) generatedView.pin(.bottom, to: .bottom, of: self) - self.customView?.removeFromSuperview() // Just in case - self.customView = generatedView - - case .threadInfo: break + customView?.removeFromSuperview() // Just in case + customView = generatedView + minWidthConstraint.isActive = true } } @@ -370,6 +533,27 @@ extension SessionCell { func setSelected(_ selected: Bool, animated: Bool) { highlightingBackgroundLabel.setSelected(selected, animated: animated) } + + @objc private func buttonTapped() { + onTap?(button) + } + + // MARK: - UISearchBarDelegate + + public func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { + searchTermChanged?(searchText) + } + + public func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) { + searchBar.setShowsCancelButton(true, animated: true) + } + + public func searchBarTextDidEndEditing(_ searchBar: UISearchBar) { + searchBar.setShowsCancelButton(false, animated: true) + } + + public func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { + searchBar.endEditing(true) + } } - } diff --git a/Session/Shared/Views/SessionCell.swift b/Session/Shared/Views/SessionCell.swift index e6fa6c033..d390fb1a2 100644 --- a/Session/Shared/Views/SessionCell.swift +++ b/Session/Shared/Views/SessionCell.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit +import Combine import GRDB import DifferenceKit import SessionUIKit @@ -9,17 +10,13 @@ import SessionUtilitiesKit public class SessionCell: UITableViewCell { public static let cornerRadius: CGFloat = 17 - public enum Style { - case rounded - case roundedEdgeToEdge - case edgeToEdge - } - - /// This value is here to allow the theming update callback to be released when preparing for reuse - private var instanceView: UIView = UIView() - private var position: Position? + private var isEditingTitle = false + public private(set) var interactionMode: SessionCell.TextInfo.Interaction = .none + private var shouldHighlightTitle: Bool = true + private var originalInputValue: String? + private var titleExtraView: UIView? private var subtitleExtraView: UIView? - private var onExtraActionTap: (() -> Void)? + var disposables: Set = Set() // MARK: - UI @@ -29,8 +26,18 @@ public class SessionCell: UITableViewCell { private var topSeparatorRightConstraint: NSLayoutConstraint = NSLayoutConstraint() private var botSeparatorLeftConstraint: NSLayoutConstraint = NSLayoutConstraint() private var botSeparatorRightConstraint: NSLayoutConstraint = NSLayoutConstraint() + private lazy var contentStackViewTopConstraint: NSLayoutConstraint = contentStackView.pin(.top, to: .top, of: cellBackgroundView) + private lazy var contentStackViewLeadingConstraint: NSLayoutConstraint = contentStackView.pin(.leading, to: .leading, of: cellBackgroundView) + private lazy var contentStackViewTrailingConstraint: NSLayoutConstraint = contentStackView.pin(.trailing, to: .trailing, of: cellBackgroundView) + private lazy var contentStackViewBottomConstraint: NSLayoutConstraint = contentStackView.pin(.bottom, to: .bottom, of: cellBackgroundView) + private lazy var contentStackViewHorizontalCenterConstraint: NSLayoutConstraint = contentStackView.center(.horizontal, in: cellBackgroundView) private lazy var leftAccessoryFillConstraint: NSLayoutConstraint = contentStackView.set(.height, to: .height, of: leftAccessoryView) + private lazy var titleTextFieldLeadingConstraint: NSLayoutConstraint = titleTextField.pin(.leading, to: .leading, of: cellBackgroundView) + private lazy var titleTextFieldTrailingConstraint: NSLayoutConstraint = titleTextField.pin(.trailing, to: .trailing, of: cellBackgroundView) + private lazy var titleMinHeightConstraint: NSLayoutConstraint = titleStackView.heightAnchor + .constraint(greaterThanOrEqualTo: titleTextField.heightAnchor) private lazy var rightAccessoryFillConstraint: NSLayoutConstraint = contentStackView.set(.height, to: .height, of: rightAccessoryView) + private lazy var accessoryWidthMatchConstraint: NSLayoutConstraint = leftAccessoryView.set(.width, to: .width, of: rightAccessoryView) private let cellBackgroundView: UIView = { let result: UIView = UIView() @@ -65,7 +72,6 @@ public class SessionCell: UITableViewCell { result.distribution = .fill result.alignment = .center result.spacing = Values.mediumSpacing - result.isLayoutMarginsRelativeArrangement = true return result }() @@ -89,10 +95,10 @@ public class SessionCell: UITableViewCell { return result }() - private let titleLabel: UILabel = { - let result: UILabel = UILabel() + private let titleLabel: SRCopyableLabel = { + let result: SRCopyableLabel = SRCopyableLabel() result.translatesAutoresizingMaskIntoConstraints = false - result.font = .boldSystemFont(ofSize: 15) + result.isUserInteractionEnabled = false result.themeTextColor = .textPrimary result.numberOfLines = 0 result.setCompressionResistanceHorizontalLow() @@ -101,10 +107,21 @@ public class SessionCell: UITableViewCell { return result }() - private let subtitleLabel: UILabel = { - let result: UILabel = UILabel() + fileprivate let titleTextField: UITextField = { + let textField: TextField = TextField(placeholder: "", usesDefaultHeight: false) + textField.translatesAutoresizingMaskIntoConstraints = false + textField.textAlignment = .center + textField.alpha = 0 + textField.isHidden = true + textField.set(.height, to: Values.largeButtonHeight) + + return textField + }() + + private let subtitleLabel: SRCopyableLabel = { + let result: SRCopyableLabel = SRCopyableLabel() result.translatesAutoresizingMaskIntoConstraints = false - result.font = .systemFont(ofSize: 13) + result.isUserInteractionEnabled = false result.themeTextColor = .textPrimary result.numberOfLines = 0 result.isHidden = true @@ -114,33 +131,6 @@ public class SessionCell: UITableViewCell { return result }() - private lazy var extraActionTopSpacingView: UIView = UIView.spacer(withHeight: Values.smallSpacing) - - private lazy var extraActionButton: UIButton = { - let result: UIButton = UIButton() - result.translatesAutoresizingMaskIntoConstraints = false - result.titleLabel?.font = .boldSystemFont(ofSize: Values.smallFontSize) - result.titleLabel?.numberOfLines = 0 - result.contentHorizontalAlignment = .left - result.contentEdgeInsets = UIEdgeInsets( - top: 8, - left: 0, - bottom: 0, - right: 0 - ) - result.addTarget(self, action: #selector(extraActionTapped), for: .touchUpInside) - result.isHidden = true - - ThemeManager.onThemeChange(observer: result) { [weak result] theme, _ in - switch theme.interfaceStyle { - case .light: result?.setThemeTitleColor(.textPrimary, for: .normal) - default: result?.setThemeTitleColor(.primary, for: .normal) - } - } - - return result - }() - public let rightAccessoryView: AccessoryView = { let result: AccessoryView = AccessoryView() result.isHidden = true @@ -186,8 +176,8 @@ public class SessionCell: UITableViewCell { titleStackView.addArrangedSubview(titleLabel) titleStackView.addArrangedSubview(subtitleLabel) - titleStackView.addArrangedSubview(extraActionTopSpacingView) - titleStackView.addArrangedSubview(extraActionButton) + + cellBackgroundView.addSubview(titleTextField) setupLayout() } @@ -204,7 +194,10 @@ public class SessionCell: UITableViewCell { topSeparatorLeftConstraint = topSeparator.pin(.left, to: .left, of: cellBackgroundView) topSeparatorRightConstraint = topSeparator.pin(.right, to: .right, of: cellBackgroundView) - contentStackView.pin(to: cellBackgroundView) + contentStackViewTopConstraint.isActive = true + contentStackViewBottomConstraint.isActive = true + + titleTextField.center(.vertical, in: titleLabel) botSeparatorLeftConstraint = botSeparator.pin(.left, to: .left, of: cellBackgroundView) botSeparatorRightConstraint = botSeparator.pin(.right, to: .right, of: cellBackgroundView) @@ -217,55 +210,59 @@ public class SessionCell: UITableViewCell { // Need to force the contentStackView to layout if needed as it might not have updated it's // sizing yet self.contentStackView.layoutIfNeeded() + repositionExtraView(titleExtraView, for: titleLabel) + repositionExtraView(subtitleExtraView, for: subtitleLabel) + } + + private func repositionExtraView(_ targetView: UIView?, for label: UILabel) { + guard + let targetView: UIView = targetView, + let content: String = label.text, + let font: UIFont = label.font + else { return } - // Position the 'subtitleExtraView' at the end of the last line of text - if - let subtitleExtraView: UIView = self.subtitleExtraView, - let subtitle: String = subtitleLabel.text, - let font: UIFont = subtitleLabel.font - { - let layoutManager: NSLayoutManager = NSLayoutManager() - let textStorage = NSTextStorage( - attributedString: NSAttributedString( - string: subtitle, - attributes: [ .font: font ] - ) + // Position the 'targetView' at the end of the last line of text + let layoutManager: NSLayoutManager = NSLayoutManager() + let textStorage = NSTextStorage( + attributedString: NSAttributedString( + string: content, + attributes: [ .font: font ] ) - textStorage.addLayoutManager(layoutManager) - - let textContainer: NSTextContainer = NSTextContainer( - size: CGSize( - width: subtitleLabel.bounds.size.width, - height: 999 - ) + ) + textStorage.addLayoutManager(layoutManager) + + let textContainer: NSTextContainer = NSTextContainer( + size: CGSize( + width: label.bounds.size.width, + height: 999 ) - textContainer.lineFragmentPadding = 0 - layoutManager.addTextContainer(textContainer) - - var glyphRange: NSRange = NSRange() - layoutManager.characterRange( - forGlyphRange: NSRange(location: subtitle.glyphCount - 1, length: 1), - actualGlyphRange: &glyphRange - ) - let lastGlyphRect: CGRect = layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer) - - // Remove and re-add the 'subtitleExtraView' to clear any old constraints - subtitleExtraView.removeFromSuperview() - contentView.addSubview(subtitleExtraView) - - subtitleExtraView.pin( - .top, - to: .top, - of: subtitleLabel, - withInset: (lastGlyphRect.minY + ((lastGlyphRect.height / 2) - (subtitleExtraView.bounds.height / 2))) - ) - subtitleExtraView.pin( - .leading, - to: .leading, - of: subtitleLabel, - withInset: lastGlyphRect.maxX - ) - } + ) + textContainer.lineFragmentPadding = 0 + layoutManager.addTextContainer(textContainer) + + var glyphRange: NSRange = NSRange() + layoutManager.characterRange( + forGlyphRange: NSRange(location: content.glyphCount - 1, length: 1), + actualGlyphRange: &glyphRange + ) + let lastGlyphRect: CGRect = layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer) + + // Remove and re-add the 'subtitleExtraView' to clear any old constraints + targetView.removeFromSuperview() + contentView.addSubview(targetView) + + targetView.pin( + .top, + to: .top, + of: label, + withInset: (lastGlyphRect.minY + ((lastGlyphRect.height / 2) - (targetView.bounds.height / 2))) + ) + targetView.pin( + .leading, + to: .leading, + of: label, + withInset: lastGlyphRect.maxX + ) } // MARK: - Content @@ -273,108 +270,185 @@ public class SessionCell: UITableViewCell { public override func prepareForReuse() { super.prepareForReuse() - self.instanceView = UIView() - self.position = nil - self.onExtraActionTap = nil - self.accessibilityIdentifier = nil + isEditingTitle = false + interactionMode = .none + shouldHighlightTitle = true + accessibilityIdentifier = nil + accessibilityLabel = nil + originalInputValue = nil + titleExtraView?.removeFromSuperview() + titleExtraView = nil + subtitleExtraView?.removeFromSuperview() + subtitleExtraView = nil + disposables = Set() + contentStackView.spacing = Values.mediumSpacing + contentStackViewLeadingConstraint.isActive = false + contentStackViewTrailingConstraint.isActive = false + contentStackViewHorizontalCenterConstraint.isActive = false + titleMinHeightConstraint.isActive = false leftAccessoryView.prepareForReuse() + leftAccessoryView.alpha = 1 leftAccessoryFillConstraint.isActive = false titleLabel.text = "" + titleLabel.textAlignment = .left titleLabel.themeTextColor = .textPrimary + titleLabel.alpha = 1 + titleTextField.text = "" + titleTextField.textAlignment = .center + titleTextField.themeTextColor = .textPrimary + titleTextField.isHidden = true + titleTextField.alpha = 0 + subtitleLabel.isUserInteractionEnabled = false subtitleLabel.text = "" subtitleLabel.themeTextColor = .textPrimary rightAccessoryView.prepareForReuse() + rightAccessoryView.alpha = 1 rightAccessoryFillConstraint.isActive = false + accessoryWidthMatchConstraint.isActive = false topSeparator.isHidden = true subtitleLabel.isHidden = true - extraActionTopSpacingView.isHidden = true - extraActionButton.setTitle("", for: .normal) - extraActionButton.isHidden = true botSeparator.isHidden = true - - subtitleExtraView?.removeFromSuperview() - subtitleExtraView = nil } - public func update( - with info: Info, - style: Style, - position: Position - ) { - self.instanceView = UIView() - self.position = position - self.subtitleExtraView = info.subtitleExtraViewGenerator?() - self.onExtraActionTap = info.extraAction?.onTap - self.accessibilityIdentifier = info.accessibilityIdentifier - self.accessibilityLabel = info.accessibilityLabel - self.isAccessibilityElement = true + public func update(with info: Info) { + interactionMode = (info.title?.interaction ?? .none) + shouldHighlightTitle = (info.title?.interaction != .copy) + titleExtraView = info.title?.extraViewGenerator?() + subtitleExtraView = info.subtitle?.extraViewGenerator?() + accessibilityIdentifier = info.accessibility?.identifier + accessibilityLabel = info.accessibility?.label + originalInputValue = info.title?.text + // Convenience Flags let leftFitToEdge: Bool = (info.leftAccessory?.shouldFitToEdge == true) let rightFitToEdge: Bool = (!leftFitToEdge && info.rightAccessory?.shouldFitToEdge == true) - leftAccessoryFillConstraint.isActive = leftFitToEdge + + // Content + contentStackView.spacing = (info.styling.customPadding?.interItem ?? Values.mediumSpacing) leftAccessoryView.update( with: info.leftAccessory, - tintColor: info.tintColor, - isEnabled: info.isEnabled, - accessibilityLabel: info.leftAccessoryAccessibilityLabel + tintColor: info.styling.tintColor, + isEnabled: info.isEnabled ) + titleStackView.isHidden = (info.title == nil && info.subtitle == nil) + titleLabel.isUserInteractionEnabled = (info.title?.interaction == .copy) + titleLabel.font = info.title?.font + titleLabel.text = info.title?.text + titleLabel.themeTextColor = info.styling.tintColor + titleLabel.textAlignment = (info.title?.textAlignment ?? .left) + titleLabel.isHidden = (info.title == nil) + titleTextField.text = info.title?.text + titleTextField.textAlignment = (info.title?.textAlignment ?? .left) + titleTextField.placeholder = info.title?.editingPlaceholder + titleTextField.isHidden = (info.title == nil) + subtitleLabel.isUserInteractionEnabled = (info.subtitle?.interaction == .copy) + subtitleLabel.font = info.subtitle?.font + subtitleLabel.text = info.subtitle?.text + subtitleLabel.themeTextColor = info.styling.tintColor + subtitleLabel.textAlignment = (info.subtitle?.textAlignment ?? .left) + subtitleLabel.isHidden = (info.subtitle == nil) rightAccessoryView.update( with: info.rightAccessory, - tintColor: info.tintColor, - isEnabled: info.isEnabled, - accessibilityLabel: info.rightAccessoryAccessibilityLabel - ) - rightAccessoryFillConstraint.isActive = rightFitToEdge - contentStackView.layoutMargins = UIEdgeInsets( - top: (leftFitToEdge || rightFitToEdge ? 0 : Values.mediumSpacing), - left: (leftFitToEdge ? 0 : Values.largeSpacing), - bottom: (leftFitToEdge || rightFitToEdge ? 0 : Values.mediumSpacing), - right: (rightFitToEdge ? 0 : Values.largeSpacing) + tintColor: info.styling.tintColor, + isEnabled: info.isEnabled ) - titleLabel.text = info.title - titleLabel.themeTextColor = info.tintColor - subtitleLabel.text = info.subtitle - subtitleLabel.themeTextColor = info.tintColor - subtitleLabel.isHidden = (info.subtitle == nil) - extraActionTopSpacingView.isHidden = (info.extraAction == nil) - extraActionButton.setTitle(info.extraAction?.title, for: .normal) - extraActionButton.isHidden = (info.extraAction == nil) + contentStackViewLeadingConstraint.isActive = (info.styling.alignment == .leading) + contentStackViewTrailingConstraint.isActive = (info.styling.alignment == .leading) + contentStackViewHorizontalCenterConstraint.constant = ((info.styling.customPadding?.leading ?? 0) + (info.styling.customPadding?.trailing ?? 0)) + contentStackViewHorizontalCenterConstraint.isActive = (info.styling.alignment == .centerHugging) + leftAccessoryFillConstraint.isActive = leftFitToEdge + rightAccessoryFillConstraint.isActive = rightFitToEdge + accessoryWidthMatchConstraint.isActive = { + switch (info.leftAccessory, info.rightAccessory) { + case (.button, .button): return true + default: return false + } + }() + titleLabel.setContentHuggingPriority( + (info.rightAccessory != nil ? .defaultLow : .required), + for: .horizontal + ) + titleLabel.setContentCompressionResistancePriority( + (info.rightAccessory != nil ? .defaultLow : .required), + for: .horizontal + ) + contentStackViewTopConstraint.constant = { + if let customPadding: CGFloat = info.styling.customPadding?.top { + return customPadding + } + + return (leftFitToEdge || rightFitToEdge ? 0 : Values.mediumSpacing) + }() + contentStackViewLeadingConstraint.constant = { + if let customPadding: CGFloat = info.styling.customPadding?.leading { + return customPadding + } + + return (leftFitToEdge ? 0 : Values.mediumSpacing) + }() + contentStackViewTrailingConstraint.constant = { + if let customPadding: CGFloat = info.styling.customPadding?.trailing { + return -customPadding + } + + return -(rightFitToEdge ? 0 : Values.mediumSpacing) + }() + contentStackViewBottomConstraint.constant = { + if let customPadding: CGFloat = info.styling.customPadding?.bottom { + return -customPadding + } + + return -(leftFitToEdge || rightFitToEdge ? 0 : Values.mediumSpacing) + }() + titleTextFieldLeadingConstraint.constant = { + guard info.styling.backgroundStyle != .noBackground else { return 0 } + + return (leftFitToEdge ? 0 : Values.mediumSpacing) + }() + titleTextFieldTrailingConstraint.constant = { + guard info.styling.backgroundStyle != .noBackground else { return 0 } + + return -(rightFitToEdge ? 0 : Values.mediumSpacing) + }() // Styling and positioning let defaultEdgePadding: CGFloat - cellBackgroundView.themeBackgroundColor = (info.shouldHaveBackground ? - .settings_tabBackground : - nil - ) - cellSelectedBackgroundView.isHidden = (!info.isEnabled || !info.shouldHaveBackground) - switch style { + switch info.styling.backgroundStyle { case .rounded: + cellBackgroundView.themeBackgroundColor = .settings_tabBackground + cellSelectedBackgroundView.isHidden = !info.isEnabled + defaultEdgePadding = Values.mediumSpacing backgroundLeftConstraint.constant = Values.largeSpacing backgroundRightConstraint.constant = -Values.largeSpacing cellBackgroundView.layer.cornerRadius = SessionCell.cornerRadius case .edgeToEdge: + cellBackgroundView.themeBackgroundColor = .settings_tabBackground + cellSelectedBackgroundView.isHidden = !info.isEnabled + defaultEdgePadding = 0 backgroundLeftConstraint.constant = 0 backgroundRightConstraint.constant = 0 cellBackgroundView.layer.cornerRadius = 0 - case .roundedEdgeToEdge: + case .noBackground: defaultEdgePadding = Values.mediumSpacing - backgroundLeftConstraint.constant = 0 - backgroundRightConstraint.constant = 0 - cellBackgroundView.layer.cornerRadius = SessionCell.cornerRadius + backgroundLeftConstraint.constant = Values.largeSpacing + backgroundRightConstraint.constant = -Values.largeSpacing + cellBackgroundView.themeBackgroundColor = nil + cellBackgroundView.layer.cornerRadius = 0 + cellSelectedBackgroundView.isHidden = true } let fittedEdgePadding: CGFloat = { func targetSize(accessory: Accessory?) -> CGFloat { switch accessory { - case .icon(_, let iconSize, _, _), .iconAsync(let iconSize, _, _, _): + case .icon(_, let iconSize, _, _, _), .iconAsync(let iconSize, _, _, _, _): return iconSize.size default: return defaultEdgePadding @@ -394,43 +468,103 @@ public class SessionCell: UITableViewCell { botSeparatorLeftConstraint.constant = (leftFitToEdge ? fittedEdgePadding : defaultEdgePadding) botSeparatorRightConstraint.constant = (rightFitToEdge ? -fittedEdgePadding : -defaultEdgePadding) - switch position { + switch info.position { case .top: cellBackgroundView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] - topSeparator.isHidden = (style != .edgeToEdge) - botSeparator.isHidden = false + topSeparator.isHidden = ( + !info.styling.allowedSeparators.contains(.top) || + info.styling.backgroundStyle != .edgeToEdge + ) + botSeparator.isHidden = ( + !info.styling.allowedSeparators.contains(.bottom) || + info.styling.backgroundStyle == .noBackground + ) case .middle: cellBackgroundView.layer.maskedCorners = [] topSeparator.isHidden = true - botSeparator.isHidden = false + botSeparator.isHidden = ( + !info.styling.allowedSeparators.contains(.bottom) || + info.styling.backgroundStyle == .noBackground + ) case .bottom: cellBackgroundView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner] - topSeparator.isHidden = false - botSeparator.isHidden = (style != .edgeToEdge) + topSeparator.isHidden = true + botSeparator.isHidden = ( + !info.styling.allowedSeparators.contains(.bottom) || + info.styling.backgroundStyle != .edgeToEdge + ) case .individual: cellBackgroundView.layer.maskedCorners = [ .layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner ] - topSeparator.isHidden = (style != .edgeToEdge) - botSeparator.isHidden = (style != .edgeToEdge) + topSeparator.isHidden = ( + !info.styling.allowedSeparators.contains(.top) || + info.styling.backgroundStyle != .edgeToEdge + ) + botSeparator.isHidden = ( + !info.styling.allowedSeparators.contains(.bottom) || + info.styling.backgroundStyle != .edgeToEdge + ) } } - public func update(isEditing: Bool, animated: Bool) {} + public func update(isEditing: Bool, becomeFirstResponder: Bool, animated: Bool) { + // Note: We set 'isUserInteractionEnabled' based on the 'info.isEditable' flag + // so can use that to determine whether this element can become editable + guard interactionMode == .editable || interactionMode == .alwaysEditing else { return } + + self.isEditingTitle = isEditing + + let changes = { [weak self] in + self?.titleLabel.alpha = (isEditing ? 0 : 1) + self?.titleTextField.alpha = (isEditing ? 1 : 0) + self?.leftAccessoryView.alpha = (isEditing ? 0 : 1) + self?.rightAccessoryView.alpha = (isEditing ? 0 : 1) + self?.titleMinHeightConstraint.isActive = isEditing + } + let completion: (Bool) -> Void = { [weak self] complete in + self?.titleTextField.text = self?.originalInputValue + } + + if animated { + UIView.animate(withDuration: 0.25, animations: changes, completion: completion) + } + else { + changes() + completion(true) + } + + if isEditing && becomeFirstResponder { + titleTextField.becomeFirstResponder() + } + else if !isEditing { + titleTextField.resignFirstResponder() + } + } // MARK: - Interaction public override func setHighlighted(_ highlighted: Bool, animated: Bool) { super.setHighlighted(highlighted, animated: animated) + // When editing disable the highlighted state changes (would result in UI elements + // reappearing otherwise) + guard !self.isEditingTitle else { return } + // If the 'cellSelectedBackgroundView' is hidden then there is no background so we // should update the titleLabel to indicate the highlighted state - if cellSelectedBackgroundView.isHidden { - titleLabel.alpha = (highlighted ? 0.8 : 1) + if cellSelectedBackgroundView.isHidden && shouldHighlightTitle { + // Note: We delay the "unhighlight" of the titleLabel so that the transition doesn't + // conflict with the transition into edit mode + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(10)) { [weak self] in + guard self?.isEditingTitle == false else { return } + + self?.titleLabel.alpha = (highlighted ? 0.8 : 1) + } } cellSelectedBackgroundView.alpha = (highlighted ? 1 : 0) @@ -440,12 +574,18 @@ public class SessionCell: UITableViewCell { public override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) - + leftAccessoryView.setSelected(selected, animated: animated) rightAccessoryView.setSelected(selected, animated: animated) } - - @objc private func extraActionTapped() { - onExtraActionTap?() +} + +// MARK: - Compose + +extension CombineCompatible where Self: SessionCell { + var textPublisher: AnyPublisher { + return self.titleTextField.publisher(for: .editingChanged) + .map { textField -> String in (textField.text ?? "") } + .eraseToAnyPublisher() } } diff --git a/Session/Shared/Views/SessionHeaderView.swift b/Session/Shared/Views/SessionHeaderView.swift index bcb0fb768..4cb54f798 100644 --- a/Session/Shared/Views/SessionHeaderView.swift +++ b/Session/Shared/Views/SessionHeaderView.swift @@ -4,34 +4,44 @@ import UIKit import SessionUIKit class SessionHeaderView: UITableViewHeaderFooterView { - private lazy var emptyHeightConstraint: NSLayoutConstraint = self.heightAnchor - .constraint(equalToConstant: (Values.verySmallSpacing * 2)) - private lazy var filledHeightConstraint: NSLayoutConstraint = self.heightAnchor - .constraint(greaterThanOrEqualToConstant: Values.mediumSpacing) - // MARK: - UI - private let stackView: UIStackView = { - let result: UIStackView = UIStackView() - result.translatesAutoresizingMaskIntoConstraints = false - result.axis = .vertical - result.distribution = .fill - result.alignment = .fill - result.isLayoutMarginsRelativeArrangement = true - - return result - }() + private lazy var titleLabelConstraints: [NSLayoutConstraint] = [ + titleLabel.pin(.top, to: .top, of: self, withInset: Values.mediumSpacing), + titleLabel.pin(.bottom, to: .bottom, of: self, withInset: -Values.mediumSpacing) + ] + private lazy var titleLabelLeadingConstraint: NSLayoutConstraint = titleLabel.pin(.leading, to: .leading, of: self) + private lazy var titleLabelTrailingConstraint: NSLayoutConstraint = titleLabel.pin(.trailing, to: .trailing, of: self) + private lazy var titleSeparatorLeadingConstraint: NSLayoutConstraint = titleSeparator.pin(.leading, to: .leading, of: self) + private lazy var titleSeparatorTrailingConstraint: NSLayoutConstraint = titleSeparator.pin(.trailing, to: .trailing, of: self) private let titleLabel: UILabel = { let result: UILabel = UILabel() result.translatesAutoresizingMaskIntoConstraints = false result.font = .systemFont(ofSize: Values.mediumFontSize) result.themeTextColor = .textSecondary + result.isHidden = true return result }() - private let separator: UIView = UIView.separator() + private let titleSeparator: Separator = { + let result: Separator = Separator() + result.isHidden = true + + return result + }() + + private let loadingIndicator: UIActivityIndicatorView = { + let result: UIActivityIndicatorView = UIActivityIndicatorView(style: .medium) + result.themeTintColor = .textPrimary + result.alpha = 0.5 + result.startAnimating() + result.hidesWhenStopped = true + result.isHidden = true + + return result + }() // MARK: - Initialization @@ -41,10 +51,9 @@ class SessionHeaderView: UITableViewHeaderFooterView { self.backgroundView = UIView() self.backgroundView?.themeBackgroundColor = .backgroundPrimary - addSubview(stackView) - addSubview(separator) - - stackView.addArrangedSubview(titleLabel) + addSubview(titleLabel) + addSubview(titleSeparator) + addSubview(loadingIndicator) setupLayout() } @@ -54,42 +63,59 @@ class SessionHeaderView: UITableViewHeaderFooterView { } private func setupLayout() { - stackView.pin(to: self) + titleLabel.pin(.top, to: .top, of: self, withInset: Values.mediumSpacing) + titleLabel.pin(.bottom, to: .bottom, of: self, withInset: Values.mediumSpacing) + titleLabel.center(.vertical, in: self) - separator.pin(.left, to: .left, of: self) - separator.pin(.right, to: .right, of: self) - separator.pin(.bottom, to: .bottom, of: self) + titleSeparator.center(.vertical, in: self) + loadingIndicator.center(in: self) } // MARK: - Content + override func prepareForReuse() { + super.prepareForReuse() + + titleLabel.isHidden = true + titleSeparator.isHidden = true + loadingIndicator.isHidden = true + + titleLabelLeadingConstraint.isActive = false + titleLabelTrailingConstraint.isActive = false + titleLabelConstraints.forEach { $0.isActive = false } + + titleSeparator.center(.vertical, in: self) + titleSeparatorLeadingConstraint.isActive = false + titleSeparatorTrailingConstraint.isActive = false + } + public func update( - style: SessionCell.Style = .rounded, title: String?, - hasSeparator: Bool + style: SessionTableSectionStyle = .titleRoundedContent ) { let titleIsEmpty: Bool = (title ?? "").isEmpty - let edgePadding: CGFloat = { - switch style { - case .rounded: - // Align to the start of the text in the cell - return (Values.largeSpacing + Values.mediumSpacing) - - case .edgeToEdge, .roundedEdgeToEdge: return Values.largeSpacing - } - }() - titleLabel.text = title - titleLabel.isHidden = titleIsEmpty - stackView.layoutMargins = UIEdgeInsets( - top: (titleIsEmpty ? Values.verySmallSpacing : Values.mediumSpacing), - left: edgePadding, - bottom: (titleIsEmpty ? Values.verySmallSpacing : Values.mediumSpacing), - right: edgePadding - ) - emptyHeightConstraint.isActive = titleIsEmpty - filledHeightConstraint.isActive = !titleIsEmpty - separator.isHidden = (style == .rounded || !hasSeparator) + switch style { + case .titleRoundedContent, .titleEdgeToEdgeContent, .titleNoBackgroundContent: + titleLabel.text = title + titleLabel.isHidden = titleIsEmpty + titleLabelLeadingConstraint.constant = style.edgePadding + titleLabelTrailingConstraint.constant = -style.edgePadding + titleLabelLeadingConstraint.isActive = !titleIsEmpty + titleLabelTrailingConstraint.isActive = !titleIsEmpty + titleLabelConstraints.forEach { $0.isActive = true } + + case .titleSeparator: + titleSeparator.update(title: title) + titleSeparator.isHidden = false + titleSeparatorLeadingConstraint.constant = style.edgePadding + titleSeparatorTrailingConstraint.constant = -style.edgePadding + titleSeparatorLeadingConstraint.isActive = !titleIsEmpty + titleSeparatorTrailingConstraint.isActive = !titleIsEmpty + + case .none, .padding: break + case .loadMore: loadingIndicator.isHidden = false + } self.layoutIfNeeded() } diff --git a/Session/Utilities/BackgroundPoller.swift b/Session/Utilities/BackgroundPoller.swift index c618c40b1..5b5745127 100644 --- a/Session/Utilities/BackgroundPoller.swift +++ b/Session/Utilities/BackgroundPoller.swift @@ -110,22 +110,14 @@ public final class BackgroundPoller { .eraseToAnyPublisher() } - let promises: [Promise] = jobsToRun.map { job -> Promise in - let (promise, seal) = Promise.pending() - - // Note: In the background we just want jobs to fail silently - MessageReceiveJob.run( - job, - queue: DispatchQueue.main, - success: { _, _ in seal.fulfill(()) }, - failure: { _, _, _ in seal.fulfill(()) }, - deferred: { _ in seal.fulfill(()) } - ) - - return promise - } - - return when(fulfilled: promises) + return ClosedGroupPoller.poll( + namespaces: ClosedGroupPoller.namespaces, + from: snode, + for: groupPublicKey, + on: DispatchQueue.main, + calledFromBackgroundPoller: true, + isBackgroundPollValid: { BackgroundPoller.isValid } + ) } .eraseToAnyPublisher() } diff --git a/SessionMessagingKit/Common Networking/Header.swift b/SessionMessagingKit/Common Networking/Header.swift deleted file mode 100644 index 6c33e41a3..000000000 --- a/SessionMessagingKit/Common Networking/Header.swift +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import Foundation - -enum Header: String { - case authorization = "Authorization" - case contentType = "Content-Type" - case contentDisposition = "Content-Disposition" - - case sogsPubKey = "X-SOGS-Pubkey" - case sogsNonce = "X-SOGS-Nonce" - case sogsTimestamp = "X-SOGS-Timestamp" - case sogsSignature = "X-SOGS-Signature" -} - -// MARK: - Convenience - -extension Dictionary where Key == Header, Value == String { - func toHTTPHeaders() -> [String: String] { - return self.reduce(into: [:]) { result, next in result[next.key.rawValue] = next.value } - } -} diff --git a/SessionMessagingKit/Common Networking/QueryParam.swift b/SessionMessagingKit/Common Networking/QueryParam.swift deleted file mode 100644 index d50ffbab5..000000000 --- a/SessionMessagingKit/Common Networking/QueryParam.swift +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import Foundation - -enum QueryParam: String { - case publicKey = "public_key" - case fromServerId = "from_server_id" - - case required = "required" - case limit // For messages - number between 1 and 256 (default is 100) - case platform // For file server session version check - case updateTypes = "t" // String indicating the types of updates that the client supports - - case reactors = "reactors" -} diff --git a/SessionMessagingKit/Database/Models/ClosedGroup.swift b/SessionMessagingKit/Database/Models/ClosedGroup.swift index 9016643c3..085744805 100644 --- a/SessionMessagingKit/Database/Models/ClosedGroup.swift +++ b/SessionMessagingKit/Database/Models/ClosedGroup.swift @@ -92,16 +92,6 @@ public extension ClosedGroup { // MARK: - Convenience public extension ClosedGroup { - func asProfile() -> Profile { - return Profile( - id: threadId, - name: name, - profilePictureUrl: groupImageUrl, - profilePictureFileName: groupImageFileName, - profileEncryptionKey: groupImageEncryptionKey - ) - } - static func removeKeysAndUnsubscribe( _ db: Database? = nil, threadId: String, diff --git a/SessionMessagingKit/File Server/Types/FSEndpoint.swift b/SessionMessagingKit/File Server/Types/FSEndpoint.swift index 5e242bea8..edc555f4f 100644 --- a/SessionMessagingKit/File Server/Types/FSEndpoint.swift +++ b/SessionMessagingKit/File Server/Types/FSEndpoint.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import SessionUtilitiesKit extension FileServerAPI { public enum Endpoint: EndpointType { @@ -8,7 +9,7 @@ extension FileServerAPI { case fileIndividual(fileId: String) case sessionVersion - var path: String { + public var path: String { switch self { case .file: return "file" case .fileIndividual(let fileId): return "file/\(fileId)" diff --git a/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift b/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift index ec17d153e..4331ba7a4 100644 --- a/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift +++ b/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift @@ -338,8 +338,6 @@ public final class ClosedGroupControlMessage: ControlMessage { let contentProto = SNProtoContent.builder() let dataMessageProto = SNProtoDataMessage.builder() dataMessageProto.setClosedGroupControlMessage(try closedGroupControlMessage.build()) - // Group context - try setGroupContextIfNeeded(db, on: dataMessageProto) contentProto.setDataMessage(try dataMessageProto.build()) return try contentProto.build() } catch { diff --git a/SessionMessagingKit/Messages/Control Messages/ExpirationTimerUpdate.swift b/SessionMessagingKit/Messages/Control Messages/ExpirationTimerUpdate.swift index 3d7aadda5..1d8de57df 100644 --- a/SessionMessagingKit/Messages/Control Messages/ExpirationTimerUpdate.swift +++ b/SessionMessagingKit/Messages/Control Messages/ExpirationTimerUpdate.swift @@ -77,13 +77,6 @@ public final class ExpirationTimerUpdate: ControlMessage { dataMessageProto.setFlags(UInt32(SNProtoDataMessage.SNProtoDataMessageFlags.expirationTimerUpdate.rawValue)) dataMessageProto.setExpireTimer(duration) if let syncTarget = syncTarget { dataMessageProto.setSyncTarget(syncTarget) } - // Group context - do { - try setGroupContextIfNeeded(db, on: dataMessageProto) - } catch { - SNLog("Couldn't construct expiration timer update proto from: \(self).") - return nil - } let contentProto = SNProtoContent.builder() do { contentProto.setDataMessage(try dataMessageProto.build()) diff --git a/SessionMessagingKit/Messages/Message+Destination.swift b/SessionMessagingKit/Messages/Message+Destination.swift index 9694b2670..40211e90f 100644 --- a/SessionMessagingKit/Messages/Message+Destination.swift +++ b/SessionMessagingKit/Messages/Message+Destination.swift @@ -24,6 +24,13 @@ public extension Message { ) case openGroupInbox(server: String, openGroupPublicKey: String, blindedPublicKey: String) + var namespace: SnodeAPI.Namespace { + switch self { + case .contact(_, let namespace), .closedGroup(_, let namespace): return namespace + default: preconditionFailure("Attepted to retrieve namespace for invalid destination") + } + } + public static func from( _ db: Database, thread: SessionThread, diff --git a/SessionMessagingKit/Messages/Message.swift b/SessionMessagingKit/Messages/Message.swift index 6d8c63a64..dc31d502e 100644 --- a/SessionMessagingKit/Messages/Message.swift +++ b/SessionMessagingKit/Messages/Message.swift @@ -63,18 +63,6 @@ public class Message: Codable { public func toProto(_ db: Database) -> SNProtoContent? { preconditionFailure("toProto(_:) is abstract and must be overridden.") } - - public func setGroupContextIfNeeded(_ db: Database, on dataMessage: SNProtoDataMessage.SNProtoDataMessageBuilder) throws { - guard - let threadId: String = threadId, - (try? ClosedGroup.exists(db, id: threadId)) == true, - let legacyGroupId: Data = "\(SMKLegacy.closedGroupIdPrefix)\(threadId)".data(using: .utf8) - else { return } - - // Android needs a group context or it'll interpret the message as a one-to-one message - let groupProto = SNProtoGroupContext.builder(id: legacyGroupId, type: .deliver) - dataMessage.setGroup(try groupProto.build()) - } } // MARK: - Message Parsing/Processing diff --git a/SessionMessagingKit/Messages/Visible Messages/VisibleMessage.swift b/SessionMessagingKit/Messages/Visible Messages/VisibleMessage.swift index e48da7f33..8bd727094 100644 --- a/SessionMessagingKit/Messages/Visible Messages/VisibleMessage.swift +++ b/SessionMessagingKit/Messages/Visible Messages/VisibleMessage.swift @@ -158,7 +158,7 @@ public final class VisibleMessage: Message { // Attachments - let attachments: [Attachment]? = try? Attachment.fetchAll(db, ids: self.attachmentIds) + let attachments: [Attachment]? = try? Attachment.fetchAll(db, ids: self.attachmentIds) let attachmentProtos = (attachments ?? []).compactMap { $0.buildProto() } dataMessage.setAttachments(attachmentProtos) @@ -175,14 +175,6 @@ public final class VisibleMessage: Message { dataMessage.setReaction(reactionProto) } - // Group context - do { - try setGroupContextIfNeeded(db, on: dataMessage) - } catch { - SNLog("Couldn't construct visible message proto from: \(self).") - return nil - } - // Sync target if let syncTarget = syncTarget { dataMessage.setSyncTarget(syncTarget) diff --git a/SessionMessagingKit/Open Groups/Models/BatchRequestInfo.swift b/SessionMessagingKit/Open Groups/Models/BatchRequestInfo.swift deleted file mode 100644 index fb3ac4e41..000000000 --- a/SessionMessagingKit/Open Groups/Models/BatchRequestInfo.swift +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import Foundation -import PromiseKit -import SessionUtilitiesKit -import SessionSnodeKit - -extension OpenGroupAPI { - // MARK: - BatchSubRequest - - struct BatchSubRequest: Encodable { - enum CodingKeys: String, CodingKey { - case method - case path - case headers - case json - case b64 - case bytes - } - - let method: HTTP.Verb - let path: String - let headers: [String: String]? - - /// The `jsonBodyEncoder` is used to avoid having to make `BatchSubRequest` a generic type (haven't found a good way - /// to keep `BatchSubRequest` encodable using protocols unfortunately so need this work around) - private let jsonBodyEncoder: ((inout KeyedEncodingContainer, CodingKeys) throws -> ())? - private let b64: String? - private let bytes: [UInt8]? - - init(request: Request) { - self.method = request.method - self.path = request.urlPathAndParamsString - self.headers = (request.headers.isEmpty ? nil : request.headers.toHTTPHeaders()) - - // Note: Need to differentiate between JSON, b64 string and bytes body values to ensure they are - // encoded correctly so the server knows how to handle them - switch request.body { - case let bodyString as String: - self.jsonBodyEncoder = nil - self.b64 = bodyString - self.bytes = nil - - case let bodyBytes as [UInt8]: - self.jsonBodyEncoder = nil - self.b64 = nil - self.bytes = bodyBytes - - default: - self.jsonBodyEncoder = { [body = request.body] container, key in - try container.encodeIfPresent(body, forKey: key) - } - self.b64 = nil - self.bytes = nil - } - } - - func encode(to encoder: Encoder) throws { - var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(method, forKey: .method) - try container.encode(path, forKey: .path) - try container.encodeIfPresent(headers, forKey: .headers) - try jsonBodyEncoder?(&container, .json) - try container.encodeIfPresent(b64, forKey: .b64) - try container.encodeIfPresent(bytes, forKey: .bytes) - } - } - - // MARK: - BatchSubResponse - - struct BatchSubResponse: Codable { - /// The numeric http response code (e.g. 200 for success) - let code: Int32 - - /// This should always include the content type of the request - let headers: [String: String] - - /// The body of the request; will be plain json if content-type is `application/json`, otherwise it will be base64 encoded data - let body: T? - - /// A flag to indicate that there was a body but it failed to parse - let failedToParseBody: Bool - } - - // MARK: - BatchRequestInfo - - struct BatchRequestInfo: BatchRequestInfoType { - let request: Request - let responseType: Codable.Type - - var endpoint: Endpoint { request.endpoint } - - init(request: Request, responseType: R.Type) { - self.request = request - self.responseType = BatchSubResponse.self - } - - init(request: Request) { - self.init( - request: request, - responseType: NoResponse.self - ) - } - - func toSubRequest() -> BatchSubRequest { - return BatchSubRequest(request: request) - } - } - - // MARK: - BatchRequest - - typealias BatchRequest = [BatchSubRequest] - typealias BatchResponseTypes = [Codable.Type] - typealias BatchResponse = [(OnionRequestResponseInfoType, Codable?)] -} - -extension OpenGroupAPI.BatchSubResponse { - init(from decoder: Decoder) throws { - let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) - let body: T? = try? container.decode(T.self, forKey: .body) - - self = OpenGroupAPI.BatchSubResponse( - code: try container.decode(Int32.self, forKey: .code), - headers: try container.decode([String: String].self, forKey: .headers), - body: body, - failedToParseBody: (body == nil && T.self != NoResponse.self && !(T.self is ExpressibleByNilLiteral.Type)) - ) - } -} - -// MARK: - BatchRequestInfoType - -/// This protocol is designed to erase the types from `BatchRequestInfo` so multiple types can be used -/// in arrays when doing `/batch` and `/sequence` requests -protocol BatchRequestInfoType { - var responseType: Codable.Type { get } - var endpoint: OpenGroupAPI.Endpoint { get } - - func toSubRequest() -> OpenGroupAPI.BatchSubRequest -} - -// MARK: - Convenience - -public extension Decodable { - static func decoded(from data: Data, using dependencies: Dependencies = Dependencies()) throws -> Self { - return try data.decoded(as: Self.self, using: dependencies) - } -} - -extension Promise where T == (OnionRequestResponseInfoType, Data?) { - func decoded(as types: OpenGroupAPI.BatchResponseTypes, on queue: DispatchQueue? = nil, using dependencies: Dependencies = Dependencies()) -> Promise { - self.map(on: queue) { responseInfo, maybeData -> OpenGroupAPI.BatchResponse in - // Need to split the data into an array of data so each item can be Decoded correctly - guard let data: Data = maybeData else { throw HTTP.Error.parsingFailed } - guard let jsonObject: Any = try? JSONSerialization.jsonObject(with: data, options: [.fragmentsAllowed]) else { - throw HTTP.Error.parsingFailed - } - guard let anyArray: [Any] = jsonObject as? [Any] else { throw HTTP.Error.parsingFailed } - - let dataArray: [Data] = anyArray.compactMap { try? JSONSerialization.data(withJSONObject: $0) } - guard dataArray.count == types.count else { throw HTTP.Error.parsingFailed } - - do { - return try zip(dataArray, types) - .map { data, type in try type.decoded(from: data, using: dependencies) } - .map { data in (responseInfo, data) } - } - catch { - throw HTTP.Error.parsingFailed - } - } - } -} diff --git a/SessionMessagingKit/Open Groups/Models/SOGSMessage.swift b/SessionMessagingKit/Open Groups/Models/SOGSMessage.swift index 8ce774b31..5bbccaf02 100644 --- a/SessionMessagingKit/Open Groups/Models/SOGSMessage.swift +++ b/SessionMessagingKit/Open Groups/Models/SOGSMessage.swift @@ -67,10 +67,10 @@ extension OpenGroupAPI.Message { // If we have data and a signature (ie. the message isn't a deletion) then validate the signature if let base64EncodedData: String = maybeBase64EncodedData, let base64EncodedSignature: String = maybeBase64EncodedSignature { guard let sender: String = maybeSender, let data = Data(base64Encoded: base64EncodedData), let signature = Data(base64Encoded: base64EncodedSignature) else { - throw HTTP.Error.parsingFailed + throw HTTPError.parsingFailed } guard let dependencies: SMKDependencies = decoder.userInfo[Dependencies.userInfoKey] as? SMKDependencies else { - throw HTTP.Error.parsingFailed + throw HTTPError.parsingFailed } // Verify the signature based on the SessionId.Prefix type @@ -80,18 +80,18 @@ extension OpenGroupAPI.Message { case .blinded: guard dependencies.sign.verify(message: data.bytes, publicKey: publicKey.bytes, signature: signature.bytes) else { SNLog("Ignoring message with invalid signature.") - throw HTTP.Error.parsingFailed + throw HTTPError.parsingFailed } case .standard, .unblinded: guard (try? dependencies.ed25519.verifySignature(signature, publicKey: publicKey, data: data)) == true else { SNLog("Ignoring message with invalid signature.") - throw HTTP.Error.parsingFailed + throw HTTPError.parsingFailed } case .none: SNLog("Ignoring message with invalid sender.") - throw HTTP.Error.parsingFailed + throw HTTPError.parsingFailed } } diff --git a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift index 1261d47b3..f485c57bb 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift @@ -54,8 +54,8 @@ public enum OpenGroupAPI { .defaulting(to: []) // Generate the requests - let requestResponseType: [BatchRequestInfoType] = [ - BatchRequestInfo( + let requestResponseType: [BatchRequest.Info] = [ + BatchRequest.Info( request: Request( server: server, endpoint: .capabilities @@ -71,7 +71,7 @@ public enum OpenGroupAPI { .filter(OpenGroup.Columns.roomToken != "") .fetchAll(db)) .defaulting(to: []) - .flatMap { openGroup -> [BatchRequestInfoType] in + .flatMap { openGroup -> [BatchRequest.Info] in let shouldRetrieveRecentMessages: Bool = ( openGroup.sequenceNumber == 0 || ( // If it's the first poll for this launch and it's been longer than @@ -83,14 +83,14 @@ public enum OpenGroupAPI { ) return [ - BatchRequestInfo( + BatchRequest.Info( request: Request( server: server, endpoint: .roomPollInfo(openGroup.roomToken, openGroup.infoUpdates) ), responseType: RoomPollInfo.self ), - BatchRequestInfo( + BatchRequest.Info( request: Request( server: server, endpoint: (shouldRetrieveRecentMessages ? @@ -113,7 +113,7 @@ public enum OpenGroupAPI { !capabilities.contains(.blind) ? [] : [ // Inbox - BatchRequestInfo( + BatchRequest.Info( request: Request( server: server, endpoint: (lastInboxMessageId == 0 ? @@ -125,7 +125,7 @@ public enum OpenGroupAPI { ), // Outbox - BatchRequestInfo( + BatchRequest.Info( request: Request( server: server, endpoint: (lastOutboxMessageId == 0 ? @@ -151,7 +151,7 @@ public enum OpenGroupAPI { private static func batch( _ db: Database, server: String, - requests: [BatchRequestInfoType], + requests: [BatchRequest.Info], using dependencies: SMKDependencies = SMKDependencies() ) -> AnyPublisher<[Endpoint: (ResponseInfoType, Codable?)], Error> { let responseTypes = requests.map { $0.responseType } @@ -163,7 +163,7 @@ public enum OpenGroupAPI { method: .post, server: server, endpoint: Endpoint.batch, - body: requestBody + body: BatchRequest(requests: requests) ), using: dependencies ) @@ -183,7 +183,7 @@ public enum OpenGroupAPI { private static func sequence( _ db: Database, server: String, - requests: [BatchRequestInfoType], + requests: [BatchRequest.Info], using dependencies: SMKDependencies = SMKDependencies() ) -> AnyPublisher<[Endpoint: (ResponseInfoType, Codable?)], Error> { let responseTypes = requests.map { $0.responseType } @@ -195,7 +195,7 @@ public enum OpenGroupAPI { method: .post, server: server, endpoint: Endpoint.sequence, - body: requestBody + body: BatchRequest(requests: requests) ), using: dependencies ) @@ -315,7 +315,7 @@ public enum OpenGroupAPI { ) -> AnyPublisher<(capabilities: (info: ResponseInfoType, data: Capabilities), room: (info: ResponseInfoType, data: Room)), Error> { let requestResponseType: [BatchRequest.Info] = [ // Get the latest capabilities for the server (in case it's a new server or the cached ones are stale) - BatchRequestInfo( + BatchRequest.Info( request: Request( server: server, endpoint: .capabilities @@ -324,7 +324,7 @@ public enum OpenGroupAPI { ), // And the room info - BatchRequestInfo( + BatchRequest.Info( request: Request( server: server, endpoint: .room(roomToken) @@ -351,13 +351,13 @@ public enum OpenGroupAPI { } }) .map { _, value in value } - let maybeRoom: (info: OnionRequestResponseInfoType, data: Room?)? = maybeRoomResponse - .map { info, data in (info, (data as? BatchSubResponse)?.body) } + let maybeRoom: (info: ResponseInfoType, data: Room?)? = maybeRoomResponse + .map { info, data in (info, (data as? HTTP.BatchSubResponse)?.body) } guard - let capabilitiesInfo: OnionRequestResponseInfoType = maybeCapabilities?.info, + let capabilitiesInfo: ResponseInfoType = maybeCapabilities?.info, let capabilities: Capabilities = maybeCapabilities?.data, - let roomInfo: OnionRequestResponseInfoType = maybeRoom?.info, + let roomInfo: ResponseInfoType = maybeRoom?.info, let room: Room = maybeRoom?.data else { return Fail(error: HTTPError.parsingFailed) @@ -383,7 +383,7 @@ public enum OpenGroupAPI { ) -> AnyPublisher<(capabilities: (info: ResponseInfoType, data: Capabilities), rooms: (info: ResponseInfoType, data: [Room])), Error> { let requestResponseType: [BatchRequest.Info] = [ // Get the latest capabilities for the server (in case it's a new server or the cached ones are stale) - BatchRequestInfo( + BatchRequest.Info( request: Request( server: server, endpoint: .capabilities @@ -392,7 +392,7 @@ public enum OpenGroupAPI { ), // And the room info - BatchRequestInfo( + BatchRequest.Info( request: Request( server: server, endpoint: .rooms @@ -419,13 +419,13 @@ public enum OpenGroupAPI { } }) .map { _, value in value } - let maybeRooms: (info: OnionRequestResponseInfoType, data: [Room]?)? = maybeRoomResponse - .map { info, data in (info, (data as? BatchSubResponse<[Room]>)?.body) } + let maybeRooms: (info: ResponseInfoType, data: [Room]?)? = maybeRoomResponse + .map { info, data in (info, (data as? HTTP.BatchSubResponse<[Room]>)?.body) } guard - let capabilitiesInfo: OnionRequestResponseInfoType = maybeCapabilities?.info, + let capabilitiesInfo: ResponseInfoType = maybeCapabilities?.info, let capabilities: Capabilities = maybeCapabilities?.data, - let roomsInfo: OnionRequestResponseInfoType = maybeRooms?.info, + let roomsInfo: ResponseInfoType = maybeRooms?.info, let rooms: [Room] = maybeRooms?.data else { return Fail(error: HTTPError.parsingFailed) @@ -1239,16 +1239,16 @@ public enum OpenGroupAPI { ) // Generate the requests - let requestResponseType: [BatchRequestInfoType] = [ - BatchRequestInfo( - request: Request( + let requestResponseType: [BatchRequest.Info] = [ + BatchRequest.Info( + request: Request( method: .post, server: server, endpoint: .userBan(sessionId), body: banRequestBody ) ), - BatchRequestInfo( + BatchRequest.Info( request: Request( method: .delete, server: server, @@ -1390,10 +1390,10 @@ public enum OpenGroupAPI { updatedRequest.allHTTPHeaderFields = (request.allHTTPHeaderFields ?? [:]) .updated(with: [ - Header.sogsPubKey.rawValue: signResult.publicKey, - Header.sogsTimestamp.rawValue: "\(timestamp)", - Header.sogsNonce.rawValue: nonce.base64EncodedString(), - Header.sogsSignature.rawValue: signResult.signature.toBase64() + HTTPHeader.sogsPubKey: signResult.publicKey, + HTTPHeader.sogsTimestamp: "\(timestamp)", + HTTPHeader.sogsNonce: nonce.base64EncodedString(), + HTTPHeader.sogsSignature: signResult.signature.toBase64() ]) return updatedRequest diff --git a/SessionMessagingKit/Open Groups/Types/HTTPHeader+OpenGroup.swift b/SessionMessagingKit/Open Groups/Types/HTTPHeader+OpenGroup.swift new file mode 100644 index 000000000..9b844a9bf --- /dev/null +++ b/SessionMessagingKit/Open Groups/Types/HTTPHeader+OpenGroup.swift @@ -0,0 +1,11 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import SessionUtilitiesKit + +public extension HTTPHeader { + static let sogsPubKey: HTTPHeader = "X-SOGS-Pubkey" + static let sogsNonce: HTTPHeader = "X-SOGS-Nonce" + static let sogsTimestamp: HTTPHeader = "X-SOGS-Timestamp" + static let sogsSignature: HTTPHeader = "X-SOGS-Signature" +} diff --git a/SessionMessagingKit/Open Groups/Types/HTTPQueryParam+OpenGroup.swift b/SessionMessagingKit/Open Groups/Types/HTTPQueryParam+OpenGroup.swift new file mode 100644 index 000000000..eac835a7f --- /dev/null +++ b/SessionMessagingKit/Open Groups/Types/HTTPQueryParam+OpenGroup.swift @@ -0,0 +1,21 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import SessionUtilitiesKit + +public extension HTTPQueryParam { + static let publicKey: HTTPQueryParam = "public_key" + static let fromServerId: HTTPQueryParam = "from_server_id" + + static let required: HTTPQueryParam = "required" + + /// For messages - number between 1 and 256 (default is 100) + static let limit: HTTPQueryParam = "limit" + + /// For file server session version check + static let platform: HTTPQueryParam = "platform" + + /// String indicating the types of updates that the client supports + static let updateTypes: HTTPQueryParam = "t" + static let reactors: HTTPQueryParam = "reactors" +} diff --git a/SessionMessagingKit/Open Groups/Types/SOGSEndpoint.swift b/SessionMessagingKit/Open Groups/Types/SOGSEndpoint.swift index 60c148595..483d2f253 100644 --- a/SessionMessagingKit/Open Groups/Types/SOGSEndpoint.swift +++ b/SessionMessagingKit/Open Groups/Types/SOGSEndpoint.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import SessionUtilitiesKit extension OpenGroupAPI { public enum Endpoint: EndpointType { @@ -58,7 +59,7 @@ extension OpenGroupAPI { case userUnban(String) case userModerator(String) - var path: String { + public var path: String { switch self { // Utility diff --git a/SessionMessagingKit/SMKDependencies.swift b/SessionMessagingKit/SMKDependencies.swift new file mode 100644 index 000000000..c8d9c18e0 --- /dev/null +++ b/SessionMessagingKit/SMKDependencies.swift @@ -0,0 +1,93 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +import Foundation +import GRDB +import Sodium +import SessionSnodeKit +import SessionUtilitiesKit + +public class SMKDependencies: SSKDependencies { + internal var _sodium: Atomic + public var sodium: SodiumType { + get { Dependencies.getValueSettingIfNull(&_sodium) { Sodium() } } + set { _sodium.mutate { $0 = newValue } } + } + + internal var _box: Atomic + public var box: BoxType { + get { Dependencies.getValueSettingIfNull(&_box) { sodium.getBox() } } + set { _box.mutate { $0 = newValue } } + } + + internal var _genericHash: Atomic + public var genericHash: GenericHashType { + get { Dependencies.getValueSettingIfNull(&_genericHash) { sodium.getGenericHash() } } + set { _genericHash.mutate { $0 = newValue } } + } + + internal var _sign: Atomic + public var sign: SignType { + get { Dependencies.getValueSettingIfNull(&_sign) { sodium.getSign() } } + set { _sign.mutate { $0 = newValue } } + } + + internal var _aeadXChaCha20Poly1305Ietf: Atomic + public var aeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType { + get { Dependencies.getValueSettingIfNull(&_aeadXChaCha20Poly1305Ietf) { sodium.getAeadXChaCha20Poly1305Ietf() } } + set { _aeadXChaCha20Poly1305Ietf.mutate { $0 = newValue } } + } + + internal var _ed25519: Atomic + public var ed25519: Ed25519Type { + get { Dependencies.getValueSettingIfNull(&_ed25519) { Ed25519Wrapper() } } + set { _ed25519.mutate { $0 = newValue } } + } + + internal var _nonceGenerator16: Atomic + public var nonceGenerator16: NonceGenerator16ByteType { + get { Dependencies.getValueSettingIfNull(&_nonceGenerator16) { OpenGroupAPI.NonceGenerator16Byte() } } + set { _nonceGenerator16.mutate { $0 = newValue } } + } + + internal var _nonceGenerator24: Atomic + public var nonceGenerator24: NonceGenerator24ByteType { + get { Dependencies.getValueSettingIfNull(&_nonceGenerator24) { OpenGroupAPI.NonceGenerator24Byte() } } + set { _nonceGenerator24.mutate { $0 = newValue } } + } + + // MARK: - Initialization + + public init( + onionApi: OnionRequestAPIType.Type? = nil, + generalCache: Atomic? = nil, + storage: Storage? = nil, + scheduler: ValueObservationScheduler? = nil, + sodium: SodiumType? = nil, + box: BoxType? = nil, + genericHash: GenericHashType? = nil, + sign: SignType? = nil, + aeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType? = nil, + ed25519: Ed25519Type? = nil, + nonceGenerator16: NonceGenerator16ByteType? = nil, + nonceGenerator24: NonceGenerator24ByteType? = nil, + standardUserDefaults: UserDefaultsType? = nil, + date: Date? = nil + ) { + _sodium = Atomic(sodium) + _box = Atomic(box) + _genericHash = Atomic(genericHash) + _sign = Atomic(sign) + _aeadXChaCha20Poly1305Ietf = Atomic(aeadXChaCha20Poly1305Ietf) + _ed25519 = Atomic(ed25519) + _nonceGenerator16 = Atomic(nonceGenerator16) + _nonceGenerator24 = Atomic(nonceGenerator24) + + super.init( + onionApi: onionApi, + generalCache: generalCache, + storage: storage, + scheduler: scheduler, + standardUserDefaults: standardUserDefaults, + date: date + ) + } +} diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift index a1fcb82a8..0d1a02951 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift @@ -140,7 +140,7 @@ extension MessageReceiver { ).insert(db) // Start polling - ClosedGroupPoller.shared.startPolling(for: groupPublicKey) + ClosedGroupPoller.shared.startIfNeeded(for: groupPublicKey) // Notify the PN server let _ = PushNotificationAPI.performOperation(.subscribe, for: groupPublicKey, publicKey: getUserHexEncodedPublicKey(db)) diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift index f051c4750..ba6b1903c 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift @@ -83,7 +83,7 @@ extension MessageSender { .map { memberId -> MessageSender.PreparedSendData in try MessageSender.preparedSendData( db, - message: LegacyClosedGroupControlMessage( + message: ClosedGroupControlMessage( kind: .new( publicKey: Data(hex: groupPublicKey), name: name, @@ -99,7 +99,7 @@ extension MessageSender { // the 'ClosedGroup' object we created sentTimestampMs: UInt64(floor(formationTimestamp * 1000)) ), - to: .contact(publicKey: memberId), + to: .contact(publicKey: memberId, namespace: .default), interactionId: nil ) } @@ -263,7 +263,7 @@ extension MessageSender { threadId: thread.id, authorId: userPublicKey, variant: .infoClosedGroupUpdated, - body: LegacyClosedGroupControlMessage.Kind + body: ClosedGroupControlMessage.Kind .nameChange(name: name) .infoMessage(db, sender: userPublicKey), timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)) @@ -274,7 +274,7 @@ extension MessageSender { // Send the update to the group try MessageSender.send( db, - message: LegacyClosedGroupControlMessage(kind: .nameChange(name: name)), + message: ClosedGroupControlMessage(kind: .nameChange(name: name)), interactionId: interactionId, in: thread ) @@ -493,7 +493,7 @@ extension MessageSender { preparedSendData: try MessageSender .preparedSendData( db, - message: LegacyClosedGroupControlMessage( + message: ClosedGroupControlMessage( kind: .membersRemoved( members: removedMembers.map { Data(hex: $0) } ) @@ -546,7 +546,7 @@ extension MessageSender { threadId: thread.id, authorId: userPublicKey, variant: .infoClosedGroupCurrentUserLeft, - body: LegacyClosedGroupControlMessage.Kind + body: ClosedGroupControlMessage.Kind .memberLeft .infoMessage(db, sender: userPublicKey), timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)) @@ -561,7 +561,7 @@ extension MessageSender { sendData = try MessageSender .preparedSendData( db, - message: LegacyClosedGroupControlMessage( + message: ClosedGroupControlMessage( kind: .memberLeft ), to: try Message.Destination.from(db, thread: thread), diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift b/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift index 1b934ead5..0aa47a509 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift @@ -85,8 +85,8 @@ extension MessageSender { let threadId: String = { switch destination { - case .contact(let publicKey): return publicKey - case .closedGroup(let groupPublicKey): return groupPublicKey + case .contact(let publicKey, _): return publicKey + case .closedGroup(let groupPublicKey, _): return groupPublicKey case .openGroup(let roomToken, let server, _, _, _): return OpenGroup.idFor(roomToken: roomToken, server: server) @@ -162,7 +162,10 @@ extension MessageSender { // If we don't have a userKeyPair yet then there is no need to sync the configuration // as the user doesn't exist yet (this will get triggered on the first launch of a // fresh install due to the migrations getting run) - guard Identity.userExists(db) else { + guard + Identity.userExists(db), + let ed25519SecretKey: [UInt8] = Identity.fetchUserEd25519KeyPair(db)?.secretKey + else { return Fail(error: StorageError.generic) .eraseToAnyPublisher() } @@ -205,41 +208,31 @@ extension MessageSender { to: legacyDestination, interactionId: nil ) - - when( - resolved: try userConfigMessageChanges.map { message in - try MessageSender - .sendImmediate( - db, - message: message, - to: destination, - interactionId: nil - ) + + let userConfigSendData: [PreparedSendData] = try userConfigMessageChanges + .map { message in + try MessageSender.preparedSendData( + db, + message: message, + to: destination, + interactionId: nil + ) } - ) - .done { results in - let hadError: Bool = results.contains { result in - switch result { - case .fulfilled: return false - case .rejected: return true - } - } - - guard !hadError else { - seal.reject(StorageError.generic) - return - } - - seal.fulfill(()) - } - .catch { _ in seal.reject(StorageError.generic) } - .retainUntilComplete() /// We want to avoid blocking the db write thread so we dispatch the API call to a different thread return Just(()) .setFailureType(to: Error.self) .receive(on: DispatchQueue.global(qos: .userInitiated)) - .flatMap { _ in MessageSender.sendImmediate(preparedSendData: sendData) } + .flatMap { _ -> AnyPublisher in + Publishers + .MergeMany( + ([sendData] + userConfigSendData) + .map { MessageSender.sendImmediate(preparedSendData: $0) } + ) + .collect() + .map { _ in () } + .eraseToAnyPublisher() + } .eraseToAnyPublisher() } } diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index 5b8eccd82..f60609b0d 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -23,10 +23,6 @@ public final class MessageSender { let plaintext: Data? let ciphertext: Data? - // TODO: Replace these with the target namespaces - let isClosedGroupMessage: Bool - let isConfigMessage: Bool - private init( shouldSend: Bool, message: Message?, @@ -36,9 +32,7 @@ public final class MessageSender { totalAttachmentsUploaded: Int = 0, snodeMessage: SnodeMessage?, plaintext: Data?, - ciphertext: Data?, - isClosedGroupMessage: Bool, - isConfigMessage: Bool + ciphertext: Data? ) { self.shouldSend = shouldSend @@ -51,8 +45,6 @@ public final class MessageSender { self.snodeMessage = snodeMessage self.plaintext = plaintext self.ciphertext = ciphertext - self.isClosedGroupMessage = isClosedGroupMessage - self.isConfigMessage = isConfigMessage } // The default constructor creats an instance that doesn't actually send a message @@ -68,8 +60,6 @@ public final class MessageSender { self.snodeMessage = nil self.plaintext = nil self.ciphertext = nil - self.isClosedGroupMessage = false - self.isConfigMessage = false } /// This should be used to send a message to one-to-one or closed group conversations @@ -78,9 +68,7 @@ public final class MessageSender { destination: Message.Destination, interactionId: Int64?, isSyncMessage: Bool?, - snodeMessage: SnodeMessage, - isClosedGroupMessage: Bool, - isConfigMessage: Bool + snodeMessage: SnodeMessage ) { self.shouldSend = true @@ -93,8 +81,6 @@ public final class MessageSender { self.snodeMessage = snodeMessage self.plaintext = nil self.ciphertext = nil - self.isClosedGroupMessage = isClosedGroupMessage - self.isConfigMessage = isConfigMessage } /// This should be used to send a message to open group conversations @@ -115,8 +101,6 @@ public final class MessageSender { self.snodeMessage = nil self.plaintext = plaintext self.ciphertext = nil - self.isClosedGroupMessage = false - self.isConfigMessage = false } /// This should be used to send a message to an open group inbox @@ -137,8 +121,6 @@ public final class MessageSender { self.snodeMessage = nil self.plaintext = nil self.ciphertext = ciphertext - self.isClosedGroupMessage = false - self.isConfigMessage = false } // MARK: - Mutation @@ -153,9 +135,7 @@ public final class MessageSender { totalAttachmentsUploaded: fileIds.count, snodeMessage: snodeMessage, plaintext: plaintext, - ciphertext: ciphertext, - isClosedGroupMessage: isClosedGroupMessage, - isConfigMessage: isConfigMessage + ciphertext: ciphertext ) } } @@ -333,18 +313,15 @@ public final class MessageSender { // Wrap the result let kind: SNProtoEnvelope.SNProtoEnvelopeType let senderPublicKey: String - let namespace: SnodeAPI.Namespace switch destination { - case .contact(_, let targetNamespace): + case .contact: kind = .sessionMessage senderPublicKey = "" - namespace = targetNamespace - case .closedGroup(let groupPublicKey, let targetNamespace): + case .closedGroup(let groupPublicKey, _): kind = .closedGroupMessage senderPublicKey = groupPublicKey - namespace = targetNamespace case .openGroup, .openGroupInbox: preconditionFailure() } @@ -384,9 +361,7 @@ public final class MessageSender { destination: destination, interactionId: interactionId, isSyncMessage: isSyncMessage, - snodeMessage: snodeMessage, - isClosedGroupMessage: (kind == .closedGroupMessage), - isConfigMessage: (message is ConfigurationMessage) + snodeMessage: snodeMessage ) } @@ -667,10 +642,7 @@ public final class MessageSender { return SnodeAPI .sendMessage( snodeMessage, - in: (data.isClosedGroupMessage ? - .legacyClosedGroup : - .default - ) + in: destination.namespace ) .subscribe(on: DispatchQueue.global(qos: .default)) .flatMap { result, totalCount -> AnyPublisher in @@ -1014,7 +986,7 @@ public final class MessageSender { data: try prepareSendToSnodeDestination( db, message: message, - to: .contact(publicKey: userPublicKey), + to: .contact(publicKey: userPublicKey, namespace: namespace), interactionId: interactionId, userPublicKey: userPublicKey, messageSendTimestamp: Int64(floor(Date().timeIntervalSince1970 * 1000)), diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift index 53453fe80..735b96503 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift +++ b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift @@ -90,7 +90,7 @@ public enum PushNotificationAPI { let url = URL(string: "\(server)/unregister")! var request: URLRequest = URLRequest(url: url) request.httpMethod = "POST" - request.allHTTPHeaderFields = [ Header.contentType.rawValue: "application/json" ] + request.allHTTPHeaderFields = [ HTTPHeader.contentType: "application/json" ] request.httpBody = body return OnionRequestAPI @@ -144,7 +144,7 @@ public enum PushNotificationAPI { let url = URL(string: "\(server)/register")! var request: URLRequest = URLRequest(url: url) request.httpMethod = "POST" - request.allHTTPHeaderFields = [ Header.contentType.rawValue: "application/json" ] + request.allHTTPHeaderFields = [ HTTPHeader.contentType: "application/json" ] request.httpBody = body return Publishers @@ -227,7 +227,7 @@ public enum PushNotificationAPI { let url = URL(string: "\(server)/\(operation.endpoint)")! var request: URLRequest = URLRequest(url: url) request.httpMethod = "POST" - request.allHTTPHeaderFields = [ Header.contentType.rawValue: "application/json" ] + request.allHTTPHeaderFields = [ HTTPHeader.contentType: "application/json" ] request.httpBody = body return OnionRequestAPI @@ -272,7 +272,7 @@ public enum PushNotificationAPI { let url = URL(string: "\(server)/notify")! var request: URLRequest = URLRequest(url: url) request.httpMethod = "POST" - request.allHTTPHeaderFields = [ Header.contentType.rawValue: "application/json" ] + request.allHTTPHeaderFields = [ HTTPHeader.contentType: "application/json" ] request.httpBody = body return OnionRequestAPI diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift index 65827d871..317c8c99d 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift @@ -8,7 +8,7 @@ import SessionUtilitiesKit extension OpenGroupAPI { public final class Poller { - typealias PollResponse = [OpenGroupAPI.Endpoint: (info: OnionRequestResponseInfoType, data: Codable?)] + typealias PollResponse = [OpenGroupAPI.Endpoint: (info: ResponseInfoType, data: Codable?)] private let server: String private var timer: Timer? = nil @@ -283,7 +283,7 @@ extension OpenGroupAPI { .filter { endpoint, endpointResponse in switch endpoint { case .capabilities: - guard (endpointResponse.data as? BatchSubResponse)?.body != nil else { + guard (endpointResponse.data as? HTTP.BatchSubResponse)?.body != nil else { SNLog("Open group polling failed due to invalid capability data.") return false } @@ -291,8 +291,8 @@ extension OpenGroupAPI { return true case .roomPollInfo(let roomToken, _): - guard (endpointResponse.data as? BatchSubResponse)?.body != nil else { - switch (endpointResponse.data as? BatchSubResponse)?.code { + guard (endpointResponse.data as? HTTP.BatchSubResponse)?.body != nil else { + switch (endpointResponse.data as? HTTP.BatchSubResponse)?.code { case 404: SNLog("Open group polling failed to retrieve info for unknown room '\(roomToken)'.") default: SNLog("Open group polling failed due to invalid room info data.") } @@ -303,10 +303,10 @@ extension OpenGroupAPI { case .roomMessagesRecent(let roomToken), .roomMessagesBefore(let roomToken, _), .roomMessagesSince(let roomToken, _): guard - let responseData: BatchSubResponse<[Failable]> = endpointResponse.data as? BatchSubResponse<[Failable]>, + let responseData: HTTP.BatchSubResponse<[Failable]> = endpointResponse.data as? HTTP.BatchSubResponse<[Failable]>, let responseBody: [Failable] = responseData.body else { - switch (endpointResponse.data as? BatchSubResponse<[Failable]>)?.code { + switch (endpointResponse.data as? HTTP.BatchSubResponse<[Failable]>)?.code { case 404: SNLog("Open group polling failed to retrieve messages for unknown room '\(roomToken)'.") default: SNLog("Open group polling failed due to invalid messages data.") } @@ -325,7 +325,7 @@ extension OpenGroupAPI { case .inbox, .inboxSince, .outbox, .outboxSince: guard - let responseData: BatchSubResponse<[DirectMessage]?> = endpointResponse.data as? BatchSubResponse<[DirectMessage]?>, + let responseData: HTTP.BatchSubResponse<[DirectMessage]?> = endpointResponse.data as? HTTP.BatchSubResponse<[DirectMessage]?>, !responseData.failedToParseBody else { SNLog("Open group polling failed due to invalid inbox/outbox data.") @@ -383,7 +383,7 @@ extension OpenGroupAPI { switch endpoint { case .capabilities: guard - let responseData: BatchSubResponse = endpointResponse.data as? BatchSubResponse, + let responseData: HTTP.BatchSubResponse = endpointResponse.data as? HTTP.BatchSubResponse, let responseBody: Capabilities = responseData.body else { return false } @@ -391,7 +391,7 @@ extension OpenGroupAPI { case .roomPollInfo(let roomToken, _): guard - let responseData: BatchSubResponse = endpointResponse.data as? BatchSubResponse, + let responseData: HTTP.BatchSubResponse = endpointResponse.data as? HTTP.BatchSubResponse, let responseBody: RoomPollInfo = responseData.body else { return false } guard let existingOpenGroup: OpenGroup = currentInfo?.groups.first(where: { $0.roomToken == roomToken }) else { @@ -428,7 +428,7 @@ extension OpenGroupAPI { switch endpoint { case .capabilities: guard - let responseData: BatchSubResponse = endpointResponse.data as? BatchSubResponse, + let responseData: HTTP.BatchSubResponse = endpointResponse.data as? HTTP.BatchSubResponse, let responseBody: Capabilities = responseData.body else { return } @@ -440,7 +440,7 @@ extension OpenGroupAPI { case .roomPollInfo(let roomToken, _): guard - let responseData: BatchSubResponse = endpointResponse.data as? BatchSubResponse, + let responseData: HTTP.BatchSubResponse = endpointResponse.data as? HTTP.BatchSubResponse, let responseBody: RoomPollInfo = responseData.body else { return } @@ -455,7 +455,7 @@ extension OpenGroupAPI { case .roomMessagesRecent(let roomToken), .roomMessagesBefore(let roomToken, _), .roomMessagesSince(let roomToken, _): guard - let responseData: BatchSubResponse<[Failable]> = endpointResponse.data as? BatchSubResponse<[Failable]>, + let responseData: HTTP.BatchSubResponse<[Failable]> = endpointResponse.data as? HTTP.BatchSubResponse<[Failable]>, let responseBody: [Failable] = responseData.body else { return } @@ -469,7 +469,7 @@ extension OpenGroupAPI { case .inbox, .inboxSince, .outbox, .outboxSince: guard - let responseData: BatchSubResponse<[DirectMessage]?> = endpointResponse.data as? BatchSubResponse<[DirectMessage]?>, + let responseData: HTTP.BatchSubResponse<[DirectMessage]?> = endpointResponse.data as? HTTP.BatchSubResponse<[DirectMessage]?>, !responseData.failedToParseBody else { return } diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift index 03cf63502..8a5ab36a4 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift @@ -191,12 +191,12 @@ public class Poller { .setFailureType(to: Error.self) .eraseToAnyPublisher() } - + let pollerName: String = ( poller?.pollerName(for: publicKey) ?? "poller with public key \(publicKey)" ) - + // Fetch the messages return SnodeAPI.getMessages(in: namespaces, from: snode, associatedWith: publicKey) .flatMap { namespacedResults -> AnyPublisher in @@ -239,8 +239,8 @@ public class Poller { } catch { switch error { - // Ignore duplicate & selfSend message errors (and don't bother logging - // them as there will be a lot since we each service node duplicates messages) + // Ignore duplicate & selfSend message errors (and don't bother logging + // them as there will be a lot since we each service node duplicates messages) case DatabaseError.SQLITE_CONSTRAINT_UNIQUE, MessageReceiverError.duplicateMessage, MessageReceiverError.duplicateControlMessage, @@ -252,9 +252,13 @@ public class Poller { break case DatabaseError.SQLITE_ABORT: - SNLog("Failed to the database being suspended (running in background with no background task).") + // In the background ignore 'SQLITE_ABORT' (it generally means + // the BackgroundPoller has timed out + if !calledFromBackgroundPoller { + SNLog("Failed to the database being suspended (running in background with no background task).") + } break - + default: SNLog("Failed to deserialize envelope due to error: \(error).") } @@ -265,33 +269,41 @@ public class Poller { .forEach { threadId, threadMessages in messageCount += threadMessages.count - JobRunner.add( - db, - job: Job( - variant: .messageReceive, - behaviour: .runOnce, - threadId: threadId, - details: MessageReceiveJob.Details( - messages: threadMessages.map { $0.messageInfo }, - calledFromBackgroundPoller: false - ) + let jobToRun: Job? = Job( + variant: .messageReceive, + behaviour: .runOnce, + threadId: threadId, + details: MessageReceiveJob.Details( + messages: threadMessages.map { $0.messageInfo }, + calledFromBackgroundPoller: calledFromBackgroundPoller ) ) + jobsToRun = jobsToRun.appending(jobToRun) + + // If we are force-polling then add to the JobRunner so they are + // persistent and will retry on the next app run if they fail but + // don't let them auto-start + JobRunner.add(db, job: jobToRun, canStartJob: !calledFromBackgroundPoller) } + } + + // Clean up message hashes and add some logs about the poll results + if allMessagesCount == 0 && !hadValidHashUpdate { + if !calledFromBackgroundPoller { + SNLog("Received \(allMessagesCount) new message\(allMessagesCount == 1 ? "" : "s"), all duplicates - marking the hash we polled with as invalid") + } - if messageCount == 0 && !hadValidHashUpdate, let lastHash: String = lastHash { - SNLog("Received \(messages.count) new message\(messages.count == 1 ? "" : "s"), all duplicates - marking the hash we polled with as invalid") - - // Update the cached validity of the messages - try SnodeReceivedMessageInfo.handlePotentialDeletedOrInvalidHash( - db, - potentiallyInvalidHashes: [lastHash], - otherKnownValidHashes: messages.map { $0.info.hash } - ) - } - else { - SNLog("Received \(messageCount) new message\(messageCount == 1 ? "" : "s") (duplicates: \(messages.count - messageCount))") - } + // Update the cached validity of the messages + try SnodeReceivedMessageInfo.handlePotentialDeletedOrInvalidHash( + db, + potentiallyInvalidHashes: lastHashes, + otherKnownValidHashes: namespacedResults + .compactMap { $0.value.data?.messages.map { $0.info.hash } } + .reduce([], +) + ) + } + else if !calledFromBackgroundPoller { + SNLog("Received \(messageCount) new message\(messageCount == 1 ? "" : "s") in \(pollerName) (duplicates: \(allMessagesCount - messageCount))") } } diff --git a/SessionMessagingKit/Utilities/Data+Utilities.swift b/SessionMessagingKit/Utilities/Data+Utilities.swift index 967e8a263..47b3e074e 100644 --- a/SessionMessagingKit/Utilities/Data+Utilities.swift +++ b/SessionMessagingKit/Utilities/Data+Utilities.swift @@ -5,23 +5,7 @@ import SessionUtilitiesKit // MARK: - Decoding -extension Dependencies { - static let userInfoKey: CodingUserInfoKey = CodingUserInfoKey(rawValue: "io.oxen.dependencies.codingOptions")! -} - public extension Data { - func decoded(as type: T.Type, using dependencies: Dependencies = Dependencies()) throws -> T { - do { - let decoder: JSONDecoder = JSONDecoder() - decoder.userInfo = [ Dependencies.userInfoKey: dependencies ] - - return try decoder.decode(type, from: self) - } - catch { - throw HTTP.Error.parsingFailed - } - } - func removePadding() -> Data { let bytes: [UInt8] = self.bytes var paddingStart: Int = self.count diff --git a/SessionMessagingKit/Utilities/ProfileManager.swift b/SessionMessagingKit/Utilities/ProfileManager.swift index 39aa0f270..a4c671ccf 100644 --- a/SessionMessagingKit/Utilities/ProfileManager.swift +++ b/SessionMessagingKit/Utilities/ProfileManager.swift @@ -78,6 +78,13 @@ public struct ProfileManager { return data } + public static func hasProfileImageData(with fileName: String?) -> Bool { + guard let fileName: String = fileName, !fileName.isEmpty else { return false } + + return FileManager.default + .fileExists(atPath: ProfileManager.profileAvatarFilepath(filename: fileName)) + } + private static func loadProfileData(with fileName: String) -> Data? { let filePath: String = ProfileManager.profileAvatarFilepath(filename: fileName) @@ -228,7 +235,7 @@ public struct ProfileManager { return } - guard let decryptedData: Data = decryptProfileData(data: data, key: profileKeyAtStart) else { + guard let decryptedData: Data = decryptData(data: data, key: profileKeyAtStart) else { OWSLogger.warn("Avatar data for \(profile.id) could not be decrypted.") return } @@ -388,38 +395,22 @@ public struct ProfileManager { return } + // If we have no image then we should succeed (database changes happen in the callback) guard let data: Data = avatarImageData else { - // If we have no image then we need to make sure to remove it from the profile - Storage.shared.writeAsync { db in - let existingProfile: Profile = Profile.fetchOrCreateCurrentUser(db) - - OWSLogger.verbose(existingProfile.profilePictureUrl != nil ? - "Updating local profile on service with cleared avatar." : - "Updating local profile on service with no avatar." - ) - - let updatedProfile: Profile = try existingProfile - .with( - name: profileName, - profilePictureUrl: nil, - profilePictureFileName: nil, - profileEncryptionKey: (existingProfile.profilePictureUrl != nil ? - .update(newProfileKey) : - .existing - ) - ) - .saved(db) - - // Remove any cached avatar image value - if let fileName: String = existingProfile.profilePictureFileName { - profileAvatarCache.mutate { $0[fileName] = nil } + // Remove any cached avatar image value + let maybeExistingFileName: String? = Storage.shared + .read { db in + try Profile + .select(.profilePictureFileName) + .asRequest(of: String.self) + .fetchOne(db) } - - SNLog("Successfully updated service with profile.") - - try success?(db, updatedProfile) + + if let fileName: String = maybeExistingFileName { + profileAvatarCache.mutate { $0[fileName] = nil } } - return + + return success(nil, newProfileKey) } // If we have a new avatar image, we must first: @@ -447,7 +438,7 @@ public struct ProfileManager { } // Encrypt the avatar for upload - guard let encryptedAvatarData: Data = encryptProfileData(data: data, key: newProfileKey) else { + guard let encryptedAvatarData: Data = encryptData(data: data, key: newProfileKey) else { SNLog("Updating service with profile failed.") failure?(.avatarEncryptionFailed) return diff --git a/SessionShareExtension/ShareVC.swift b/SessionShareExtension/ShareNavController.swift similarity index 98% rename from SessionShareExtension/ShareVC.swift rename to SessionShareExtension/ShareNavController.swift index 47dac8d38..5edc9cedc 100644 --- a/SessionShareExtension/ShareVC.swift +++ b/SessionShareExtension/ShareNavController.swift @@ -7,7 +7,7 @@ import SignalUtilitiesKit import SessionUIKit import SignalCoreKit -final class ShareVC: UINavigationController, ShareViewDelegate { +final class ShareNavController: UINavigationController, ShareViewDelegate { private var areVersionMigrationsComplete = false public static var attachmentPrepPublisher: AnyPublisher<[SignalAttachment], Error>? @@ -183,7 +183,7 @@ final class ShareVC: UINavigationController, ShareViewDelegate { private func showMainContent() { let threadPickerVC: ThreadPickerVC = ThreadPickerVC() - threadPickerVC.shareVC = self + threadPickerVC.shareNavController = self setViewControllers([ threadPickerVC ], animated: false) @@ -427,7 +427,7 @@ final class ShareVC: UINavigationController, ShareViewDelegate { // * UTIs aren't very descriptive (there are far more MIME types than UTI types) // so in the case of file attachments we try to refine the attachment type // using the file extension. - guard let srcUtiType = ShareVC.utiType(itemProvider: itemProvider) else { + guard let srcUtiType = ShareNavController.utiType(itemProvider: itemProvider) else { let error = ShareViewControllerError.unsupportedMedia return Fail(error: error) .eraseToAnyPublisher() @@ -613,7 +613,7 @@ final class ShareVC: UINavigationController, ShareViewDelegate { Logger.debug("building DataSource with url: \(url), utiType: \(utiType)") - guard let dataSource = ShareVC.createDataSource(utiType: utiType, url: url, customFileName: loadedItem.customFileName) else { + guard let dataSource = ShareNavController.createDataSource(utiType: utiType, url: url, customFileName: loadedItem.customFileName) else { let error = ShareViewControllerError.assertionError(description: "Unable to read attachment data") return Fail(error: error) .eraseToAnyPublisher() diff --git a/SessionShareExtension/SimplifiedConversationCell.swift b/SessionShareExtension/SimplifiedConversationCell.swift index 3c2fa1e32..48fd3009d 100644 --- a/SessionShareExtension/SimplifiedConversationCell.swift +++ b/SessionShareExtension/SimplifiedConversationCell.swift @@ -92,12 +92,10 @@ final class SimplifiedConversationCell: UITableViewCell { accentLineView.alpha = (cellViewModel.threadIsBlocked == true ? 1 : 0) profilePictureView.update( publicKey: cellViewModel.threadId, - profile: cellViewModel.profile, - additionalProfile: cellViewModel.additionalProfile, threadVariant: cellViewModel.threadVariant, - openGroupProfilePictureData: cellViewModel.openGroupProfilePictureData, - useFallbackPicture: (cellViewModel.threadVariant == .openGroup && cellViewModel.openGroupProfilePictureData == nil), - showMultiAvatarForClosedGroup: true + customImageData: cellViewModel.openGroupProfilePictureData, + profile: cellViewModel.profile, + additionalProfile: cellViewModel.additionalProfile ) displayNameLabel.text = cellViewModel.displayName } diff --git a/SessionShareExtension/ThreadPickerVC.swift b/SessionShareExtension/ThreadPickerVC.swift index c5b298362..9878b4eb1 100644 --- a/SessionShareExtension/ThreadPickerVC.swift +++ b/SessionShareExtension/ThreadPickerVC.swift @@ -13,7 +13,7 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView private var dataChangeObservable: DatabaseCancellable? private var hasLoadedInitialData: Bool = false - var shareVC: ShareVC? + var shareNavController: ShareNavController? // MARK: - Intialization @@ -182,9 +182,9 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView messageText ) - shareVC?.dismiss(animated: true, completion: nil) + shareNavController?.dismiss(animated: true, completion: nil) - ModalActivityIndicatorViewController.present(fromViewController: shareVC!, canCancel: false, message: "vc_share_sending_message".localized()) { activityIndicator in + ModalActivityIndicatorViewController.present(fromViewController: shareNavController!, canCancel: false, message: "vc_share_sending_message".localized()) { activityIndicator in // Resume database NotificationCenter.default.post(name: Database.resumeNotification, object: self) diff --git a/SessionSnodeKit/Database/Models/Snode.swift b/SessionSnodeKit/Database/Models/Snode.swift index 9bcf6bc8c..199ef2139 100644 --- a/SessionSnodeKit/Database/Models/Snode.swift +++ b/SessionSnodeKit/Database/Models/Snode.swift @@ -60,7 +60,7 @@ extension Snode { } catch { SNLog("Failed to parse snode: \(error.localizedDescription).") - throw HTTP.Error.invalidJSON + throw HTTPError.invalidJSON } } } diff --git a/SessionSnodeKit/Database/Models/SnodeReceivedMessageInfo.swift b/SessionSnodeKit/Database/Models/SnodeReceivedMessageInfo.swift index 7addb56e5..985ef335e 100644 --- a/SessionSnodeKit/Database/Models/SnodeReceivedMessageInfo.swift +++ b/SessionSnodeKit/Database/Models/SnodeReceivedMessageInfo.swift @@ -52,18 +52,18 @@ public struct SnodeReceivedMessageInfo: Codable, FetchableRecord, MutablePersist // MARK: - Convenience public extension SnodeReceivedMessageInfo { - private static func key(for snode: Snode, publicKey: String, namespace: Int) -> String { - guard namespace != SnodeAPI.defaultNamespace else { + private static func key(for snode: Snode, publicKey: String, namespace: SnodeAPI.Namespace) -> String { + guard namespace != .default else { return "\(snode.address):\(snode.port).\(publicKey)" } - return "\(snode.address):\(snode.port).\(publicKey).\(namespace)" + return "\(snode.address):\(snode.port).\(publicKey).\(namespace.rawValue)" } init( snode: Snode, publicKey: String, - namespace: Int, + namespace: SnodeAPI.Namespace, hash: String, expirationDateMs: Int64? ) { @@ -76,15 +76,15 @@ public extension SnodeReceivedMessageInfo { // MARK: - GRDB Interactions public extension SnodeReceivedMessageInfo { - static func pruneExpiredMessageHashInfo(for snode: Snode, namespace: Int, associatedWith publicKey: String) { - // Delete any expired SnodeReceivedMessageInfo values associated to a specific node (even though - // this runs very quickly we fetch the rowIds we want to delete from a 'read' call to avoid - // blocking the write queue since this method is called very frequently) + static func pruneExpiredMessageHashInfo(for snode: Snode, namespace: SnodeAPI.Namespace, associatedWith publicKey: String) { + // Delete any expired SnodeReceivedMessageInfo values associated to a specific node (even + // though this runs very quickly we fetch the rowIds we want to delete from a 'read' call + // to avoid blocking the write queue since this method is called very frequently) let rowIds: [Int64] = Storage.shared .read { db in - // Only prune the hashes if new hashes exist for this Snode (if they don't then we don't want - // to clear out the legacy hashes) - let hasNonLegacyHash: Bool = try SnodeReceivedMessageInfo + // Only prune the hashes if new hashes exist for this Snode (if they don't then + // we don't want to clear out the legacy hashes) + let hasNonLegacyHash: Bool = SnodeReceivedMessageInfo .filter(SnodeReceivedMessageInfo.Columns.key == key(for: snode, publicKey: publicKey, namespace: namespace)) .isNotEmpty(db) @@ -111,10 +111,10 @@ public extension SnodeReceivedMessageInfo { /// This method fetches the last non-expired hash from the database for message retrieval /// - /// **Note:** This method uses a `write` instead of a read because there is a single write queue for the database and it's very common for - /// this method to be called after the hash value has been updated but before the various `read` threads have been updated, resulting in a - /// pointless fetch for data the app has already received - static func fetchLastNotExpired(for snode: Snode, namespace: Int, associatedWith publicKey: String) -> SnodeReceivedMessageInfo? { + /// **Note:** This method uses a `write` instead of a read because there is a single write queue for the database and it's + /// very common for this method to be called after the hash value has been updated but before the various `read` threads + /// have been updated, resulting in a pointless fetch for data the app has already received + static func fetchLastNotExpired(for snode: Snode, namespace: SnodeAPI.Namespace, associatedWith publicKey: String) -> SnodeReceivedMessageInfo? { return Storage.shared.read { db in let nonLegacyHash: SnodeReceivedMessageInfo? = try SnodeReceivedMessageInfo .filter( diff --git a/SessionSnodeKit/GetSnodePoolJob.swift b/SessionSnodeKit/Jobs/GetSnodePoolJob.swift similarity index 98% rename from SessionSnodeKit/GetSnodePoolJob.swift rename to SessionSnodeKit/Jobs/GetSnodePoolJob.swift index 4739bc411..71156a4c7 100644 --- a/SessionSnodeKit/GetSnodePoolJob.swift +++ b/SessionSnodeKit/Jobs/GetSnodePoolJob.swift @@ -3,6 +3,7 @@ import Foundation import Combine import GRDB +import SignalCoreKit import SessionUtilitiesKit public enum GetSnodePoolJob: JobExecutor { diff --git a/SessionSnodeKit/Models/DeleteAllBeforeRequest.swift b/SessionSnodeKit/Models/DeleteAllBeforeRequest.swift new file mode 100644 index 000000000..6f4a83ad9 --- /dev/null +++ b/SessionSnodeKit/Models/DeleteAllBeforeRequest.swift @@ -0,0 +1,81 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +extension SnodeAPI { + public class DeleteAllBeforeRequest: SnodeAuthenticatedRequestBody { + enum CodingKeys: String, CodingKey { + case beforeMs = "before" + case namespace + } + + let beforeMs: UInt64 + let namespace: SnodeAPI.Namespace? + + // MARK: - Init + + public init( + beforeMs: UInt64, + namespace: SnodeAPI.Namespace?, + pubkey: String, + timestampMs: UInt64, + ed25519PublicKey: [UInt8], + ed25519SecretKey: [UInt8] + ) { + self.beforeMs = beforeMs + self.namespace = namespace + + super.init( + pubkey: pubkey, + ed25519PublicKey: ed25519PublicKey, + ed25519SecretKey: ed25519SecretKey, + timestampMs: timestampMs + ) + } + + // MARK: - Coding + + override public func encode(to encoder: Encoder) throws { + var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(beforeMs, forKey: .beforeMs) + + // If no namespace is specified it defaults to the default namespace only (namespace + // 0), so instead in this case we want to explicitly delete from `all` namespaces + switch namespace { + case .some(let namespace): try container.encode(namespace, forKey: .namespace) + case .none: try container.encode("all", forKey: .namespace) + } + + try super.encode(to: encoder) + } + + // MARK: - Abstract Methods + + override func generateSignature() throws -> [UInt8] { + /// Ed25519 signature of `("delete_before" || namespace || before)`, signed by + /// `pubkey`. Must be base64 encoded (json) or bytes (OMQ). `namespace` is the stringified + /// version of the given non-default namespace parameter (i.e. "-42" or "all"), or the empty + /// string for the default namespace (whether explicitly given or not). + let verificationBytes: [UInt8] = SnodeAPI.Endpoint.deleteAllBefore.rawValue.bytes + .appending( + contentsOf: (namespace == nil ? + "all" : + namespace?.verificationString + )?.bytes + ) + .appending(contentsOf: "\(beforeMs)".data(using: .ascii)?.bytes) + + guard + let signatureBytes: [UInt8] = sodium.wrappedValue.sign.signature( + message: verificationBytes, + secretKey: ed25519SecretKey + ) + else { + throw SnodeAPIError.signingFailed + } + + return signatureBytes + } + } +} diff --git a/SessionSnodeKit/Models/DeleteAllBeforeResponse.swift b/SessionSnodeKit/Models/DeleteAllBeforeResponse.swift new file mode 100644 index 000000000..25e71d9ff --- /dev/null +++ b/SessionSnodeKit/Models/DeleteAllBeforeResponse.swift @@ -0,0 +1,45 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import Sodium +import SessionUtilitiesKit + +public class DeleteAllBeforeResponse: SnodeRecursiveResponse { + // MARK: - Convenience + + internal func validResultMap( + userX25519PublicKey: String, + beforeMs: UInt64, + sodium: Sodium + ) -> [String: Bool] { + return swarm.reduce(into: [:]) { result, next in + guard + !next.value.failed, + let encodedSignature: Data = Data(base64Encoded: next.value.signatureBase64) + else { + result[next.key] = false + + if let reason: String = next.value.reason, let statusCode: Int = next.value.code { + SNLog("Couldn't delete data from: \(next.key) due to error: \(reason) (\(statusCode)).") + } + else { + SNLog("Couldn't delete data from: \(next.key).") + } + return + } + + /// Signature of `( PUBKEY_HEX || BEFORE || DELETEDHASH[0] || ... || DELETEDHASH[N] )` + /// signed by the node's ed25519 pubkey. When doing a multi-namespace delete the `DELETEDHASH` + /// values are totally ordered (i.e. among all the hashes deleted regardless of namespace) + let verificationBytes: [UInt8] = userX25519PublicKey.bytes + .appending(contentsOf: "\(beforeMs)".data(using: .ascii)?.bytes) + .appending(contentsOf: next.value.deleted.joined().bytes) + + result[next.key] = sodium.sign.verify( + message: verificationBytes, + publicKey: Data(hex: next.key).bytes, + signature: encodedSignature.bytes + ) + } + } +} diff --git a/SessionSnodeKit/Models/DeleteAllMessagesRequest.swift b/SessionSnodeKit/Models/DeleteAllMessagesRequest.swift index b90ce4bcf..bd3526273 100644 --- a/SessionSnodeKit/Models/DeleteAllMessagesRequest.swift +++ b/SessionSnodeKit/Models/DeleteAllMessagesRequest.swift @@ -64,7 +64,7 @@ extension SnodeAPI { namespace?.verificationString )?.bytes ) - .appending(contentsOf: timestampMs.map { "\($0)" }?.data(using: .utf8)?.bytes) + .appending(contentsOf: timestampMs.map { "\($0)" }?.data(using: .ascii)?.bytes) guard let signatureBytes: [UInt8] = sodium.wrappedValue.sign.signature( diff --git a/SessionSnodeKit/Models/DeleteAllMessagesResponse.swift b/SessionSnodeKit/Models/DeleteAllMessagesResponse.swift new file mode 100644 index 000000000..b311e3af7 --- /dev/null +++ b/SessionSnodeKit/Models/DeleteAllMessagesResponse.swift @@ -0,0 +1,80 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import Sodium +import SessionUtilitiesKit + +public class DeleteAllMessagesResponse: SnodeRecursiveResponse { + // MARK: - Convenience + + internal func validResultMap( + userX25519PublicKey: String, + timestampMs: UInt64, + sodium: Sodium + ) -> [String: Bool] { + return swarm.reduce(into: [:]) { result, next in + guard + !next.value.failed, + let encodedSignature: Data = Data(base64Encoded: next.value.signatureBase64) + else { + result[next.key] = false + + if let reason: String = next.value.reason, let statusCode: Int = next.value.code { + SNLog("Couldn't delete data from: \(next.key) due to error: \(reason) (\(statusCode)).") + } + else { + SNLog("Couldn't delete data from: \(next.key).") + } + return + } + + /// Signature of `( PUBKEY_HEX || TIMESTAMP || DELETEDHASH[0] || ... || DELETEDHASH[N] )` + /// signed by the node's ed25519 pubkey. When doing a multi-namespace delete the `DELETEDHASH` + /// values are totally ordered (i.e. among all the hashes deleted regardless of namespace) + let verificationBytes: [UInt8] = userX25519PublicKey.bytes + .appending(contentsOf: "\(timestampMs)".data(using: .ascii)?.bytes) + .appending(contentsOf: next.value.deleted.joined().bytes) + + result[next.key] = sodium.sign.verify( + message: verificationBytes, + publicKey: Data(hex: next.key).bytes, + signature: encodedSignature.bytes + ) + } + } +} + +// MARK: - SwarmItem + +public extension DeleteAllMessagesResponse { + class SwarmItem: SnodeSwarmItem { + private enum CodingKeys: String, CodingKey { + case deleted + } + + public let deleted: [String] + public let deletedNamespaced: [String: [String]] + + // MARK: - Initialization + + required init(from decoder: Decoder) throws { + let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) + + if let decodedDeletedNamespaced: [String: [String]] = try? container.decode([String: [String]].self, forKey: .deleted) { + deletedNamespaced = decodedDeletedNamespaced + + /// **Note:** When doing a multi-namespace delete the `DELETEDHASH` values are totally + /// ordered (i.e. among all the hashes deleted regardless of namespace) + deleted = decodedDeletedNamespaced + .reduce(into: []) { result, next in result.append(contentsOf: next.value) } + .sorted() + } + else { + deleted = ((try? container.decode([String].self, forKey: .deleted)) ?? []) + deletedNamespaced = [:] + } + + try super.init(from: decoder) + } + } +} diff --git a/SessionSnodeKit/Models/DeleteMessagesRequest.swift b/SessionSnodeKit/Models/DeleteMessagesRequest.swift new file mode 100644 index 000000000..1210d78a3 --- /dev/null +++ b/SessionSnodeKit/Models/DeleteMessagesRequest.swift @@ -0,0 +1,70 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +extension SnodeAPI { + public class DeleteMessagesRequest: SnodeAuthenticatedRequestBody { + enum CodingKeys: String, CodingKey { + case messageHashes = "messages" + case requireSuccessfulDeletion = "required" + } + + let messageHashes: [String] + let requireSuccessfulDeletion: Bool + + // MARK: - Init + + public init( + messageHashes: [String], + requireSuccessfulDeletion: Bool, + pubkey: String, + ed25519PublicKey: [UInt8], + ed25519SecretKey: [UInt8] + ) { + self.messageHashes = messageHashes + self.requireSuccessfulDeletion = requireSuccessfulDeletion + + super.init( + pubkey: pubkey, + ed25519PublicKey: ed25519PublicKey, + ed25519SecretKey: ed25519SecretKey + ) + } + + // MARK: - Coding + + override public func encode(to encoder: Encoder) throws { + var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(messageHashes, forKey: .messageHashes) + + // Omitting the value is the same as false so omit to save data + if requireSuccessfulDeletion { + try container.encode(requireSuccessfulDeletion, forKey: .requireSuccessfulDeletion) + } + + try super.encode(to: encoder) + } + + // MARK: - Abstract Methods + + override func generateSignature() throws -> [UInt8] { + /// Ed25519 signature of `("delete" || messages...)`; this signs the value constructed + /// by concatenating "delete" and all `messages` values, using `pubkey` to sign. Must be base64 + /// encoded for json requests; binary for OMQ requests. + let verificationBytes: [UInt8] = SnodeAPI.Endpoint.deleteMessages.rawValue.bytes + .appending(contentsOf: messageHashes.joined().bytes) + + guard + let signatureBytes: [UInt8] = sodium.wrappedValue.sign.signature( + message: verificationBytes, + secretKey: ed25519SecretKey + ) + else { + throw SnodeAPIError.signingFailed + } + + return signatureBytes + } + } +} diff --git a/SessionSnodeKit/Models/DeleteMessagesResponse.swift b/SessionSnodeKit/Models/DeleteMessagesResponse.swift new file mode 100644 index 000000000..50fedb56e --- /dev/null +++ b/SessionSnodeKit/Models/DeleteMessagesResponse.swift @@ -0,0 +1,65 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import Sodium +import SessionUtilitiesKit + +public class DeleteMessagesResponse: SnodeRecursiveResponse { + // MARK: - Convenience + + internal func validResultMap( + userX25519PublicKey: String, + serverHashes: [String], + sodium: Sodium + ) -> [String: Bool] { + return swarm.reduce(into: [:]) { result, next in + guard + !next.value.failed, + let encodedSignature: Data = Data(base64Encoded: next.value.signatureBase64) + else { + result[next.key] = false + + if let reason: String = next.value.reason, let statusCode: Int = next.value.code { + SNLog("Couldn't delete data from: \(next.key) due to error: \(reason) (\(statusCode)).") + } + else { + SNLog("Couldn't delete data from: \(next.key).") + } + return + } + + /// The signature format is `( PUBKEY_HEX || RMSG[0] || ... || RMSG[N] || DMSG[0] || ... || DMSG[M] )` + let verificationBytes: [UInt8] = userX25519PublicKey.bytes + .appending(contentsOf: serverHashes.joined().bytes) + .appending(contentsOf: next.value.deleted.joined().bytes) + + result[next.key] = sodium.sign.verify( + message: verificationBytes, + publicKey: Data(hex: next.key).bytes, + signature: encodedSignature.bytes + ) + } + } +} + +// MARK: - SwarmItem + +public extension DeleteMessagesResponse { + class SwarmItem: SnodeSwarmItem { + private enum CodingKeys: String, CodingKey { + case deleted + } + + public let deleted: [String] + + // MARK: - Initialization + + required init(from decoder: Decoder) throws { + let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) + + deleted = try container.decode([String].self, forKey: .deleted) + + try super.init(from: decoder) + } + } +} diff --git a/SessionSnodeKit/Models/GetMessagesRequest.swift b/SessionSnodeKit/Models/GetMessagesRequest.swift index 6eeedf406..8f17d949d 100644 --- a/SessionSnodeKit/Models/GetMessagesRequest.swift +++ b/SessionSnodeKit/Models/GetMessagesRequest.swift @@ -55,7 +55,7 @@ extension SnodeAPI { /// encoded for json requests; binary for OMQ requests. let verificationBytes: [UInt8] = SnodeAPI.Endpoint.getMessages.rawValue.bytes .appending(contentsOf: namespace?.verificationString.bytes) - .appending(contentsOf: timestampMs.map { "\($0)" }?.data(using: .utf8)?.bytes) + .appending(contentsOf: timestampMs.map { "\($0)" }?.data(using: .ascii)?.bytes) guard let signatureBytes: [UInt8] = sodium.wrappedValue.sign.signature( diff --git a/SessionSnodeKit/Models/GetMessagesResponse.swift b/SessionSnodeKit/Models/GetMessagesResponse.swift new file mode 100644 index 000000000..b0aa02809 --- /dev/null +++ b/SessionSnodeKit/Models/GetMessagesResponse.swift @@ -0,0 +1,38 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public class GetMessagesResponse: SnodeResponse { + private enum CodingKeys: String, CodingKey { + case messages + case more + } + + public class RawMessage: Codable { + private enum CodingKeys: String, CodingKey { + case data + case expiration + case hash + case timestamp + } + + public let data: String + public let expiration: Int64? + public let hash: String + public let timestamp: Int64 + } + + public let messages: [RawMessage] + public let more: Bool + + // MARK: - Initialization + + required init(from decoder: Decoder) throws { + let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) + + messages = try container.decode([RawMessage].self, forKey: .messages) + more = try container.decode(Bool.self, forKey: .more) + + try super.init(from: decoder) + } +} diff --git a/SessionSnodeKit/Models/GetNetworkTimestampResponse.swift b/SessionSnodeKit/Models/GetNetworkTimestampResponse.swift new file mode 100644 index 000000000..71428bab9 --- /dev/null +++ b/SessionSnodeKit/Models/GetNetworkTimestampResponse.swift @@ -0,0 +1,15 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +extension SnodeAPI { + public struct GetNetworkTimestampResponse: Decodable { + enum CodingKeys: String, CodingKey { + case timestamp + case version + } + + let timestamp: UInt64 + let version: [UInt64] + } +} diff --git a/SessionSnodeKit/Models/GetServiceNodesRequest.swift b/SessionSnodeKit/Models/GetServiceNodesRequest.swift new file mode 100644 index 000000000..6fae67c3e --- /dev/null +++ b/SessionSnodeKit/Models/GetServiceNodesRequest.swift @@ -0,0 +1,31 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +extension SnodeAPI { + public struct GetServiceNodesRequest: Encodable { + enum CodingKeys: String, CodingKey { + case activeOnly = "active_only" + case limit + case fields + } + + let activeOnly: Bool + let limit: Int? + let fields: Fields + + public struct Fields: Encodable { + enum CodingKeys: String, CodingKey { + case publicIp = "public_ip" + case storagePort = "storage_port" + case pubkeyEd25519 = "pubkey_ed25519" + case pubkeyX25519 = "pubkey_x25519" + } + + let publicIp: Bool + let storagePort: Bool + let pubkeyEd25519: Bool + let pubkeyX25519: Bool + } + } +} diff --git a/SessionSnodeKit/Models/GetSwarmRequest.swift b/SessionSnodeKit/Models/GetSwarmRequest.swift new file mode 100644 index 000000000..86d1534b9 --- /dev/null +++ b/SessionSnodeKit/Models/GetSwarmRequest.swift @@ -0,0 +1,13 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +extension SnodeAPI { + public struct GetSwarmRequest: Encodable { + enum CodingKeys: String, CodingKey { + case pubkey + } + + let pubkey: String + } +} diff --git a/SessionSnodeKit/Models/LegacyGetMessagesRequest.swift b/SessionSnodeKit/Models/LegacyGetMessagesRequest.swift new file mode 100644 index 000000000..644cdcafc --- /dev/null +++ b/SessionSnodeKit/Models/LegacyGetMessagesRequest.swift @@ -0,0 +1,28 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +extension SnodeAPI { + /// This is the legacy unauthenticated message retrieval request + public struct LegacyGetMessagesRequest: Encodable { + enum CodingKeys: String, CodingKey { + case pubkey + case lastHash = "last_hash" + case namespace + } + + let pubkey: String + let lastHash: String + let namespace: SnodeAPI.Namespace? + + // MARK: - Coding + + public func encode(to encoder: Encoder) throws { + var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(pubkey, forKey: .pubkey) + try container.encode(lastHash, forKey: .lastHash) + try container.encodeIfPresent(namespace, forKey: .namespace) + } + } +} diff --git a/SessionSnodeKit/Models/LegacySendMessageRequest.swift b/SessionSnodeKit/Models/LegacySendMessageRequest.swift new file mode 100644 index 000000000..08cfe72ef --- /dev/null +++ b/SessionSnodeKit/Models/LegacySendMessageRequest.swift @@ -0,0 +1,24 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +extension SnodeAPI { + /// This is the legacy unauthenticated message store request + public struct LegacySendMessagesRequest: Encodable { + enum CodingKeys: String, CodingKey { + case namespace + } + + let message: SnodeMessage + let namespace: SnodeAPI.Namespace + + // MARK: - Coding + + public func encode(to encoder: Encoder) throws { + var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) + + try message.encode(to: encoder) + try container.encode(namespace, forKey: .namespace) + } + } +} diff --git a/SessionSnodeKit/Models/ONSResolveRequest.swift b/SessionSnodeKit/Models/ONSResolveRequest.swift new file mode 100644 index 000000000..eaef29085 --- /dev/null +++ b/SessionSnodeKit/Models/ONSResolveRequest.swift @@ -0,0 +1,15 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +extension SnodeAPI { + public struct ONSResolveRequest: Encodable { + enum CodingKeys: String, CodingKey { + case type + case base64EncodedNameHash = "name_hash" + } + + let type: Int64 + let base64EncodedNameHash: String + } +} diff --git a/SessionSnodeKit/Models/ONSResolveResponse.swift b/SessionSnodeKit/Models/ONSResolveResponse.swift new file mode 100644 index 000000000..5208bbb4c --- /dev/null +++ b/SessionSnodeKit/Models/ONSResolveResponse.swift @@ -0,0 +1,84 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import Sodium +import SessionUtilitiesKit + +extension SnodeAPI { + public class ONSResolveResponse: SnodeResponse { + private struct Result: Codable { + enum CodingKeys: String, CodingKey { + case nonce + case encryptedValue = "encrypted_value" + } + + fileprivate let nonce: String? + fileprivate let encryptedValue: String + } + + enum CodingKeys: String, CodingKey { + case result + } + + private let result: Result + + // MARK: - Initialization + + required init(from decoder: Decoder) throws { + let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) + + result = try container.decode(Result.self, forKey: .result) + + try super.init(from: decoder) + } + + // MARK: - Convenience + + func sessionId(sodium: Sodium, nameBytes: [UInt8], nameHashBytes: [UInt8]) throws -> String { + let ciphertext: [UInt8] = Data(hex: result.encryptedValue).bytes + + // Handle old Argon2-based encryption used before HF16 + guard let hexEncodedNonce: String = result.nonce else { + let salt: [UInt8] = Data(repeating: 0, count: sodium.pwHash.SaltBytes).bytes + + guard + let key: [UInt8] = sodium.pwHash.hash( + outputLength: sodium.secretBox.KeyBytes, + passwd: nameBytes, + salt: salt, + opsLimit: sodium.pwHash.OpsLimitModerate, + memLimit: sodium.pwHash.MemLimitModerate, + alg: .Argon2ID13 + ) + else { throw SnodeAPIError.hashingFailed } + + let nonce: [UInt8] = Data(repeating: 0, count: sodium.secretBox.NonceBytes).bytes + + guard let sessionIdAsData: [UInt8] = sodium.secretBox.open(authenticatedCipherText: ciphertext, secretKey: key, nonce: nonce) else { + throw SnodeAPIError.decryptionFailed + } + + return sessionIdAsData.toHexString() + } + + let nonceBytes: [UInt8] = Data(hex: hexEncodedNonce).bytes + + // xchacha-based encryption + // key = H(name, key=H(name)) + guard let key: [UInt8] = sodium.genericHash.hash(message: nameBytes, key: nameHashBytes) else { + throw SnodeAPIError.hashingFailed + } + guard + // Should always be equal in practice + ciphertext.count >= (SessionId.byteCount + sodium.aead.xchacha20poly1305ietf.ABytes), + let sessionIdAsData = sodium.aead.xchacha20poly1305ietf.decrypt( + authenticatedCipherText: ciphertext, + secretKey: key, + nonce: nonceBytes + ) + else { throw SnodeAPIError.decryptionFailed } + + return sessionIdAsData.toHexString() + } + } +} diff --git a/SessionSnodeKit/Models/OxenDaemonRPCRequest.swift b/SessionSnodeKit/Models/OxenDaemonRPCRequest.swift new file mode 100644 index 000000000..935961963 --- /dev/null +++ b/SessionSnodeKit/Models/OxenDaemonRPCRequest.swift @@ -0,0 +1,21 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public struct OxenDaemonRPCRequest: Encodable { + private enum CodingKeys: String, CodingKey { + case endpoint + case body = "params" + } + + private let endpoint: String + private let body: T + + public init( + endpoint: SnodeAPI.Endpoint, + body: T + ) { + self.endpoint = endpoint.rawValue + self.body = body + } +} diff --git a/SessionSnodeKit/Models/RequestInfo.swift b/SessionSnodeKit/Models/RequestInfo.swift deleted file mode 100644 index 8072364df..000000000 --- a/SessionSnodeKit/Models/RequestInfo.swift +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import Foundation - -extension OnionRequestAPI { - struct RequestInfo: Codable { - let method: String - let endpoint: String - let headers: [String: String] - } -} diff --git a/SessionSnodeKit/Models/RevokeSubkeyRequest.swift b/SessionSnodeKit/Models/RevokeSubkeyRequest.swift new file mode 100644 index 000000000..203532123 --- /dev/null +++ b/SessionSnodeKit/Models/RevokeSubkeyRequest.swift @@ -0,0 +1,60 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +extension SnodeAPI { + public class RevokeSubkeyRequest: SnodeAuthenticatedRequestBody { + enum CodingKeys: String, CodingKey { + case subkeyToRevoke = "revoke_subkey" + } + + let subkeyToRevoke: String + + // MARK: - Init + + public init( + subkeyToRevoke: String, + pubkey: String, + ed25519PublicKey: [UInt8], + ed25519SecretKey: [UInt8] + ) { + self.subkeyToRevoke = subkeyToRevoke + + super.init( + pubkey: pubkey, + ed25519PublicKey: ed25519PublicKey, + ed25519SecretKey: ed25519SecretKey + ) + } + + // MARK: - Coding + + override public func encode(to encoder: Encoder) throws { + var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(subkeyToRevoke, forKey: .subkeyToRevoke) + + try super.encode(to: encoder) + } + + // MARK: - Abstract Methods + + override func generateSignature() throws -> [UInt8] { + /// Ed25519 signature of `("revoke_subkey" || subkey)`; this signs the subkey tag, + /// using `pubkey` to sign. Must be base64 encoded for json requests; binary for OMQ requests. + let verificationBytes: [UInt8] = SnodeAPI.Endpoint.revokeSubkey.rawValue.bytes + .appending(contentsOf: subkeyToRevoke.bytes) + + guard + let signatureBytes: [UInt8] = sodium.wrappedValue.sign.signature( + message: verificationBytes, + secretKey: ed25519SecretKey + ) + else { + throw SnodeAPIError.signingFailed + } + + return signatureBytes + } + } +} diff --git a/SessionSnodeKit/Models/RevokeSubkeyResponse.swift b/SessionSnodeKit/Models/RevokeSubkeyResponse.swift new file mode 100644 index 000000000..f633fc4ed --- /dev/null +++ b/SessionSnodeKit/Models/RevokeSubkeyResponse.swift @@ -0,0 +1,43 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import Sodium +import SessionUtilitiesKit + +public class RevokeSubkeyResponse: SnodeRecursiveResponse { + // MARK: - Convenience + + internal func validateResult( + userX25519PublicKey: String, + subkeyToRevoke: String, + sodium: Sodium + ) throws { + try swarm.forEach { snodePublicKey, swarmItem in + guard + !swarmItem.failed, + let encodedSignature: Data = Data(base64Encoded: swarmItem.signatureBase64) + else { + if let reason: String = swarmItem.reason, let statusCode: Int = swarmItem.code { + SNLog("Couldn't revoke subkey from: \(snodePublicKey) due to error: \(reason) (\(statusCode)).") + } + else { + SNLog("Couldn't revoke subkey from: \(snodePublicKey).") + } + return + } + + /// Signature of `( PUBKEY_HEX || SUBKEY_TAG_BYTES )` where `SUBKEY_TAG_BYTES` is the + /// requested subkey tag for revocation + let verificationBytes: [UInt8] = userX25519PublicKey.bytes + .appending(contentsOf: subkeyToRevoke.bytes) + let isValid: Bool = sodium.sign.verify( + message: verificationBytes, + publicKey: Data(hex: snodePublicKey).bytes, + signature: encodedSignature.bytes + ) + + // If the update signature is invalid then we want to fail here + guard isValid else { throw SnodeAPIError.signatureVerificationFailed } + } + } +} diff --git a/SessionSnodeKit/Models/SendMessageRequest.swift b/SessionSnodeKit/Models/SendMessageRequest.swift index 94c5ab0a9..b64748ddc 100644 --- a/SessionSnodeKit/Models/SendMessageRequest.swift +++ b/SessionSnodeKit/Models/SendMessageRequest.swift @@ -6,7 +6,7 @@ extension SnodeAPI { public class SendMessageRequest: SnodeAuthenticatedRequestBody { enum CodingKeys: String, CodingKey { case namespace - case signatureTimestamp = "timestamp"//"sig_timestamp" // TODO: Add this back once the snodes are fixed + case signatureTimestamp = "sig_timestamp" } let message: SnodeMessage @@ -60,7 +60,7 @@ extension SnodeAPI { /// option. let verificationBytes: [UInt8] = SnodeAPI.Endpoint.sendMessage.rawValue.bytes .appending(contentsOf: namespace.verificationString.bytes) - .appending(contentsOf: timestampMs.map { "\($0)" }?.data(using: .utf8)?.bytes) + .appending(contentsOf: timestampMs.map { "\($0)" }?.data(using: .ascii)?.bytes) guard let signatureBytes: [UInt8] = sodium.wrappedValue.sign.signature( diff --git a/SessionSnodeKit/Models/SendMessageResponse.swift b/SessionSnodeKit/Models/SendMessageResponse.swift new file mode 100644 index 000000000..d49dd9b55 --- /dev/null +++ b/SessionSnodeKit/Models/SendMessageResponse.swift @@ -0,0 +1,54 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public class SendMessagesResponse: SnodeRecursiveResponse { + private enum CodingKeys: String, CodingKey { + case difficulty + case hash + case swarm + } + + public let difficulty: Int64 + public let hash: String + + // MARK: - Initialization + + required init(from decoder: Decoder) throws { + let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) + + difficulty = try container.decode(Int64.self, forKey: .difficulty) + hash = try container.decode(String.self, forKey: .hash) + + try super.init(from: decoder) + } +} + +// MARK: - SwarmItem + +public extension SendMessagesResponse { + class SwarmItem: SnodeSwarmItem { + private enum CodingKeys: String, CodingKey { + case hash + case already + } + + public let hash: String + + /// `true` if a message with this hash was already stored + /// + /// **Note:** The `hash` is still included and signed even if this occurs + public let already: Bool + + // MARK: - Initialization + + required init(from decoder: Decoder) throws { + let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) + + hash = try container.decode(String.self, forKey: .hash) + already = ((try? container.decode(Bool.self, forKey: .already)) ?? false) + + try super.init(from: decoder) + } + } +} diff --git a/SessionSnodeKit/Models/SnodeAPIEndpoint.swift b/SessionSnodeKit/Models/SnodeAPIEndpoint.swift deleted file mode 100644 index 63ffd5334..000000000 --- a/SessionSnodeKit/Models/SnodeAPIEndpoint.swift +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import Foundation - -public enum SnodeAPIEndpoint: String { - case getSwarm = "get_snodes_for_pubkey" - case getMessages = "retrieve" - case sendMessage = "store" - case deleteMessage = "delete" - case oxenDaemonRPCCall = "oxend_request" - case getInfo = "info" - case clearAllData = "delete_all" - case expire = "expire" - case batch = "batch" - case sequence = "sequence" -} diff --git a/SessionSnodeKit/Models/SnodeAuthenticatedRequestBody.swift b/SessionSnodeKit/Models/SnodeAuthenticatedRequestBody.swift new file mode 100644 index 000000000..7e349a719 --- /dev/null +++ b/SessionSnodeKit/Models/SnodeAuthenticatedRequestBody.swift @@ -0,0 +1,56 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import SessionUtilitiesKit + +public class SnodeAuthenticatedRequestBody: Encodable { + private enum CodingKeys: String, CodingKey { + case pubkey + case subkey + case timestampMs = "timestamp" + case ed25519PublicKey = "pubkey_ed25519" + case signatureBase64 = "signature" + } + + private let pubkey: String + private let ed25519PublicKey: [UInt8] + internal let ed25519SecretKey: [UInt8] + private let subkey: String? + internal let timestampMs: UInt64? + + // MARK: - Initialization + + public init( + pubkey: String, + ed25519PublicKey: [UInt8], + ed25519SecretKey: [UInt8], + subkey: String? = nil, + timestampMs: UInt64? = nil + ) { + self.pubkey = pubkey + self.ed25519PublicKey = ed25519PublicKey + self.ed25519SecretKey = ed25519SecretKey + self.subkey = subkey + self.timestampMs = timestampMs + } + + // MARK: - Codable + + public func encode(to encoder: Encoder) throws { + var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) + + // Generate the signature for the request for encoding + let signatureBase64: String = try generateSignature().toBase64() + try container.encode(pubkey, forKey: .pubkey) + try container.encodeIfPresent(subkey, forKey: .subkey) + try container.encodeIfPresent(timestampMs, forKey: .timestampMs) + try container.encode(ed25519PublicKey.toHexString(), forKey: .ed25519PublicKey) + try container.encode(signatureBase64, forKey: .signatureBase64) + } + + // MARK: - Abstract Functions + + func generateSignature() throws -> [UInt8] { + preconditionFailure("abstract class - override in subclass") + } +} diff --git a/SessionSnodeKit/SnodeMessage.swift b/SessionSnodeKit/Models/SnodeMessage.swift similarity index 98% rename from SessionSnodeKit/SnodeMessage.swift rename to SessionSnodeKit/Models/SnodeMessage.swift index dedc33c2e..fea1d5bf7 100644 --- a/SessionSnodeKit/SnodeMessage.swift +++ b/SessionSnodeKit/Models/SnodeMessage.swift @@ -5,7 +5,7 @@ import SessionUtilitiesKit public final class SnodeMessage: Codable { private enum CodingKeys: String, CodingKey { - case recipient = "pubKey" + case recipient = "pubkey" case data case ttl case timestampMs = "timestamp" diff --git a/SessionSnodeKit/Models/SnodeReceivedMessage.swift b/SessionSnodeKit/Models/SnodeReceivedMessage.swift index bf2832f5c..a4659cb78 100644 --- a/SessionSnodeKit/Models/SnodeReceivedMessage.swift +++ b/SessionSnodeKit/Models/SnodeReceivedMessage.swift @@ -11,24 +11,23 @@ public struct SnodeReceivedMessage: CustomDebugStringConvertible { public let info: SnodeReceivedMessageInfo public let data: Data - init?(snode: Snode, publicKey: String, namespace: Int, rawMessage: JSON) { - guard let hash: String = rawMessage["hash"] as? String else { return nil } - - guard - let base64EncodedString: String = rawMessage["data"] as? String, - let data: Data = Data(base64Encoded: base64EncodedString) - else { + init?( + snode: Snode, + publicKey: String, + namespace: SnodeAPI.Namespace, + rawMessage: GetMessagesResponse.RawMessage + ) { + guard let data: Data = Data(base64Encoded: rawMessage.data) else { SNLog("Failed to decode data for message: \(rawMessage).") return nil } - let expirationDateMs: Int64? = (rawMessage["expiration"] as? Int64) self.info = SnodeReceivedMessageInfo( snode: snode, publicKey: publicKey, namespace: namespace, - hash: hash, - expirationDateMs: (expirationDateMs ?? SnodeReceivedMessage.defaultExpirationSeconds) + hash: rawMessage.hash, + expirationDateMs: (rawMessage.expiration ?? SnodeReceivedMessage.defaultExpirationSeconds) ) self.data = data } diff --git a/SessionSnodeKit/Models/SnodeRecursiveResponse.swift b/SessionSnodeKit/Models/SnodeRecursiveResponse.swift new file mode 100644 index 000000000..2d4dbb1e4 --- /dev/null +++ b/SessionSnodeKit/Models/SnodeRecursiveResponse.swift @@ -0,0 +1,21 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public class SnodeRecursiveResponse: SnodeResponse { + private enum CodingKeys: String, CodingKey { + case swarm + } + + internal let swarm: [String: T] + + // MARK: - Initialization + + required init(from decoder: Decoder) throws { + let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) + + swarm = try container.decode([String: T].self, forKey: .swarm) + + try super.init(from: decoder) + } +} diff --git a/SessionSnodeKit/Models/SnodeRequest.swift b/SessionSnodeKit/Models/SnodeRequest.swift new file mode 100644 index 000000000..f8d777569 --- /dev/null +++ b/SessionSnodeKit/Models/SnodeRequest.swift @@ -0,0 +1,33 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import SessionUtilitiesKit + +public struct SnodeRequest: Encodable { + private enum CodingKeys: String, CodingKey { + case method + case body = "params" + } + + internal let endpoint: SnodeAPI.Endpoint + internal let body: T + + // MARK: - Initialization + + public init( + endpoint: SnodeAPI.Endpoint, + body: T + ) { + self.endpoint = endpoint + self.body = body + } + + // MARK: - Codable + + public func encode(to encoder: Encoder) throws { + var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(endpoint.rawValue, forKey: .method) + try container.encode(body, forKey: .body) + } +} diff --git a/SessionSnodeKit/Models/SnodeResponse.swift b/SessionSnodeKit/Models/SnodeResponse.swift new file mode 100644 index 000000000..cba1f63b0 --- /dev/null +++ b/SessionSnodeKit/Models/SnodeResponse.swift @@ -0,0 +1,13 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public class SnodeResponse: Codable { + private enum CodingKeys: String, CodingKey { + case hardFork = "hf" + case timeOffset = "t" + } + + internal let hardFork: [Int] + internal let timeOffset: Int64 +} diff --git a/SessionSnodeKit/Models/SnodeSwarmItem.swift b/SessionSnodeKit/Models/SnodeSwarmItem.swift new file mode 100644 index 000000000..456b0ed86 --- /dev/null +++ b/SessionSnodeKit/Models/SnodeSwarmItem.swift @@ -0,0 +1,51 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public class SnodeSwarmItem: Codable { + private enum CodingKeys: String, CodingKey { + case signatureBase64 = "signature" + + case failed + case timeout + case code + case reason + case badPeerResponse = "bad_peer_response" + case queryFailure = "query_failure" + } + + public let signatureBase64: String + + /// `true` if the request failed, possibly accompanied by one of the following: `timeout`, `code`, + /// `reason`, `badPeerResponse`, `queryFailure` + public let failed: Bool + + /// `true` if the inter-swarm request timed out + public let timeout: Bool? + + /// `X` if the inter-swarm request returned error code `X` + public let code: Int? + + /// a reason string, e.g. propagating a thrown exception messages + public let reason: String? + + /// `true` if the peer returned an unparseable response + public let badPeerResponse: Bool? + + /// `true` if the database failed to perform the query + public let queryFailure: Bool? + + // MARK: - Initialization + + public required init(from decoder: Decoder) throws { + let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) + + signatureBase64 = try container.decode(String.self, forKey: .signatureBase64) + failed = ((try? container.decode(Bool.self, forKey: .failed)) ?? false) + timeout = try? container.decode(Bool.self, forKey: .timeout) + code = try? container.decode(Int.self, forKey: .code) + reason = try? container.decode(String.self, forKey: .reason) + badPeerResponse = try? container.decode(Bool.self, forKey: .badPeerResponse) + queryFailure = try? container.decode(Bool.self, forKey: .queryFailure) + } +} diff --git a/SessionSnodeKit/Models/SwarmSnode.swift b/SessionSnodeKit/Models/SwarmSnode.swift index 727d79191..9ba62558d 100644 --- a/SessionSnodeKit/Models/SwarmSnode.swift +++ b/SessionSnodeKit/Models/SwarmSnode.swift @@ -40,7 +40,7 @@ extension SwarmSnode { } catch { SNLog("Failed to parse snode: \(error.localizedDescription).") - throw HTTP.Error.invalidJSON + throw HTTPError.invalidJSON } } diff --git a/SessionSnodeKit/Models/UpdateExpiryAllRequest.swift b/SessionSnodeKit/Models/UpdateExpiryAllRequest.swift new file mode 100644 index 000000000..92e5b0de6 --- /dev/null +++ b/SessionSnodeKit/Models/UpdateExpiryAllRequest.swift @@ -0,0 +1,85 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +extension SnodeAPI { + public class UpdateExpiryAllRequest: SnodeAuthenticatedRequestBody { + enum CodingKeys: String, CodingKey { + case expiryMs = "expiry" + case namespace + } + + let expiryMs: UInt64 + + /// The message namespace from which to change message expiries. The request will update the expiry for + /// all messages from the specific namespace, or from all namespaces when not provided + /// + /// **Note:** If omitted when sending the request, message expiries are updated from the default namespace + /// only (namespace 0) + let namespace: SnodeAPI.Namespace? + + // MARK: - Init + + public init( + expiryMs: UInt64, + namespace: SnodeAPI.Namespace?, + pubkey: String, + ed25519PublicKey: [UInt8], + ed25519SecretKey: [UInt8] + ) { + self.expiryMs = expiryMs + self.namespace = namespace + + super.init( + pubkey: pubkey, + ed25519PublicKey: ed25519PublicKey, + ed25519SecretKey: ed25519SecretKey + ) + } + + // MARK: - Coding + + override public func encode(to encoder: Encoder) throws { + var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(expiryMs, forKey: .expiryMs) + + // If no namespace is specified it defaults to the default namespace only (namespace + // 0), so instead in this case we want to explicitly delete from `all` namespaces + switch namespace { + case .some(let namespace): try container.encode(namespace, forKey: .namespace) + case .none: try container.encode("all", forKey: .namespace) + } + + try super.encode(to: encoder) + } + + // MARK: - Abstract Methods + + override func generateSignature() throws -> [UInt8] { + /// Ed25519 signature of `("expire_all" || namespace || expiry)`, signed by `pubkey`. Must be + /// base64 encoded (json) or bytes (OMQ). namespace should be the stringified namespace for + /// non-default namespace expiries (i.e. "42", "-99", "all"), or an empty string for the default + /// namespace (whether or not explicitly provided). + let verificationBytes: [UInt8] = SnodeAPI.Endpoint.expireAll.rawValue.bytes + .appending( + contentsOf: (namespace == nil ? + "all" : + namespace?.verificationString + )?.bytes + ) + .appending(contentsOf: "\(expiryMs)".data(using: .ascii)?.bytes) + + guard + let signatureBytes: [UInt8] = sodium.wrappedValue.sign.signature( + message: verificationBytes, + secretKey: ed25519SecretKey + ) + else { + throw SnodeAPIError.signingFailed + } + + return signatureBytes + } + } +} diff --git a/SessionSnodeKit/Models/UpdateExpiryAllResponse.swift b/SessionSnodeKit/Models/UpdateExpiryAllResponse.swift new file mode 100644 index 000000000..7deba77d5 --- /dev/null +++ b/SessionSnodeKit/Models/UpdateExpiryAllResponse.swift @@ -0,0 +1,85 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import Sodium +import SessionUtilitiesKit + +public class UpdateExpiryAllResponse: SnodeRecursiveResponse { + // MARK: - Convenience + + internal func validResultMap( + userX25519PublicKey: String, + expiryMs: UInt64, + sodium: Sodium + ) throws -> [String: [String]] { + return try swarm.reduce(into: [:]) { result, next in + guard + !next.value.failed, + let encodedSignature: Data = Data(base64Encoded: next.value.signatureBase64) + else { + result[next.key] = [] + + if let reason: String = next.value.reason, let statusCode: Int = next.value.code { + SNLog("Couldn't update expiry from: \(next.key) due to error: \(reason) (\(statusCode)).") + } + else { + SNLog("Couldn't update expiry from: \(next.key).") + } + return + } + + /// Signature of `( PUBKEY_HEX || EXPIRY || UPDATED[0] || ... || UPDATED[N] )` + /// signed by the node's ed25519 pubkey. When doing a multi-namespace delete the `UPDATED` + /// values are totally ordered (i.e. among all the hashes deleted regardless of namespace) + let verificationBytes: [UInt8] = userX25519PublicKey.bytes + .appending(contentsOf: "\(expiryMs)".data(using: .ascii)?.bytes) + .appending(contentsOf: next.value.updated.joined().bytes) + + let isValid: Bool = sodium.sign.verify( + message: verificationBytes, + publicKey: Data(hex: next.key).bytes, + signature: encodedSignature.bytes + ) + + // If the update signature is invalid then we want to fail here + guard isValid else { throw SnodeAPIError.signatureVerificationFailed } + + result[next.key] = next.value.updated + } + } +} + +// MARK: - SwarmItem + +public extension UpdateExpiryAllResponse { + class SwarmItem: SnodeSwarmItem { + private enum CodingKeys: String, CodingKey { + case updated + } + + public let updated: [String] + public let updatedNamespaced: [String: [String]] + + // MARK: - Initialization + + required init(from decoder: Decoder) throws { + let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) + + if let decodedUpdatedNamespaced: [String: [String]] = try? container.decode([String: [String]].self, forKey: .updated) { + updatedNamespaced = decodedUpdatedNamespaced + + /// **Note:** When doing a multi-namespace delete the `UPDATED` values are totally + /// ordered (i.e. among all the hashes deleted regardless of namespace) + updated = decodedUpdatedNamespaced + .reduce(into: []) { result, next in result.append(contentsOf: next.value) } + .sorted() + } + else { + updated = ((try? container.decode([String].self, forKey: .updated)) ?? []) + updatedNamespaced = [:] + } + + try super.init(from: decoder) + } + } +} diff --git a/SessionSnodeKit/Models/UpdateExpiryRequest.swift b/SessionSnodeKit/Models/UpdateExpiryRequest.swift new file mode 100644 index 000000000..4add9bd5d --- /dev/null +++ b/SessionSnodeKit/Models/UpdateExpiryRequest.swift @@ -0,0 +1,69 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +extension SnodeAPI { + public class UpdateExpiryRequest: SnodeAuthenticatedRequestBody { + enum CodingKeys: String, CodingKey { + case messageHashes = "messages" + case expiryMs = "expiry" + } + + let messageHashes: [String] + let expiryMs: UInt64 + + // MARK: - Init + + public init( + messageHashes: [String], + expiryMs: UInt64, + pubkey: String, + ed25519PublicKey: [UInt8], + ed25519SecretKey: [UInt8], + subkey: String? + ) { + self.messageHashes = messageHashes + self.expiryMs = expiryMs + + super.init( + pubkey: pubkey, + ed25519PublicKey: ed25519PublicKey, + ed25519SecretKey: ed25519SecretKey, + subkey: subkey + ) + } + + // MARK: - Coding + + override public func encode(to encoder: Encoder) throws { + var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(messageHashes, forKey: .messageHashes) + try container.encode(expiryMs, forKey: .expiryMs) + + try super.encode(to: encoder) + } + + // MARK: - Abstract Methods + + override func generateSignature() throws -> [UInt8] { + /// Ed25519 signature of `("expire" || expiry || messages[0] || ... || messages[N])` + /// where `expiry` is the expiry timestamp expressed as a string. The signature must be base64 + /// encoded (json) or bytes (bt). + let verificationBytes: [UInt8] = SnodeAPI.Endpoint.expire.rawValue.bytes + .appending(contentsOf: "\(expiryMs)".data(using: .ascii)?.bytes) + .appending(contentsOf: messageHashes.joined().bytes) + + guard + let signatureBytes: [UInt8] = sodium.wrappedValue.sign.signature( + message: verificationBytes, + secretKey: ed25519SecretKey + ) + else { + throw SnodeAPIError.signingFailed + } + + return signatureBytes + } + } +} diff --git a/SessionSnodeKit/Models/UpdateExpiryResponse.swift b/SessionSnodeKit/Models/UpdateExpiryResponse.swift new file mode 100644 index 000000000..41f7a5ede --- /dev/null +++ b/SessionSnodeKit/Models/UpdateExpiryResponse.swift @@ -0,0 +1,76 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import Sodium +import SessionUtilitiesKit + +public class UpdateExpiryResponse: SnodeRecursiveResponse { + // MARK: - Convenience + + internal func validResultMap( + userX25519PublicKey: String, + messageHashes: [String], + sodium: Sodium + ) throws -> [String: (hashes: [String], expiry: UInt64)] { + return try swarm.reduce(into: [:]) { result, next in + guard + !next.value.failed, + let encodedSignature: Data = Data(base64Encoded: next.value.signatureBase64) + else { + result[next.key] = ([], 0) + + if let reason: String = next.value.reason, let statusCode: Int = next.value.code { + SNLog("Couldn't update expiry from: \(next.key) due to error: \(reason) (\(statusCode)).") + } + else { + SNLog("Couldn't update expiry from: \(next.key).") + } + return + } + + /// Signature of + /// `( PUBKEY_HEX || EXPIRY || RMSG[0] || ... || RMSG[N] || UMSG[0] || ... || UMSG[M] )` + /// where RMSG are the requested expiry hashes and UMSG are the actual updated hashes. The signature uses + /// the node's ed25519 pubkey. + let verificationBytes: [UInt8] = userX25519PublicKey.bytes + .appending(contentsOf: "\(next.value.expiry)".data(using: .ascii)?.bytes) + .appending(contentsOf: messageHashes.joined().bytes) + .appending(contentsOf: next.value.updated.joined().bytes) + let isValid: Bool = sodium.sign.verify( + message: verificationBytes, + publicKey: Data(hex: next.key).bytes, + signature: encodedSignature.bytes + ) + + // If the update signature is invalid then we want to fail here + guard isValid else { throw SnodeAPIError.signatureVerificationFailed } + + result[next.key] = (hashes: next.value.updated, expiry: next.value.expiry) + } + } +} + +// MARK: - SwarmItem + +public extension UpdateExpiryResponse { + class SwarmItem: SnodeSwarmItem { + private enum CodingKeys: String, CodingKey { + case updated + case expiry + } + + public let updated: [String] + public let expiry: UInt64 + + // MARK: - Initialization + + required init(from decoder: Decoder) throws { + let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) + + updated = ((try? container.decode([String].self, forKey: .updated)) ?? []) + expiry = try container.decode(UInt64.self, forKey: .expiry) + + try super.init(from: decoder) + } + } +} diff --git a/SessionSnodeKit/Notification+OnionRequestAPI.swift b/SessionSnodeKit/Networking/Notification+OnionRequestAPI.swift similarity index 100% rename from SessionSnodeKit/Notification+OnionRequestAPI.swift rename to SessionSnodeKit/Networking/Notification+OnionRequestAPI.swift diff --git a/SessionSnodeKit/OnionRequestAPI+Encryption.swift b/SessionSnodeKit/Networking/OnionRequestAPI+Encryption.swift similarity index 100% rename from SessionSnodeKit/OnionRequestAPI+Encryption.swift rename to SessionSnodeKit/Networking/OnionRequestAPI+Encryption.swift diff --git a/SessionSnodeKit/OnionRequestAPI.swift b/SessionSnodeKit/Networking/OnionRequestAPI.swift similarity index 89% rename from SessionSnodeKit/OnionRequestAPI.swift rename to SessionSnodeKit/Networking/OnionRequestAPI.swift index 997de4088..f3cabbee1 100644 --- a/SessionSnodeKit/OnionRequestAPI.swift +++ b/SessionSnodeKit/Networking/OnionRequestAPI.swift @@ -412,25 +412,17 @@ public enum OnionRequestAPI: OnionRequestAPIType { to snode: Snode ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { /// **Note:** Currently the service nodes only support V3 Onion Requests - return sendOnionRequest(with: payload, to: OnionRequestAPIDestination.snode(snode), version: .v3) - .map { _, maybeData in - guard let data: Data = maybeData else { throw HTTP.Error.invalidResponse } - - return data - } - .recover2 { error -> Promise in - guard case OnionRequestAPIError.httpRequestFailedAtDestination(let statusCode, let data, _) = error else { - throw error - } - - throw SnodeAPI.handleError(withStatusCode: statusCode, data: data, forSnode: snode, associatedWith: publicKey) ?? error - } + return sendOnionRequest( + with: payload, + to: OnionRequestAPIDestination.snode(snode), + version: .v3 + ) } /// Sends an onion request to `server`. Builds new paths as needed. public static func sendOnionRequest( _ request: URLRequest, - to server: String, // TODO: Remove this 'server' value (unused) + to server: String, with x25519PublicKey: String ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { guard let url = request.url, let host = request.url?.host else { @@ -594,78 +586,40 @@ public enum OnionRequestAPI: OnionRequestAPIType { // MARK: - Version Handling - private static func generatePayload(for request: URLRequest, with version: OnionRequestAPIVersion) -> Data? { + private static func generateV4Payload(for request: URLRequest) -> Data? { guard let url = request.url else { return nil } - switch version { - // V2 and V3 Onion Requests have the same structure - case .v2, .v3: - var rawHeaders = request.allHTTPHeaderFields ?? [:] - rawHeaders.removeValue(forKey: "User-Agent") - var headers: JSON = rawHeaders.mapValues { value in - switch value.lowercased() { - case "true": return true - case "false": return false - default: return value - } - } - - var endpoint = url.path.removingPrefix("/") - if let query = url.query { endpoint += "?\(query)" } - let bodyAsString: String - - if let body: Data = request.httpBody { - headers["Content-Type"] = "application/json" // Assume data is JSON - bodyAsString = (String(data: body, encoding: .utf8) ?? "null") - } - else { - bodyAsString = "null" - } - - let payload: JSON = [ - "body" : bodyAsString, - "endpoint" : endpoint, - "method" : request.httpMethod!, - "headers" : headers - ] - - guard let jsonData: Data = try? JSONSerialization.data(withJSONObject: payload, options: []) else { return nil } - - return jsonData - - // V4 Onion Requests have a very different structure - case .v4: - // Note: We need to remove the leading forward slash unless we are explicitly hitting a legacy - // endpoint (in which case we need it to ensure the request signing works correctly - let endpoint: String = url.path - .appending(url.query.map { value in "?\(value)" }) - - let requestInfo: RequestInfo = RequestInfo( - method: (request.httpMethod ?? "GET"), // The default (if nil) is 'GET' - endpoint: endpoint, - headers: (request.allHTTPHeaderFields ?? [:]) - .setting( - "Content-Type", - (request.httpBody == nil ? nil : - // Default to JSON if not defined - ((request.allHTTPHeaderFields ?? [:])["Content-Type"] ?? "application/json") - ) - ) - .removingValue(forKey: "User-Agent") + // Note: We need to remove the leading forward slash unless we are explicitly hitting + // a legacy endpoint (in which case we need it to ensure the request signing works + // correctly + let endpoint: String = url.path + .appending(url.query.map { value in "?\(value)" }) + + let requestInfo: HTTP.RequestInfo = HTTP.RequestInfo( + method: (request.httpMethod ?? "GET"), // The default (if nil) is 'GET' + endpoint: endpoint, + headers: (request.allHTTPHeaderFields ?? [:]) + .setting( + "Content-Type", + (request.httpBody == nil ? nil : + // Default to JSON if not defined + ((request.allHTTPHeaderFields ?? [:])["Content-Type"] ?? "application/json") + ) ) - - /// Generate the Bencoded payload in the form `l{requestInfoLength}:{requestInfo}{bodyLength}:{body}e` - guard let requestInfoData: Data = try? JSONEncoder().encode(requestInfo) else { return nil } - guard let prefixData: Data = "l\(requestInfoData.count):".data(using: .ascii), let suffixData: Data = "e".data(using: .ascii) else { - return nil - } - - if let body: Data = request.httpBody, let bodyCountData: Data = "\(body.count):".data(using: .ascii) { - return (prefixData + requestInfoData + bodyCountData + body + suffixData) - } - - return (prefixData + requestInfoData + suffixData) + .removingValue(forKey: "User-Agent") + ) + + /// Generate the Bencoded payload in the form `l{requestInfoLength}:{requestInfo}{bodyLength}:{body}e` + guard let requestInfoData: Data = try? JSONEncoder().encode(requestInfo) else { return nil } + guard let prefixData: Data = "l\(requestInfoData.count):".data(using: .ascii), let suffixData: Data = "e".data(using: .ascii) else { + return nil } + + if let body: Data = request.httpBody, let bodyCountData: Data = "\(body.count):".data(using: .ascii) { + return (prefixData + requestInfoData + bodyCountData + body + suffixData) + } + + return (prefixData + requestInfoData + suffixData) } private static func handleResponse( @@ -836,7 +790,7 @@ public enum OnionRequestAPI: OnionRequestAPIType { let infoStringEndIndex: String.Index = responseString.index(infoStringStartIndex, offsetBy: infoLength) let infoString: String = String(responseString[infoStringStartIndex.. = Atomic(false) - private static var loadedSwarms: Atomic> = Atomic([]) - private static var getSnodePoolPromise: Atomic>?> = Atomic(nil) - - /// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions. - internal static var snodeFailureCount: Atomic<[Snode: UInt]> = Atomic([:]) - /// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions. - internal static var snodePool: Atomic> = Atomic([]) - - /// The offset between the user's clock and the Service Node's clock. Used in cases where the - /// user's clock is incorrect. - /// - /// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions. - public static var clockOffset: Atomic = Atomic(0) - /// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions. - public static var swarmCache: Atomic<[String: Set]> = Atomic([:]) - - // MARK: - Namespaces - - public static let defaultNamespace = 0 - public static let closedGroupNamespace = -10 - public static let configNamespace = 5 - - // MARK: - Hardfork version - - public static var hardfork = UserDefaults.standard[.hardfork] - public static var softfork = UserDefaults.standard[.softfork] - - // MARK: - Settings - - private static let maxRetryCount: UInt = 8 - private static let minSwarmSnodeCount = 3 - private static let seedNodePool: Set = Features.useTestnet ? [ "http://public.loki.foundation:38157" ] : [ "https://storage.seed1.loki.network:4433", "https://storage.seed3.loki.network:4433", "https://public.loki.foundation:4433" ] - private static let snodeFailureThreshold = 3 - private static let targetSwarmSnodeCount = 2 - private static let minSnodePoolCount = 12 - - // MARK: Snode Pool Interaction - - private static var hasInsufficientSnodes: Bool { snodePool.wrappedValue.count < minSnodePoolCount } - - private static func loadSnodePoolIfNeeded() { - guard !hasLoadedSnodePool.wrappedValue else { return } - - let fetchedSnodePool: Set = Storage.shared - .read { db in try Snode.fetchSet(db) } - .defaulting(to: []) - - snodePool.mutate { $0 = fetchedSnodePool } - hasLoadedSnodePool.mutate { $0 = true } - } - - private static func setSnodePool(to newValue: Set, db: Database? = nil) { - snodePool.mutate { $0 = newValue } - - if let db: Database = db { - _ = try? Snode.deleteAll(db) - newValue.forEach { try? $0.save(db) } - } - else { - Storage.shared.write { db in - _ = try? Snode.deleteAll(db) - newValue.forEach { try? $0.save(db) } - } - } - } - - private static func dropSnodeFromSnodePool(_ snode: Snode) { - #if DEBUG - dispatchPrecondition(condition: .onQueue(Threading.workQueue)) - #endif - var snodePool = SnodeAPI.snodePool.wrappedValue - snodePool.remove(snode) - setSnodePool(to: snodePool) - } - - @objc public static func clearSnodePool() { - snodePool.mutate { $0.removeAll() } - - Threading.workQueue.async { - setSnodePool(to: []) - } - } - - // MARK: Swarm Interaction - private static func loadSwarmIfNeeded(for publicKey: String) { - guard !loadedSwarms.wrappedValue.contains(publicKey) else { return } - - let updatedCacheForKey: Set = Storage.shared - .read { db in try Snode.fetchSet(db, publicKey: publicKey) } - .defaulting(to: []) - - swarmCache.mutate { $0[publicKey] = updatedCacheForKey } - loadedSwarms.mutate { $0.insert(publicKey) } - } - - private static func setSwarm(to newValue: Set, for publicKey: String, persist: Bool = true) { - #if DEBUG - dispatchPrecondition(condition: .onQueue(Threading.workQueue)) - #endif - swarmCache.mutate { $0[publicKey] = newValue } - - guard persist else { return } - - Storage.shared.write { db in - try? newValue.save(db, key: publicKey) - } - } - - public static func dropSnodeFromSwarmIfNeeded(_ snode: Snode, publicKey: String) { - #if DEBUG - dispatchPrecondition(condition: .onQueue(Threading.workQueue)) - #endif - let swarmOrNil = swarmCache.wrappedValue[publicKey] - guard var swarm = swarmOrNil, let index = swarm.firstIndex(of: snode) else { return } - swarm.remove(at: index) - setSwarm(to: swarm, for: publicKey) - } - - // MARK: Internal API - - internal static func invoke(_ method: SnodeAPIEndpoint, on snode: Snode, associatedWith publicKey: String? = nil, parameters: JSON) -> Promise { - if Features.useOnionRequests { - return OnionRequestAPI - .sendOnionRequest( - to: snode, - invoking: method, - with: parameters, - associatedWith: publicKey - ) - .map2 { responseData in - guard let responseJson: JSON = try? JSONSerialization.jsonObject(with: responseData, options: [ .fragmentsAllowed ]) as? JSON else { - throw HTTP.Error.invalidJSON - } - - if let hf = responseJson["hf"] as? [Int] { - if hf[1] > softfork { - softfork = hf[1] - UserDefaults.standard[.softfork] = softfork - } - - if hf[0] > hardfork { - hardfork = hf[0] - UserDefaults.standard[.hardfork] = hardfork - softfork = hf[1] - UserDefaults.standard[.softfork] = softfork - } - } - - return responseData - } - } - else { - let url = "\(snode.address):\(snode.port)/storage_rpc/v1" - return HTTP.execute(.post, url, parameters: parameters) - .recover2 { error -> Promise in - guard case HTTP.Error.httpRequestFailed(let statusCode, let data) = error else { throw error } - throw SnodeAPI.handleError(withStatusCode: statusCode, data: data, forSnode: snode, associatedWith: publicKey) ?? error - } - } - } - - private static func getNetworkTime(from snode: Snode) -> Promise { - return invoke(.getInfo, on: snode, parameters: [:]).map2 { responseData in - guard let responseJson: JSON = try? JSONSerialization.jsonObject(with: responseData, options: [ .fragmentsAllowed ]) as? JSON else { - throw HTTP.Error.invalidJSON - } - guard let timestamp = responseJson["timestamp"] as? UInt64 else { throw HTTP.Error.invalidJSON } - return timestamp - } - } - - internal static func getRandomSnode() -> Promise { - // randomElement() uses the system's default random generator, which is cryptographically secure - return getSnodePool().map2 { $0.randomElement()! } - } - - private static func getSnodePoolFromSeedNode() -> Promise> { - let target = seedNodePool.randomElement()! - let url = "\(target)/json_rpc" - let parameters: JSON = [ - "method": "get_n_service_nodes", - "params": [ - "active_only": true, - "limit": 256, - "fields": [ - "public_ip": true, - "storage_port": true, - "pubkey_ed25519": true, - "pubkey_x25519": true - ] - ] - ] - SNLog("Populating snode pool using seed node: \(target).") - let (promise, seal) = Promise>.pending() - - Threading.workQueue.async { - attempt(maxRetryCount: 4, recoveringOn: Threading.workQueue) { - HTTP.execute(.post, url, parameters: parameters, useSeedNodeURLSession: true) - .map2 { responseData -> Set in - guard let snodePool: SnodePoolResponse = try? JSONDecoder().decode(SnodePoolResponse.self, from: responseData) else { - throw SnodeAPIError.snodePoolUpdatingFailed - } - - return snodePool.result - .serviceNodeStates - .compactMap { $0.value } - .asSet() - } - } - .done2 { snodePool in - SNLog("Got snode pool from seed node: \(target).") - seal.fulfill(snodePool) - } - .catch2 { error in - SNLog("Failed to contact seed node at: \(target).") - seal.reject(error) - } - } - - return promise - } - - private static func getSnodePoolFromSnode() -> Promise> { - var snodePool = SnodeAPI.snodePool.wrappedValue - var snodes: Set = [] - (0..<3).forEach { _ in - guard let snode = snodePool.randomElement() else { return } - - snodePool.remove(snode) - snodes.insert(snode) - } - - let snodePoolPromises: [Promise>] = snodes.map { snode in - return attempt(maxRetryCount: 4, recoveringOn: Threading.workQueue) { - // Don't specify a limit in the request. Service nodes return a shuffled - // list of nodes so if we specify a limit the 3 responses we get might have - // very little overlap. - let parameters: JSON = [ - "endpoint": "get_service_nodes", - "params": [ - "active_only": true, - "fields": [ - "public_ip": true, - "storage_port": true, - "pubkey_ed25519": true, - "pubkey_x25519": true - ] - ] - ] - - return invoke(.oxenDaemonRPCCall, on: snode, parameters: parameters) - .map2 { responseData in - guard let snodePool: SnodePoolResponse = try? JSONDecoder().decode(SnodePoolResponse.self, from: responseData) else { - throw SnodeAPIError.snodePoolUpdatingFailed - } - - return snodePool.result - .serviceNodeStates - .compactMap { $0.value } - .asSet() - } - } - } - - let promise = when(fulfilled: snodePoolPromises).map2 { results -> Set in - let result: Set = results.reduce(Set()) { prev, next in prev.intersection(next) } - - // We want the snodes to agree on at least this many snodes - guard result.count > 24 else { throw SnodeAPIError.inconsistentSnodePools } - - // Limit the snode pool size to 256 so that we don't go too long without - // refreshing it - return Set(result.prefix(256)) - } - - return promise - } - - // MARK: Public API - - public static func hasCachedSnodesInclusingExpired() -> Bool { - loadSnodePoolIfNeeded() - - return !hasInsufficientSnodes - } - - public static func getSnodePool() -> Promise> { - loadSnodePoolIfNeeded() - let now = Date() - let hasSnodePoolExpired = given(Storage.shared[.lastSnodePoolRefreshDate]) { - now.timeIntervalSince($0) > 2 * 60 * 60 - }.defaulting(to: true) - let snodePool: Set = SnodeAPI.snodePool.wrappedValue - - guard hasInsufficientSnodes || hasSnodePoolExpired else { - return Promise.value(snodePool) - } - - if let getSnodePoolPromise = getSnodePoolPromise.wrappedValue { return getSnodePoolPromise } - - let promise: Promise> - if snodePool.count < minSnodePoolCount { - promise = getSnodePoolFromSeedNode() - } - else { - promise = getSnodePoolFromSnode().recover2 { _ in - getSnodePoolFromSeedNode() - } - } - - getSnodePoolPromise.mutate { $0 = promise } - promise.map2 { snodePool -> Set in - guard !snodePool.isEmpty else { throw SnodeAPIError.snodePoolUpdatingFailed } - - return snodePool - } - - promise.then2 { snodePool -> Promise> in - let (promise, seal) = Promise>.pending() - - Storage.shared.writeAsync( - updates: { db in - db[.lastSnodePoolRefreshDate] = now - setSnodePool(to: snodePool, db: db) - }, - completion: { _, _ in - seal.fulfill(snodePool) - } - ) - - return promise - } - promise.done2 { _ in - getSnodePoolPromise.mutate { $0 = nil } - } - promise.catch2 { _ in - getSnodePoolPromise.mutate { $0 = nil } - } - - return promise - } - - public static func getSessionID(for onsName: String) -> Promise { - let validationCount = 3 - let sessionIDByteCount = 33 - // The name must be lowercased - let onsName = onsName.lowercased() - // Hash the ONS name using BLAKE2b - let nameAsData = [UInt8](onsName.data(using: String.Encoding.utf8)!) - - guard let nameHash = sodium.genericHash.hash(message: nameAsData) else { - return Promise(error: SnodeAPIError.hashingFailed) - } - - // Ask 3 different snodes for the Session ID associated with the given name hash - let base64EncodedNameHash = nameHash.toBase64() - let parameters: [String:Any] = [ - "endpoint" : "ons_resolve", - "params" : [ - "type" : 0, // type 0 means Session - "name_hash" : base64EncodedNameHash - ] - ] - let promises = (0...pending() - - when(resolved: promises).done2 { results in - var sessionIDs: [String] = [] - for result in results { - switch result { - case .rejected(let error): return seal.reject(error) - - case .fulfilled(let responseData): - guard let responseJson: JSON = try? JSONSerialization.jsonObject(with: responseData, options: [ .fragmentsAllowed ]) as? JSON else { - throw HTTP.Error.invalidJSON - } - guard - let intermediate = responseJson["result"] as? JSON, - let hexEncodedCiphertext = intermediate["encrypted_value"] as? String - else { return seal.reject(HTTP.Error.invalidJSON) } - - let ciphertext = [UInt8](Data(hex: hexEncodedCiphertext)) - let isArgon2Based = (intermediate["nonce"] == nil) - - if isArgon2Based { - // Handle old Argon2-based encryption used before HF16 - let salt = [UInt8](Data(repeating: 0, count: sodium.pwHash.SaltBytes)) - guard - let key = sodium.pwHash.hash( - outputLength: sodium.secretBox.KeyBytes, - passwd: nameAsData, - salt: salt, - opsLimit: sodium.pwHash.OpsLimitModerate, - memLimit: sodium.pwHash.MemLimitModerate, - alg: .Argon2ID13 - ) - else { return seal.reject(SnodeAPIError.hashingFailed) } - - let nonce = [UInt8](Data(repeating: 0, count: sodium.secretBox.NonceBytes)) - - guard let sessionIDAsData = sodium.secretBox.open(authenticatedCipherText: ciphertext, secretKey: key, nonce: nonce) else { - return seal.reject(SnodeAPIError.decryptionFailed) - } - - sessionIDs.append(sessionIDAsData.toHexString()) - } - else { - guard let hexEncodedNonce = intermediate["nonce"] as? String else { - return seal.reject(HTTP.Error.invalidJSON) - } - - let nonce = [UInt8](Data(hex: hexEncodedNonce)) - - // xchacha-based encryption - guard let key = sodium.genericHash.hash(message: nameAsData, key: nameHash) else { // key = H(name, key=H(name)) - return seal.reject(SnodeAPIError.hashingFailed) - } - guard ciphertext.count >= (sessionIDByteCount + sodium.aead.xchacha20poly1305ietf.ABytes) else { // Should always be equal in practice - return seal.reject(SnodeAPIError.decryptionFailed) - } - guard let sessionIDAsData = sodium.aead.xchacha20poly1305ietf.decrypt(authenticatedCipherText: ciphertext, secretKey: key, nonce: nonce) else { - return seal.reject(SnodeAPIError.decryptionFailed) - } - - sessionIDs.append(sessionIDAsData.toHexString()) - } - } - } - - guard sessionIDs.count == validationCount && Set(sessionIDs).count == 1 else { - return seal.reject(SnodeAPIError.validationFailed) - } - - seal.fulfill(sessionIDs.first!) - } - - return promise - } - - public static func getTargetSnodes(for publicKey: String) -> Promise<[Snode]> { - // shuffled() uses the system's default random generator, which is cryptographically secure - return getSwarm(for: publicKey).map2 { Array($0.shuffled().prefix(targetSwarmSnodeCount)) } - } - - public static func getSwarm(for publicKey: String) -> Promise> { - loadSwarmIfNeeded(for: publicKey) - - if let cachedSwarm = swarmCache.wrappedValue[publicKey], cachedSwarm.count >= minSwarmSnodeCount { - return Promise> { $0.fulfill(cachedSwarm) } - } - - SNLog("Getting swarm for: \((publicKey == getUserHexEncodedPublicKey()) ? "self" : publicKey).") - let parameters: [String: Any] = [ - "pubKey": (Features.useTestnet ? publicKey.removingIdPrefixIfNeeded() : publicKey) - ] - - return getRandomSnode() - .then2 { snode in - attempt(maxRetryCount: 4, recoveringOn: Threading.workQueue) { - invoke(.getSwarm, on: snode, associatedWith: publicKey, parameters: parameters) - } - } - .map2 { responseData in - let swarm = parseSnodes(from: responseData) - - setSwarm(to: swarm, for: publicKey) - return swarm - } - } - - // MARK: - Retrieve - - // Not in use until we can batch delete and store config messages - public static func getConfigMessages(from snode: Snode, associatedWith publicKey: String) -> Promise<([SnodeReceivedMessage], String?)> { - let (promise, seal) = Promise<([SnodeReceivedMessage], String?)>.pending() - - Threading.workQueue.async { - getMessagesWithAuthentication(from: snode, associatedWith: publicKey, namespace: configNamespace) - .done2 { - seal.fulfill($0) - } - .catch2 { - seal.reject($0) - } - } - - return promise - } - - public static func getMessages(from snode: Snode, associatedWith publicKey: String, authenticated: Bool = true) -> Promise<([SnodeReceivedMessage], String?)> { - let (promise, seal) = Promise<([SnodeReceivedMessage], String?)>.pending() - - Threading.workQueue.async { - let retrievePromise = (authenticated ? - getMessagesWithAuthentication(from: snode, associatedWith: publicKey, namespace: defaultNamespace) : - getMessagesUnauthenticated(from: snode, associatedWith: publicKey) - ) - - retrievePromise - .done2 { seal.fulfill($0) } - .catch2 { seal.reject($0) } - } - - return promise - } - - public static func getClosedGroupMessagesFromDefaultNamespace(from snode: Snode, associatedWith publicKey: String) -> Promise<([SnodeReceivedMessage], String?)> { - let (promise, seal) = Promise<([SnodeReceivedMessage], String?)>.pending() - - Threading.workQueue.async { - getMessagesUnauthenticated(from: snode, associatedWith: publicKey, namespace: defaultNamespace) - .done2 { seal.fulfill($0) } - .catch2 { seal.reject($0) } - } - - return promise - } - - private static func getMessagesWithAuthentication(from snode: Snode, associatedWith publicKey: String, namespace: Int) -> Promise<([SnodeReceivedMessage], String?)> { - /// **Note:** All authentication logic is only apply to 1-1 chats, the reason being that we can't currently support it yet for - /// closed groups. The Storage Server requires an ed25519 key pair, but we don't have that for our closed groups. - guard let userED25519KeyPair: Box.KeyPair = Storage.shared.read({ db in Identity.fetchUserEd25519KeyPair(db) }) else { - return Promise(error: SnodeAPIError.noKeyPair) - } - - // Get last message hash - SnodeReceivedMessageInfo.pruneExpiredMessageHashInfo(for: snode, namespace: namespace, associatedWith: publicKey) - let lastHash = SnodeReceivedMessageInfo.fetchLastNotExpired(for: snode, namespace: namespace, associatedWith: publicKey)?.hash ?? "" - - // Construct signature - let timestamp = UInt64(Int64(floor(Date().timeIntervalSince1970 * 1000)) + SnodeAPI.clockOffset.wrappedValue) - let ed25519PublicKey = userED25519KeyPair.publicKey.toHexString() - let namespaceVerificationString = (namespace == defaultNamespace ? "" : String(namespace)) - - guard - let verificationData = ("retrieve" + namespaceVerificationString + String(timestamp)).data(using: String.Encoding.utf8), - let signature = sodium.sign.signature(message: Bytes(verificationData), secretKey: userED25519KeyPair.secretKey) - else { return Promise(error: SnodeAPIError.signingFailed) } - - // Make the request - let parameters: JSON = [ - "pubKey": Features.useTestnet ? publicKey.removingIdPrefixIfNeeded() : publicKey, - "namespace": namespace, - "lastHash": lastHash, - "timestamp": timestamp, - "pubkey_ed25519": ed25519PublicKey, - "signature": signature.toBase64() - ] - - return invoke(.getMessages, on: snode, associatedWith: publicKey, parameters: parameters) - .map { responseData -> [SnodeReceivedMessage] in - guard - let responseJson: JSON = try? JSONSerialization.jsonObject(with: responseData, options: [ .fragmentsAllowed ]) as? JSON, - let rawMessages: [JSON] = responseJson["messages"] as? [JSON] - else { - return [] - } - - return rawMessages - .compactMap { rawMessage -> SnodeReceivedMessage? in - SnodeReceivedMessage( - snode: snode, - publicKey: publicKey, - namespace: namespace, - rawMessage: rawMessage - ) - } - } - .map { ($0, lastHash) } - } - - private static func getMessagesUnauthenticated( - from snode: Snode, - associatedWith publicKey: String, - namespace: Int = closedGroupNamespace - ) -> Promise<([SnodeReceivedMessage], String?)> { - // Get last message hash - SnodeReceivedMessageInfo.pruneExpiredMessageHashInfo(for: snode, namespace: namespace, associatedWith: publicKey) - let lastHash = SnodeReceivedMessageInfo.fetchLastNotExpired(for: snode, namespace: namespace, associatedWith: publicKey)?.hash ?? "" - - // Make the request - var parameters: JSON = [ - "pubKey": (Features.useTestnet ? publicKey.removingIdPrefixIfNeeded() : publicKey), - "lastHash": lastHash - ] - - // Don't include namespace if polling for 0 with no authentication - if namespace != defaultNamespace { - parameters["namespace"] = namespace - } - - return invoke(.getMessages, on: snode, associatedWith: publicKey, parameters: parameters) - .map { responseData -> [SnodeReceivedMessage] in - guard - let responseJson: JSON = try? JSONSerialization.jsonObject(with: responseData, options: [ .fragmentsAllowed ]) as? JSON, - let rawMessages: [JSON] = responseJson["messages"] as? [JSON] - else { - return [] - } - - return rawMessages - .compactMap { rawMessage -> SnodeReceivedMessage? in - SnodeReceivedMessage( - snode: snode, - publicKey: publicKey, - namespace: namespace, - rawMessage: rawMessage - ) - } - } - .map { ($0, lastHash) } - } - - // MARK: Store - - public static func sendMessage(_ message: SnodeMessage, isClosedGroupMessage: Bool, isConfigMessage: Bool) -> Promise>> { - return sendMessageUnauthenticated(message, isClosedGroupMessage: isClosedGroupMessage) - } - - // Not in use until we can batch delete and store config messages - private static func sendMessageWithAuthentication(_ message: SnodeMessage, namespace: Int) -> Promise>> { - guard - let messageData: Data = try? JSONEncoder().encode(message), - let messageJson: JSON = try? JSONSerialization.jsonObject(with: messageData, options: [ .fragmentsAllowed ]) as? JSON - else { return Promise(error: HTTP.Error.invalidJSON) } - - guard let userED25519KeyPair: Box.KeyPair = Storage.shared.read({ db in Identity.fetchUserEd25519KeyPair(db) }) else { - return Promise(error: SnodeAPIError.noKeyPair) - } - - // Construct signature - let timestamp = UInt64(Int64(floor(Date().timeIntervalSince1970 * 1000)) + SnodeAPI.clockOffset.wrappedValue) - let ed25519PublicKey = userED25519KeyPair.publicKey.toHexString() - - guard - let verificationData = ("store" + String(namespace) + String(timestamp)).data(using: String.Encoding.utf8), - let signature = sodium.sign.signature(message: Bytes(verificationData), secretKey: userED25519KeyPair.secretKey) - else { return Promise(error: SnodeAPIError.signingFailed) } - - // Make the request - let (promise, seal) = Promise>>.pending() - let publicKey = (Features.useTestnet ? message.recipient.removingIdPrefixIfNeeded() : message.recipient) - - Threading.workQueue.async { - getTargetSnodes(for: publicKey) - .map2 { targetSnodes in - var parameters: JSON = messageJson - parameters["namespace"] = namespace - parameters["sig_timestamp"] = timestamp - parameters["pubkey_ed25519"] = ed25519PublicKey - parameters["signature"] = signature.toBase64() - - return Set(targetSnodes.map { targetSnode in - attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) { - invoke(.sendMessage, on: targetSnode, associatedWith: publicKey, parameters: parameters) - } - }) - } - .done2 { seal.fulfill($0) } - .catch2 { seal.reject($0) } - } - - return promise - } - - private static func sendMessageUnauthenticated(_ message: SnodeMessage, isClosedGroupMessage: Bool) -> Promise>> { - guard - let messageData: Data = try? JSONEncoder().encode(message), - let messageJson: JSON = try? JSONSerialization.jsonObject(with: messageData, options: [ .fragmentsAllowed ]) as? JSON - else { return Promise(error: HTTP.Error.invalidJSON) } - - let (promise, seal) = Promise>>.pending() - let publicKey = Features.useTestnet ? message.recipient.removingIdPrefixIfNeeded() : message.recipient - - Threading.workQueue.async { - getTargetSnodes(for: publicKey) - .map2 { targetSnodes in - var rawResponsePromises: Set> = Set() - var parameters: JSON = messageJson - parameters["namespace"] = (isClosedGroupMessage ? closedGroupNamespace : defaultNamespace) - - for targetSnode in targetSnodes { - let rawResponsePromise = attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) { - invoke(.sendMessage, on: targetSnode, associatedWith: publicKey, parameters: parameters) - } - rawResponsePromises.insert(rawResponsePromise) - } - - // Send closed group messages to default namespace as well - if hardfork == 19 && softfork == 0 && isClosedGroupMessage { - parameters["namespace"] = defaultNamespace - for targetSnode in targetSnodes { - let rawResponsePromise = attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) { - invoke(.sendMessage, on: targetSnode, associatedWith: publicKey, parameters: parameters) - } - rawResponsePromises.insert(rawResponsePromise) - } - } - - return rawResponsePromises - } - .done2 { seal.fulfill($0) } - .catch2 { seal.reject($0) } - } - - return promise - } - - // MARK: Edit - - public static func updateExpiry( - publicKey: String, - edKeyPair: Box.KeyPair, - updatedExpiryMs: UInt64, - serverHashes: [String] - ) -> Promise<[String: (hashes: [String], expiry: UInt64)]> { - let publicKey = (Features.useTestnet ? publicKey.removingIdPrefixIfNeeded() : publicKey) - - return attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) { - getSwarm(for: publicKey) - .then2 { swarm -> Promise<[String: (hashes: [String], expiry: UInt64)]> in - // "expire" || expiry || messages[0] || ... || messages[N] - let verificationBytes = SnodeAPIEndpoint.expire.rawValue.bytes - .appending(contentsOf: "\(updatedExpiryMs)".data(using: .ascii)?.bytes) - .appending(contentsOf: serverHashes.joined().bytes) - - guard - let snode = swarm.randomElement(), - let signature = sodium.sign.signature( - message: verificationBytes, - secretKey: edKeyPair.secretKey - ) - else { - throw SnodeAPIError.signingFailed - } - - let parameters: JSON = [ - "pubkey" : publicKey, - "pubkey_ed25519" : edKeyPair.publicKey.toHexString(), - "expiry": updatedExpiryMs, - "messages": serverHashes, - "signature": signature.toBase64() - ] - - return attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) { - invoke(.expire, on: snode, associatedWith: publicKey, parameters: parameters) - .map2 { responseData -> [String: (hashes: [String], expiry: UInt64)] in - guard let responseJson: JSON = try? JSONSerialization.jsonObject(with: responseData, options: [ .fragmentsAllowed ]) as? JSON else { - throw HTTP.Error.invalidJSON - } - guard let swarm = responseJson["swarm"] as? JSON else { throw HTTP.Error.invalidJSON } - - var result: [String: (hashes: [String], expiry: UInt64)] = [:] - - for (snodePublicKey, rawJSON) in swarm { - guard let json = rawJSON as? JSON else { throw HTTP.Error.invalidJSON } - guard (json["failed"] as? Bool ?? false) == false else { - if let reason = json["reason"] as? String, let statusCode = json["code"] as? String { - SNLog("Couldn't delete data from: \(snodePublicKey) due to error: \(reason) (\(statusCode)).") - } - else { - SNLog("Couldn't delete data from: \(snodePublicKey).") - } - result[snodePublicKey] = ([], 0) - continue - } - - guard - let hashes: [String] = json["updated"] as? [String], - let expiryApplied: UInt64 = json["expiry"] as? UInt64, - let signature: String = json["signature"] as? String - else { - throw HTTP.Error.invalidJSON - } - - // The signature format is ( PUBKEY_HEX || EXPIRY || RMSG[0] || ... || RMSG[N] || UMSG[0] || ... || UMSG[M] ) - let verificationBytes = publicKey.bytes - .appending(contentsOf: "\(expiryApplied)".data(using: .ascii)?.bytes) - .appending(contentsOf: serverHashes.joined().bytes) - .appending(contentsOf: hashes.joined().bytes) - let isValid = sodium.sign.verify( - message: verificationBytes, - publicKey: Bytes(Data(hex: snodePublicKey)), - signature: Bytes(Data(base64Encoded: signature)!) - ) - - // Ensure the signature is valid - guard isValid else { - throw SnodeAPIError.signatureVerificationFailed - } - - result[snodePublicKey] = (hashes, expiryApplied) - } - - return result - } - } - } - } - } - - // MARK: Delete - - public static func deleteMessage(publicKey: String, serverHashes: [String]) -> Promise<[String: Bool]> { - guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else { - return Promise(error: SnodeAPIError.noKeyPair) - } - - let publicKey = (Features.useTestnet ? publicKey.removingIdPrefixIfNeeded() : publicKey) - let userX25519PublicKey: String = getUserHexEncodedPublicKey() - - return attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) { - getSwarm(for: publicKey) - .then2 { swarm -> Promise<[String: Bool]> in - // "delete" || messages... - let verificationBytes = SnodeAPIEndpoint.deleteMessage.rawValue.bytes - .appending(contentsOf: serverHashes.joined().bytes) - - guard - let snode = swarm.randomElement(), - let signature = sodium.sign.signature( - message: verificationBytes, - secretKey: userED25519KeyPair.secretKey - ) - else { - throw SnodeAPIError.signingFailed - } - - let parameters: JSON = [ - "pubkey" : userX25519PublicKey, - "pubkey_ed25519" : userED25519KeyPair.publicKey.toHexString(), - "messages": serverHashes, - "signature": signature.toBase64() - ] - - return attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) { - invoke(.deleteMessage, on: snode, associatedWith: publicKey, parameters: parameters) - .map2 { responseData -> [String: Bool] in - guard let responseJson: JSON = try? JSONSerialization.jsonObject(with: responseData, options: [ .fragmentsAllowed ]) as? JSON else { - throw HTTP.Error.invalidJSON - } - guard let swarm = responseJson["swarm"] as? JSON else { throw HTTP.Error.invalidJSON } - - var result: [String: Bool] = [:] - - for (snodePublicKey, rawJSON) in swarm { - guard let json = rawJSON as? JSON else { throw HTTP.Error.invalidJSON } - - let isFailed = (json["failed"] as? Bool ?? false) - - if !isFailed { - guard - let hashes = json["deleted"] as? [String], - let signature = json["signature"] as? String - else { - throw HTTP.Error.invalidJSON - } - - // The signature format is ( PUBKEY_HEX || RMSG[0] || ... || RMSG[N] || DMSG[0] || ... || DMSG[M] ) - let verificationBytes = userX25519PublicKey.bytes - .appending(contentsOf: serverHashes.joined().bytes) - .appending(contentsOf: hashes.joined().bytes) - let isValid = sodium.sign.verify( - message: verificationBytes, - publicKey: Bytes(Data(hex: snodePublicKey)), - signature: Bytes(Data(base64Encoded: signature)!) - ) - - result[snodePublicKey] = isValid - } - else { - if let reason = json["reason"] as? String, let statusCode = json["code"] as? String { - SNLog("Couldn't delete data from: \(snodePublicKey) due to error: \(reason) (\(statusCode)).") - } - else { - SNLog("Couldn't delete data from: \(snodePublicKey).") - } - result[snodePublicKey] = false - } - } - - // If we get to here then we assume it's been deleted from at least one - // service node and as a result we need to mark the hash as invalid so - // we don't try to fetch updates since that hash going forward (if we do - // we would end up re-fetching all old messages) - Storage.shared.writeAsync { db in - try? SnodeReceivedMessageInfo.handlePotentialDeletedOrInvalidHash( - db, - potentiallyInvalidHashes: serverHashes - ) - } - - return result - } - } - } - } - } - - /// Clears all the user's data from their swarm. Returns a dictionary of snode public key to deletion confirmation. - public static func clearAllData() -> Promise<[String:Bool]> { - guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else { - return Promise(error: SnodeAPIError.noKeyPair) - } - - let userX25519PublicKey: String = getUserHexEncodedPublicKey() - - return attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) { - getSwarm(for: userX25519PublicKey) - .then2 { swarm -> Promise<[String:Bool]> in - let snode = swarm.randomElement()! - - return attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) { - getNetworkTime(from: snode).then2 { timestamp -> Promise<[String: Bool]> in - let verificationData = (SnodeAPIEndpoint.clearAllData.rawValue + String(timestamp)).data(using: String.Encoding.utf8)! - - guard let signature = sodium.sign.signature(message: Bytes(verificationData), secretKey: userED25519KeyPair.secretKey) else { - throw SnodeAPIError.signingFailed - } - - let parameters: JSON = [ - "pubkey": userX25519PublicKey, - "pubkey_ed25519": userED25519KeyPair.publicKey.toHexString(), - "timestamp": timestamp, - "signature": signature.toBase64() - ] - - return attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) { - invoke(.clearAllData, on: snode, parameters: parameters) - .map2 { responseData -> [String: Bool] in - guard let responseJson: JSON = try? JSONSerialization.jsonObject(with: responseData, options: [ .fragmentsAllowed ]) as? JSON else { - throw HTTP.Error.invalidJSON - } - guard let swarm = responseJson["swarm"] as? JSON else { throw HTTP.Error.invalidJSON } - - var result: [String: Bool] = [:] - - for (snodePublicKey, rawJSON) in swarm { - guard let json = rawJSON as? JSON else { throw HTTP.Error.invalidJSON } - - let isFailed = json["failed"] as? Bool ?? false - - if !isFailed { - guard - let hashes = json["deleted"] as? [String], - let signature = json["signature"] as? String - else { throw HTTP.Error.invalidJSON } - - // The signature format is ( PUBKEY_HEX || TIMESTAMP || DELETEDHASH[0] || ... || DELETEDHASH[N] ) - let verificationData = [ - userX25519PublicKey, - String(timestamp), - hashes.joined() - ] - .joined() - .data(using: String.Encoding.utf8)! - let isValid = sodium.sign.verify( - message: Bytes(verificationData), - publicKey: Bytes(Data(hex: snodePublicKey)), - signature: Bytes(Data(base64Encoded: signature)!) - ) - - result[snodePublicKey] = isValid - } - else { - if let reason = json["reason"] as? String, let statusCode = json["code"] as? String { - SNLog("Couldn't delete data from: \(snodePublicKey) due to error: \(reason) (\(statusCode)).") - } else { - SNLog("Couldn't delete data from: \(snodePublicKey).") - } - - result[snodePublicKey] = false - } - } - - return result - } - } - } - } - } - } - } - - // MARK: Parsing - - // The parsing utilities below use a best attempt approach to parsing; they warn for parsing failures but don't throw exceptions. - - private static func parseSnodes(from responseData: Data) -> Set { - guard let responseJson: JSON = try? JSONSerialization.jsonObject(with: responseData, options: [ .fragmentsAllowed ]) as? JSON else { - SNLog("Failed to parse snodes from response data.") - return [] - } - guard let rawSnodes = responseJson["snodes"] as? [JSON] else { - SNLog("Failed to parse snodes from: \(responseJson).") - return [] - } - - guard let snodeData: Data = try? JSONSerialization.data(withJSONObject: rawSnodes, options: []) else { - return [] - } - - // FIXME: Hopefully at some point this different Snode structure will be deprecated and can be removed - if - let swarmSnodes: [SwarmSnode] = try? JSONDecoder().decode([Failable].self, from: snodeData).compactMap({ $0.value }), - !swarmSnodes.isEmpty - { - return swarmSnodes.map { $0.toSnode() }.asSet() - } - - return ((try? JSONDecoder().decode([Failable].self, from: snodeData)) ?? []) - .compactMap { $0.value } - .asSet() - } - - // MARK: Error Handling - - /// - Note: Should only be invoked from `Threading.workQueue` to avoid race conditions. - @discardableResult - internal static func handleError(withStatusCode statusCode: UInt, data: Data?, forSnode snode: Snode, associatedWith publicKey: String? = nil) -> Error? { - #if DEBUG - dispatchPrecondition(condition: .onQueue(Threading.workQueue)) - #endif - func handleBadSnode() { - let oldFailureCount = (SnodeAPI.snodeFailureCount.wrappedValue[snode] ?? 0) - let newFailureCount = oldFailureCount + 1 - SnodeAPI.snodeFailureCount.mutate { $0[snode] = newFailureCount } - SNLog("Couldn't reach snode at: \(snode); setting failure count to \(newFailureCount).") - if newFailureCount >= SnodeAPI.snodeFailureThreshold { - SNLog("Failure threshold reached for: \(snode); dropping it.") - if let publicKey = publicKey { - SnodeAPI.dropSnodeFromSwarmIfNeeded(snode, publicKey: publicKey) - } - SnodeAPI.dropSnodeFromSnodePool(snode) - SNLog("Snode pool count: \(snodePool.wrappedValue.count).") - SnodeAPI.snodeFailureCount.mutate { $0[snode] = 0 } - } - } - - switch statusCode { - case 500, 502, 503: - // The snode is unreachable - handleBadSnode() - - case 404: - // May caused by invalid open groups - SNLog("Can't reach the server.") - - case 406: - SNLog("The user's clock is out of sync with the service node network.") - return SnodeAPIError.clockOutOfSync - - case 421: - // The snode isn't associated with the given public key anymore - if let publicKey = publicKey { - func invalidateSwarm() { - SNLog("Invalidating swarm for: \(publicKey).") - SnodeAPI.dropSnodeFromSwarmIfNeeded(snode, publicKey: publicKey) - } - - if let data: Data = data { - let snodes = parseSnodes(from: data) - - if !snodes.isEmpty { - setSwarm(to: snodes, for: publicKey) - } - else { - invalidateSwarm() - } - } - else { - invalidateSwarm() - } - } - else { - SNLog("Got a 421 without an associated public key.") - } - - default: - handleBadSnode() - SNLog("Unhandled response code: \(statusCode).") - } - - return nil - } -} diff --git a/SessionSnodeKit/Models/OnionRequestAPIDestination.swift b/SessionSnodeKit/Types/OnionRequestAPIDestination.swift similarity index 85% rename from SessionSnodeKit/Models/OnionRequestAPIDestination.swift rename to SessionSnodeKit/Types/OnionRequestAPIDestination.swift index 235bb817e..8483ce347 100644 --- a/SessionSnodeKit/Models/OnionRequestAPIDestination.swift +++ b/SessionSnodeKit/Types/OnionRequestAPIDestination.swift @@ -2,7 +2,7 @@ import Foundation -public enum OnionRequestAPIDestination: CustomStringConvertible, Codable { +public enum OnionRequestAPIDestination: CustomStringConvertible { case snode(Snode) case server(host: String, target: String, x25519PublicKey: String, scheme: String?, port: UInt16?) diff --git a/SessionSnodeKit/Models/OnionRequestAPIError.swift b/SessionSnodeKit/Types/OnionRequestAPIError.swift similarity index 100% rename from SessionSnodeKit/Models/OnionRequestAPIError.swift rename to SessionSnodeKit/Types/OnionRequestAPIError.swift diff --git a/SessionSnodeKit/Models/OnionRequestAPIVersion.swift b/SessionSnodeKit/Types/OnionRequestAPIVersion.swift similarity index 100% rename from SessionSnodeKit/Models/OnionRequestAPIVersion.swift rename to SessionSnodeKit/Types/OnionRequestAPIVersion.swift diff --git a/SessionSnodeKit/Types/SnodeAPIEndpoint.swift b/SessionSnodeKit/Types/SnodeAPIEndpoint.swift new file mode 100644 index 000000000..ca988964e --- /dev/null +++ b/SessionSnodeKit/Types/SnodeAPIEndpoint.swift @@ -0,0 +1,33 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public extension SnodeAPI { + enum Endpoint: String { + case sendMessage = "store" + case getMessages = "retrieve" + case deleteMessages = "delete" + case deleteAll = "delete_all" + case deleteAllBefore = "delete_before" + case revokeSubkey = "revoke_subkey" + case expire = "expire" + case expireAll = "expire_all" + case batch = "batch" + case sequence = "sequence" + + case getInfo = "info" + case getSwarm = "get_snodes_for_pubkey" + + case jsonRPCCall = "json_rpc" + case oxenDaemonRPCCall = "oxend_request" + + // jsonRPCCall proxied calls + + case jsonGetNServiceNodes = "get_n_service_nodes" + + // oxenDaemonRPCCall proxied calls + + case daemonOnsResolve = "ons_resolve" + case daemonGetServiceNodes = "get_service_nodes" + } +} diff --git a/SessionSnodeKit/Models/SnodeAPIError.swift b/SessionSnodeKit/Types/SnodeAPIError.swift similarity index 100% rename from SessionSnodeKit/Models/SnodeAPIError.swift rename to SessionSnodeKit/Types/SnodeAPIError.swift diff --git a/SessionUIKit/Components/Separator.swift b/SessionUIKit/Components/Separator.swift index 178c27748..d35add8fd 100644 --- a/SessionUIKit/Components/Separator.swift +++ b/SessionUIKit/Components/Separator.swift @@ -3,7 +3,7 @@ import UIKit public final class Separator: UIView { - private static let height: CGFloat = 24 + public static let height: CGFloat = 32 // MARK: - Components @@ -25,7 +25,6 @@ public final class Separator: UIView { private lazy var titleLabel: UILabel = { let result = UILabel() - result.setContentCompressionResistancePriority(.required, for: .vertical) result.font = .systemFont(ofSize: Values.smallFontSize) result.themeTextColor = .textSecondary result.textAlignment = .center diff --git a/SessionUIKit/Components/SessionButton.swift b/SessionUIKit/Components/SessionButton.swift index 6f2c5443b..f30ca2ede 100644 --- a/SessionUIKit/Components/SessionButton.swift +++ b/SessionUIKit/Components/SessionButton.swift @@ -17,6 +17,25 @@ public final class SessionButton: UIButton { case large } + public struct Info { + public let style: Style + public let title: String + public let isEnabled: Bool + public let onTap: () -> () + + public init( + style: Style, + title: String, + isEnabled: Bool, + onTap: @escaping () -> () + ) { + self.style = style + self.title = title + self.isEnabled = isEnabled + self.onTap = onTap + } + } + private let style: Style public override var isEnabled: Bool { @@ -85,9 +104,9 @@ public final class SessionButton: UIButton { clipsToBounds = true contentEdgeInsets = UIEdgeInsets( top: 0, - left: Values.smallSpacing, + left: Values.largeSpacing, bottom: 0, - right: Values.smallSpacing + right: Values.largeSpacing ) titleLabel?.font = .boldSystemFont(ofSize: (size == .small ? Values.smallFontSize : @@ -121,12 +140,23 @@ public final class SessionButton: UIButton { }(), for: .normal ) + setThemeTitleColor( + { + switch style { + case .borderless: return .highlighted(.sessionButton_text) + case .destructiveBorderless: return .highlighted(.sessionButton_destructiveText) + case .bordered, .destructive, .filled: return nil + } + }(), + for: .highlighted + ) setThemeBackgroundColor( { switch style { - case .bordered, .borderless: return .sessionButton_background - case .destructive, .destructiveBorderless: return .sessionButton_destructiveBackground + case .bordered: return .sessionButton_background + case .destructive: return .sessionButton_destructiveBackground + case .borderless, .destructiveBorderless: return .clear case .filled: return .sessionButton_filledBackground } }(), @@ -135,8 +165,9 @@ public final class SessionButton: UIButton { setThemeBackgroundColor( { switch style { - case .bordered, .borderless: return .sessionButton_highlight - case .destructive, .destructiveBorderless: return .sessionButton_destructiveHighlight + case .bordered: return .sessionButton_highlight + case .destructive: return .sessionButton_destructiveHighlight + case .borderless, .destructiveBorderless: return nil case .filled: return .sessionButton_filledHighlight } }(), @@ -157,4 +188,10 @@ public final class SessionButton: UIButton { } }() } + + // MARK: - Functions + + public func setStyle(_ style: Style) { + setup(style: style) + } } diff --git a/SessionUIKit/Style Guide/Format.swift b/SessionUIKit/Style Guide/Format.swift new file mode 100644 index 000000000..ab25db8e5 --- /dev/null +++ b/SessionUIKit/Style Guide/Format.swift @@ -0,0 +1,32 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public enum Format { + private static let fileSizeFormatter: NumberFormatter = { + let result: NumberFormatter = NumberFormatter() + result.numberStyle = .decimal + result.minimumFractionDigits = 0 + result.maximumFractionDigits = 1 + + return result + }() + private static let oneKilobyte: Double = 1024; + private static let oneMegabyte: Double = (oneKilobyte * oneKilobyte) + + public static func fileSize(_ fileSize: UInt) -> String { + let fileSizeDouble: Double = Double(fileSize) + + switch fileSizeDouble { + case oneMegabyte...Double.greatestFiniteMagnitude: + return (Format.fileSizeFormatter + .string(from: NSNumber(floatLiteral: (fileSizeDouble / oneMegabyte)))? + .appending("MB") ?? "n/a") + + default: + return (Format.fileSizeFormatter + .string(from: NSNumber(floatLiteral: max(0.1, (fileSizeDouble / oneKilobyte))))? + .appending("KB") ?? "n/a") + } + } +} diff --git a/SessionUIKit/Style Guide/Themes/Theme+ClassicDark.swift b/SessionUIKit/Style Guide/Themes/Theme+ClassicDark.swift index 61ed9914e..988c9e49d 100644 --- a/SessionUIKit/Style Guide/Themes/Theme+ClassicDark.swift +++ b/SessionUIKit/Style Guide/Themes/Theme+ClassicDark.swift @@ -64,6 +64,7 @@ internal enum Theme_ClassicDark: ThemeColors { .solidButton_background: .classicDark3, // Settings + .settings_tertiaryAction: .primary, .settings_tabBackground: .classicDark1, // Appearance diff --git a/SessionUIKit/Style Guide/Themes/Theme+ClassicLight.swift b/SessionUIKit/Style Guide/Themes/Theme+ClassicLight.swift index d2554fc0d..ed857bce9 100644 --- a/SessionUIKit/Style Guide/Themes/Theme+ClassicLight.swift +++ b/SessionUIKit/Style Guide/Themes/Theme+ClassicLight.swift @@ -64,6 +64,7 @@ internal enum Theme_ClassicLight: ThemeColors { .solidButton_background: .classicLight3, // Settings + .settings_tertiaryAction: .classicLight0, .settings_tabBackground: .classicLight5, // AppearanceButton diff --git a/SessionUIKit/Style Guide/Themes/Theme+OceanDark.swift b/SessionUIKit/Style Guide/Themes/Theme+OceanDark.swift index 1775bfed0..eb5794531 100644 --- a/SessionUIKit/Style Guide/Themes/Theme+OceanDark.swift +++ b/SessionUIKit/Style Guide/Themes/Theme+OceanDark.swift @@ -64,6 +64,7 @@ internal enum Theme_OceanDark: ThemeColors { .solidButton_background: .oceanDark2, // Settings + .settings_tertiaryAction: .primary, .settings_tabBackground: .oceanDark1, // Appearance diff --git a/SessionUIKit/Style Guide/Themes/Theme+OceanLight.swift b/SessionUIKit/Style Guide/Themes/Theme+OceanLight.swift index c22f4fb34..cbdd87dbf 100644 --- a/SessionUIKit/Style Guide/Themes/Theme+OceanLight.swift +++ b/SessionUIKit/Style Guide/Themes/Theme+OceanLight.swift @@ -64,6 +64,7 @@ internal enum Theme_OceanLight: ThemeColors { .solidButton_background: .oceanLight5, // Settings + .settings_tertiaryAction: .oceanLight1, .settings_tabBackground: .oceanLight6, // Appearance diff --git a/SessionUIKit/Style Guide/Themes/Theme.swift b/SessionUIKit/Style Guide/Themes/Theme.swift index d81a5a3bf..f2045b034 100644 --- a/SessionUIKit/Style Guide/Themes/Theme.swift +++ b/SessionUIKit/Style Guide/Themes/Theme.swift @@ -152,6 +152,7 @@ public indirect enum ThemeValue: Hashable { case solidButton_background // Settings + case settings_tertiaryAction case settings_tabBackground // Appearance diff --git a/SessionUIKit/Types/IconSize.swift b/SessionUIKit/Types/IconSize.swift index 3735676f5..c652e63ce 100644 --- a/SessionUIKit/Types/IconSize.swift +++ b/SessionUIKit/Types/IconSize.swift @@ -4,19 +4,23 @@ import Foundation import DifferenceKit public enum IconSize: Differentiable { + case verySmall case small case medium case large case veryLarge + case extraLarge case fit public var size: CGFloat { switch self { + case .verySmall: return 12 case .small: return 20 case .medium: return 24 case .large: return 32 - case .veryLarge: return 80 + case .veryLarge: return 40 + case .extraLarge: return 80 case .fit: return 0 } } diff --git a/SessionUtilitiesKit/Database/Types/PagedDatabaseObserver.swift b/SessionUtilitiesKit/Database/Types/PagedDatabaseObserver.swift index 88ecd76cc..d052e4e11 100644 --- a/SessionUtilitiesKit/Database/Types/PagedDatabaseObserver.swift +++ b/SessionUtilitiesKit/Database/Types/PagedDatabaseObserver.swift @@ -26,7 +26,7 @@ public class PagedDatabaseObserver: TransactionObserver where private let filterSQL: SQL private let groupSQL: SQL? private let orderSQL: SQL - private let dataQuery: ([Int64]) -> AdaptedFetchRequest> + private let dataQuery: ([Int64]) -> any FetchRequest private let associatedRecords: [ErasedAssociatedRecord] private var dataCache: Atomic> = Atomic(DataCache()) @@ -45,7 +45,7 @@ public class PagedDatabaseObserver: TransactionObserver where filterSQL: SQL, groupSQL: SQL? = nil, orderSQL: SQL, - dataQuery: @escaping ([Int64]) -> AdaptedFetchRequest>, + dataQuery: @escaping ([Int64]) -> any FetchRequest, associatedRecords: [ErasedAssociatedRecord] = [], onChangeUnsorted: @escaping ([T], PagedData.PageInfo) -> () ) { @@ -463,7 +463,7 @@ public class PagedDatabaseObserver: TransactionObserver where let filterSQL: SQL = self.filterSQL let groupSQL: SQL? = self.groupSQL let orderSQL: SQL = self.orderSQL - let dataQuery: ([Int64]) -> AdaptedFetchRequest> = self.dataQuery + let dataQuery: ([Int64]) -> any FetchRequest = self.dataQuery let loadedPage: (data: [T]?, pageInfo: PagedData.PageInfo, failureCallback: (() -> ())?)? = Storage.shared.read { [weak self] db in typealias QueryInfo = (limit: Int, offset: Int, updatedCacheOffset: Int) @@ -759,34 +759,6 @@ public class PagedDatabaseObserver: TransactionObserver where // MARK: - Convenience public extension PagedDatabaseObserver { - convenience init( - pagedTable: ObservedTable.Type, - pageSize: Int, - idColumn: ObservedTable.Columns, - observedChanges: [PagedData.ObservedChanges], - joinSQL: SQL? = nil, - filterSQL: SQL, - groupSQL: SQL? = nil, - orderSQL: SQL, - dataQuery: @escaping ([Int64]) -> SQLRequest, - associatedRecords: [ErasedAssociatedRecord] = [], - onChangeUnsorted: @escaping ([T], PagedData.PageInfo) -> () - ) { - self.init( - pagedTable: pagedTable, - pageSize: pageSize, - idColumn: idColumn, - observedChanges: observedChanges, - joinSQL: joinSQL, - filterSQL: filterSQL, - groupSQL: groupSQL, - orderSQL: orderSQL, - dataQuery: { rowIds in dataQuery(rowIds).adapted { _ in ScopeAdapter([:]) } }, - associatedRecords: associatedRecords, - onChangeUnsorted: onChangeUnsorted - ) - } - func load(_ target: PagedData.PageInfo.Target) where ObservedTable.ID: SQLExpressible { self.load(target.internalTarget) } @@ -1235,7 +1207,7 @@ public class AssociatedRecord: ErasedAssociatedRecord where T: Fet public let joinToPagedType: SQL fileprivate let dataCache: Atomic> = Atomic(DataCache()) - fileprivate let dataQuery: (SQL?) -> AdaptedFetchRequest> + fileprivate let dataQuery: (SQL?) -> any FetchRequest fileprivate let associateData: (DataCache, DataCache) -> DataCache // MARK: - Initialization @@ -1243,7 +1215,7 @@ public class AssociatedRecord: ErasedAssociatedRecord where T: Fet public init( trackedAgainst: Table.Type, observedChanges: [PagedData.ObservedChanges], - dataQuery: @escaping (SQL?) -> AdaptedFetchRequest>, + dataQuery: @escaping (SQL?) -> any FetchRequest, joinToPagedType: SQL, associateData: @escaping (DataCache, DataCache) -> DataCache ) { @@ -1254,24 +1226,6 @@ public class AssociatedRecord: ErasedAssociatedRecord where T: Fet self.associateData = associateData } - public convenience init( - trackedAgainst: Table.Type, - observedChanges: [PagedData.ObservedChanges], - dataQuery: @escaping (SQL?) -> SQLRequest, - joinToPagedType: SQL, - associateData: @escaping (DataCache, DataCache) -> DataCache - ) { - self.init( - trackedAgainst: trackedAgainst, - observedChanges: observedChanges, - dataQuery: { additionalFilters in - dataQuery(additionalFilters).adapted { _ in ScopeAdapter([:]) } - }, - joinToPagedType: joinToPagedType, - associateData: associateData - ) - } - // MARK: - AssociatedRecord public func settingPagedTableName(pagedTableName: String) -> Self { diff --git a/SessionUtilitiesKit/Database/Utilities/QueryInterfaceRequest+Utilities.swift b/SessionUtilitiesKit/Database/Utilities/QueryInterfaceRequest+Utilities.swift index fa9419022..69ce03db5 100644 --- a/SessionUtilitiesKit/Database/Utilities/QueryInterfaceRequest+Utilities.swift +++ b/SessionUtilitiesKit/Database/Utilities/QueryInterfaceRequest+Utilities.swift @@ -10,7 +10,7 @@ public extension QueryInterfaceRequest { /// /// - parameter db: A database connection. /// - returns: Whether the request matches a row in the database. - func isNotEmpty(_ db: Database) throws -> Bool { + func isNotEmpty(_ db: Database) -> Bool { return ((try? SQLRequest("SELECT \(exists())").fetchOne(db)) ?? false) } } diff --git a/SessionUtilitiesKit/General/Data+Utilities.swift b/SessionUtilitiesKit/General/Data+Utilities.swift index 7db8c81da..bac5db7d9 100644 --- a/SessionUtilitiesKit/General/Data+Utilities.swift +++ b/SessionUtilitiesKit/General/Data+Utilities.swift @@ -2,7 +2,22 @@ import Foundation +public extension Dependencies { + static let userInfoKey: CodingUserInfoKey = CodingUserInfoKey(rawValue: "io.oxen.dependencies.codingOptions")! +} + public extension Data { + func decoded(as type: T.Type, using dependencies: Dependencies = Dependencies()) throws -> T { + do { + let decoder: JSONDecoder = JSONDecoder() + decoder.userInfo = [ Dependencies.userInfoKey: dependencies ] + + return try decoder.decode(type, from: self) + } + catch { + throw HTTPError.parsingFailed + } + } func removingIdPrefixIfNeeded() -> Data { var result = self diff --git a/SessionUtilitiesKit/General/SessionId.swift b/SessionUtilitiesKit/General/SessionId.swift index 7e251876e..4e892d489 100644 --- a/SessionUtilitiesKit/General/SessionId.swift +++ b/SessionUtilitiesKit/General/SessionId.swift @@ -5,6 +5,8 @@ import Sodium import Curve25519Kit public struct SessionId { + public static let byteCount: Int = 33 + public enum Prefix: String, CaseIterable { case standard = "05" // Used for identified users, open groups, etc. case blinded = "15" // Used for authentication and participants in open groups with blinding enabled diff --git a/SessionUtilitiesKit/Networking/HTTP.swift b/SessionUtilitiesKit/Networking/HTTP.swift index 5dbd54a64..33f3b2a4e 100644 --- a/SessionUtilitiesKit/Networking/HTTP.swift +++ b/SessionUtilitiesKit/Networking/HTTP.swift @@ -9,7 +9,8 @@ public enum HTTP { private static let snodeURLSession = URLSession(configuration: .ephemeral, delegate: snodeURLSessionDelegate, delegateQueue: nil) private static let snodeURLSessionDelegate = SnodeURLSessionDelegateImplementation() - // MARK: Certificates + // MARK: - Certificates + private static let storageSeed1Cert: SecCertificate = { let path = Bundle.main.path(forResource: "storage-seed-1", ofType: "der")! let data = try! Data(contentsOf: URL(fileURLWithPath: path)) @@ -28,10 +29,12 @@ public enum HTTP { return SecCertificateCreateWithData(nil, data as CFData)! }() - // MARK: Settings - public static let timeout: TimeInterval = 10 + // MARK: - Settings + + public static let defaultTimeout: TimeInterval = 10 - // MARK: Seed Node URL Session Delegate Implementation + // MARK: - Seed Node URL Session Delegate Implementation + private final class SeedNodeURLSessionDelegateImplementation : NSObject, URLSessionDelegate { func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { @@ -60,7 +63,8 @@ public enum HTTP { } } - // MARK: Snode URL Session Delegate Implementation + // MARK: - Snode URL Session Delegate Implementation + private final class SnodeURLSessionDelegateImplementation : NSObject, URLSessionDelegate { func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { diff --git a/SessionUtilitiesKit/Networking/HTTPError.swift b/SessionUtilitiesKit/Networking/HTTPError.swift new file mode 100644 index 000000000..7a3d2af08 --- /dev/null +++ b/SessionUtilitiesKit/Networking/HTTPError.swift @@ -0,0 +1,26 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public enum HTTPError: LocalizedError, Equatable { + case generic + case invalidURL + case invalidJSON + case parsingFailed + case invalidResponse + case maxFileSizeExceeded + case httpRequestFailed(statusCode: UInt, data: Data?) + case timeout + + public var errorDescription: String? { + switch self { + case .generic: return "An error occurred." + case .invalidURL: return "Invalid URL." + case .invalidJSON: return "Invalid JSON." + case .parsingFailed, .invalidResponse: return "Invalid response." + case .maxFileSizeExceeded: return "Maximum file size exceeded." + case .httpRequestFailed(let statusCode, _): return "HTTP request failed with status code: \(statusCode)." + case .timeout: return "The request timed out." + } + } +} diff --git a/SessionUtilitiesKit/Networking/HTTPHeader.swift b/SessionUtilitiesKit/Networking/HTTPHeader.swift new file mode 100644 index 000000000..1ace5ed88 --- /dev/null +++ b/SessionUtilitiesKit/Networking/HTTPHeader.swift @@ -0,0 +1,19 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public typealias HTTPHeader = String + +public extension HTTPHeader { + static let authorization: HTTPHeader = "Authorization" + static let contentType: HTTPHeader = "Content-Type" + static let contentDisposition: HTTPHeader = "Content-Disposition" +} + +// MARK: - Convenience + +public extension Dictionary where Key == HTTPHeader, Value == String { + func toHTTPHeaders() -> [String: String] { + return self.reduce(into: [:]) { result, next in result[next.key] = next.value } + } +} diff --git a/SessionUtilitiesKit/Networking/HTTPMethod.swift b/SessionUtilitiesKit/Networking/HTTPMethod.swift new file mode 100644 index 000000000..940ca4fe3 --- /dev/null +++ b/SessionUtilitiesKit/Networking/HTTPMethod.swift @@ -0,0 +1,10 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public enum HTTPMethod: String, Codable { + case get = "GET" + case put = "PUT" + case post = "POST" + case delete = "DELETE" +} diff --git a/SessionUtilitiesKit/Networking/HTTPQueryParam.swift b/SessionUtilitiesKit/Networking/HTTPQueryParam.swift new file mode 100644 index 000000000..a766bf1ed --- /dev/null +++ b/SessionUtilitiesKit/Networking/HTTPQueryParam.swift @@ -0,0 +1,5 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public typealias HTTPQueryParam = String diff --git a/SessionMessagingKit/Common Networking/Request.swift b/SessionUtilitiesKit/Networking/Request.swift similarity index 70% rename from SessionMessagingKit/Common Networking/Request.swift rename to SessionUtilitiesKit/Networking/Request.swift index f32620bb2..df2a04e78 100644 --- a/SessionMessagingKit/Common Networking/Request.swift +++ b/SessionUtilitiesKit/Networking/Request.swift @@ -1,39 +1,38 @@ import Foundation -import SessionUtilitiesKit // MARK: - Convenience Types -struct Empty: Codable {} +public struct Empty: Codable {} -typealias NoBody = Empty -typealias NoResponse = Empty +public typealias NoBody = Empty +public typealias NoResponse = Empty -protocol EndpointType: Hashable { +public protocol EndpointType: Hashable { var path: String { get } } // MARK: - Request -struct Request { - let method: HTTP.Verb - let server: String - let endpoint: Endpoint - let queryParameters: [QueryParam: String] - let headers: [Header: String] +public struct Request { + public let method: HTTPMethod + public let server: String + public let endpoint: Endpoint + public let queryParameters: [HTTPQueryParam: String] + public let headers: [HTTPHeader: String] /// This is the body value sent during the request /// /// **Warning:** The `bodyData` value should be used to when making the actual request instead of this as there /// is custom handling for certain data types - let body: T? + public let body: T? // MARK: - Initialization - init( - method: HTTP.Verb = .get, + public init( + method: HTTPMethod = .get, server: String, endpoint: Endpoint, - queryParameters: [QueryParam: String] = [:], - headers: [Header: String] = [:], + queryParameters: [HTTPQueryParam: String] = [:], + headers: [HTTPHeader: String] = [:], body: T? = nil ) { self.method = method @@ -56,7 +55,9 @@ struct Request { switch body { case let bodyString as String: // The only acceptable string body is a base64 encoded one - guard let encodedData: Data = Data(base64Encoded: bodyString) else { throw HTTP.Error.parsingFailed } + guard let encodedData: Data = Data(base64Encoded: bodyString) else { + throw HTTPError.parsingFailed + } return encodedData @@ -73,11 +74,11 @@ struct Request { // MARK: - Request Generation - var urlPathAndParamsString: String { + public var urlPathAndParamsString: String { return [ "/\(endpoint.path)", queryParameters - .map { key, value in "\(key.rawValue)=\(value)" } + .map { key, value in "\(key)=\(value)" } .joined(separator: "&") ] .compactMap { $0 } @@ -85,8 +86,8 @@ struct Request { .joined(separator: "?") } - func generateUrlRequest() throws -> URLRequest { - guard let url: URL = url else { throw HTTP.Error.invalidURL } + public func generateUrlRequest() throws -> URLRequest { + guard let url: URL = url else { throw HTTPError.invalidURL } var urlRequest: URLRequest = URLRequest(url: url) urlRequest.httpMethod = method.rawValue diff --git a/SessionUtilitiesKit/Networking/RequestInfo.swift b/SessionUtilitiesKit/Networking/RequestInfo.swift new file mode 100644 index 000000000..156cd54da --- /dev/null +++ b/SessionUtilitiesKit/Networking/RequestInfo.swift @@ -0,0 +1,21 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public extension HTTP { + struct RequestInfo: Codable { + let method: String + let endpoint: String + let headers: [String: String] + + public init( + method: String, + endpoint: String, + headers: [String: String] + ) { + self.method = method + self.endpoint = endpoint + self.headers = headers + } + } +} diff --git a/SessionSnodeKit/Models/ResponseInfo.swift b/SessionUtilitiesKit/Networking/ResponseInfo.swift similarity index 72% rename from SessionSnodeKit/Models/ResponseInfo.swift rename to SessionUtilitiesKit/Networking/ResponseInfo.swift index 80e9b5f87..9d2508c0d 100644 --- a/SessionSnodeKit/Models/ResponseInfo.swift +++ b/SessionUtilitiesKit/Networking/ResponseInfo.swift @@ -2,13 +2,13 @@ import Foundation -public protocol OnionRequestResponseInfoType: Codable { +public protocol ResponseInfoType: Codable { var code: Int { get } var headers: [String: String] { get } } -extension OnionRequestAPI { - public struct ResponseInfo: OnionRequestResponseInfoType { +public extension HTTP { + struct ResponseInfo: ResponseInfoType { public let code: Int public let headers: [String: String] @@ -18,3 +18,4 @@ extension OnionRequestAPI { } } } + diff --git a/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift b/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift index 904648310..9ce21e92d 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift @@ -353,7 +353,7 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate { // Format string for file size label in call interstitial view. // Embeds: {{file size as 'N mb' or 'N kb'}}. let fileSize: UInt = attachment.dataLength - label.text = String(format: "ATTACHMENT_APPROVAL_FILE_SIZE_FORMAT".localized(), OWSFormat.formatFileSize(UInt(fileSize))) + label.text = String(format: "ATTACHMENT_APPROVAL_FILE_SIZE_FORMAT".localized(), Format.fileSize(fileSize)) label.textAlignment = .center } diff --git a/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h b/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h index bbaac0073..aceb514c8 100644 --- a/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h +++ b/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h @@ -14,7 +14,6 @@ FOUNDATION_EXPORT const unsigned char SignalUtilitiesKitVersionString[]; #import #import #import -#import #import #import #import diff --git a/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift b/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift index 1f87c047f..6348bbdf0 100644 --- a/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift +++ b/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift @@ -7,7 +7,6 @@ import SessionUIKit import SessionMessagingKit public final class ProfilePictureView: UIView { - private var hasTappableProfilePicture: Bool = false public var size: CGFloat = 0 // Constraints @@ -51,6 +50,7 @@ public final class ProfilePictureView: UIView { result.clipsToBounds = true result.themeBackgroundColor = .primary result.themeBorderColor = .backgroundPrimary + result.layer.borderWidth = 1 result.layer.cornerRadius = (Values.smallProfilePictureSize / 2) result.isHidden = true @@ -132,60 +132,29 @@ public final class ProfilePictureView: UIView { additionalProfilePlaceholderImageView.pin(.right, to: .right, of: additionalImageContainerView) additionalProfilePlaceholderImageView.pin(.bottom, to: .bottom, of: additionalImageContainerView, withInset: 5) } - - public func update( - publicKey: String = "", - profile: Profile? = nil, - additionalProfile: Profile? = nil, - threadVariant: SessionThread.Variant, - openGroupProfilePictureData: Data? = nil, - useFallbackPicture: Bool = false, - showMultiAvatarForClosedGroup: Bool = false - ) { - AssertIsOnMainThread() - guard !useFallbackPicture else { - switch self.size { - case Values.smallProfilePictureSize.. (image: UIImage?, animatedImage: YYImage?, isTappable: Bool) { - if let profile: Profile = profile, let profileData: Data = ProfileManager.profileAvatar(profile: profile) { - let format: ImageFormat = profileData.guessedImageFormat - - let image: UIImage? = (format == .gif || format == .webp ? - nil : - UIImage(data: profileData) - ) - let animatedImage: YYImage? = (format != .gif && format != .webp ? - nil : - YYImage(data: profileData) - ) - - if image != nil || animatedImage != nil { - return (image, animatedImage, true) - } - } - +// TODO: Update this to be more explicit? (or add a helper method? current code requires duplicate logic around deciding what properties should be set in what cases) + private func prepareForReuse() { + imageView.contentMode = .scaleAspectFill + imageView.isHidden = true + animatedImageView.contentMode = .scaleAspectFill + animatedImageView.isHidden = true + imageContainerView.themeBackgroundColor = .backgroundSecondary + additionalImageContainerView.isHidden = true + animatedImageView.image = nil + additionalImageView.image = nil + additionalAnimatedImageView.image = nil + additionalImageView.isHidden = true + additionalAnimatedImageView.isHidden = true + additionalProfilePlaceholderImageView.isHidden = true + } + + private func getProfilePicture( + of size: CGFloat, + for publicKey: String, + profile: Profile?, + threadVariant: SessionThread.Variant + ) -> (image: UIImage?, animatedImage: YYImage?) { + guard let profile: Profile = profile, let profileData: Data = ProfileManager.profileAvatar(profile: profile) else { return ( Identicon.generatePlaceholderIcon( seed: publicKey, @@ -193,37 +162,113 @@ public final class ProfilePictureView: UIView { .defaulting(to: publicKey), size: size ), - nil, - false + nil ) } - // Calulate the sizes (and set the additional image content) - let targetSize: CGFloat + switch profileData.guessedImageFormat { + case .gif, .webp: return (nil, YYImage(data: profileData)) + default: return (UIImage(data: profileData), nil) + } + } + + public func update( + publicKey: String, + threadVariant: SessionThread.Variant, + customImageData: Data?, + profile: Profile?, + additionalProfile: Profile? + ) { + prepareForReuse() - switch (threadVariant, showMultiAvatarForClosedGroup) { - case (.closedGroup, true): - if self.size == 40 { - targetSize = 32 - } - else if self.size == Values.largeProfilePictureSize { - targetSize = 56 - } - else { - targetSize = Values.smallProfilePictureSize + // If we are given 'customImageData' then only use that + if let customImageData: Data = customImageData { + switch customImageData.guessedImageFormat { + case .gif, .webp: + animatedImageView.image = YYImage(data: customImageData) + animatedImageView.isHidden = false + + default: + imageView.image = UIImage(data: customImageData) + imageView.isHidden = false + } + + imageViewWidthConstraint.constant = self.size + imageViewHeightConstraint.constant = self.size + imageContainerView.layer.cornerRadius = (self.size / 2) + return + } + + // Otherwise there are conversation-type-specific behaviours + switch threadVariant { + case .openGroup: + switch self.size { + case Values.smallProfilePictureSize.. UIImage? { - return (hasTappableProfilePicture ? imageView.image : nil) } } diff --git a/SignalUtilitiesKit/Utilities/OWSFormat.h b/SignalUtilitiesKit/Utilities/OWSFormat.h deleted file mode 100644 index aa0fb794d..000000000 --- a/SignalUtilitiesKit/Utilities/OWSFormat.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSFormat : NSObject - -- (instancetype)init NS_UNAVAILABLE; - -+ (NSString *)formatInt:(int)value; - -+ (NSString *)formatFileSize:(unsigned long)fileSize; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalUtilitiesKit/Utilities/OWSFormat.m b/SignalUtilitiesKit/Utilities/OWSFormat.m deleted file mode 100644 index f94660e52..000000000 --- a/SignalUtilitiesKit/Utilities/OWSFormat.m +++ /dev/null @@ -1,47 +0,0 @@ -// -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. -// - -#import "OWSFormat.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation OWSFormat - -+ (NSString *)formatInt:(int)value -{ - static NSNumberFormatter *formatter = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - formatter = [NSNumberFormatter new]; - formatter.numberStyle = NSNumberFormatterNoStyle; - }); - return [formatter stringFromNumber:@(value)]; -} - -+ (NSString *)formatFileSize:(unsigned long)fileSize -{ - static NSNumberFormatter *formatter = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - formatter = [NSNumberFormatter new]; - formatter.numberStyle = NSNumberFormatterDecimalStyle; - }); - - const unsigned long kOneKilobyte = 1024; - const unsigned long kOneMegabyte = kOneKilobyte * kOneKilobyte; - - if (fileSize > kOneMegabyte) { - return [[formatter stringFromNumber:@((double)lround(fileSize * 100 / (CGFloat)kOneMegabyte) / 100)] - stringByAppendingString:@" MB"]; - } else if (fileSize > kOneKilobyte) { - return [[formatter stringFromNumber:@((double)lround(fileSize * 100 / (CGFloat)kOneKilobyte) / 100)] - stringByAppendingString:@" KB"]; - } else { - return [NSString stringWithFormat:@"%lu Bytes", fileSize]; - } -} - -@end - -NS_ASSUME_NONNULL_END From 174725c7fd31a0f8f27426728999a46a876e089e Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 8 Dec 2022 18:07:02 +1100 Subject: [PATCH 017/135] Removed unneeded imports of libraries to understand usage Refactored some 3rd-part standard encryption to use CryptoKit Removed the PromiseKit pod Fixed the broken tests --- Podfile | 1 - Podfile.lock | 14 +-- Session.xcodeproj/project.pbxproj | 28 ++--- ...sappearingMessagesSettingsViewModel.swift} | 2 +- .../Settings/ThreadSettingsViewModel.swift | 17 ++- Session/Home/New Conversation/NewDMVC.swift | 3 +- .../MediaGalleryViewModel.swift | 24 ---- Session/Onboarding/Onboarding.swift | 3 +- Session/Onboarding/RegisterVC.swift | 5 +- Session/Settings/QRCodeVC.swift | 3 +- Session/Settings/SettingsViewModel.swift | 4 +- .../Migrations/_002_SetupStandardJobs.swift | 1 - .../Migrations/_003_YDBToGRDBMigration.swift | 1 - .../Migrations/_004_RemoveLegacyYDB.swift | 1 - .../DisappearingMessageConfiguration.swift | 97 ---------------- .../Database/Models/GroupMember.swift | 36 ------ .../Database/Models/Profile.swift | 30 ----- .../Open Groups/OpenGroupAPI.swift | 9 +- .../MessageSender+ClosedGroups.swift | 8 +- .../MessageReceiver+Decryption.swift | 2 - .../_TestUtilities/TestOnionRequestAPI.swift | 18 +-- .../OnionRequestAPI+Encryption.swift | 18 +-- .../Networking/OnionRequestAPI.swift | 22 ++-- .../Types/OnionRequestAPIDestination.swift | 2 +- ...eadDisappearingMessagesViewModelSpec.swift | 36 +++--- .../ThreadSettingsViewModelSpec.swift | 44 +++---- .../NotificationContentViewModelSpec.swift | 16 +-- SessionUtilitiesKit/Crypto/AESGCM.swift | 77 ------------- .../Crypto/CryptoKit+Utilities.swift | 109 ++++++++++++++++++ .../Crypto/Data+SecureRandom.swift | 2 + .../Crypto/DiffieHellman.swift | 49 -------- .../Crypto/ECKeyPair+Hexadecimal.swift | 26 ----- SessionUtilitiesKit/Crypto/Hex.swift | 68 ++++++++++- SessionUtilitiesKit/Crypto/KeyPair.swift | 35 ++++++ SessionUtilitiesKit/Crypto/Mnemonic.swift | 61 +++++----- .../Migrations/_002_SetupStandardJobs.swift | 1 - .../Database/Models/Identity.swift | 45 +++----- SessionUtilitiesKit/General/General.swift | 1 - SessionUtilitiesKit/General/SessionId.swift | 3 +- .../Profile Pictures/PlaceholderIcon.swift | 8 +- 40 files changed, 381 insertions(+), 549 deletions(-) rename Session/Conversations/Settings/{ThreadDisappearingMessagesViewModel.swift => ThreadDisappearingMessagesSettingsViewModel.swift} (96%) delete mode 100644 SessionUtilitiesKit/Crypto/AESGCM.swift create mode 100644 SessionUtilitiesKit/Crypto/CryptoKit+Utilities.swift delete mode 100644 SessionUtilitiesKit/Crypto/DiffieHellman.swift delete mode 100644 SessionUtilitiesKit/Crypto/ECKeyPair+Hexadecimal.swift create mode 100644 SessionUtilitiesKit/Crypto/KeyPair.swift diff --git a/Podfile b/Podfile index 9ebc39636..5efd26a36 100644 --- a/Podfile +++ b/Podfile @@ -6,7 +6,6 @@ inhibit_all_warnings! # Dependencies to be included in the app and all extensions/frameworks abstract_target 'GlobalDependencies' do - pod 'PromiseKit' pod 'CryptoSwift' # FIXME: If https://github.com/jedisct1/swift-sodium/pull/249 gets resolved then revert this back to the standard pod pod 'Sodium', :git => 'https://github.com/oxen-io/session-ios-swift-sodium.git', branch: 'session-build' diff --git a/Podfile.lock b/Podfile.lock index 603dc6ae7..eca83a9d1 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -28,15 +28,6 @@ PODS: - NVActivityIndicatorView/Base (= 5.1.1) - NVActivityIndicatorView/Base (5.1.1) - OpenSSL-Universal (1.1.1300) - - PromiseKit (6.18.1): - - PromiseKit/CorePromise (= 6.18.1) - - PromiseKit/Foundation (= 6.18.1) - - PromiseKit/UIKit (= 6.18.1) - - PromiseKit/CorePromise (6.18.1) - - PromiseKit/Foundation (6.18.1): - - PromiseKit/CorePromise - - PromiseKit/UIKit (6.18.1): - - PromiseKit/CorePromise - PureLayout (3.1.9) - Quick (5.0.1) - Reachability (3.2) @@ -130,7 +121,6 @@ DEPENDENCIES: - GRDB.swift/SQLCipher - Nimble - NVActivityIndicatorView - - PromiseKit - PureLayout (~> 3.1.8) - Quick - Reachability @@ -155,7 +145,6 @@ SPEC REPOS: - Nimble - NVActivityIndicatorView - OpenSSL-Universal - - PromiseKit - PureLayout - Quick - Reachability @@ -209,7 +198,6 @@ SPEC CHECKSUMS: Nimble: 5316ef81a170ce87baf72dd961f22f89a602ff84 NVActivityIndicatorView: 1f6c5687f1171810aa27a3296814dc2d7dec3667 OpenSSL-Universal: e7311447fd2419f57420c79524b641537387eff2 - PromiseKit: 49d70c53d5d20e346beaea4b276b5dd2ab446bb4 PureLayout: 5fb5e5429519627d60d079ccb1eaa7265ce7cf88 Quick: 749aa754fd1e7d984f2000fe051e18a3a9809179 Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 @@ -224,6 +212,6 @@ SPEC CHECKSUMS: YYImage: f1ddd15ac032a58b78bbed1e012b50302d318331 ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: ce9209e5c7ea252b767ee08075d4e4f8b49e487b +PODFILE CHECKSUM: 6ee08fc446436a2534ec34c041c3b9bc39801870 COCOAPODS: 1.11.3 diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index a1d6257db..638c753ea 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -278,7 +278,6 @@ C32C598A256D0664003C73A2 /* SNProtoEnvelope+Conversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EEF09255B49A8007E1867 /* SNProtoEnvelope+Conversion.swift */; }; C32C599E256DB02B003C73A2 /* TypingIndicators.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA87255A57FC00E217F9 /* TypingIndicators.swift */; }; C32C5A24256DB7DB003C73A2 /* SNUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB6B255A580F00E217F9 /* SNUserDefaults.swift */; }; - C32C5A47256DB8F0003C73A2 /* ECKeyPair+Hexadecimal.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA73255A57FA00E217F9 /* ECKeyPair+Hexadecimal.swift */; }; C32C5A48256DB8F0003C73A2 /* BuildConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDAA8255A57FF00E217F9 /* BuildConfiguration.swift */; }; C32C5A88256DBCF9003C73A2 /* MessageReceiver+ClosedGroups.swift in Sources */ = {isa = PBXBuildFile; fileRef = C32C5A87256DBCF9003C73A2 /* MessageReceiver+ClosedGroups.swift */; }; C32C5B48256DC211003C73A2 /* NSNotificationCenter+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB6C255A580F00E217F9 /* NSNotificationCenter+OWS.m */; }; @@ -344,7 +343,6 @@ C33FDDD0255A582000E217F9 /* FunctionalUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDC16255A581E00E217F9 /* FunctionalUtil.h */; settings = {ATTRIBUTES = (Public, ); }; }; C3402FE52559036600EA6424 /* SessionUIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C331FF1B2558F9D300070591 /* SessionUIKit.framework */; }; C3471ECB2555356A00297E91 /* MessageSender+Encryption.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3471ECA2555356A00297E91 /* MessageSender+Encryption.swift */; }; - C3471ED42555386B00297E91 /* AESGCM.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D72553860B00C340D1 /* AESGCM.swift */; }; C3471F4C25553AB000297E91 /* MessageReceiver+Decryption.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3471F4B25553AB000297E91 /* MessageReceiver+Decryption.swift */; }; C34C8F7423A7830B00D82669 /* SpaceMono-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C34C8F7323A7830A00D82669 /* SpaceMono-Bold.ttf */; }; C352A2FF25574B6300338F3E /* MessageSendJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = C352A2FE25574B6300338F3E /* MessageSendJob.swift */; }; @@ -427,7 +425,6 @@ C3A71D1E25589AC30043A11F /* WebSocketProto.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A71D1C25589AC30043A11F /* WebSocketProto.swift */; }; C3A71D1F25589AC30043A11F /* WebSocketResources.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A71D1D25589AC30043A11F /* WebSocketResources.pb.swift */; }; C3A71F892558BA9F0043A11F /* Mnemonic.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A71F882558BA9F0043A11F /* Mnemonic.swift */; }; - C3A7211A2558BCA10043A11F /* DiffieHellman.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A71D662558A0170043A11F /* DiffieHellman.swift */; }; C3AAFFF225AE99710089E6DD /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3AAFFF125AE99710089E6DD /* AppDelegate.swift */; }; C3ADC66126426688005F1414 /* ShareNavController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3ADC66026426688005F1414 /* ShareNavController.swift */; }; C3BBE0A82554D4DE0050F1E3 /* JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D92553860B00C340D1 /* JSON.swift */; }; @@ -655,7 +652,7 @@ FD705A92278D051200F16121 /* ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD705A91278D051200F16121 /* ReusableView.swift */; }; FD7115EB28C5D78E00B47552 /* ThreadSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7115EA28C5D78E00B47552 /* ThreadSettingsViewModel.swift */; }; FD7115F228C6CB3900B47552 /* _010_AddThreadIdToFTS.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7115F128C6CB3900B47552 /* _010_AddThreadIdToFTS.swift */; }; - FD7115F428C71EB200B47552 /* ThreadDisappearingMessagesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7115F328C71EB200B47552 /* ThreadDisappearingMessagesViewModel.swift */; }; + FD7115F428C71EB200B47552 /* ThreadDisappearingMessagesSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7115F328C71EB200B47552 /* ThreadDisappearingMessagesSettingsViewModel.swift */; }; FD7115F828C8151C00B47552 /* DisposableBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7115F728C8151C00B47552 /* DisposableBarButtonItem.swift */; }; FD7115FA28C8153400B47552 /* UIBarButtonItem+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7115F928C8153400B47552 /* UIBarButtonItem+Combine.swift */; }; FD7115FC28C8155800B47552 /* Publisher+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7115FB28C8155800B47552 /* Publisher+Utilities.swift */; }; @@ -801,6 +798,8 @@ FDD2506E283711D600198BDA /* DifferenceKit+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD2506D283711D600198BDA /* DifferenceKit+Utilities.swift */; }; FDD250702837199200198BDA /* GarbageCollectionJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD2506F2837199200198BDA /* GarbageCollectionJob.swift */; }; FDD250722837234B00198BDA /* MediaGalleryNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD250712837234B00198BDA /* MediaGalleryNavigationController.swift */; }; + FDE658A129418C7900A33BC1 /* CryptoKit+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE658A029418C7900A33BC1 /* CryptoKit+Utilities.swift */; }; + FDE658A329418E2F00A33BC1 /* KeyPair.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE658A229418E2F00A33BC1 /* KeyPair.swift */; }; FDE77F6B280FEB28002CFC5D /* ControlMessageProcessRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE77F6A280FEB28002CFC5D /* ControlMessageProcessRecord.swift */; }; FDED2E3C282E1B5D00B2CD2A /* UICollectionView+ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDED2E3B282E1B5D00B2CD2A /* UICollectionView+ReusableView.swift */; }; FDF0B73C27FFD3D6004C14C5 /* LinkPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B73B27FFD3D6004C14C5 /* LinkPreview.swift */; }; @@ -1405,7 +1404,6 @@ C33FD9AD255A548A00E217F9 /* SignalUtilitiesKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SignalUtilitiesKit.h; sourceTree = ""; }; C33FD9AE255A548A00E217F9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; C33FDA6F255A57FA00E217F9 /* ReachabilityManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReachabilityManager.swift; sourceTree = ""; }; - C33FDA73255A57FA00E217F9 /* ECKeyPair+Hexadecimal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ECKeyPair+Hexadecimal.swift"; sourceTree = ""; }; C33FDA7A255A57FB00E217F9 /* NSRegularExpression+SSK.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSRegularExpression+SSK.swift"; sourceTree = ""; }; C33FDA87255A57FC00E217F9 /* TypingIndicators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypingIndicators.swift; sourceTree = ""; }; C33FDA8B255A57FD00E217F9 /* AppVersion.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppVersion.m; sourceTree = ""; }; @@ -1573,7 +1571,6 @@ C3A71D0A2558989C0043A11F /* MessageWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageWrapper.swift; sourceTree = ""; }; C3A71D1C25589AC30043A11F /* WebSocketProto.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebSocketProto.swift; sourceTree = ""; }; C3A71D1D25589AC30043A11F /* WebSocketResources.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebSocketResources.pb.swift; sourceTree = ""; }; - C3A71D662558A0170043A11F /* DiffieHellman.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiffieHellman.swift; sourceTree = ""; }; C3A71F882558BA9F0043A11F /* Mnemonic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mnemonic.swift; sourceTree = ""; }; C3A8AF752665B03900A467FE /* hi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hi; path = hi.lproj/Localizable.strings; sourceTree = ""; }; C3A8AF762665F97A00A467FE /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = ""; }; @@ -1592,7 +1589,6 @@ C3C2A5D22553860900C340D1 /* String+Trimming.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Trimming.swift"; sourceTree = ""; }; C3C2A5D42553860A00C340D1 /* Threading.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Threading.swift; sourceTree = ""; }; C3C2A5D52553860A00C340D1 /* Dictionary+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Dictionary+Utilities.swift"; sourceTree = ""; }; - C3C2A5D72553860B00C340D1 /* AESGCM.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AESGCM.swift; sourceTree = ""; }; C3C2A5D82553860B00C340D1 /* Data+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+Utilities.swift"; sourceTree = ""; }; C3C2A5D92553860B00C340D1 /* JSON.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSON.swift; sourceTree = ""; }; C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SessionUtilitiesKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1773,7 +1769,7 @@ FD7115EA28C5D78E00B47552 /* ThreadSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadSettingsViewModel.swift; sourceTree = ""; }; FD7115EF28C5D7DE00B47552 /* SessionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionHeaderView.swift; sourceTree = ""; }; FD7115F128C6CB3900B47552 /* _010_AddThreadIdToFTS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _010_AddThreadIdToFTS.swift; sourceTree = ""; }; - FD7115F328C71EB200B47552 /* ThreadDisappearingMessagesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadDisappearingMessagesViewModel.swift; sourceTree = ""; }; + FD7115F328C71EB200B47552 /* ThreadDisappearingMessagesSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadDisappearingMessagesSettingsViewModel.swift; sourceTree = ""; }; FD7115F728C8151C00B47552 /* DisposableBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisposableBarButtonItem.swift; sourceTree = ""; }; FD7115F928C8153400B47552 /* UIBarButtonItem+Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIBarButtonItem+Combine.swift"; sourceTree = ""; }; FD7115FB28C8155800B47552 /* Publisher+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Publisher+Utilities.swift"; sourceTree = ""; }; @@ -1910,6 +1906,8 @@ FDD2506D283711D600198BDA /* DifferenceKit+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DifferenceKit+Utilities.swift"; sourceTree = ""; }; FDD2506F2837199200198BDA /* GarbageCollectionJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GarbageCollectionJob.swift; sourceTree = ""; }; FDD250712837234B00198BDA /* MediaGalleryNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaGalleryNavigationController.swift; sourceTree = ""; }; + FDE658A029418C7900A33BC1 /* CryptoKit+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CryptoKit+Utilities.swift"; sourceTree = ""; }; + FDE658A229418E2F00A33BC1 /* KeyPair.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyPair.swift; sourceTree = ""; }; FDE7214F287E50D50093DF33 /* ProtoWrappers.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = ProtoWrappers.py; sourceTree = ""; }; FDE72150287E50D50093DF33 /* LintLocalizableStrings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LintLocalizableStrings.swift; sourceTree = ""; }; FDE77F68280F9EDA002CFC5D /* JobRunnerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JobRunnerError.swift; sourceTree = ""; }; @@ -2506,11 +2504,10 @@ B8A582AC258C653C00AFD84C /* Crypto */ = { isa = PBXGroup; children = ( - C3C2A5D72553860B00C340D1 /* AESGCM.swift */, + FDE658A029418C7900A33BC1 /* CryptoKit+Utilities.swift */, C3C2ABD12553C6C900C340D1 /* Data+SecureRandom.swift */, - C3A71D662558A0170043A11F /* DiffieHellman.swift */, - C33FDA73255A57FA00E217F9 /* ECKeyPair+Hexadecimal.swift */, B88FA7FA26114EA70049422F /* Hex.swift */, + FDE658A229418E2F00A33BC1 /* KeyPair.swift */, C3A71F882558BA9F0043A11F /* Mnemonic.swift */, ); path = Crypto; @@ -2741,7 +2738,7 @@ 3427C64120F500DE00EEC730 /* OWSMessageTimerView.h */, 3427C64220F500DF00EEC730 /* OWSMessageTimerView.m */, FD7115EA28C5D78E00B47552 /* ThreadSettingsViewModel.swift */, - FD7115F328C71EB200B47552 /* ThreadDisappearingMessagesViewModel.swift */, + FD7115F328C71EB200B47552 /* ThreadDisappearingMessagesSettingsViewModel.swift */, ); path = Settings; sourceTree = ""; @@ -5457,8 +5454,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + FDE658A129418C7900A33BC1 /* CryptoKit+Utilities.swift in Sources */, FDFD645927F26C6800808CA1 /* Array+Utilities.swift in Sources */, - C32C5A47256DB8F0003C73A2 /* ECKeyPair+Hexadecimal.swift in Sources */, 7B1D74B027C365960030B423 /* Timer+MainThread.swift in Sources */, C32C5D83256DD5B6003C73A2 /* SSKKeychainStorage.swift in Sources */, FDF8488329405A12007DCAE5 /* BatchResponse.swift in Sources */, @@ -5486,13 +5483,13 @@ FD71160228C8255900B47552 /* UIControl+Combine.swift in Sources */, FD9004152818B46300ABAAF6 /* JobRunner.swift in Sources */, FDF8487929405906007DCAE5 /* HTTPQueryParam.swift in Sources */, - C3A7211A2558BCA10043A11F /* DiffieHellman.swift in Sources */, FD17D7CA27F546D900122BE0 /* _001_InitialSetupMigration.swift in Sources */, C3D9E4D12567777D0040E4F3 /* OWSMediaUtils.swift in Sources */, C3BBE0AA2554D4DE0050F1E3 /* Dictionary+Utilities.swift in Sources */, FD37EA1128AB34B3003AE748 /* TypedTableAlteration.swift in Sources */, C3D9E4DA256778410040E4F3 /* UIImage+OWS.m in Sources */, C32C600F256E07F5003C73A2 /* NSUserDefaults+OWS.m in Sources */, + FDE658A329418E2F00A33BC1 /* KeyPair.swift in Sources */, FD37E9FF28A5F2CD003AE748 /* Configuration.swift in Sources */, FDB7400B28EB99A70094D718 /* TimeInterval+Utilities.swift in Sources */, C3D9E35E25675F640040E4F3 /* OWSFileSystem.m in Sources */, @@ -5513,7 +5510,6 @@ FD77289E284EF1C50018502F /* Sodium+Utilities.swift in Sources */, B8856DE6256F15F2001CE70E /* String+SSK.swift in Sources */, FDF2220F281B55E6000A4995 /* QueryInterfaceRequest+Utilities.swift in Sources */, - C3471ED42555386B00297E91 /* AESGCM.swift in Sources */, FDF848EF294067E4007DCAE5 /* URLResponse+Utilities.swift in Sources */, FD848B9A28442CE6000E298B /* StorageError.swift in Sources */, FD17D7B827F51ECA00122BE0 /* Migration.swift in Sources */, @@ -5875,7 +5871,7 @@ 45B5360E206DD8BB00D61655 /* UIResponder+OWS.swift in Sources */, 7B9F71C928470667006DFE7B /* ReactionListSheet.swift in Sources */, 7B7037452834BCC0000DCF35 /* ReactionView.swift in Sources */, - FD7115F428C71EB200B47552 /* ThreadDisappearingMessagesViewModel.swift in Sources */, + FD7115F428C71EB200B47552 /* ThreadDisappearingMessagesSettingsViewModel.swift in Sources */, B8D84ECF25E3108A005A043E /* ExpandingAttachmentsButton.swift in Sources */, 7B7CB190270FB2150079FF93 /* MiniCallView.swift in Sources */, 7B13E1E92810F01300BD4F64 /* SessionCallManager+Action.swift in Sources */, diff --git a/Session/Conversations/Settings/ThreadDisappearingMessagesViewModel.swift b/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift similarity index 96% rename from Session/Conversations/Settings/ThreadDisappearingMessagesViewModel.swift rename to Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift index 8cdfdce1a..ae636edb5 100644 --- a/Session/Conversations/Settings/ThreadDisappearingMessagesViewModel.swift +++ b/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift @@ -8,7 +8,7 @@ import SessionUIKit import SessionMessagingKit import SessionUtilitiesKit -class ThreadDisappearingMessagesViewModel: SessionTableViewModel { +class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel { // MARK: - Config enum NavButton: Equatable { diff --git a/Session/Conversations/Settings/ThreadSettingsViewModel.swift b/Session/Conversations/Settings/ThreadSettingsViewModel.swift index aa0e42944..2f225fabe 100644 --- a/Session/Conversations/Settings/ThreadSettingsViewModel.swift +++ b/Session/Conversations/Settings/ThreadSettingsViewModel.swift @@ -83,8 +83,19 @@ class ThreadSettingsViewModel: SessionTableViewModel = { - isEditing - .map { isEditing in (isEditing ? .editing : .standard) } + Publishers + .CombineLatest( + isEditing, + textChanged + .handleEvents( + receiveOutput: { [weak self] value, _ in + self?.editedDisplayName = value + } + ) + .filter { _ in false } + .prepend((nil, .nickname)) + ) + .map { isEditing, _ -> NavState in (isEditing ? .editing : .standard) } .removeDuplicates() .prepend(.standard) // Initial value .shareReplay(1) @@ -429,7 +440,7 @@ class ThreadSettingsViewModel: SessionTableViewModel = { Publishers .CombineLatest( - isEditing - .map { isEditing in isEditing }, + isEditing, textChanged .handleEvents( receiveOutput: { [weak self] value, _ in @@ -111,6 +110,7 @@ class SettingsViewModel: SessionTableViewModel NavState in (isEditing ? .editing : .standard) } .removeDuplicates() .prepend(.standard) // Initial value + .shareReplay(1) .eraseToAnyPublisher() }() diff --git a/SessionMessagingKit/Database/Migrations/_002_SetupStandardJobs.swift b/SessionMessagingKit/Database/Migrations/_002_SetupStandardJobs.swift index dfc99d450..8c869e44d 100644 --- a/SessionMessagingKit/Database/Migrations/_002_SetupStandardJobs.swift +++ b/SessionMessagingKit/Database/Migrations/_002_SetupStandardJobs.swift @@ -2,7 +2,6 @@ import Foundation import GRDB -import Curve25519Kit import SessionUtilitiesKit import SessionSnodeKit diff --git a/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift b/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift index b428c4713..d0aedf700 100644 --- a/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift +++ b/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift @@ -4,7 +4,6 @@ import Foundation import AVKit import GRDB import YapDatabase -import Curve25519Kit import SessionUtilitiesKit import SessionSnodeKit diff --git a/SessionMessagingKit/Database/Migrations/_004_RemoveLegacyYDB.swift b/SessionMessagingKit/Database/Migrations/_004_RemoveLegacyYDB.swift index 97aa7462e..f8943a967 100644 --- a/SessionMessagingKit/Database/Migrations/_004_RemoveLegacyYDB.swift +++ b/SessionMessagingKit/Database/Migrations/_004_RemoveLegacyYDB.swift @@ -2,7 +2,6 @@ import Foundation import GRDB -import Curve25519Kit import SessionUtilitiesKit import SessionSnodeKit diff --git a/SessionMessagingKit/Database/Models/DisappearingMessageConfiguration.swift b/SessionMessagingKit/Database/Models/DisappearingMessageConfiguration.swift index fbbf24242..e46e06cbb 100644 --- a/SessionMessagingKit/Database/Models/DisappearingMessageConfiguration.swift +++ b/SessionMessagingKit/Database/Models/DisappearingMessageConfiguration.swift @@ -125,100 +125,3 @@ extension DisappearingMessagesConfiguration { return (validDurationsSeconds.max() ?? 0) }() } - -// MARK: - Objective-C Support - -// TODO: Remove this when possible - -@objc(SMKDisappearingMessagesConfiguration) -public class SMKDisappearingMessagesConfiguration: NSObject { - @objc public static var maxDurationSeconds: UInt = UInt(DisappearingMessagesConfiguration.maxDurationSeconds) - - @objc public static var validDurationsSeconds: [UInt] = DisappearingMessagesConfiguration - .validDurationsSeconds - .map { UInt($0) } - - @objc(isEnabledFor:) - public static func isEnabled(for threadId: String) -> Bool { - return Storage.shared - .read { db in - try DisappearingMessagesConfiguration - .select(.isEnabled) - .filter(id: threadId) - .asRequest(of: Bool.self) - .fetchOne(db) - } - .defaulting(to: false) - } - - @objc(durationIndexFor:) - public static func durationIndex(for threadId: String) -> Int { - let durationSeconds: TimeInterval = Storage.shared - .read { db in - try DisappearingMessagesConfiguration - .select(.durationSeconds) - .filter(id: threadId) - .asRequest(of: TimeInterval.self) - .fetchOne(db) - } - .defaulting(to: DisappearingMessagesConfiguration.defaultDuration) - - return DisappearingMessagesConfiguration.validDurationsSeconds - .firstIndex(of: durationSeconds) - .defaulting(to: 0) - } - - @objc(durationStringFor:) - public static func durationString(for index: Int) -> String { - let durationSeconds: TimeInterval = ( - index >= 0 && index < DisappearingMessagesConfiguration.validDurationsSeconds.count ? - DisappearingMessagesConfiguration.validDurationsSeconds[index] : - DisappearingMessagesConfiguration.validDurationsSeconds[0] - ) - - return floor(durationSeconds).formatted(format: .long) - } - - @objc(update:isEnabled:durationIndex:) - public static func update(_ threadId: String, isEnabled: Bool, durationIndex: Int) { - let durationSeconds: TimeInterval = ( - durationIndex >= 0 && durationIndex < DisappearingMessagesConfiguration.validDurationsSeconds.count ? - DisappearingMessagesConfiguration.validDurationsSeconds[durationIndex] : - DisappearingMessagesConfiguration.validDurationsSeconds[0] - ) - - Storage.shared.write { db in - guard let thread: SessionThread = try SessionThread.fetchOne(db, id: threadId) else { - return - } - - let config: DisappearingMessagesConfiguration = try DisappearingMessagesConfiguration - .fetchOne(db, id: threadId) - .defaulting(to: DisappearingMessagesConfiguration.defaultWith(threadId)) - .with( - isEnabled: isEnabled, - durationSeconds: durationSeconds - ) - .saved(db) - - let interaction: Interaction = try Interaction( - threadId: threadId, - authorId: getUserHexEncodedPublicKey(db), - variant: .infoDisappearingMessagesUpdate, - body: config.messageInfoString(with: nil), - timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)) - ) - .inserted(db) - - try MessageSender.send( - db, - message: ExpirationTimerUpdate( - syncTarget: nil, - duration: UInt32(floor(isEnabled ? durationSeconds : 0)) - ), - interactionId: interaction.id, - in: thread - ) - } - } -} diff --git a/SessionMessagingKit/Database/Models/GroupMember.swift b/SessionMessagingKit/Database/Models/GroupMember.swift index 4cfe0abd4..2ccd56d68 100644 --- a/SessionMessagingKit/Database/Models/GroupMember.swift +++ b/SessionMessagingKit/Database/Models/GroupMember.swift @@ -60,39 +60,3 @@ public struct GroupMember: Codable, Equatable, FetchableRecord, PersistableRecor self.isHidden = isHidden } } - -// MARK: - Objective-C Support - -// FIXME: Remove when possible - -@objc(SMKGroupMember) -public class SMKGroupMember: NSObject { - @objc(isCurrentUserMemberOf:) - public static func isCurrentUserMember(of groupId: String) -> Bool { - return Storage.shared.read { db in - let userPublicKey: String = getUserHexEncodedPublicKey(db) - let numEntries: Int = try GroupMember - .filter(GroupMember.Columns.groupId == groupId) - .filter(GroupMember.Columns.profileId == userPublicKey) - .fetchCount(db) - - return (numEntries > 0) - } - .defaulting(to: false) - } - - @objc(isCurrentUserAdminOf:) - public static func isCurrentUserAdmin(of groupId: String) -> Bool { - return Storage.shared.read { db in - let userPublicKey: String = getUserHexEncodedPublicKey(db) - let numEntries: Int = try GroupMember - .filter(GroupMember.Columns.groupId == groupId) - .filter(GroupMember.Columns.profileId == userPublicKey) - .filter(GroupMember.Columns.role == GroupMember.Role.admin) - .fetchCount(db) - - return (numEntries > 0) - } - .defaulting(to: false) - } -} diff --git a/SessionMessagingKit/Database/Models/Profile.swift b/SessionMessagingKit/Database/Models/Profile.swift index f2e6c5237..a37a7bb2f 100644 --- a/SessionMessagingKit/Database/Models/Profile.swift +++ b/SessionMessagingKit/Database/Models/Profile.swift @@ -351,33 +351,3 @@ public extension Profile { } } } - -// MARK: - Objective-C Support - -// FIXME: Remove when possible - -@objc(SMKProfile) -public class SMKProfile: NSObject { - @objc public static func displayName(id: String) -> String { - return Profile.displayName(id: id) - } - - @objc public static func displayName(id: String, customFallback: String) -> String { - return Profile.displayName(id: id, customFallback: customFallback) - } - - @objc(displayNameAfterSavingNickname:forProfileId:) - public static func displayNameAfterSaving(nickname: String?, for profileId: String) -> String { - return Storage.shared.write { db in - let profile: Profile = Profile.fetchOrCreate(id: profileId) - let targetNickname: String? = ((nickname ?? "").count > 0 ? nickname : nil) - - try Profile - .filter(id: profile.id) - .updateAll(db, Profile.Columns.nickname.set(to: targetNickname)) - - return (targetNickname ?? profile.name) - } - .defaulting(to: "") - } -} diff --git a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift index f485c57bb..688ec61fb 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift @@ -4,7 +4,6 @@ import Foundation import Combine import GRDB import Sodium -import Curve25519Kit import SessionSnodeKit import SessionUtilitiesKit @@ -1300,24 +1299,24 @@ public enum OpenGroupAPI { guard let blindedKeyPair: Box.KeyPair = dependencies.sodium.blindedKeyPair(serverPublicKey: serverPublicKey, edKeyPair: userEdKeyPair, genericHash: dependencies.genericHash) else { return nil } - + guard let signatureResult: Bytes = dependencies.sodium.sogsSignature(message: messageBytes, secretKey: userEdKeyPair.secretKey, blindedSecretKey: blindedKeyPair.secretKey, blindedPublicKey: blindedKeyPair.publicKey) else { return nil } - + return ( publicKey: SessionId(.blinded, publicKey: blindedKeyPair.publicKey).hexString, signature: signatureResult ) } - + // Otherwise sign using the fallback type switch signingType { case .unblinded: guard let signatureResult: Bytes = dependencies.sign.signature(message: messageBytes, secretKey: userEdKeyPair.secretKey) else { return nil } - + return ( publicKey: SessionId(.unblinded, publicKey: userEdKeyPair.publicKey).hexString, signature: signatureResult diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift index ba6b1903c..e3fa2ec73 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift @@ -19,9 +19,13 @@ extension MessageSender { var members: Set = members // Generate the group's public key - let groupPublicKey = Curve25519.generateKeyPair().hexEncodedPublicKey // Includes the 'SessionId.Prefix.standard' prefix + let groupKeyPair: ECKeyPair = Curve25519.generateKeyPair() + let groupPublicKey: String = KeyPair( + publicKey: groupKeyPair.publicKey.bytes, + secretKey: groupKeyPair.privateKey.bytes + ).hexEncodedPublicKey // Includes the 'SessionId.Prefix.standard' prefix // Generate the key pair that'll be used for encryption and decryption - let encryptionKeyPair = Curve25519.generateKeyPair() + let encryptionKeyPair: ECKeyPair = Curve25519.generateKeyPair() // Create the group members.insert(userPublicKey) // Ensure the current user is included in the member list diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Decryption.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Decryption.swift index e1ac4b392..0f2f7b2d5 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Decryption.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Decryption.swift @@ -3,8 +3,6 @@ import Foundation import GRDB import Sodium -import CryptoSwift -import Curve25519Kit import SessionUtilitiesKit extension MessageReceiver { diff --git a/SessionMessagingKitTests/_TestUtilities/TestOnionRequestAPI.swift b/SessionMessagingKitTests/_TestUtilities/TestOnionRequestAPI.swift index bb272d204..f92380f95 100644 --- a/SessionMessagingKitTests/_TestUtilities/TestOnionRequestAPI.swift +++ b/SessionMessagingKitTests/_TestUtilities/TestOnionRequestAPI.swift @@ -45,9 +45,13 @@ class TestOnionRequestAPI: OnionRequestAPIType { httpMethod: (request.httpMethod ?? "GET"), headers: (request.allHTTPHeaderFields ?? [:]), body: request.httpBody, - server: server, - version: .v4, - publicKey: x25519PublicKey + destination: OnionRequestAPIDestination.server( + host: (request.url?.host ?? ""), + target: OnionRequestAPIVersion.v4.rawValue, + x25519PublicKey: x25519PublicKey, + scheme: request.url!.scheme, + port: request.url!.port.map { UInt16($0) } + ) ), code: 200, headers: [:] @@ -61,15 +65,11 @@ class TestOnionRequestAPI: OnionRequestAPIType { static func sendOnionRequest(_ payload: Data, to snode: Snode) -> AnyPublisher<(ResponseInfoType, Data?), Error> { let responseInfo: ResponseInfo = ResponseInfo( requestData: RequestData( - urlString: nil, + urlString: "\(snode.address):\(snode.port)/onion_req/v2", httpMethod: "POST", headers: [:], - snodeMethod: nil, body: payload, - - server: "", - version: .v3, - publicKey: snode.x25519PublicKey + destination: OnionRequestAPIDestination.snode(snode) ), code: 200, headers: [:] diff --git a/SessionSnodeKit/Networking/OnionRequestAPI+Encryption.swift b/SessionSnodeKit/Networking/OnionRequestAPI+Encryption.swift index 4d39e5954..86f25c0fd 100644 --- a/SessionSnodeKit/Networking/OnionRequestAPI+Encryption.swift +++ b/SessionSnodeKit/Networking/OnionRequestAPI+Encryption.swift @@ -2,7 +2,7 @@ import Foundation import Combine -import CryptoSwift +import CryptoKit import SessionUtilitiesKit internal extension OnionRequestAPI { @@ -28,14 +28,14 @@ internal extension OnionRequestAPI { static func encrypt( _ payload: Data, for destination: OnionRequestAPIDestination - ) -> AnyPublisher { + ) -> AnyPublisher { switch destination { case .snode(let snode): // Need to wrap the payload for snode requests return encode(ciphertext: payload, json: [ "headers" : "" ]) - .flatMap { data -> AnyPublisher in + .flatMap { data -> AnyPublisher in do { - return Just(try AESGCM.encrypt(data, for: snode.x25519PublicKey)) + return Just(try AES.GCM.encrypt(data, for: snode.x25519PublicKey)) .setFailureType(to: Error.self) .eraseToAnyPublisher() } @@ -48,7 +48,7 @@ internal extension OnionRequestAPI { case .server(_, _, let serverX25519PublicKey, _, _): do { - return Just(try AESGCM.encrypt(payload, for: serverX25519PublicKey)) + return Just(try AES.GCM.encrypt(payload, for: serverX25519PublicKey)) .setFailureType(to: Error.self) .eraseToAnyPublisher() } @@ -63,8 +63,8 @@ internal extension OnionRequestAPI { static func encryptHop( from lhs: OnionRequestAPIDestination, to rhs: OnionRequestAPIDestination, - using previousEncryptionResult: AESGCM.EncryptionResult - ) -> AnyPublisher { + using previousEncryptionResult: AES.GCM.EncryptionResult + ) -> AnyPublisher { var parameters: JSON switch rhs { @@ -89,9 +89,9 @@ internal extension OnionRequestAPI { }() return encode(ciphertext: previousEncryptionResult.ciphertext, json: parameters) - .flatMap { data -> AnyPublisher in + .flatMap { data -> AnyPublisher in do { - return Just(try AESGCM.encrypt(data, for: x25519PublicKey)) + return Just(try AES.GCM.encrypt(data, for: x25519PublicKey)) .setFailureType(to: Error.self) .eraseToAnyPublisher() } diff --git a/SessionSnodeKit/Networking/OnionRequestAPI.swift b/SessionSnodeKit/Networking/OnionRequestAPI.swift index f3cabbee1..e078abe1e 100644 --- a/SessionSnodeKit/Networking/OnionRequestAPI.swift +++ b/SessionSnodeKit/Networking/OnionRequestAPI.swift @@ -2,7 +2,7 @@ import Foundation import Combine -import CryptoSwift +import CryptoKit import GRDB import SessionUtilitiesKit @@ -51,7 +51,7 @@ public enum OnionRequestAPI: OnionRequestAPIType { // MARK: - Onion Building Result - private typealias OnionBuildingResult = (guardSnode: Snode, finalEncryptionResult: AESGCM.EncryptionResult, destinationSymmetricKey: Data) + private typealias OnionBuildingResult = (guardSnode: Snode, finalEncryptionResult: AES.GCM.EncryptionResult, destinationSymmetricKey: Data) // MARK: - Private API @@ -359,18 +359,18 @@ public enum OnionRequestAPI: OnionRequestAPIType { ) -> AnyPublisher { var guardSnode: Snode! var targetSnodeSymmetricKey: Data! // Needed by invoke(_:on:with:) to decrypt the response sent back by the destination - var encryptionResult: AESGCM.EncryptionResult! + var encryptionResult: AES.GCM.EncryptionResult! var snodeToExclude: Snode? if case .snode(let snode) = destination { snodeToExclude = snode } return getPath(excluding: snodeToExclude) - .flatMap { path -> AnyPublisher in + .flatMap { path -> AnyPublisher in guardSnode = path.first! // Encrypt in reverse order, i.e. the destination first return encrypt(payload, for: destination) - .flatMap { r -> AnyPublisher in + .flatMap { r -> AnyPublisher in targetSnodeSymmetricKey = r.symmetricKey // Recursively encrypt the layers of the onion (again in reverse order) @@ -378,7 +378,7 @@ public enum OnionRequestAPI: OnionRequestAPIType { var path = path var rhs = destination - func addLayer() -> AnyPublisher { + func addLayer() -> AnyPublisher { guard !path.isEmpty else { return Just(encryptionResult) .setFailureType(to: Error.self) @@ -388,7 +388,7 @@ public enum OnionRequestAPI: OnionRequestAPIType { let lhs = OnionRequestAPIDestination.snode(path.removeLast()) return OnionRequestAPI .encryptHop(from: lhs, to: rhs, using: encryptionResult) - .flatMap { r -> AnyPublisher in + .flatMap { r -> AnyPublisher in encryptionResult = r rhs = lhs return addLayer() @@ -644,13 +644,13 @@ public enum OnionRequestAPI: OnionRequestAPIType { .eraseToAnyPublisher() } - guard let base64EncodedIVAndCiphertext = json["result"] as? String, let ivAndCiphertext = Data(base64Encoded: base64EncodedIVAndCiphertext), ivAndCiphertext.count >= AESGCM.ivSize else { + guard let base64EncodedIVAndCiphertext = json["result"] as? String, let ivAndCiphertext = Data(base64Encoded: base64EncodedIVAndCiphertext), ivAndCiphertext.count >= AES.GCM.ivSize else { return Fail(error: HTTPError.invalidJSON) .eraseToAnyPublisher() } do { - let data = try AESGCM.decrypt(ivAndCiphertext, with: destinationSymmetricKey) + let data = try AES.GCM.decrypt(ivAndCiphertext, with: destinationSymmetricKey) guard let json = try JSONSerialization.jsonObject(with: data, options: [ .fragmentsAllowed ]) as? JSON, let statusCode = json["status_code"] as? Int ?? json["status"] as? Int else { return Fail(error: HTTPError.invalidJSON) @@ -725,13 +725,13 @@ public enum OnionRequestAPI: OnionRequestAPIType { // V4 Onion Requests have a very different structure for responses case .v4: - guard responseData.count >= AESGCM.ivSize else { + guard responseData.count >= AES.GCM.ivSize else { return Fail(error: HTTPError.invalidResponse) .eraseToAnyPublisher() } do { - let data: Data = try AESGCM.decrypt(responseData, with: destinationSymmetricKey) + let data: Data = try AES.GCM.decrypt(responseData, with: destinationSymmetricKey) // Process the bencoded response guard let processedResponse: (info: ResponseInfoType, body: Data?) = process(bencodedData: data) else { diff --git a/SessionSnodeKit/Types/OnionRequestAPIDestination.swift b/SessionSnodeKit/Types/OnionRequestAPIDestination.swift index 8483ce347..235bb817e 100644 --- a/SessionSnodeKit/Types/OnionRequestAPIDestination.swift +++ b/SessionSnodeKit/Types/OnionRequestAPIDestination.swift @@ -2,7 +2,7 @@ import Foundation -public enum OnionRequestAPIDestination: CustomStringConvertible { +public enum OnionRequestAPIDestination: CustomStringConvertible, Codable { case snode(Snode) case server(host: String, target: String, x25519PublicKey: String, scheme: String?, port: UInt16?) diff --git a/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift b/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift index 062114944..5bf5be0a5 100644 --- a/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift +++ b/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift @@ -7,8 +7,8 @@ import Nimble @testable import Session -class ThreadDisappearingMessagesViewModelSpec: QuickSpec { - typealias ParentType = SessionTableViewModel +class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec { + typealias ParentType = SessionTableViewModel // MARK: - Spec @@ -16,9 +16,9 @@ class ThreadDisappearingMessagesViewModelSpec: QuickSpec { var mockStorage: Storage! var cancellables: [AnyCancellable] = [] var dependencies: Dependencies! - var viewModel: ThreadDisappearingMessagesViewModel! + var viewModel: ThreadDisappearingMessagesSettingsViewModel! - describe("a ThreadDisappearingMessagesViewModel") { + describe("a ThreadDisappearingMessagesSettingsViewModel") { // MARK: - Configuration beforeEach { @@ -41,17 +41,17 @@ class ThreadDisappearingMessagesViewModelSpec: QuickSpec { variant: .contact ).insert(db) } - viewModel = ThreadDisappearingMessagesViewModel( + viewModel = ThreadDisappearingMessagesSettingsViewModel( dependencies: dependencies, threadId: "TestId", config: DisappearingMessagesConfiguration.defaultWith("TestId") ) cancellables.append( - viewModel.observableSettingsData + viewModel.observableTableData .receiveOnMain(immediately: true) .sink( receiveCompletion: { _ in }, - receiveValue: { viewModel.updateSettings($0) } + receiveValue: { viewModel.updateTableData($0.0) } ) ) } @@ -72,18 +72,18 @@ class ThreadDisappearingMessagesViewModelSpec: QuickSpec { } it("has the correct number of items") { - expect(viewModel.settingsData.count) + expect(viewModel.tableData.count) .to(equal(1)) - expect(viewModel.settingsData.first?.elements.count) + expect(viewModel.tableData.first?.elements.count) .to(equal(12)) } it("has the correct default state") { - expect(viewModel.settingsData.first?.elements.first) + expect(viewModel.tableData.first?.elements.first) .to( equal( SessionCell.Info( - id: ThreadDisappearingMessagesViewModel.Item( + id: ThreadDisappearingMessagesSettingsViewModel.Item( title: "DISAPPEARING_MESSAGES_OFF".localized() ), position: .top, @@ -98,7 +98,7 @@ class ThreadDisappearingMessagesViewModelSpec: QuickSpec { let title: String = (DisappearingMessagesConfiguration.validDurationsSeconds.last? .formatted(format: .long)) .defaulting(to: "") - expect(viewModel.settingsData.first?.elements.last) + expect(viewModel.tableData.first?.elements.last) .to( equal( SessionCell.Info( @@ -123,25 +123,25 @@ class ThreadDisappearingMessagesViewModelSpec: QuickSpec { mockStorage.write { db in _ = try config.saved(db) } - viewModel = ThreadDisappearingMessagesViewModel( + viewModel = ThreadDisappearingMessagesSettingsViewModel( dependencies: dependencies, threadId: "TestId", config: config ) cancellables.append( - viewModel.observableSettingsData + viewModel.observableTableData .receiveOnMain(immediately: true) .sink( receiveCompletion: { _ in }, - receiveValue: { viewModel.updateSettings($0) } + receiveValue: { viewModel.updateTableData($0.0) } ) ) - expect(viewModel.settingsData.first?.elements.first) + expect(viewModel.tableData.first?.elements.first) .to( equal( SessionCell.Info( - id: ThreadDisappearingMessagesViewModel.Item( + id: ThreadDisappearingMessagesSettingsViewModel.Item( title: "DISAPPEARING_MESSAGES_OFF".localized() ), position: .top, @@ -156,7 +156,7 @@ class ThreadDisappearingMessagesViewModelSpec: QuickSpec { let title: String = (DisappearingMessagesConfiguration.validDurationsSeconds.last? .formatted(format: .long)) .defaulting(to: "") - expect(viewModel.settingsData.first?.elements.last) + expect(viewModel.tableData.first?.elements.last) .to( equal( SessionCell.Info( diff --git a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift index 645fe5ac4..02b7f9817 100644 --- a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift +++ b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift @@ -69,10 +69,6 @@ class ThreadSettingsViewModelSpec: QuickSpec { didTriggerSearchCallbackTriggered = true } ) - setupStandardBinding() - } - - func setupStandardBinding() { disposables.append( viewModel.observableTableData .receiveOnMain(immediately: true) @@ -81,14 +77,6 @@ class ThreadSettingsViewModelSpec: QuickSpec { receiveValue: { viewModel.updateTableData($0.0) } ) ) - disposables.append( - viewModel.transitionToScreen - .receiveOnMain(immediately: true) - .sink( - receiveCompletion: { _ in }, - receiveValue: { transitionInfo = $0 } - ) - ) } afterEach { @@ -105,7 +93,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { context("with any conversation type") { it("triggers the search callback when tapping search") { - viewModel.settingsData + viewModel.tableData .first(where: { $0.model == .content })? .elements .first(where: { $0.id == .searchConversation })? @@ -115,10 +103,10 @@ class ThreadSettingsViewModelSpec: QuickSpec { } it("mutes a conversation") { - viewModel.settingsData + viewModel.tableData .first(where: { $0.model == .content })? .elements - .first(where: { $0.id == .notifications })? + .first(where: { $0.id == .notificationMute })? .onTap?() expect( @@ -145,11 +133,11 @@ class ThreadSettingsViewModelSpec: QuickSpec { ) .toNot(beNil()) - viewModel.settingsData + viewModel.tableData .first(where: { $0.model == .content })? .elements .first(where: { $0.id == .notificationMute })? - .onTap?(nil) + .onTap?() expect( mockStorage @@ -179,12 +167,12 @@ class ThreadSettingsViewModelSpec: QuickSpec { didTriggerSearchCallbackTriggered = true } ) - cancellables.append( - viewModel.observableSettingsData + disposables.append( + viewModel.observableTableData .receiveOnMain(immediately: true) .sink( receiveCompletion: { _ in }, - receiveValue: { viewModel.updateSettings($0) } + receiveValue: { viewModel.updateTableData($0.0) } ) ) } @@ -210,7 +198,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { it("has no mute button") { expect( - viewModel.settingsData + viewModel.tableData .first(where: { $0.model == .content })? .elements .first(where: { $0.id == .notificationMute }) @@ -233,7 +221,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { ParentType.NavItem( id: .cancel, systemItem: .cancel, - accessibilityIdentifier: "Cancel" + accessibilityIdentifier: "Cancel button" ) ])) expect(viewModel.rightNavItems.firstValue()) @@ -453,12 +441,12 @@ class ThreadSettingsViewModelSpec: QuickSpec { didTriggerSearchCallbackTriggered = true } ) - cancellables.append( - viewModel.observableSettingsData + disposables.append( + viewModel.observableTableData .receiveOnMain(immediately: true) .sink( receiveCompletion: { _ in }, - receiveValue: { viewModel.updateSettings($0) } + receiveValue: { viewModel.updateTableData($0.0) } ) ) } @@ -495,12 +483,12 @@ class ThreadSettingsViewModelSpec: QuickSpec { didTriggerSearchCallbackTriggered = true } ) - cancellables.append( - viewModel.observableSettingsData + disposables.append( + viewModel.observableTableData .receiveOnMain(immediately: true) .sink( receiveCompletion: { _ in }, - receiveValue: { viewModel.updateSettings($0) } + receiveValue: { viewModel.updateTableData($0.0) } ) ) } diff --git a/SessionTests/Settings/NotificationContentViewModelSpec.swift b/SessionTests/Settings/NotificationContentViewModelSpec.swift index 91166959f..04f11d74b 100644 --- a/SessionTests/Settings/NotificationContentViewModelSpec.swift +++ b/SessionTests/Settings/NotificationContentViewModelSpec.swift @@ -30,11 +30,11 @@ class NotificationContentViewModelSpec: QuickSpec { ] ) viewModel = NotificationContentViewModel(storage: mockStorage, scheduling: .immediate) - dataChangeCancellable = viewModel.observableSettingsData + dataChangeCancellable = viewModel.observableTableData .receiveOnMain(immediately: true) .sink( receiveCompletion: { _ in }, - receiveValue: { viewModel.updateSettings($0) } + receiveValue: { viewModel.updateTableData($0.0) } ) } @@ -55,14 +55,14 @@ class NotificationContentViewModelSpec: QuickSpec { } it("has the correct number of items") { - expect(viewModel.settingsData.count) + expect(viewModel.tableData.count) .to(equal(1)) - expect(viewModel.settingsData.first?.elements.count) + expect(viewModel.tableData.first?.elements.count) .to(equal(3)) } it("has the correct default state") { - expect(viewModel.settingsData.first?.elements) + expect(viewModel.tableData.first?.elements) .to( equal([ SessionCell.Info( @@ -98,14 +98,14 @@ class NotificationContentViewModelSpec: QuickSpec { db[.preferencesNotificationPreviewType] = Preferences.NotificationPreviewType.nameNoPreview } viewModel = NotificationContentViewModel(storage: mockStorage, scheduling: .immediate) - dataChangeCancellable = viewModel.observableSettingsData + dataChangeCancellable = viewModel.observableTableData .receiveOnMain(immediately: true) .sink( receiveCompletion: { _ in }, - receiveValue: { viewModel.updateSettings($0) } + receiveValue: { viewModel.updateTableData($0.0) } ) - expect(viewModel.settingsData.first?.elements) + expect(viewModel.tableData.first?.elements) .to( equal([ SessionCell.Info( diff --git a/SessionUtilitiesKit/Crypto/AESGCM.swift b/SessionUtilitiesKit/Crypto/AESGCM.swift deleted file mode 100644 index e20c8987d..000000000 --- a/SessionUtilitiesKit/Crypto/AESGCM.swift +++ /dev/null @@ -1,77 +0,0 @@ -import CryptoSwift -import Curve25519Kit - -public enum AESGCM { - public static let gcmTagSize: UInt = 16 - public static let ivSize: UInt = 12 - - public struct EncryptionResult { public let ciphertext: Data, symmetricKey: Data, ephemeralPublicKey: Data } - - public enum Error : LocalizedError { - case keyPairGenerationFailed - case sharedSecretGenerationFailed - - public var errorDescription: String? { - switch self { - case .keyPairGenerationFailed: return "Couldn't generate a key pair." - case .sharedSecretGenerationFailed: return "Couldn't generate a shared secret." - } - } - } - - /// - Note: Sync. Don't call from the main thread. - public static func generateSymmetricKey(x25519PublicKey: Data, x25519PrivateKey: Data) throws -> Data { - if Thread.isMainThread { - #if DEBUG - preconditionFailure("It's illegal to call encrypt(_:forSnode:) from the main thread.") - #endif - } - guard let sharedSecret = try? Curve25519.generateSharedSecret(fromPublicKey: x25519PublicKey, privateKey: x25519PrivateKey) else { - throw Error.sharedSecretGenerationFailed - } - let salt = "LOKI" - return try Data(HMAC(key: salt.bytes, variant: .sha256).authenticate(sharedSecret.bytes)) - } - - /// - Note: Sync. Don't call from the main thread. - public static func decrypt(_ ivAndCiphertext: Data, with symmetricKey: Data) throws -> Data { - if Thread.isMainThread { - #if DEBUG - preconditionFailure("It's illegal to call decrypt(_:usingAESGCMWithSymmetricKey:) from the main thread.") - #endif - } - let iv = ivAndCiphertext[0.. Data { - if Thread.isMainThread { - #if DEBUG - preconditionFailure("It's illegal to call encrypt(_:usingAESGCMWithSymmetricKey:) from the main thread.") - #endif - } - let iv = Data.getSecureRandomData(ofSize: ivSize)! - let gcm = GCM(iv: iv.bytes, tagLength: Int(gcmTagSize), mode: .combined) - let aes = try AES(key: symmetricKey.bytes, blockMode: gcm, padding: .noPadding) - let ciphertext = try aes.encrypt(plaintext.bytes) - return iv + Data(ciphertext) - } - - /// - Note: Sync. Don't call from the main thread. - public static func encrypt(_ plaintext: Data, for hexEncodedX25519PublicKey: String) throws -> EncryptionResult { - if Thread.isMainThread { - #if DEBUG - preconditionFailure("It's illegal to call encrypt(_:forSnode:) from the main thread.") - #endif - } - let x25519PublicKey = Data(hex: hexEncodedX25519PublicKey) - let ephemeralKeyPair = Curve25519.generateKeyPair() - let symmetricKey = try generateSymmetricKey(x25519PublicKey: x25519PublicKey, x25519PrivateKey: ephemeralKeyPair.privateKey) - let ciphertext = try encrypt(plaintext, with: Data(symmetricKey)) - return EncryptionResult(ciphertext: ciphertext, symmetricKey: Data(symmetricKey), ephemeralPublicKey: ephemeralKeyPair.publicKey) - } -} diff --git a/SessionUtilitiesKit/Crypto/CryptoKit+Utilities.swift b/SessionUtilitiesKit/Crypto/CryptoKit+Utilities.swift new file mode 100644 index 000000000..c3af90b73 --- /dev/null +++ b/SessionUtilitiesKit/Crypto/CryptoKit+Utilities.swift @@ -0,0 +1,109 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import CryptoKit +import Curve25519Kit + +public extension Digest { + var bytes: [UInt8] { Array(makeIterator()) } + var data: Data { Data(bytes) } + + var hexString: String { + bytes.map { String(format: "%02X", $0) }.joined() + } +} + +// MARK: - AES.GCM + +public extension AES.GCM { + static let ivSize: Int = 12 + + struct EncryptionResult { + public let ciphertext: Data + public let symmetricKey: Data + public let ephemeralPublicKey: Data + } + + enum Error: LocalizedError { + case keyPairGenerationFailed + case sharedSecretGenerationFailed + + public var errorDescription: String? { + switch self { + case .keyPairGenerationFailed: return "Couldn't generate a key pair." + case .sharedSecretGenerationFailed: return "Couldn't generate a shared secret." + } + } + } + + /// - Note: Sync. Don't call from the main thread. + static func generateSymmetricKey(x25519PublicKey: Data, x25519PrivateKey: Data) throws -> Data { + if Thread.isMainThread { + #if DEBUG + preconditionFailure("It's illegal to call encrypt(_:forSnode:) from the main thread.") + #endif + } + guard let sharedSecret: Data = try? Curve25519.generateSharedSecret(fromPublicKey: x25519PublicKey, privateKey: x25519PrivateKey) else { + throw Error.sharedSecretGenerationFailed + } + let salt = "LOKI" + + return Data( + HMAC.authenticationCode( + for: sharedSecret, + using: SymmetricKey(data: salt.bytes) + ) + ) + } + + /// - Note: Sync. Don't call from the main thread. + static func decrypt(_ nonceAndCiphertext: Data, with symmetricKey: Data) throws -> Data { + if Thread.isMainThread { + #if DEBUG + preconditionFailure("It's illegal to call decrypt(_:usingAESGCMWithSymmetricKey:) from the main thread.") + #endif + } + + return try AES.GCM.open( + try AES.GCM.SealedBox(combined: nonceAndCiphertext), + using: SymmetricKey(data: symmetricKey) + ) + } + + /// - Note: Sync. Don't call from the main thread. + static func encrypt(_ plaintext: Data, with symmetricKey: Data) throws -> Data { + if Thread.isMainThread { + #if DEBUG + preconditionFailure("It's illegal to call encrypt(_:usingAESGCMWithSymmetricKey:) from the main thread.") + #endif + } + + let nonceData: Data = try Randomness.generateRandomBytes(numberBytes: ivSize) + let sealedData: AES.GCM.SealedBox = try AES.GCM.seal( + plaintext, + using: SymmetricKey(data: symmetricKey), + nonce: try AES.GCM.Nonce(data: nonceData) + ) + + guard let cipherText: Data = sealedData.combined else { + throw GeneralError.keyGenerationFailed + } + + return cipherText + } + + /// - Note: Sync. Don't call from the main thread. + static func encrypt(_ plaintext: Data, for hexEncodedX25519PublicKey: String) throws -> EncryptionResult { + if Thread.isMainThread { + #if DEBUG + preconditionFailure("It's illegal to call encrypt(_:forSnode:) from the main thread.") + #endif + } + let x25519PublicKey = Data(hex: hexEncodedX25519PublicKey) + let ephemeralKeyPair = Curve25519.generateKeyPair() + let symmetricKey = try generateSymmetricKey(x25519PublicKey: x25519PublicKey, x25519PrivateKey: ephemeralKeyPair.privateKey) + let ciphertext = try encrypt(plaintext, with: Data(symmetricKey)) + + return EncryptionResult(ciphertext: ciphertext, symmetricKey: Data(symmetricKey), ephemeralPublicKey: ephemeralKeyPair.publicKey) + } +} diff --git a/SessionUtilitiesKit/Crypto/Data+SecureRandom.swift b/SessionUtilitiesKit/Crypto/Data+SecureRandom.swift index 1ca9749f8..fa9b1d7f2 100644 --- a/SessionUtilitiesKit/Crypto/Data+SecureRandom.swift +++ b/SessionUtilitiesKit/Crypto/Data+SecureRandom.swift @@ -1,3 +1,5 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + import Foundation public extension Data { diff --git a/SessionUtilitiesKit/Crypto/DiffieHellman.swift b/SessionUtilitiesKit/Crypto/DiffieHellman.swift deleted file mode 100644 index cbc7b9950..000000000 --- a/SessionUtilitiesKit/Crypto/DiffieHellman.swift +++ /dev/null @@ -1,49 +0,0 @@ -import CryptoSwift -import Curve25519Kit - -public final class DiffieHellman : NSObject { - public static let ivSize: UInt = 16 - - public enum Error : LocalizedError { - case decryptionFailed - case sharedSecretGenerationFailed - - public var errorDescription: String { - switch self { - case .decryptionFailed: return "Couldn't decrypt data" - case .sharedSecretGenerationFailed: return "Couldn't generate a shared secret." - } - } - } - - private override init() { } - - public static func encrypt(_ plaintext: Data, using symmetricKey: Data) throws -> Data { - let iv = Data.getSecureRandomData(ofSize: ivSize)! - let cbc = CBC(iv: iv.bytes) - let aes = try AES(key: symmetricKey.bytes, blockMode: cbc) - let ciphertext = try aes.encrypt(plaintext.bytes) - let ivAndCiphertext = iv.bytes + ciphertext - return Data(ivAndCiphertext) - } - - public static func encrypt(_ plaintext: Data, publicKey: Data, privateKey: Data) throws -> Data { - guard let symmetricKey = try? Curve25519.generateSharedSecret(fromPublicKey: publicKey, privateKey: privateKey) else { throw Error.sharedSecretGenerationFailed } - return try encrypt(plaintext, using: symmetricKey) - } - - public static func decrypt(_ ivAndCiphertext: Data, using symmetricKey: Data) throws -> Data { - guard ivAndCiphertext.count >= ivSize else { throw Error.decryptionFailed } - let iv = ivAndCiphertext[.. Data { - guard let symmetricKey = try? Curve25519.generateSharedSecret(fromPublicKey: publicKey, privateKey: privateKey) else { throw Error.sharedSecretGenerationFailed } - return try decrypt(ivAndCiphertext, using: symmetricKey) - } -} diff --git a/SessionUtilitiesKit/Crypto/ECKeyPair+Hexadecimal.swift b/SessionUtilitiesKit/Crypto/ECKeyPair+Hexadecimal.swift deleted file mode 100644 index 5dd86097d..000000000 --- a/SessionUtilitiesKit/Crypto/ECKeyPair+Hexadecimal.swift +++ /dev/null @@ -1,26 +0,0 @@ -import Curve25519Kit - -public extension ECKeyPair { - - @objc var hexEncodedPrivateKey: String { - return privateKey.map { String(format: "%02hhx", $0) }.joined() - } - - @objc var hexEncodedPublicKey: String { - // Prefixing with 'SessionId.Prefix.standard' is necessary for what seems to be a sort of Signal public key versioning system - return SessionId(.standard, publicKey: publicKey.bytes).hexString - } - - @objc static func isValidHexEncodedPublicKey(candidate: String) -> Bool { - // Note: If the logic in here changes ensure it doesn't break `SessionId.Prefix(from:)` - // Check that it's a valid hexadecimal encoding - guard Hex.isValid(candidate) else { return false } - // Check that it has length 66 and a valid prefix - guard candidate.count == 66 && SessionId.Prefix.allCases.first(where: { candidate.hasPrefix($0.rawValue) }) != nil else { - return false - } - - // It appears to be a valid public key - return true - } -} diff --git a/SessionUtilitiesKit/Crypto/Hex.swift b/SessionUtilitiesKit/Crypto/Hex.swift index b90a916fa..f9b01f406 100644 --- a/SessionUtilitiesKit/Crypto/Hex.swift +++ b/SessionUtilitiesKit/Crypto/Hex.swift @@ -1,8 +1,74 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation public enum Hex { - public static func isValid(_ string: String) -> Bool { let allowedCharacters = CharacterSet(charactersIn: "0123456789ABCDEF") + return string.uppercased().unicodeScalars.allSatisfy { allowedCharacters.contains($0) } } } + +// MARK: - Data + +public extension Data { + var bytes: [UInt8] { return Array(self) } + + func toHexString() -> String { + return bytes.map { String(format: "%02x", $0) }.joined() + } + + init(hex: String) { + self.init(Array(hex: hex)) + } +} + +// MARK: - Array + +public extension Array where Element == UInt8 { + init(hex: String) { + self = Array() + self.reserveCapacity(hex.unicodeScalars.lazy.underestimatedCount) + + var buffer: UInt8? + var skip = (hex.hasPrefix("0x") ? 2 : 0) + + for char in hex.unicodeScalars.lazy { + guard skip == 0 else { + skip -= 1 + continue + } + + guard char.value >= 48 && char.value <= 102 else { + removeAll() + return + } + + let v: UInt8 + let c: UInt8 = UInt8(char.value) + + switch c { + case let c where c <= 57: v = c - 48 + case let c where c >= 65 && c <= 70: v = c - 55 + case let c where c >= 97: v = c - 87 + + default: + removeAll() + return + } + + if let b = buffer { + append(b << 4 | v) + buffer = nil + } + else { + buffer = v + } + } + + if let b = buffer { + append(b) + } + } +} diff --git a/SessionUtilitiesKit/Crypto/KeyPair.swift b/SessionUtilitiesKit/Crypto/KeyPair.swift new file mode 100644 index 000000000..4c7ec0a74 --- /dev/null +++ b/SessionUtilitiesKit/Crypto/KeyPair.swift @@ -0,0 +1,35 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public struct KeyPair { + public let publicKey: [UInt8] + public let secretKey: [UInt8] + + public var hexEncodedPublicKey: String { + return SessionId(.standard, publicKey: publicKey).hexString + } + + // MARK: - Initialization + + public init(publicKey: [UInt8], secretKey: [UInt8]) { + self.publicKey = publicKey + self.secretKey = secretKey + } + + // MARK: - Functions + + public static func isValidHexEncodedPublicKey(candidate: String) -> Bool { + // Note: If the logic in here changes ensure it doesn't break `SessionId.Prefix(from:)` + // Check that it's a valid hexadecimal encoding + guard Hex.isValid(candidate) else { return false } + + // Check that it has length 66 and a valid prefix + guard candidate.count == 66 && SessionId.Prefix.allCases.first(where: { candidate.hasPrefix($0.rawValue) }) != nil else { + return false + } + + // It appears to be a valid public key + return true + } +} diff --git a/SessionUtilitiesKit/Crypto/Mnemonic.swift b/SessionUtilitiesKit/Crypto/Mnemonic.swift index b420a89f7..ac8f46af5 100644 --- a/SessionUtilitiesKit/Crypto/Mnemonic.swift +++ b/SessionUtilitiesKit/Crypto/Mnemonic.swift @@ -1,9 +1,11 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation import CryptoSwift /// Based on [mnemonic.js](https://github.com/loki-project/loki-messenger/blob/development/libloki/modules/mnemonic.js) . public enum Mnemonic { - - public struct Language : Hashable { + public struct Language: Hashable { fileprivate let filename: String fileprivate let prefixLength: UInt @@ -12,8 +14,8 @@ public enum Mnemonic { public static let portuguese = Language(filename: "portuguese", prefixLength: 4) public static let spanish = Language(filename: "spanish", prefixLength: 4) - private static var wordSetCache: [Language:[String]] = [:] - private static var truncatedWordSetCache: [Language:[String]] = [:] + private static var wordSetCache: [Language: [String]] = [:] + private static var truncatedWordSetCache: [Language: [String]] = [:] private init(filename: String, prefixLength: UInt) { self.filename = filename @@ -23,23 +25,25 @@ public enum Mnemonic { fileprivate func loadWordSet() -> [String] { if let cachedResult = Language.wordSetCache[self] { return cachedResult - } else { - let url = Bundle.main.url(forResource: filename, withExtension: "txt")! - let contents = try! String(contentsOf: url) - let result = contents.split(separator: ",").map { String($0) } - Language.wordSetCache[self] = result - return result } + + let url = Bundle.main.url(forResource: filename, withExtension: "txt")! + let contents = try! String(contentsOf: url) + let result = contents.split(separator: ",").map { String($0) } + Language.wordSetCache[self] = result + + return result } fileprivate func loadTruncatedWordSet() -> [String] { if let cachedResult = Language.truncatedWordSetCache[self] { return cachedResult - } else { - let result = loadWordSet().map { $0.prefix(length: prefixLength) } - Language.truncatedWordSetCache[self] = result - return result } + + let result = loadWordSet().map { $0.prefix(length: prefixLength) } + Language.truncatedWordSetCache[self] = result + + return result } } @@ -68,6 +72,7 @@ public enum Mnemonic { var result: [String] = [] let n = wordSet.count let characterCount = string.indices.count // Safe for this particular case + for chunkStartIndexAsInt in stride(from: 0, to: characterCount, by: 8) { let chunkStartIndex = string.index(string.startIndex, offsetBy: chunkStartIndexAsInt) let chunkEndIndex = string.index(chunkStartIndex, offsetBy: 8) @@ -76,6 +81,7 @@ public enum Mnemonic { let p3 = string[chunkEndIndex..= 12 else { throw DecodingError.inputTooShort } guard !words.count.isMultiple(of: 3) else { throw DecodingError.missingLastWord } + // Get checksum word let checksumWord = words.popLast()! + // Decode for chunkStartIndex in stride(from: 0, to: words.count, by: 3) { guard let w1 = truncatedWordSet.firstIndex(of: words[chunkStartIndex].prefix(length: prefixLength)), @@ -112,10 +123,12 @@ public enum Mnemonic { let string = "0000000" + String(x, radix: 16) result += swap(String(string[string.index(string.endIndex, offsetBy: -8).. String.Index { return x.index(x.startIndex, offsetBy: indexAsInt) } + let p1 = x[toStringIndex(6).. Int { let checksum = Array(x.map { $0.prefix(length: prefixLength) }.joined().utf8).crc32() + return Int(checksum) % x.count } } private extension String { - func prefix(length: UInt) -> String { return String(self[startIndex.. String { - return Mnemonic.hash(hexEncodedString: string) - } - - @objc(encodeHexEncodedString:) - public static func encode(hexEncodedString string: String) -> String { - return Mnemonic.encode(hexEncodedString: string) - } -} diff --git a/SessionUtilitiesKit/Database/Migrations/_002_SetupStandardJobs.swift b/SessionUtilitiesKit/Database/Migrations/_002_SetupStandardJobs.swift index a4df73df3..7d085d68c 100644 --- a/SessionUtilitiesKit/Database/Migrations/_002_SetupStandardJobs.swift +++ b/SessionUtilitiesKit/Database/Migrations/_002_SetupStandardJobs.swift @@ -2,7 +2,6 @@ import Foundation import GRDB -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` diff --git a/SessionUtilitiesKit/Database/Models/Identity.swift b/SessionUtilitiesKit/Database/Models/Identity.swift index 9d08731f5..4eb224273 100644 --- a/SessionUtilitiesKit/Database/Models/Identity.swift +++ b/SessionUtilitiesKit/Database/Models/Identity.swift @@ -4,7 +4,6 @@ import Foundation import GRDB import Sodium import Curve25519Kit -import CryptoSwift public struct Identity: Codable, Identifiable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { public static var databaseTableName: String { "identity" } @@ -39,20 +38,10 @@ public struct Identity: Codable, Identifiable, FetchableRecord, PersistableRecor } } -// MARK: - Convenience - -extension ECKeyPair { - func toData() -> Data { - var targetValue: ECKeyPair = self - - return Data(bytes: &targetValue, count: MemoryLayout.size(ofValue: targetValue)) - } -} - // MARK: - GRDB Interactions public extension Identity { - static func generate(from seed: Data) throws -> (ed25519KeyPair: Sign.KeyPair, x25519KeyPair: ECKeyPair) { + static func generate(from seed: Data) throws -> (ed25519KeyPair: KeyPair, x25519KeyPair: KeyPair) { assert(seed.count == 16) let padding = Data(repeating: 0, count: 16) @@ -64,18 +53,25 @@ public extension Identity { throw GeneralError.keyGenerationFailed } - let x25519KeyPair = try ECKeyPair(publicKeyData: Data(x25519PublicKey), privateKeyData: Data(x25519SecretKey)) - - return (ed25519KeyPair: ed25519KeyPair, x25519KeyPair: x25519KeyPair) + return ( + ed25519KeyPair: KeyPair( + publicKey: ed25519KeyPair.publicKey, + secretKey: ed25519KeyPair.secretKey + ), + x25519KeyPair: KeyPair( + publicKey: x25519PublicKey, + secretKey: x25519SecretKey + ) + ) } - static func store(seed: Data, ed25519KeyPair: Sign.KeyPair, x25519KeyPair: ECKeyPair) { + static func store(seed: Data, ed25519KeyPair: KeyPair, x25519KeyPair: KeyPair) { Storage.shared.write { db in try Identity(variant: .seed, data: seed).save(db) try Identity(variant: .ed25519SecretKey, data: Data(ed25519KeyPair.secretKey)).save(db) try Identity(variant: .ed25519PublicKey, data: Data(ed25519KeyPair.publicKey)).save(db) - try Identity(variant: .x25519PrivateKey, data: x25519KeyPair.privateKey).save(db) - try Identity(variant: .x25519PublicKey, data: x25519KeyPair.publicKey).save(db) + try Identity(variant: .x25519PrivateKey, data: Data(x25519KeyPair.secretKey)).save(db) + try Identity(variant: .x25519PublicKey, data: Data(x25519KeyPair.publicKey)).save(db) } } @@ -153,16 +149,3 @@ public extension Identity { NotificationCenter.default.post(name: .registrationStateDidChange, object: nil, userInfo: nil) } } - -// MARK: - Objective-C Support - -// TODO: Remove this when possible -@objc(SUKIdentity) -public class SUKIdentity: NSObject { - @objc(userExists) - public static func userExists() -> Bool { - return Storage.shared - .read { db in Identity.userExists(db) } - .defaulting(to: false) - } -} diff --git a/SessionUtilitiesKit/General/General.swift b/SessionUtilitiesKit/General/General.swift index 2931e47c2..e78c33a89 100644 --- a/SessionUtilitiesKit/General/General.swift +++ b/SessionUtilitiesKit/General/General.swift @@ -2,7 +2,6 @@ import Foundation import GRDB -import Curve25519Kit public protocol GeneralCacheType { var encodedPublicKey: String? { get set } diff --git a/SessionUtilitiesKit/General/SessionId.swift b/SessionUtilitiesKit/General/SessionId.swift index 4e892d489..c9391c6c7 100644 --- a/SessionUtilitiesKit/General/SessionId.swift +++ b/SessionUtilitiesKit/General/SessionId.swift @@ -2,7 +2,6 @@ import Foundation import Sodium -import Curve25519Kit public struct SessionId { public static let byteCount: Int = 33 @@ -21,7 +20,7 @@ public struct SessionId { return } - guard ECKeyPair.isValidHexEncodedPublicKey(candidate: stringValue) else { return nil } + guard KeyPair.isValidHexEncodedPublicKey(candidate: stringValue) else { return nil } guard let targetPrefix: Prefix = Prefix(rawValue: String(stringValue.prefix(2))) else { return nil } self = targetPrefix diff --git a/SignalUtilitiesKit/Profile Pictures/PlaceholderIcon.swift b/SignalUtilitiesKit/Profile Pictures/PlaceholderIcon.swift index 32f0c60f4..d0911e4e8 100644 --- a/SignalUtilitiesKit/Profile Pictures/PlaceholderIcon.swift +++ b/SignalUtilitiesKit/Profile Pictures/PlaceholderIcon.swift @@ -1,9 +1,10 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit -import CryptoSwift +import CryptoKit import SessionUIKit import SignalCoreKit +import SessionUtilitiesKit public class PlaceholderIcon { private let seed: Int @@ -19,7 +20,10 @@ public class PlaceholderIcon { convenience init(seed: String, colors: [UIColor]? = nil) { // Ensure we have a correct hash var hash = seed - if (hash.matches("^[0-9A-Fa-f]+$") && hash.count >= 12) { hash = seed.sha512() } + + if (hash.matches("^[0-9A-Fa-f]+$") && hash.count >= 12) { + hash = SHA512.hash(data: Data(seed.bytes)).hexString + } guard let number = Int(hash.substring(to: 12), radix: 16) else { owsFailDebug("Failed to generate number from seed string: \(seed).") From 22130f734ece130ac6929bf7755f19a1586d97b6 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 8 Dec 2022 18:13:12 +1100 Subject: [PATCH 018/135] Removed an unneeded duplicate file --- .../Settings2.bundle/Acknowledgements.plist | 2160 --------------- ...nMessagingKitTests-settings-metadata.plist | 2340 ----------------- ...essionMessagingKit-settings-metadata.plist | 1854 ------------- ...sionShareExtension-settings-metadata.plist | 1618 ------------ ...nUtilitiesKitTests-settings-metadata.plist | 2103 --------------- ...essionUtilitiesKit-settings-metadata.plist | 1685 ------------ ...SignalUtilitiesKit-settings-metadata.plist | 1951 -------------- ...onServiceExtension-settings-metadata.plist | 1364 ---------- ...es-SessionSnodeKit-settings-metadata.plist | 1364 ---------- ...ssion-SessionTests-settings-metadata.plist | 1221 --------- ...pendencies-Session-settings-metadata.plist | 2160 --------------- ...ncies-SessionUIKit-settings-metadata.plist | 462 ---- Session/Meta/Settings2.bundle/Root.plist | 19 - .../en.lproj/Acknowledgements.strings | 1 - .../Settings2.bundle/en.lproj/Root.strings | Bin 546 -> 0 bytes 15 files changed, 20302 deletions(-) delete mode 100644 Session/Meta/Settings2.bundle/Acknowledgements.plist delete mode 100644 Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests-settings-metadata.plist delete mode 100644 Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-settings-metadata.plist delete mode 100644 Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension-settings-metadata.plist delete mode 100644 Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests-settings-metadata.plist delete mode 100644 Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-settings-metadata.plist delete mode 100644 Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit-settings-metadata.plist delete mode 100644 Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension-settings-metadata.plist delete mode 100644 Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit-settings-metadata.plist delete mode 100644 Session/Meta/Settings2.bundle/Pods-GlobalDependencies-Session-SessionTests-settings-metadata.plist delete mode 100644 Session/Meta/Settings2.bundle/Pods-GlobalDependencies-Session-settings-metadata.plist delete mode 100644 Session/Meta/Settings2.bundle/Pods-GlobalDependencies-SessionUIKit-settings-metadata.plist delete mode 100644 Session/Meta/Settings2.bundle/Root.plist delete mode 100644 Session/Meta/Settings2.bundle/en.lproj/Acknowledgements.strings delete mode 100644 Session/Meta/Settings2.bundle/en.lproj/Root.strings diff --git a/Session/Meta/Settings2.bundle/Acknowledgements.plist b/Session/Meta/Settings2.bundle/Acknowledgements.plist deleted file mode 100644 index d17d35ab8..000000000 --- a/Session/Meta/Settings2.bundle/Acknowledgements.plist +++ /dev/null @@ -1,2160 +0,0 @@ - - - - - PreferenceSpecifiers - - - FooterText - This application makes use of the following third party libraries: - Title - Acknowledgements - Type - PSGroupSpecifier - - - FooterText - BSD 3-Clause License - -Copyright (c) 2010-2021, Deusty, LLC -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -3. Neither the name of Deusty nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Deusty, LLC. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - CocoaLumberjack - Type - PSGroupSpecifier - - - FooterText - Copyright (C) 2014-2017 Marcin Krzyżanowski <marcin.krzyzanowski@gmail.com> -This software is provided 'as-is', without any express or implied warranty. - -In no event will the authors be held liable for any damages arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: - -- The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. -- Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. -- This notice may not be removed or altered from any source or binary distribution. -- Redistributions of any form whatsoever must retain the following acknowledgment: 'This product includes software developed by the "Marcin Krzyzanowski" (http://krzyzanowskim.com/).' - - Title - CryptoSwift - Type - PSGroupSpecifier - - - FooterText - Copyright (C) 2015-2020 Gwendal Roué - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - GRDB.swift - Type - PSGroupSpecifier - - - FooterText - Copyright 2016-present, Max Howell; mxcl@me.com - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - PromiseKit - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2008, ZETETIC LLC -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the ZETETIC LLC nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY -EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - SQLCipher - Type - PSGroupSpecifier - - - FooterText - - Copyright 2012 Square Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - Title - SocketRocket - Type - PSGroupSpecifier - - - FooterText - ISC License - -Copyright (c) 2014-2020, Frank Denis <j at pureftpd dot org> - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - - Title - Sodium - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2011, The WebRTC project authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Google nor the names of its contributors may - be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - WebRTC-lib - Type - PSGroupSpecifier - - - FooterText - Software License Agreement (BSD License) - -Copyright (c) 2013, yap.TV Inc. -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Neither the name of yap.TV nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of yap.TV Inc. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - Title - YapDatabase - Type - PSGroupSpecifier - - - FooterText - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - Title - DifferenceKit - Type - PSGroupSpecifier - - - FooterText - The MIT License (MIT) - -Copyright (c) 2016 Vinh Nguyen - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - Title - NVActivityIndicatorView - Type - PSGroupSpecifier - - - FooterText - This code is distributed under the terms and conditions of the MIT license. - -Copyright (c) 2014-2015 Tyler Fox - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - PureLayout - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2011, Tony Million. -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - Reachability - Type - PSGroupSpecifier - - - FooterText - The MIT License (MIT) - -Copyright (c) 2015 ibireme <ibireme@gmail.com> - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - - Title - YYImage - Type - PSGroupSpecifier - - - FooterText - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - Title - ZXingObjC - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2010, Google Inc. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Google nor the names of its contributors may - be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - Title - libwebp - Type - PSGroupSpecifier - - - FooterText - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/> - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - {description} - Copyright (C) {year} {fullname} - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - {signature of Ty Coon}, 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. - Title - Curve25519Kit - Type - PSGroupSpecifier - - - FooterText - - LICENSE ISSUES - ============== - - The OpenSSL toolkit stays under a dual license, i.e. both the conditions of - the OpenSSL License and the original SSLeay license apply to the toolkit. - See below for the actual license texts. Actually both licenses are BSD-style - Open Source licenses. In case of any license issues related to OpenSSL - please contact openssl-core@openssl.org. - - OpenSSL License - --------------- - -/* ==================================================================== - * Copyright (c) 1998-2008 The OpenSSL Project. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. All advertising materials mentioning features or use of this - * software must display the following acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" - * - * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For written permission, please contact - * openssl-core@openssl.org. - * - * 5. Products derived from this software may not be called "OpenSSL" - * nor may "OpenSSL" appear in their names without prior written - * permission of the OpenSSL Project. - * - * 6. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit (http://www.openssl.org/)" - * - * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY - * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR - * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. - * ==================================================================== - * - * This product includes cryptographic software written by Eric Young - * (eay@cryptsoft.com). This product includes software written by Tim - * Hudson (tjh@cryptsoft.com). - * - */ - - Original SSLeay License - ----------------------- - -/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) - * All rights reserved. - * - * This package is an SSL implementation written - * by Eric Young (eay@cryptsoft.com). - * The implementation was written so as to conform with Netscapes SSL. - * - * This library is free for commercial and non-commercial use as long as - * the following conditions are aheared to. The following conditions - * apply to all code found in this distribution, be it the RC4, RSA, - * lhash, DES, etc., code; not just the SSL code. The SSL documentation - * included with this distribution is covered by the same copyright terms - * except that the holder is Tim Hudson (tjh@cryptsoft.com). - * - * Copyright remains Eric Young's, and as such any Copyright notices in - * the code are not to be removed. - * If this package is used in a product, Eric Young should be given attribution - * as the author of the parts of the library used. - * This can be in the form of a textual message at program startup or - * in documentation (online or textual) provided with the package. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * "This product includes cryptographic software written by - * Eric Young (eay@cryptsoft.com)" - * The word 'cryptographic' can be left out if the rouines from the library - * being used are not cryptographic related :-). - * 4. If you include any Windows specific code (or a derivative thereof) from - * the apps directory (application code) you must include an acknowledgement: - * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" - * - * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * The licence and distribution terms for any publically available version or - * derivative of this code cannot be changed. i.e. this code cannot simply be - * copied and put under another distribution licence - * [including the GNU Public Licence.] - */ - - - Title - OpenSSL-Universal - Type - PSGroupSpecifier - - - FooterText - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - Title - SignalCoreKit - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2010-2016 Sam Soffes, http://soff.es - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - SAMKeychain - Type - PSGroupSpecifier - - - FooterText - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - -## Runtime Library Exception to the Apache 2.0 License: ## - - - As an exception, if you use this Software to compile your source code and - portions of this Software are embedded into the binary product as a result, - you may redistribute such product without providing attribution as would - otherwise be required by Sections 4(a), 4(b) and 4(d) of the License. - - Title - SwiftProtobuf - Type - PSGroupSpecifier - - - FooterText - Generated by CocoaPods - https://cocoapods.org - Title - - Type - PSGroupSpecifier - - - StringsTable - Acknowledgements - Title - Acknowledgements - - diff --git a/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests-settings-metadata.plist b/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests-settings-metadata.plist deleted file mode 100644 index 11e81ea5a..000000000 --- a/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests-settings-metadata.plist +++ /dev/null @@ -1,2340 +0,0 @@ - - - - - PreferenceSpecifiers - - - FooterText - This application makes use of the following third party libraries: - Title - Acknowledgements - Type - PSGroupSpecifier - - - FooterText - BSD 3-Clause License - -Copyright (c) 2010-2021, Deusty, LLC -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -3. Neither the name of Deusty nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Deusty, LLC. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - CocoaLumberjack - Type - PSGroupSpecifier - - - FooterText - Copyright (C) 2014-2017 Marcin Krzyżanowski <marcin.krzyzanowski@gmail.com> -This software is provided 'as-is', without any express or implied warranty. - -In no event will the authors be held liable for any damages arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: - -- The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. -- Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. -- This notice may not be removed or altered from any source or binary distribution. -- Redistributions of any form whatsoever must retain the following acknowledgment: 'This product includes software developed by the "Marcin Krzyzanowski" (http://krzyzanowskim.com/).' - - Title - CryptoSwift - Type - PSGroupSpecifier - - - FooterText - Copyright (C) 2015-2020 Gwendal Roué - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - GRDB.swift - Type - PSGroupSpecifier - - - FooterText - Copyright 2016-present, Max Howell; mxcl@me.com - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - PromiseKit - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2008, ZETETIC LLC -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the ZETETIC LLC nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY -EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - SQLCipher - Type - PSGroupSpecifier - - - FooterText - - Copyright 2012 Square Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - Title - SocketRocket - Type - PSGroupSpecifier - - - FooterText - ISC License - -Copyright (c) 2014-2020, Frank Denis <j at pureftpd dot org> - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - - Title - Sodium - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2011, The WebRTC project authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Google nor the names of its contributors may - be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - WebRTC-lib - Type - PSGroupSpecifier - - - FooterText - Software License Agreement (BSD License) - -Copyright (c) 2013, yap.TV Inc. -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Neither the name of yap.TV nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of yap.TV Inc. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - Title - YapDatabase - Type - PSGroupSpecifier - - - FooterText - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - Title - DifferenceKit - Type - PSGroupSpecifier - - - FooterText - This code is distributed under the terms and conditions of the MIT license. - -Copyright (c) 2014-2015 Tyler Fox - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - PureLayout - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2011, Tony Million. -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - Reachability - Type - PSGroupSpecifier - - - FooterText - The MIT License (MIT) - -Copyright (c) 2015 ibireme <ibireme@gmail.com> - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - - Title - YYImage - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2010, Google Inc. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Google nor the names of its contributors may - be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - Title - libwebp - Type - PSGroupSpecifier - - - FooterText - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/> - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - {description} - Copyright (C) {year} {fullname} - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - {signature of Ty Coon}, 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. - Title - Curve25519Kit - Type - PSGroupSpecifier - - - FooterText - - LICENSE ISSUES - ============== - - The OpenSSL toolkit stays under a dual license, i.e. both the conditions of - the OpenSSL License and the original SSLeay license apply to the toolkit. - See below for the actual license texts. Actually both licenses are BSD-style - Open Source licenses. In case of any license issues related to OpenSSL - please contact openssl-core@openssl.org. - - OpenSSL License - --------------- - -/* ==================================================================== - * Copyright (c) 1998-2008 The OpenSSL Project. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. All advertising materials mentioning features or use of this - * software must display the following acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" - * - * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For written permission, please contact - * openssl-core@openssl.org. - * - * 5. Products derived from this software may not be called "OpenSSL" - * nor may "OpenSSL" appear in their names without prior written - * permission of the OpenSSL Project. - * - * 6. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit (http://www.openssl.org/)" - * - * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY - * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR - * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. - * ==================================================================== - * - * This product includes cryptographic software written by Eric Young - * (eay@cryptsoft.com). This product includes software written by Tim - * Hudson (tjh@cryptsoft.com). - * - */ - - Original SSLeay License - ----------------------- - -/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) - * All rights reserved. - * - * This package is an SSL implementation written - * by Eric Young (eay@cryptsoft.com). - * The implementation was written so as to conform with Netscapes SSL. - * - * This library is free for commercial and non-commercial use as long as - * the following conditions are aheared to. The following conditions - * apply to all code found in this distribution, be it the RC4, RSA, - * lhash, DES, etc., code; not just the SSL code. The SSL documentation - * included with this distribution is covered by the same copyright terms - * except that the holder is Tim Hudson (tjh@cryptsoft.com). - * - * Copyright remains Eric Young's, and as such any Copyright notices in - * the code are not to be removed. - * If this package is used in a product, Eric Young should be given attribution - * as the author of the parts of the library used. - * This can be in the form of a textual message at program startup or - * in documentation (online or textual) provided with the package. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * "This product includes cryptographic software written by - * Eric Young (eay@cryptsoft.com)" - * The word 'cryptographic' can be left out if the rouines from the library - * being used are not cryptographic related :-). - * 4. If you include any Windows specific code (or a derivative thereof) from - * the apps directory (application code) you must include an acknowledgement: - * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" - * - * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * The licence and distribution terms for any publically available version or - * derivative of this code cannot be changed. i.e. this code cannot simply be - * copied and put under another distribution licence - * [including the GNU Public Licence.] - */ - - - Title - OpenSSL-Universal - Type - PSGroupSpecifier - - - FooterText - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - Title - SignalCoreKit - Type - PSGroupSpecifier - - - FooterText - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2016 Quick Team - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - Title - Nimble - Type - PSGroupSpecifier - - - FooterText - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2014, Quick Team - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - Title - Quick - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2010-2016 Sam Soffes, http://soff.es - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - SAMKeychain - Type - PSGroupSpecifier - - - FooterText - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - -## Runtime Library Exception to the Apache 2.0 License: ## - - - As an exception, if you use this Software to compile your source code and - portions of this Software are embedded into the binary product as a result, - you may redistribute such product without providing attribution as would - otherwise be required by Sections 4(a), 4(b) and 4(d) of the License. - - Title - SwiftProtobuf - Type - PSGroupSpecifier - - - FooterText - Generated by CocoaPods - https://cocoapods.org - Title - - Type - PSGroupSpecifier - - - StringsTable - Acknowledgements - Title - Acknowledgements - - diff --git a/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-settings-metadata.plist b/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-settings-metadata.plist deleted file mode 100644 index a56df3b9f..000000000 --- a/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-settings-metadata.plist +++ /dev/null @@ -1,1854 +0,0 @@ - - - - - PreferenceSpecifiers - - - FooterText - This application makes use of the following third party libraries: - Title - Acknowledgements - Type - PSGroupSpecifier - - - FooterText - BSD 3-Clause License - -Copyright (c) 2010-2021, Deusty, LLC -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -3. Neither the name of Deusty nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Deusty, LLC. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - CocoaLumberjack - Type - PSGroupSpecifier - - - FooterText - Copyright (C) 2014-2017 Marcin Krzyżanowski <marcin.krzyzanowski@gmail.com> -This software is provided 'as-is', without any express or implied warranty. - -In no event will the authors be held liable for any damages arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: - -- The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. -- Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. -- This notice may not be removed or altered from any source or binary distribution. -- Redistributions of any form whatsoever must retain the following acknowledgment: 'This product includes software developed by the "Marcin Krzyzanowski" (http://krzyzanowskim.com/).' - - Title - CryptoSwift - Type - PSGroupSpecifier - - - FooterText - Copyright (C) 2015-2020 Gwendal Roué - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - GRDB.swift - Type - PSGroupSpecifier - - - FooterText - Copyright 2016-present, Max Howell; mxcl@me.com - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - PromiseKit - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2008, ZETETIC LLC -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the ZETETIC LLC nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY -EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - SQLCipher - Type - PSGroupSpecifier - - - FooterText - - Copyright 2012 Square Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - Title - SocketRocket - Type - PSGroupSpecifier - - - FooterText - ISC License - -Copyright (c) 2014-2020, Frank Denis <j at pureftpd dot org> - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - - Title - Sodium - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2011, The WebRTC project authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Google nor the names of its contributors may - be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - WebRTC-lib - Type - PSGroupSpecifier - - - FooterText - Software License Agreement (BSD License) - -Copyright (c) 2013, yap.TV Inc. -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Neither the name of yap.TV nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of yap.TV Inc. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - Title - YapDatabase - Type - PSGroupSpecifier - - - FooterText - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - Title - DifferenceKit - Type - PSGroupSpecifier - - - FooterText - This code is distributed under the terms and conditions of the MIT license. - -Copyright (c) 2014-2015 Tyler Fox - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - PureLayout - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2011, Tony Million. -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - Reachability - Type - PSGroupSpecifier - - - FooterText - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/> - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - {description} - Copyright (C) {year} {fullname} - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - {signature of Ty Coon}, 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. - Title - Curve25519Kit - Type - PSGroupSpecifier - - - FooterText - - LICENSE ISSUES - ============== - - The OpenSSL toolkit stays under a dual license, i.e. both the conditions of - the OpenSSL License and the original SSLeay license apply to the toolkit. - See below for the actual license texts. Actually both licenses are BSD-style - Open Source licenses. In case of any license issues related to OpenSSL - please contact openssl-core@openssl.org. - - OpenSSL License - --------------- - -/* ==================================================================== - * Copyright (c) 1998-2008 The OpenSSL Project. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. All advertising materials mentioning features or use of this - * software must display the following acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" - * - * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For written permission, please contact - * openssl-core@openssl.org. - * - * 5. Products derived from this software may not be called "OpenSSL" - * nor may "OpenSSL" appear in their names without prior written - * permission of the OpenSSL Project. - * - * 6. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit (http://www.openssl.org/)" - * - * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY - * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR - * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. - * ==================================================================== - * - * This product includes cryptographic software written by Eric Young - * (eay@cryptsoft.com). This product includes software written by Tim - * Hudson (tjh@cryptsoft.com). - * - */ - - Original SSLeay License - ----------------------- - -/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) - * All rights reserved. - * - * This package is an SSL implementation written - * by Eric Young (eay@cryptsoft.com). - * The implementation was written so as to conform with Netscapes SSL. - * - * This library is free for commercial and non-commercial use as long as - * the following conditions are aheared to. The following conditions - * apply to all code found in this distribution, be it the RC4, RSA, - * lhash, DES, etc., code; not just the SSL code. The SSL documentation - * included with this distribution is covered by the same copyright terms - * except that the holder is Tim Hudson (tjh@cryptsoft.com). - * - * Copyright remains Eric Young's, and as such any Copyright notices in - * the code are not to be removed. - * If this package is used in a product, Eric Young should be given attribution - * as the author of the parts of the library used. - * This can be in the form of a textual message at program startup or - * in documentation (online or textual) provided with the package. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * "This product includes cryptographic software written by - * Eric Young (eay@cryptsoft.com)" - * The word 'cryptographic' can be left out if the rouines from the library - * being used are not cryptographic related :-). - * 4. If you include any Windows specific code (or a derivative thereof) from - * the apps directory (application code) you must include an acknowledgement: - * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" - * - * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * The licence and distribution terms for any publically available version or - * derivative of this code cannot be changed. i.e. this code cannot simply be - * copied and put under another distribution licence - * [including the GNU Public Licence.] - */ - - - Title - OpenSSL-Universal - Type - PSGroupSpecifier - - - FooterText - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - Title - SignalCoreKit - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2010-2016 Sam Soffes, http://soff.es - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - SAMKeychain - Type - PSGroupSpecifier - - - FooterText - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - -## Runtime Library Exception to the Apache 2.0 License: ## - - - As an exception, if you use this Software to compile your source code and - portions of this Software are embedded into the binary product as a result, - you may redistribute such product without providing attribution as would - otherwise be required by Sections 4(a), 4(b) and 4(d) of the License. - - Title - SwiftProtobuf - Type - PSGroupSpecifier - - - FooterText - Generated by CocoaPods - https://cocoapods.org - Title - - Type - PSGroupSpecifier - - - StringsTable - Acknowledgements - Title - Acknowledgements - - diff --git a/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension-settings-metadata.plist b/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension-settings-metadata.plist deleted file mode 100644 index e24e0494c..000000000 --- a/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension-settings-metadata.plist +++ /dev/null @@ -1,1618 +0,0 @@ - - - - - PreferenceSpecifiers - - - FooterText - This application makes use of the following third party libraries: - Title - Acknowledgements - Type - PSGroupSpecifier - - - FooterText - BSD 3-Clause License - -Copyright (c) 2010-2021, Deusty, LLC -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -3. Neither the name of Deusty nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Deusty, LLC. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - CocoaLumberjack - Type - PSGroupSpecifier - - - FooterText - Copyright (C) 2014-2017 Marcin Krzyżanowski <marcin.krzyzanowski@gmail.com> -This software is provided 'as-is', without any express or implied warranty. - -In no event will the authors be held liable for any damages arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: - -- The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. -- Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. -- This notice may not be removed or altered from any source or binary distribution. -- Redistributions of any form whatsoever must retain the following acknowledgment: 'This product includes software developed by the "Marcin Krzyzanowski" (http://krzyzanowskim.com/).' - - Title - CryptoSwift - Type - PSGroupSpecifier - - - FooterText - Copyright (C) 2015-2020 Gwendal Roué - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - GRDB.swift - Type - PSGroupSpecifier - - - FooterText - Copyright 2016-present, Max Howell; mxcl@me.com - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - PromiseKit - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2008, ZETETIC LLC -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the ZETETIC LLC nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY -EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - SQLCipher - Type - PSGroupSpecifier - - - FooterText - - Copyright 2012 Square Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - Title - SocketRocket - Type - PSGroupSpecifier - - - FooterText - ISC License - -Copyright (c) 2014-2020, Frank Denis <j at pureftpd dot org> - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - - Title - Sodium - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2011, The WebRTC project authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Google nor the names of its contributors may - be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - WebRTC-lib - Type - PSGroupSpecifier - - - FooterText - Software License Agreement (BSD License) - -Copyright (c) 2013, yap.TV Inc. -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Neither the name of yap.TV nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of yap.TV Inc. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - Title - YapDatabase - Type - PSGroupSpecifier - - - FooterText - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - Title - DifferenceKit - Type - PSGroupSpecifier - - - FooterText - The MIT License (MIT) - -Copyright (c) 2016 Vinh Nguyen - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - Title - NVActivityIndicatorView - Type - PSGroupSpecifier - - - FooterText - This code is distributed under the terms and conditions of the MIT license. - -Copyright (c) 2014-2015 Tyler Fox - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - PureLayout - Type - PSGroupSpecifier - - - FooterText - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/> - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - {description} - Copyright (C) {year} {fullname} - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - {signature of Ty Coon}, 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. - Title - Curve25519Kit - Type - PSGroupSpecifier - - - FooterText - - LICENSE ISSUES - ============== - - The OpenSSL toolkit stays under a dual license, i.e. both the conditions of - the OpenSSL License and the original SSLeay license apply to the toolkit. - See below for the actual license texts. Actually both licenses are BSD-style - Open Source licenses. In case of any license issues related to OpenSSL - please contact openssl-core@openssl.org. - - OpenSSL License - --------------- - -/* ==================================================================== - * Copyright (c) 1998-2008 The OpenSSL Project. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. All advertising materials mentioning features or use of this - * software must display the following acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" - * - * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For written permission, please contact - * openssl-core@openssl.org. - * - * 5. Products derived from this software may not be called "OpenSSL" - * nor may "OpenSSL" appear in their names without prior written - * permission of the OpenSSL Project. - * - * 6. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit (http://www.openssl.org/)" - * - * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY - * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR - * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. - * ==================================================================== - * - * This product includes cryptographic software written by Eric Young - * (eay@cryptsoft.com). This product includes software written by Tim - * Hudson (tjh@cryptsoft.com). - * - */ - - Original SSLeay License - ----------------------- - -/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) - * All rights reserved. - * - * This package is an SSL implementation written - * by Eric Young (eay@cryptsoft.com). - * The implementation was written so as to conform with Netscapes SSL. - * - * This library is free for commercial and non-commercial use as long as - * the following conditions are aheared to. The following conditions - * apply to all code found in this distribution, be it the RC4, RSA, - * lhash, DES, etc., code; not just the SSL code. The SSL documentation - * included with this distribution is covered by the same copyright terms - * except that the holder is Tim Hudson (tjh@cryptsoft.com). - * - * Copyright remains Eric Young's, and as such any Copyright notices in - * the code are not to be removed. - * If this package is used in a product, Eric Young should be given attribution - * as the author of the parts of the library used. - * This can be in the form of a textual message at program startup or - * in documentation (online or textual) provided with the package. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * "This product includes cryptographic software written by - * Eric Young (eay@cryptsoft.com)" - * The word 'cryptographic' can be left out if the rouines from the library - * being used are not cryptographic related :-). - * 4. If you include any Windows specific code (or a derivative thereof) from - * the apps directory (application code) you must include an acknowledgement: - * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" - * - * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * The licence and distribution terms for any publically available version or - * derivative of this code cannot be changed. i.e. this code cannot simply be - * copied and put under another distribution licence - * [including the GNU Public Licence.] - */ - - - Title - OpenSSL-Universal - Type - PSGroupSpecifier - - - FooterText - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - Title - SignalCoreKit - Type - PSGroupSpecifier - - - FooterText - Generated by CocoaPods - https://cocoapods.org - Title - - Type - PSGroupSpecifier - - - StringsTable - Acknowledgements - Title - Acknowledgements - - diff --git a/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests-settings-metadata.plist b/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests-settings-metadata.plist deleted file mode 100644 index 9b01bc78c..000000000 --- a/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests-settings-metadata.plist +++ /dev/null @@ -1,2103 +0,0 @@ - - - - - PreferenceSpecifiers - - - FooterText - This application makes use of the following third party libraries: - Title - Acknowledgements - Type - PSGroupSpecifier - - - FooterText - BSD 3-Clause License - -Copyright (c) 2010-2021, Deusty, LLC -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -3. Neither the name of Deusty nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Deusty, LLC. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - CocoaLumberjack - Type - PSGroupSpecifier - - - FooterText - Copyright (C) 2014-2017 Marcin Krzyżanowski <marcin.krzyzanowski@gmail.com> -This software is provided 'as-is', without any express or implied warranty. - -In no event will the authors be held liable for any damages arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: - -- The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. -- Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. -- This notice may not be removed or altered from any source or binary distribution. -- Redistributions of any form whatsoever must retain the following acknowledgment: 'This product includes software developed by the "Marcin Krzyzanowski" (http://krzyzanowskim.com/).' - - Title - CryptoSwift - Type - PSGroupSpecifier - - - FooterText - Copyright (C) 2015-2020 Gwendal Roué - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - GRDB.swift - Type - PSGroupSpecifier - - - FooterText - Copyright 2016-present, Max Howell; mxcl@me.com - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - PromiseKit - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2008, ZETETIC LLC -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the ZETETIC LLC nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY -EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - SQLCipher - Type - PSGroupSpecifier - - - FooterText - - Copyright 2012 Square Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - Title - SocketRocket - Type - PSGroupSpecifier - - - FooterText - ISC License - -Copyright (c) 2014-2020, Frank Denis <j at pureftpd dot org> - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - - Title - Sodium - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2011, The WebRTC project authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Google nor the names of its contributors may - be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - WebRTC-lib - Type - PSGroupSpecifier - - - FooterText - Software License Agreement (BSD License) - -Copyright (c) 2013, yap.TV Inc. -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Neither the name of yap.TV nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of yap.TV Inc. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - Title - YapDatabase - Type - PSGroupSpecifier - - - FooterText - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - Title - DifferenceKit - Type - PSGroupSpecifier - - - FooterText - This code is distributed under the terms and conditions of the MIT license. - -Copyright (c) 2014-2015 Tyler Fox - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - PureLayout - Type - PSGroupSpecifier - - - FooterText - The MIT License (MIT) - -Copyright (c) 2015 ibireme <ibireme@gmail.com> - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - - Title - YYImage - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2010, Google Inc. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Google nor the names of its contributors may - be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - Title - libwebp - Type - PSGroupSpecifier - - - FooterText - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/> - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - {description} - Copyright (C) {year} {fullname} - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - {signature of Ty Coon}, 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. - Title - Curve25519Kit - Type - PSGroupSpecifier - - - FooterText - - LICENSE ISSUES - ============== - - The OpenSSL toolkit stays under a dual license, i.e. both the conditions of - the OpenSSL License and the original SSLeay license apply to the toolkit. - See below for the actual license texts. Actually both licenses are BSD-style - Open Source licenses. In case of any license issues related to OpenSSL - please contact openssl-core@openssl.org. - - OpenSSL License - --------------- - -/* ==================================================================== - * Copyright (c) 1998-2008 The OpenSSL Project. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. All advertising materials mentioning features or use of this - * software must display the following acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" - * - * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For written permission, please contact - * openssl-core@openssl.org. - * - * 5. Products derived from this software may not be called "OpenSSL" - * nor may "OpenSSL" appear in their names without prior written - * permission of the OpenSSL Project. - * - * 6. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit (http://www.openssl.org/)" - * - * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY - * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR - * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. - * ==================================================================== - * - * This product includes cryptographic software written by Eric Young - * (eay@cryptsoft.com). This product includes software written by Tim - * Hudson (tjh@cryptsoft.com). - * - */ - - Original SSLeay License - ----------------------- - -/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) - * All rights reserved. - * - * This package is an SSL implementation written - * by Eric Young (eay@cryptsoft.com). - * The implementation was written so as to conform with Netscapes SSL. - * - * This library is free for commercial and non-commercial use as long as - * the following conditions are aheared to. The following conditions - * apply to all code found in this distribution, be it the RC4, RSA, - * lhash, DES, etc., code; not just the SSL code. The SSL documentation - * included with this distribution is covered by the same copyright terms - * except that the holder is Tim Hudson (tjh@cryptsoft.com). - * - * Copyright remains Eric Young's, and as such any Copyright notices in - * the code are not to be removed. - * If this package is used in a product, Eric Young should be given attribution - * as the author of the parts of the library used. - * This can be in the form of a textual message at program startup or - * in documentation (online or textual) provided with the package. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * "This product includes cryptographic software written by - * Eric Young (eay@cryptsoft.com)" - * The word 'cryptographic' can be left out if the rouines from the library - * being used are not cryptographic related :-). - * 4. If you include any Windows specific code (or a derivative thereof) from - * the apps directory (application code) you must include an acknowledgement: - * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" - * - * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * The licence and distribution terms for any publically available version or - * derivative of this code cannot be changed. i.e. this code cannot simply be - * copied and put under another distribution licence - * [including the GNU Public Licence.] - */ - - - Title - OpenSSL-Universal - Type - PSGroupSpecifier - - - FooterText - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - Title - SignalCoreKit - Type - PSGroupSpecifier - - - FooterText - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2016 Quick Team - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - Title - Nimble - Type - PSGroupSpecifier - - - FooterText - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2014, Quick Team - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - Title - Quick - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2010-2016 Sam Soffes, http://soff.es - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - SAMKeychain - Type - PSGroupSpecifier - - - FooterText - Generated by CocoaPods - https://cocoapods.org - Title - - Type - PSGroupSpecifier - - - StringsTable - Acknowledgements - Title - Acknowledgements - - diff --git a/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-settings-metadata.plist b/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-settings-metadata.plist deleted file mode 100644 index f3fdd43da..000000000 --- a/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-settings-metadata.plist +++ /dev/null @@ -1,1685 +0,0 @@ - - - - - PreferenceSpecifiers - - - FooterText - This application makes use of the following third party libraries: - Title - Acknowledgements - Type - PSGroupSpecifier - - - FooterText - BSD 3-Clause License - -Copyright (c) 2010-2021, Deusty, LLC -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -3. Neither the name of Deusty nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Deusty, LLC. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - CocoaLumberjack - Type - PSGroupSpecifier - - - FooterText - Copyright (C) 2014-2017 Marcin Krzyżanowski <marcin.krzyzanowski@gmail.com> -This software is provided 'as-is', without any express or implied warranty. - -In no event will the authors be held liable for any damages arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: - -- The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. -- Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. -- This notice may not be removed or altered from any source or binary distribution. -- Redistributions of any form whatsoever must retain the following acknowledgment: 'This product includes software developed by the "Marcin Krzyzanowski" (http://krzyzanowskim.com/).' - - Title - CryptoSwift - Type - PSGroupSpecifier - - - FooterText - Copyright (C) 2015-2020 Gwendal Roué - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - GRDB.swift - Type - PSGroupSpecifier - - - FooterText - Copyright 2016-present, Max Howell; mxcl@me.com - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - PromiseKit - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2008, ZETETIC LLC -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the ZETETIC LLC nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY -EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - SQLCipher - Type - PSGroupSpecifier - - - FooterText - - Copyright 2012 Square Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - Title - SocketRocket - Type - PSGroupSpecifier - - - FooterText - ISC License - -Copyright (c) 2014-2020, Frank Denis <j at pureftpd dot org> - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - - Title - Sodium - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2011, The WebRTC project authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Google nor the names of its contributors may - be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - WebRTC-lib - Type - PSGroupSpecifier - - - FooterText - Software License Agreement (BSD License) - -Copyright (c) 2013, yap.TV Inc. -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Neither the name of yap.TV nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of yap.TV Inc. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - Title - YapDatabase - Type - PSGroupSpecifier - - - FooterText - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - Title - DifferenceKit - Type - PSGroupSpecifier - - - FooterText - This code is distributed under the terms and conditions of the MIT license. - -Copyright (c) 2014-2015 Tyler Fox - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - PureLayout - Type - PSGroupSpecifier - - - FooterText - The MIT License (MIT) - -Copyright (c) 2015 ibireme <ibireme@gmail.com> - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - - Title - YYImage - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2010, Google Inc. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Google nor the names of its contributors may - be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - Title - libwebp - Type - PSGroupSpecifier - - - FooterText - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/> - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - {description} - Copyright (C) {year} {fullname} - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - {signature of Ty Coon}, 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. - Title - Curve25519Kit - Type - PSGroupSpecifier - - - FooterText - - LICENSE ISSUES - ============== - - The OpenSSL toolkit stays under a dual license, i.e. both the conditions of - the OpenSSL License and the original SSLeay license apply to the toolkit. - See below for the actual license texts. Actually both licenses are BSD-style - Open Source licenses. In case of any license issues related to OpenSSL - please contact openssl-core@openssl.org. - - OpenSSL License - --------------- - -/* ==================================================================== - * Copyright (c) 1998-2008 The OpenSSL Project. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. All advertising materials mentioning features or use of this - * software must display the following acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" - * - * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For written permission, please contact - * openssl-core@openssl.org. - * - * 5. Products derived from this software may not be called "OpenSSL" - * nor may "OpenSSL" appear in their names without prior written - * permission of the OpenSSL Project. - * - * 6. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit (http://www.openssl.org/)" - * - * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY - * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR - * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. - * ==================================================================== - * - * This product includes cryptographic software written by Eric Young - * (eay@cryptsoft.com). This product includes software written by Tim - * Hudson (tjh@cryptsoft.com). - * - */ - - Original SSLeay License - ----------------------- - -/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) - * All rights reserved. - * - * This package is an SSL implementation written - * by Eric Young (eay@cryptsoft.com). - * The implementation was written so as to conform with Netscapes SSL. - * - * This library is free for commercial and non-commercial use as long as - * the following conditions are aheared to. The following conditions - * apply to all code found in this distribution, be it the RC4, RSA, - * lhash, DES, etc., code; not just the SSL code. The SSL documentation - * included with this distribution is covered by the same copyright terms - * except that the holder is Tim Hudson (tjh@cryptsoft.com). - * - * Copyright remains Eric Young's, and as such any Copyright notices in - * the code are not to be removed. - * If this package is used in a product, Eric Young should be given attribution - * as the author of the parts of the library used. - * This can be in the form of a textual message at program startup or - * in documentation (online or textual) provided with the package. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * "This product includes cryptographic software written by - * Eric Young (eay@cryptsoft.com)" - * The word 'cryptographic' can be left out if the rouines from the library - * being used are not cryptographic related :-). - * 4. If you include any Windows specific code (or a derivative thereof) from - * the apps directory (application code) you must include an acknowledgement: - * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" - * - * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * The licence and distribution terms for any publically available version or - * derivative of this code cannot be changed. i.e. this code cannot simply be - * copied and put under another distribution licence - * [including the GNU Public Licence.] - */ - - - Title - OpenSSL-Universal - Type - PSGroupSpecifier - - - FooterText - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - Title - SignalCoreKit - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2010-2016 Sam Soffes, http://soff.es - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - SAMKeychain - Type - PSGroupSpecifier - - - FooterText - Generated by CocoaPods - https://cocoapods.org - Title - - Type - PSGroupSpecifier - - - StringsTable - Acknowledgements - Title - Acknowledgements - - diff --git a/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit-settings-metadata.plist b/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit-settings-metadata.plist deleted file mode 100644 index e4847df30..000000000 --- a/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SignalUtilitiesKit-settings-metadata.plist +++ /dev/null @@ -1,1951 +0,0 @@ - - - - - PreferenceSpecifiers - - - FooterText - This application makes use of the following third party libraries: - Title - Acknowledgements - Type - PSGroupSpecifier - - - FooterText - BSD 3-Clause License - -Copyright (c) 2010-2021, Deusty, LLC -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -3. Neither the name of Deusty nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Deusty, LLC. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - CocoaLumberjack - Type - PSGroupSpecifier - - - FooterText - Copyright (C) 2014-2017 Marcin Krzyżanowski <marcin.krzyzanowski@gmail.com> -This software is provided 'as-is', without any express or implied warranty. - -In no event will the authors be held liable for any damages arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: - -- The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. -- Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. -- This notice may not be removed or altered from any source or binary distribution. -- Redistributions of any form whatsoever must retain the following acknowledgment: 'This product includes software developed by the "Marcin Krzyzanowski" (http://krzyzanowskim.com/).' - - Title - CryptoSwift - Type - PSGroupSpecifier - - - FooterText - Copyright (C) 2015-2020 Gwendal Roué - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - GRDB.swift - Type - PSGroupSpecifier - - - FooterText - Copyright 2016-present, Max Howell; mxcl@me.com - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - PromiseKit - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2008, ZETETIC LLC -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the ZETETIC LLC nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY -EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - SQLCipher - Type - PSGroupSpecifier - - - FooterText - - Copyright 2012 Square Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - Title - SocketRocket - Type - PSGroupSpecifier - - - FooterText - ISC License - -Copyright (c) 2014-2020, Frank Denis <j at pureftpd dot org> - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - - Title - Sodium - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2011, The WebRTC project authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Google nor the names of its contributors may - be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - WebRTC-lib - Type - PSGroupSpecifier - - - FooterText - Software License Agreement (BSD License) - -Copyright (c) 2013, yap.TV Inc. -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Neither the name of yap.TV nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of yap.TV Inc. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - Title - YapDatabase - Type - PSGroupSpecifier - - - FooterText - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - Title - DifferenceKit - Type - PSGroupSpecifier - - - FooterText - The MIT License (MIT) - -Copyright (c) 2016 Vinh Nguyen - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - Title - NVActivityIndicatorView - Type - PSGroupSpecifier - - - FooterText - This code is distributed under the terms and conditions of the MIT license. - -Copyright (c) 2014-2015 Tyler Fox - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - PureLayout - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2011, Tony Million. -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - Reachability - Type - PSGroupSpecifier - - - FooterText - The MIT License (MIT) - -Copyright (c) 2015 ibireme <ibireme@gmail.com> - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - - Title - YYImage - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2010, Google Inc. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Google nor the names of its contributors may - be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - Title - libwebp - Type - PSGroupSpecifier - - - FooterText - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/> - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - {description} - Copyright (C) {year} {fullname} - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - {signature of Ty Coon}, 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. - Title - Curve25519Kit - Type - PSGroupSpecifier - - - FooterText - - LICENSE ISSUES - ============== - - The OpenSSL toolkit stays under a dual license, i.e. both the conditions of - the OpenSSL License and the original SSLeay license apply to the toolkit. - See below for the actual license texts. Actually both licenses are BSD-style - Open Source licenses. In case of any license issues related to OpenSSL - please contact openssl-core@openssl.org. - - OpenSSL License - --------------- - -/* ==================================================================== - * Copyright (c) 1998-2008 The OpenSSL Project. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. All advertising materials mentioning features or use of this - * software must display the following acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" - * - * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For written permission, please contact - * openssl-core@openssl.org. - * - * 5. Products derived from this software may not be called "OpenSSL" - * nor may "OpenSSL" appear in their names without prior written - * permission of the OpenSSL Project. - * - * 6. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit (http://www.openssl.org/)" - * - * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY - * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR - * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. - * ==================================================================== - * - * This product includes cryptographic software written by Eric Young - * (eay@cryptsoft.com). This product includes software written by Tim - * Hudson (tjh@cryptsoft.com). - * - */ - - Original SSLeay License - ----------------------- - -/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) - * All rights reserved. - * - * This package is an SSL implementation written - * by Eric Young (eay@cryptsoft.com). - * The implementation was written so as to conform with Netscapes SSL. - * - * This library is free for commercial and non-commercial use as long as - * the following conditions are aheared to. The following conditions - * apply to all code found in this distribution, be it the RC4, RSA, - * lhash, DES, etc., code; not just the SSL code. The SSL documentation - * included with this distribution is covered by the same copyright terms - * except that the holder is Tim Hudson (tjh@cryptsoft.com). - * - * Copyright remains Eric Young's, and as such any Copyright notices in - * the code are not to be removed. - * If this package is used in a product, Eric Young should be given attribution - * as the author of the parts of the library used. - * This can be in the form of a textual message at program startup or - * in documentation (online or textual) provided with the package. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * "This product includes cryptographic software written by - * Eric Young (eay@cryptsoft.com)" - * The word 'cryptographic' can be left out if the rouines from the library - * being used are not cryptographic related :-). - * 4. If you include any Windows specific code (or a derivative thereof) from - * the apps directory (application code) you must include an acknowledgement: - * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" - * - * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * The licence and distribution terms for any publically available version or - * derivative of this code cannot be changed. i.e. this code cannot simply be - * copied and put under another distribution licence - * [including the GNU Public Licence.] - */ - - - Title - OpenSSL-Universal - Type - PSGroupSpecifier - - - FooterText - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - Title - SignalCoreKit - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2010-2016 Sam Soffes, http://soff.es - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - SAMKeychain - Type - PSGroupSpecifier - - - FooterText - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - -## Runtime Library Exception to the Apache 2.0 License: ## - - - As an exception, if you use this Software to compile your source code and - portions of this Software are embedded into the binary product as a result, - you may redistribute such product without providing attribution as would - otherwise be required by Sections 4(a), 4(b) and 4(d) of the License. - - Title - SwiftProtobuf - Type - PSGroupSpecifier - - - FooterText - Generated by CocoaPods - https://cocoapods.org - Title - - Type - PSGroupSpecifier - - - StringsTable - Acknowledgements - Title - Acknowledgements - - diff --git a/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension-settings-metadata.plist b/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension-settings-metadata.plist deleted file mode 100644 index 2579983fc..000000000 --- a/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension-settings-metadata.plist +++ /dev/null @@ -1,1364 +0,0 @@ - - - - - PreferenceSpecifiers - - - FooterText - This application makes use of the following third party libraries: - Title - Acknowledgements - Type - PSGroupSpecifier - - - FooterText - BSD 3-Clause License - -Copyright (c) 2010-2021, Deusty, LLC -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -3. Neither the name of Deusty nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Deusty, LLC. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - CocoaLumberjack - Type - PSGroupSpecifier - - - FooterText - Copyright (C) 2014-2017 Marcin Krzyżanowski <marcin.krzyzanowski@gmail.com> -This software is provided 'as-is', without any express or implied warranty. - -In no event will the authors be held liable for any damages arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: - -- The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. -- Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. -- This notice may not be removed or altered from any source or binary distribution. -- Redistributions of any form whatsoever must retain the following acknowledgment: 'This product includes software developed by the "Marcin Krzyzanowski" (http://krzyzanowskim.com/).' - - Title - CryptoSwift - Type - PSGroupSpecifier - - - FooterText - Copyright (C) 2015-2020 Gwendal Roué - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - GRDB.swift - Type - PSGroupSpecifier - - - FooterText - Copyright 2016-present, Max Howell; mxcl@me.com - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - PromiseKit - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2008, ZETETIC LLC -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the ZETETIC LLC nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY -EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - SQLCipher - Type - PSGroupSpecifier - - - FooterText - - Copyright 2012 Square Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - Title - SocketRocket - Type - PSGroupSpecifier - - - FooterText - ISC License - -Copyright (c) 2014-2020, Frank Denis <j at pureftpd dot org> - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - - Title - Sodium - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2011, The WebRTC project authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Google nor the names of its contributors may - be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - WebRTC-lib - Type - PSGroupSpecifier - - - FooterText - Software License Agreement (BSD License) - -Copyright (c) 2013, yap.TV Inc. -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Neither the name of yap.TV nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of yap.TV Inc. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - Title - YapDatabase - Type - PSGroupSpecifier - - - FooterText - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/> - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - {description} - Copyright (C) {year} {fullname} - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - {signature of Ty Coon}, 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. - Title - Curve25519Kit - Type - PSGroupSpecifier - - - FooterText - - LICENSE ISSUES - ============== - - The OpenSSL toolkit stays under a dual license, i.e. both the conditions of - the OpenSSL License and the original SSLeay license apply to the toolkit. - See below for the actual license texts. Actually both licenses are BSD-style - Open Source licenses. In case of any license issues related to OpenSSL - please contact openssl-core@openssl.org. - - OpenSSL License - --------------- - -/* ==================================================================== - * Copyright (c) 1998-2008 The OpenSSL Project. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. All advertising materials mentioning features or use of this - * software must display the following acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" - * - * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For written permission, please contact - * openssl-core@openssl.org. - * - * 5. Products derived from this software may not be called "OpenSSL" - * nor may "OpenSSL" appear in their names without prior written - * permission of the OpenSSL Project. - * - * 6. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit (http://www.openssl.org/)" - * - * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY - * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR - * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. - * ==================================================================== - * - * This product includes cryptographic software written by Eric Young - * (eay@cryptsoft.com). This product includes software written by Tim - * Hudson (tjh@cryptsoft.com). - * - */ - - Original SSLeay License - ----------------------- - -/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) - * All rights reserved. - * - * This package is an SSL implementation written - * by Eric Young (eay@cryptsoft.com). - * The implementation was written so as to conform with Netscapes SSL. - * - * This library is free for commercial and non-commercial use as long as - * the following conditions are aheared to. The following conditions - * apply to all code found in this distribution, be it the RC4, RSA, - * lhash, DES, etc., code; not just the SSL code. The SSL documentation - * included with this distribution is covered by the same copyright terms - * except that the holder is Tim Hudson (tjh@cryptsoft.com). - * - * Copyright remains Eric Young's, and as such any Copyright notices in - * the code are not to be removed. - * If this package is used in a product, Eric Young should be given attribution - * as the author of the parts of the library used. - * This can be in the form of a textual message at program startup or - * in documentation (online or textual) provided with the package. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * "This product includes cryptographic software written by - * Eric Young (eay@cryptsoft.com)" - * The word 'cryptographic' can be left out if the rouines from the library - * being used are not cryptographic related :-). - * 4. If you include any Windows specific code (or a derivative thereof) from - * the apps directory (application code) you must include an acknowledgement: - * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" - * - * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * The licence and distribution terms for any publically available version or - * derivative of this code cannot be changed. i.e. this code cannot simply be - * copied and put under another distribution licence - * [including the GNU Public Licence.] - */ - - - Title - OpenSSL-Universal - Type - PSGroupSpecifier - - - FooterText - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - Title - SignalCoreKit - Type - PSGroupSpecifier - - - FooterText - Generated by CocoaPods - https://cocoapods.org - Title - - Type - PSGroupSpecifier - - - StringsTable - Acknowledgements - Title - Acknowledgements - - diff --git a/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit-settings-metadata.plist b/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit-settings-metadata.plist deleted file mode 100644 index 2579983fc..000000000 --- a/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit-settings-metadata.plist +++ /dev/null @@ -1,1364 +0,0 @@ - - - - - PreferenceSpecifiers - - - FooterText - This application makes use of the following third party libraries: - Title - Acknowledgements - Type - PSGroupSpecifier - - - FooterText - BSD 3-Clause License - -Copyright (c) 2010-2021, Deusty, LLC -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -3. Neither the name of Deusty nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Deusty, LLC. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - CocoaLumberjack - Type - PSGroupSpecifier - - - FooterText - Copyright (C) 2014-2017 Marcin Krzyżanowski <marcin.krzyzanowski@gmail.com> -This software is provided 'as-is', without any express or implied warranty. - -In no event will the authors be held liable for any damages arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: - -- The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. -- Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. -- This notice may not be removed or altered from any source or binary distribution. -- Redistributions of any form whatsoever must retain the following acknowledgment: 'This product includes software developed by the "Marcin Krzyzanowski" (http://krzyzanowskim.com/).' - - Title - CryptoSwift - Type - PSGroupSpecifier - - - FooterText - Copyright (C) 2015-2020 Gwendal Roué - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - GRDB.swift - Type - PSGroupSpecifier - - - FooterText - Copyright 2016-present, Max Howell; mxcl@me.com - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - PromiseKit - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2008, ZETETIC LLC -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the ZETETIC LLC nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY -EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - SQLCipher - Type - PSGroupSpecifier - - - FooterText - - Copyright 2012 Square Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - Title - SocketRocket - Type - PSGroupSpecifier - - - FooterText - ISC License - -Copyright (c) 2014-2020, Frank Denis <j at pureftpd dot org> - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - - Title - Sodium - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2011, The WebRTC project authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Google nor the names of its contributors may - be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - WebRTC-lib - Type - PSGroupSpecifier - - - FooterText - Software License Agreement (BSD License) - -Copyright (c) 2013, yap.TV Inc. -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Neither the name of yap.TV nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of yap.TV Inc. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - Title - YapDatabase - Type - PSGroupSpecifier - - - FooterText - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/> - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - {description} - Copyright (C) {year} {fullname} - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - {signature of Ty Coon}, 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. - Title - Curve25519Kit - Type - PSGroupSpecifier - - - FooterText - - LICENSE ISSUES - ============== - - The OpenSSL toolkit stays under a dual license, i.e. both the conditions of - the OpenSSL License and the original SSLeay license apply to the toolkit. - See below for the actual license texts. Actually both licenses are BSD-style - Open Source licenses. In case of any license issues related to OpenSSL - please contact openssl-core@openssl.org. - - OpenSSL License - --------------- - -/* ==================================================================== - * Copyright (c) 1998-2008 The OpenSSL Project. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. All advertising materials mentioning features or use of this - * software must display the following acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" - * - * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For written permission, please contact - * openssl-core@openssl.org. - * - * 5. Products derived from this software may not be called "OpenSSL" - * nor may "OpenSSL" appear in their names without prior written - * permission of the OpenSSL Project. - * - * 6. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit (http://www.openssl.org/)" - * - * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY - * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR - * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. - * ==================================================================== - * - * This product includes cryptographic software written by Eric Young - * (eay@cryptsoft.com). This product includes software written by Tim - * Hudson (tjh@cryptsoft.com). - * - */ - - Original SSLeay License - ----------------------- - -/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) - * All rights reserved. - * - * This package is an SSL implementation written - * by Eric Young (eay@cryptsoft.com). - * The implementation was written so as to conform with Netscapes SSL. - * - * This library is free for commercial and non-commercial use as long as - * the following conditions are aheared to. The following conditions - * apply to all code found in this distribution, be it the RC4, RSA, - * lhash, DES, etc., code; not just the SSL code. The SSL documentation - * included with this distribution is covered by the same copyright terms - * except that the holder is Tim Hudson (tjh@cryptsoft.com). - * - * Copyright remains Eric Young's, and as such any Copyright notices in - * the code are not to be removed. - * If this package is used in a product, Eric Young should be given attribution - * as the author of the parts of the library used. - * This can be in the form of a textual message at program startup or - * in documentation (online or textual) provided with the package. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * "This product includes cryptographic software written by - * Eric Young (eay@cryptsoft.com)" - * The word 'cryptographic' can be left out if the rouines from the library - * being used are not cryptographic related :-). - * 4. If you include any Windows specific code (or a derivative thereof) from - * the apps directory (application code) you must include an acknowledgement: - * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" - * - * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * The licence and distribution terms for any publically available version or - * derivative of this code cannot be changed. i.e. this code cannot simply be - * copied and put under another distribution licence - * [including the GNU Public Licence.] - */ - - - Title - OpenSSL-Universal - Type - PSGroupSpecifier - - - FooterText - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - Title - SignalCoreKit - Type - PSGroupSpecifier - - - FooterText - Generated by CocoaPods - https://cocoapods.org - Title - - Type - PSGroupSpecifier - - - StringsTable - Acknowledgements - Title - Acknowledgements - - diff --git a/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-Session-SessionTests-settings-metadata.plist b/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-Session-SessionTests-settings-metadata.plist deleted file mode 100644 index 77e9f98d6..000000000 --- a/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-Session-SessionTests-settings-metadata.plist +++ /dev/null @@ -1,1221 +0,0 @@ - - - - - PreferenceSpecifiers - - - FooterText - This application makes use of the following third party libraries: - Title - Acknowledgements - Type - PSGroupSpecifier - - - FooterText - BSD 3-Clause License - -Copyright (c) 2010-2021, Deusty, LLC -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -3. Neither the name of Deusty nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Deusty, LLC. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - CocoaLumberjack - Type - PSGroupSpecifier - - - FooterText - Copyright (C) 2014-2017 Marcin Krzyżanowski <marcin.krzyzanowski@gmail.com> -This software is provided 'as-is', without any express or implied warranty. - -In no event will the authors be held liable for any damages arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: - -- The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. -- Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. -- This notice may not be removed or altered from any source or binary distribution. -- Redistributions of any form whatsoever must retain the following acknowledgment: 'This product includes software developed by the "Marcin Krzyzanowski" (http://krzyzanowskim.com/).' - - Title - CryptoSwift - Type - PSGroupSpecifier - - - FooterText - Copyright (C) 2015-2020 Gwendal Roué - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - GRDB.swift - Type - PSGroupSpecifier - - - FooterText - Copyright 2016-present, Max Howell; mxcl@me.com - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - PromiseKit - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2008, ZETETIC LLC -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the ZETETIC LLC nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY -EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - SQLCipher - Type - PSGroupSpecifier - - - FooterText - - Copyright 2012 Square Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - Title - SocketRocket - Type - PSGroupSpecifier - - - FooterText - ISC License - -Copyright (c) 2014-2020, Frank Denis <j at pureftpd dot org> - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - - Title - Sodium - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2011, The WebRTC project authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Google nor the names of its contributors may - be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - WebRTC-lib - Type - PSGroupSpecifier - - - FooterText - Software License Agreement (BSD License) - -Copyright (c) 2013, yap.TV Inc. -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Neither the name of yap.TV nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of yap.TV Inc. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - Title - YapDatabase - Type - PSGroupSpecifier - - - FooterText - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - Title - DifferenceKit - Type - PSGroupSpecifier - - - FooterText - The MIT License (MIT) - -Copyright (c) 2016 Vinh Nguyen - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - Title - NVActivityIndicatorView - Type - PSGroupSpecifier - - - FooterText - This code is distributed under the terms and conditions of the MIT license. - -Copyright (c) 2014-2015 Tyler Fox - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - PureLayout - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2011, Tony Million. -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - Reachability - Type - PSGroupSpecifier - - - FooterText - The MIT License (MIT) - -Copyright (c) 2015 ibireme <ibireme@gmail.com> - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - - Title - YYImage - Type - PSGroupSpecifier - - - FooterText - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - Title - ZXingObjC - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2010, Google Inc. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Google nor the names of its contributors may - be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - Title - libwebp - Type - PSGroupSpecifier - - - FooterText - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2016 Quick Team - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - Title - Nimble - Type - PSGroupSpecifier - - - FooterText - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2014, Quick Team - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - Title - Quick - Type - PSGroupSpecifier - - - FooterText - Generated by CocoaPods - https://cocoapods.org - Title - - Type - PSGroupSpecifier - - - StringsTable - Acknowledgements - Title - Acknowledgements - - diff --git a/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-Session-settings-metadata.plist b/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-Session-settings-metadata.plist deleted file mode 100644 index d17d35ab8..000000000 --- a/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-Session-settings-metadata.plist +++ /dev/null @@ -1,2160 +0,0 @@ - - - - - PreferenceSpecifiers - - - FooterText - This application makes use of the following third party libraries: - Title - Acknowledgements - Type - PSGroupSpecifier - - - FooterText - BSD 3-Clause License - -Copyright (c) 2010-2021, Deusty, LLC -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -3. Neither the name of Deusty nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Deusty, LLC. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - CocoaLumberjack - Type - PSGroupSpecifier - - - FooterText - Copyright (C) 2014-2017 Marcin Krzyżanowski <marcin.krzyzanowski@gmail.com> -This software is provided 'as-is', without any express or implied warranty. - -In no event will the authors be held liable for any damages arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: - -- The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. -- Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. -- This notice may not be removed or altered from any source or binary distribution. -- Redistributions of any form whatsoever must retain the following acknowledgment: 'This product includes software developed by the "Marcin Krzyzanowski" (http://krzyzanowskim.com/).' - - Title - CryptoSwift - Type - PSGroupSpecifier - - - FooterText - Copyright (C) 2015-2020 Gwendal Roué - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - GRDB.swift - Type - PSGroupSpecifier - - - FooterText - Copyright 2016-present, Max Howell; mxcl@me.com - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - PromiseKit - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2008, ZETETIC LLC -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the ZETETIC LLC nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY -EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - SQLCipher - Type - PSGroupSpecifier - - - FooterText - - Copyright 2012 Square Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - Title - SocketRocket - Type - PSGroupSpecifier - - - FooterText - ISC License - -Copyright (c) 2014-2020, Frank Denis <j at pureftpd dot org> - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - - Title - Sodium - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2011, The WebRTC project authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Google nor the names of its contributors may - be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - WebRTC-lib - Type - PSGroupSpecifier - - - FooterText - Software License Agreement (BSD License) - -Copyright (c) 2013, yap.TV Inc. -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Neither the name of yap.TV nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of yap.TV Inc. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - Title - YapDatabase - Type - PSGroupSpecifier - - - FooterText - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - Title - DifferenceKit - Type - PSGroupSpecifier - - - FooterText - The MIT License (MIT) - -Copyright (c) 2016 Vinh Nguyen - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - Title - NVActivityIndicatorView - Type - PSGroupSpecifier - - - FooterText - This code is distributed under the terms and conditions of the MIT license. - -Copyright (c) 2014-2015 Tyler Fox - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - PureLayout - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2011, Tony Million. -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - Reachability - Type - PSGroupSpecifier - - - FooterText - The MIT License (MIT) - -Copyright (c) 2015 ibireme <ibireme@gmail.com> - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - - Title - YYImage - Type - PSGroupSpecifier - - - FooterText - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - Title - ZXingObjC - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2010, Google Inc. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Google nor the names of its contributors may - be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - Title - libwebp - Type - PSGroupSpecifier - - - FooterText - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/> - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - {description} - Copyright (C) {year} {fullname} - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - {signature of Ty Coon}, 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. - Title - Curve25519Kit - Type - PSGroupSpecifier - - - FooterText - - LICENSE ISSUES - ============== - - The OpenSSL toolkit stays under a dual license, i.e. both the conditions of - the OpenSSL License and the original SSLeay license apply to the toolkit. - See below for the actual license texts. Actually both licenses are BSD-style - Open Source licenses. In case of any license issues related to OpenSSL - please contact openssl-core@openssl.org. - - OpenSSL License - --------------- - -/* ==================================================================== - * Copyright (c) 1998-2008 The OpenSSL Project. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. All advertising materials mentioning features or use of this - * software must display the following acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" - * - * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For written permission, please contact - * openssl-core@openssl.org. - * - * 5. Products derived from this software may not be called "OpenSSL" - * nor may "OpenSSL" appear in their names without prior written - * permission of the OpenSSL Project. - * - * 6. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit (http://www.openssl.org/)" - * - * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY - * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR - * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. - * ==================================================================== - * - * This product includes cryptographic software written by Eric Young - * (eay@cryptsoft.com). This product includes software written by Tim - * Hudson (tjh@cryptsoft.com). - * - */ - - Original SSLeay License - ----------------------- - -/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) - * All rights reserved. - * - * This package is an SSL implementation written - * by Eric Young (eay@cryptsoft.com). - * The implementation was written so as to conform with Netscapes SSL. - * - * This library is free for commercial and non-commercial use as long as - * the following conditions are aheared to. The following conditions - * apply to all code found in this distribution, be it the RC4, RSA, - * lhash, DES, etc., code; not just the SSL code. The SSL documentation - * included with this distribution is covered by the same copyright terms - * except that the holder is Tim Hudson (tjh@cryptsoft.com). - * - * Copyright remains Eric Young's, and as such any Copyright notices in - * the code are not to be removed. - * If this package is used in a product, Eric Young should be given attribution - * as the author of the parts of the library used. - * This can be in the form of a textual message at program startup or - * in documentation (online or textual) provided with the package. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * "This product includes cryptographic software written by - * Eric Young (eay@cryptsoft.com)" - * The word 'cryptographic' can be left out if the rouines from the library - * being used are not cryptographic related :-). - * 4. If you include any Windows specific code (or a derivative thereof) from - * the apps directory (application code) you must include an acknowledgement: - * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" - * - * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * The licence and distribution terms for any publically available version or - * derivative of this code cannot be changed. i.e. this code cannot simply be - * copied and put under another distribution licence - * [including the GNU Public Licence.] - */ - - - Title - OpenSSL-Universal - Type - PSGroupSpecifier - - - FooterText - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - Title - SignalCoreKit - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2010-2016 Sam Soffes, http://soff.es - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - SAMKeychain - Type - PSGroupSpecifier - - - FooterText - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - -## Runtime Library Exception to the Apache 2.0 License: ## - - - As an exception, if you use this Software to compile your source code and - portions of this Software are embedded into the binary product as a result, - you may redistribute such product without providing attribution as would - otherwise be required by Sections 4(a), 4(b) and 4(d) of the License. - - Title - SwiftProtobuf - Type - PSGroupSpecifier - - - FooterText - Generated by CocoaPods - https://cocoapods.org - Title - - Type - PSGroupSpecifier - - - StringsTable - Acknowledgements - Title - Acknowledgements - - diff --git a/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-SessionUIKit-settings-metadata.plist b/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-SessionUIKit-settings-metadata.plist deleted file mode 100644 index 38d5f8fa7..000000000 --- a/Session/Meta/Settings2.bundle/Pods-GlobalDependencies-SessionUIKit-settings-metadata.plist +++ /dev/null @@ -1,462 +0,0 @@ - - - - - PreferenceSpecifiers - - - FooterText - This application makes use of the following third party libraries: - Title - Acknowledgements - Type - PSGroupSpecifier - - - FooterText - BSD 3-Clause License - -Copyright (c) 2010-2021, Deusty, LLC -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -3. Neither the name of Deusty nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Deusty, LLC. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - CocoaLumberjack - Type - PSGroupSpecifier - - - FooterText - Copyright (C) 2014-2017 Marcin Krzyżanowski <marcin.krzyzanowski@gmail.com> -This software is provided 'as-is', without any express or implied warranty. - -In no event will the authors be held liable for any damages arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: - -- The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. -- Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. -- This notice may not be removed or altered from any source or binary distribution. -- Redistributions of any form whatsoever must retain the following acknowledgment: 'This product includes software developed by the "Marcin Krzyzanowski" (http://krzyzanowskim.com/).' - - Title - CryptoSwift - Type - PSGroupSpecifier - - - FooterText - Copyright (C) 2015-2020 Gwendal Roué - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - GRDB.swift - Type - PSGroupSpecifier - - - FooterText - Copyright 2016-present, Max Howell; mxcl@me.com - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Title - PromiseKit - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2008, ZETETIC LLC -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the ZETETIC LLC nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY -EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - SQLCipher - Type - PSGroupSpecifier - - - FooterText - - Copyright 2012 Square Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - Title - SocketRocket - Type - PSGroupSpecifier - - - FooterText - ISC License - -Copyright (c) 2014-2020, Frank Denis <j at pureftpd dot org> - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - - Title - Sodium - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2011, The WebRTC project authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Google nor the names of its contributors may - be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - Title - WebRTC-lib - Type - PSGroupSpecifier - - - FooterText - Software License Agreement (BSD License) - -Copyright (c) 2013, yap.TV Inc. -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Neither the name of yap.TV nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of yap.TV Inc. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - Title - YapDatabase - Type - PSGroupSpecifier - - - FooterText - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - Title - DifferenceKit - Type - PSGroupSpecifier - - - FooterText - Generated by CocoaPods - https://cocoapods.org - Title - - Type - PSGroupSpecifier - - - StringsTable - Acknowledgements - Title - Acknowledgements - - diff --git a/Session/Meta/Settings2.bundle/Root.plist b/Session/Meta/Settings2.bundle/Root.plist deleted file mode 100644 index e7769b22a..000000000 --- a/Session/Meta/Settings2.bundle/Root.plist +++ /dev/null @@ -1,19 +0,0 @@ - - - - - StringsTable - Root - PreferenceSpecifiers - - - Type - Child Pane - Title - Acknowledgements - Filename - Acknowledgements.plist - - - - diff --git a/Session/Meta/Settings2.bundle/en.lproj/Acknowledgements.strings b/Session/Meta/Settings2.bundle/en.lproj/Acknowledgements.strings deleted file mode 100644 index 61fa63855..000000000 --- a/Session/Meta/Settings2.bundle/en.lproj/Acknowledgements.strings +++ /dev/null @@ -1 +0,0 @@ -"25519" = "Curve25519-donna by Adam Langley https://github.com/agl/curve25519-donna"; "255192" = "GNU GENERAL PUBLIC LICENSE Version 2, June 1991"; "255193" = " Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed."; "255194" = " Preamble"; "255195" = " The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too."; "255196" = " When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things."; "255197" = " To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it."; "255198" = " For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights."; "255199" = " We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software."; "2551910" = " Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations."; "2551911" = " Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all."; "2551912" = " The precise terms and conditions for copying, distribution and modification follow."; "2551913" = " GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION"; "2551914" = " 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The 'Program', below, refers to any such program or work, and a 'work based on the Program' means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term 'modification'.) Each licensee is addressed as 'you'."; "2551915" = "Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does."; "2551916" = " 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program."; "2551917" = "You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee."; "2551918" = " 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions:"; "2551919" = " a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change."; "2551920" = " b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License."; "2551921" = " c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.)"; "2551922" = "These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it."; "2551923" = "Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program."; "2551924" = "In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License."; "2551925" = " 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following:"; "2551926" = " a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,"; "2551927" = " b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,"; "2551928" = " c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.)"; "2551929" = "The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable."; "2551930" = "If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code."; "2551931" = " 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance."; "2551932" = " 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it."; "2551933" = " 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License."; "2551934" = " 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program."; "2551935" = "If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances."; "2551936" = "It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice."; "2551937" = "This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License."; "2551938" = " 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License."; "2551939" = " 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns."; "2551940" = "Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and 'any later version', you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation."; "2551941" = " 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally."; "2551942" = " NO WARRANTY"; "2551943" = " 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM 'AS IS' WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION."; "2551944" = " 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES."; "2551945" = " END OF TERMS AND CONDITIONS"; "2551946" = " How to Apply These Terms to Your New Programs"; "2551947" = " If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms."; "2551948" = " To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the 'copyright' line and a pointer to where the full notice is found."; "2551949" = " {description} Copyright (C) {year} {fullname}"; "2551950" = " This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version."; "2551951" = " This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details."; "2551952" = " You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA."; "2551953" = "Also add information on how to contact you by electronic and paper mail."; "2551954" = "If the program is interactive, make it output a short notice like this when it starts in an interactive mode:"; "2551955" = " Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details."; "2551956" = "The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program."; "2551957" = "You should also get your employer (if you work as a programmer) or your school, if any, to sign a 'copyright disclaimer' for the program, if necessary. Here is a sample; alter the names:"; "2551958" = " Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker."; "2551959" = " {signature of Ty Coon}, 1 April 1989 Ty Coon, President of Vice"; "2551960" = "This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License."; "AFNetworking" = "AFNetworking https://github.com/AFNetworking/AFNetworking"; "AFNetworking2" = "Copyright (c) 2013-2015 AFNetworking (http://afnetworking.com/)"; "AFNetworking3" = "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:"; "AFNetworking4" = "The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software."; "AFNetworking5" = "THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. "; "APDropDownNavToolbar" = "APDropDownNavToolbar https://github.com/ankurp/APDropDownNavToolbar"; "APDropDownNavToolbar2" = "The MIT License (MIT)"; "APDropDownNavToolbar3" = "Copyright (c) 2013 Ankur Patel"; "APDropDownNavToolbar4" = "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:"; "APDropDownNavToolbar5" = "The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software."; "APDropDownNavToolbar6" = "THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. "; "AxolotlKit" = "AxolotlKit https://github.com/WhisperSystems/AxolotlKit"; "AxolotlKit2" = "GNU GENERAL PUBLIC LICENSE Version 2, June 1991"; "AxolotlKit3" = " Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed."; "AxolotlKit4" = " Preamble"; "AxolotlKit5" = " The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too."; "AxolotlKit6" = " When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things."; "AxolotlKit7" = " To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it."; "AxolotlKit8" = " For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights."; "AxolotlKit9" = " We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software."; "AxolotlKit10" = " Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations."; "AxolotlKit11" = " Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all."; "AxolotlKit12" = " The precise terms and conditions for copying, distribution and modification follow."; "AxolotlKit13" = " GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION"; "AxolotlKit14" = " 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The 'Program', below, refers to any such program or work, and a 'work based on the Program' means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term 'modification'.) Each licensee is addressed as 'you'."; "AxolotlKit15" = "Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does."; "AxolotlKit16" = " 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program."; "AxolotlKit17" = "You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee."; "AxolotlKit18" = " 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions:"; "AxolotlKit19" = " a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change."; "AxolotlKit20" = " b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License."; "AxolotlKit21" = " c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.)"; "AxolotlKit22" = "These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it."; "AxolotlKit23" = "Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program."; "AxolotlKit24" = "In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License."; "AxolotlKit25" = " 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following:"; "AxolotlKit26" = " a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,"; "AxolotlKit27" = " b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,"; "AxolotlKit28" = " c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.)"; "AxolotlKit29" = "The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable."; "AxolotlKit30" = "If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code."; "AxolotlKit31" = " 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance."; "AxolotlKit32" = " 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it."; "AxolotlKit33" = " 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License."; "AxolotlKit34" = " 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program."; "AxolotlKit35" = "If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances."; "AxolotlKit36" = "It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice."; "AxolotlKit37" = "This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License."; "AxolotlKit38" = " 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License."; "AxolotlKit39" = " 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns."; "AxolotlKit40" = "Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and 'any later version', you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation."; "AxolotlKit41" = " 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally."; "AxolotlKit42" = " NO WARRANTY"; "AxolotlKit43" = " 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM 'AS IS' WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION."; "AxolotlKit44" = " 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES."; "AxolotlKit45" = " END OF TERMS AND CONDITIONS"; "AxolotlKit46" = " How to Apply These Terms to Your New Programs"; "AxolotlKit47" = " If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms."; "AxolotlKit48" = " To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the 'copyright' line and a pointer to where the full notice is found."; "AxolotlKit49" = " {description} Copyright (C) {year} {fullname}"; "AxolotlKit50" = " This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version."; "AxolotlKit51" = " This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details."; "AxolotlKit52" = " You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA."; "AxolotlKit53" = "Also add information on how to contact you by electronic and paper mail."; "AxolotlKit54" = "If the program is interactive, make it output a short notice like this when it starts in an interactive mode:"; "AxolotlKit55" = " Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details."; "AxolotlKit56" = "The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program."; "AxolotlKit57" = "You should also get your employer (if you work as a programmer) or your school, if any, to sign a 'copyright disclaimer' for the program, if necessary. Here is a sample; alter the names:"; "AxolotlKit58" = " Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker."; "AxolotlKit59" = " {signature of Ty Coon}, 1 April 1989 Ty Coon, President of Vice"; "AxolotlKit60" = "This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License."; "CocoaLumberjack" = "CocoaLumberjack https://github.com/CocoaLumberjack/CocoaLumberjack"; "CocoaLumberjack2" = "Software License Agreement (BSD License)"; "CocoaLumberjack3" = "Copyright (c) 2010, Deusty, LLC All rights reserved."; "CocoaLumberjack4" = "Redistribution and use of this software in source and binary forms, with or without modification, are permitted provided that the following conditions are met:"; "CocoaLumberjack5" = "* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer."; "CocoaLumberjack6" = "* Neither the name of Deusty nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Deusty, LLC."; "CocoaLumberjack7" = "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."; "DJWActionSheet" = "DJWActionSheet by DanWilliams64 https://github.com/danwilliams64/DJWActionSheet"; "DJWActionSheet2" = "The MIT License (MIT)"; "DJWActionSheet3" = "Copyright (c) 2014 Dan Williams"; "DJWActionSheet4" = "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:"; "DJWActionSheet5" = "The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software."; "DJWActionSheet6" = "THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. "; "FFCircularProgressView" = "FFCircularProgressView https://github.com/elbryan/FFCircularProgressView"; "FFCircularProgressView2" = "Copyright (c) 2013 Fabiano Francesconi"; "FFCircularProgressView3" = "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:"; "FFCircularProgressView4" = "The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software."; "FFCircularProgressView5" = "THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."; "JSQMessagesViewController" = "JSQMessagesViewController https://github.com/jessesquires/JSQMessagesViewController"; "JSQMessagesViewController2" = "MIT License Copyright (c) 2014 Jesse Squires"; "JSQMessagesViewController3" = "http://www.hexedbits.com"; "JSQMessagesViewController4" = "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:"; "JSQMessagesViewController5" = "The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software."; "JSQMessagesViewController6" = "THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. "; "Mantle" = "Mantle https://github.com/Mantle/Mantle"; "Mantle2" = "**Copyright (c) 2012 - 2014, GitHub, Inc.** **All rights reserved.**"; "Mantle3" = "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:"; "Mantle4" = "The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software."; "Mantle5" = "THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."; "Mantle6" = "---"; "Mantle7" = "**This project uses portions of code from the Proton framework.** **Proton is copyright (c) 2012, Bitswift, Inc.** **All rights reserved.**"; "Mantle8" = "Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:"; "Mantle9" = " * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Neither the name of the Bitswift, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission."; "Mantle10" = "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. "; "OpenSSL" = "OpenSSL https://www.openssl.org/"; "OpenSSL2" = " LICENSE ISSUES =============="; "OpenSSL3" = " The OpenSSL toolkit stays under a dual license, i.e. both the conditions of the OpenSSL License and the original SSLeay license apply to the toolkit. See below for the actual license texts. Actually both licenses are BSD-style Open Source licenses. In case of any license issues related to OpenSSL please contact openssl-core@openssl.org."; "OpenSSL4" = " OpenSSL License ---------------"; "OpenSSL5" = "/* ==================================================================== * Copyright (c) 1998-2011 The OpenSSL Project. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. All advertising materials mentioning features or use of this * software must display the following acknowledgment: * 'This product includes software developed by the OpenSSL Project * for use in the OpenSSL Toolkit. (http://www.openssl.org/)' * * 4. The names 'OpenSSL Toolkit' and 'OpenSSL Project' must not be used to * endorse or promote products derived from this software without * prior written permission. For written permission, please contact * openssl-core@openssl.org. * * 5. Products derived from this software may not be called 'OpenSSL' * nor may 'OpenSSL' appear in their names without prior written * permission of the OpenSSL Project. * * 6. Redistributions of any form whatsoever must retain the following * acknowledgment: * 'This product includes software developed by the OpenSSL Project * for use in the OpenSSL Toolkit (http://www.openssl.org/)' * * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * ==================================================================== * * This product includes cryptographic software written by Eric Young * (eay@cryptsoft.com). This product includes software written by Tim * Hudson (tjh@cryptsoft.com). * */"; "OpenSSL6" = " Original SSLeay License -----------------------"; "OpenSSL7" = "/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) * All rights reserved. * * This package is an SSL implementation written * by Eric Young (eay@cryptsoft.com). * The implementation was written so as to conform with Netscapes SSL. * * This library is free for commercial and non-commercial use as long as * the following conditions are aheared to. The following conditions * apply to all code found in this distribution, be it the RC4, RSA, * lhash, DES, etc., code; not just the SSL code. The SSL documentation * included with this distribution is covered by the same copyright terms * except that the holder is Tim Hudson (tjh@cryptsoft.com). * * Copyright remains Eric Young's, and as such any Copyright notices in * the code are not to be removed. * If this package is used in a product, Eric Young should be given attribution * as the author of the parts of the library used. * This can be in the form of a textual message at program startup or * in documentation (online or textual) provided with the package. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * 'This product includes cryptographic software written by * Eric Young (eay@cryptsoft.com)' * The word 'cryptographic' can be left out if the rouines from the library * being used are not cryptographic related :-). * 4. If you include any Windows specific code (or a derivative thereof) from * the apps directory (application code) you must include an acknowledgement: * 'This product includes software written by Tim Hudson (tjh@cryptsoft.com)' * * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * The licence and distribution terms for any publically available version or * derivative of this code cannot be changed. i.e. this code cannot simply be * copied and put under another distribution licence * [including the GNU Public Licence.] */"; "SQLCipher" = "SQLCipher"; "SQLCipher2" = "Copyright (c) 2008, ZETETIC LLC All rights reserved."; "SQLCipher3" = "Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the ZETETIC LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission."; "SQLCipher4" = "THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. "; "SSKeychain" = "SSKeyChain"; "SSKeychain2" = "Copyright (c) 2010-2014 Sam Soffes, http://soff.es"; "SSKeychain3" = "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:"; "SSKeychain4" = "The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software."; "SSKeychain5" = "THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. "; "SocketRocket" = "SocketRocket https://github.com/square/SocketRocket"; "SocketRocket2" = " Copyright 2012 Square Inc."; "SocketRocket3" = " Licensed under the Apache License, Version 2.0 (the 'License'); you may not use this file except in compliance with the License. You may obtain a copy of the License at"; "SocketRocket4" = " http://www.apache.org/licenses/LICENSE-2.0"; "SocketRocket5" = " Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License."; "YapDatabase" = "YapDatabase https://github.com/yapstudios/YapDatabase"; "YapDatabase2" = "Software License Agreement (BSD License)"; "YapDatabase3" = "Copyright (c) 2013, yap.TV Inc. All rights reserved."; "YapDatabase4" = "Redistribution and use of this software in source and binary forms, with or without modification, are permitted provided that the following conditions are met:"; "YapDatabase5" = "* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer."; "YapDatabase6" = "* Neither the name of yap.TV nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of yap.TV Inc."; "YapDatabase7" = "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."; "libPhoneNumber-iOS" = "libPhoneNumber https://github.com/iziz/libPhoneNumber-iOS"; "libPhoneNumber-iOS2" = " Apache License Version 2.0, January 2004 http://www.apache.org/licenses/"; "libPhoneNumber-iOS3" = " TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION"; "libPhoneNumber-iOS4" = " 1. Definitions."; "libPhoneNumber-iOS5" = " 'License' shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document."; "libPhoneNumber-iOS6" = " 'Licensor' shall mean the copyright owner or entity authorized by the copyright owner that is granting the License."; "libPhoneNumber-iOS7" = " 'Legal Entity' shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, 'control' means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity."; "libPhoneNumber-iOS8" = " 'You' (or 'Your') shall mean an individual or Legal Entity exercising permissions granted by this License."; "libPhoneNumber-iOS9" = " 'Source' form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files."; "libPhoneNumber-iOS10" = " 'Object' form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types."; "libPhoneNumber-iOS11" = " 'Work' shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below)."; "libPhoneNumber-iOS12" = " 'Derivative Works' shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof."; "libPhoneNumber-iOS13" = " 'Contribution' shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, 'submitted' means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as 'Not a Contribution.'"; "libPhoneNumber-iOS14" = " 'Contributor' shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work."; "libPhoneNumber-iOS15" = " 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form."; "libPhoneNumber-iOS16" = " 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed."; "libPhoneNumber-iOS17" = " 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:"; "libPhoneNumber-iOS18" = " (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and"; "libPhoneNumber-iOS19" = " (b) You must cause any modified files to carry prominent notices stating that You changed the files; and"; "libPhoneNumber-iOS20" = " (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and"; "libPhoneNumber-iOS21" = " (d) If the Work includes a 'NOTICE' text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License."; "libPhoneNumber-iOS22" = " You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License."; "libPhoneNumber-iOS23" = " 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions."; "libPhoneNumber-iOS24" = " 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file."; "libPhoneNumber-iOS25" = " 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License."; "libPhoneNumber-iOS26" = " 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages."; "libPhoneNumber-iOS27" = " 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability."; "libPhoneNumber-iOS28" = " END OF TERMS AND CONDITIONS "; \ No newline at end of file diff --git a/Session/Meta/Settings2.bundle/en.lproj/Root.strings b/Session/Meta/Settings2.bundle/en.lproj/Root.strings deleted file mode 100644 index 8cd87b9d6b20c1fbf87bd4db3db267fca5ad4df9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 546 zcmaixOHRW;5JYRuDMndFh#Ua1V1d}N;sVAV2TO?uC3a9aJn*VxFrY}tnon0(S66#J z-d9>G>6W!ur(SDqlp`9nn~*(m%iWnv?yq`Qfp6XbK1?+om~~#r)ZnhkYQU_VbfjuT zHNn`CX<0sd*m1A}>&5sU$akD=GTXJ1e From 893967e380a45e893d46c2fd5a1c0f7de565fce7 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 9 Dec 2022 13:19:14 +1100 Subject: [PATCH 019/135] Updated to the latest config lib and added it's unit tests --- Session.xcodeproj/project.pbxproj | 4 + .../Database/Models/SharedConfigDump.swift | 2 + .../LibSessionUtil/SessionUtil.swift | 15 +- .../ios-arm64/libsession-util.a | Bin 863920 -> 934024 bytes .../libsession-util.a | Bin 1947080 -> 2091280 bytes .../module.modulemap | 2 + .../session/config/base.hpp | 21 +- .../session/config/contacts.h | 147 ++++++++ .../session/config/contacts.hpp | 187 ++++++++++ .../session/config/namespaces.hpp | 1 + .../session/config/profile_pic.h | 21 ++ .../session/config/profile_pic.hpp | 26 ++ .../session/config/user_profile.h | 11 +- .../session/config/user_profile.hpp | 24 +- .../SharedConfigMessage.swift | 5 + .../Protos/Generated/SNProto.swift | 3 + .../Protos/Generated/SessionProtos.pb.swift | 4 + .../Protos/SessionProtos.proto | 1 + .../LibSessionUtil/ConfigContactsSpec.swift | 324 ++++++++++++++++++ .../ConfigUserProfileSpec.swift | 6 +- .../General/Array+Utilities.swift | 8 + 21 files changed, 779 insertions(+), 33 deletions(-) create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.h create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.hpp create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/profile_pic.h create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/profile_pic.hpp create mode 100644 SessionMessagingKitTests/LibSessionUtil/ConfigContactsSpec.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 638c753ea..317003eb6 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -590,6 +590,7 @@ FD2AAAF028ED57B500A49611 /* SynchronousStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2AAAEF28ED57B500A49611 /* SynchronousStorage.swift */; }; FD2AAAF128ED57B500A49611 /* SynchronousStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2AAAEF28ED57B500A49611 /* SynchronousStorage.swift */; }; FD2AAAF228ED57B500A49611 /* SynchronousStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2AAAEF28ED57B500A49611 /* SynchronousStorage.swift */; }; + FD2B4AFB29429D1000AB4848 /* ConfigContactsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2B4AFA29429D1000AB4848 /* ConfigContactsSpec.swift */; }; FD37E9C328A1C6F3003AE748 /* ThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9C228A1C6F3003AE748 /* ThemeManager.swift */; }; FD37E9C628A1D4EC003AE748 /* Theme+ClassicDark.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9C528A1D4EC003AE748 /* Theme+ClassicDark.swift */; }; FD37E9C828A1D73F003AE748 /* Theme+Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9C728A1D73F003AE748 /* Theme+Colors.swift */; }; @@ -1705,6 +1706,7 @@ FD245C612850664300B966DD /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; FD28A4F527EAD44C00FF65E7 /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = ""; }; FD2AAAEF28ED57B500A49611 /* SynchronousStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SynchronousStorage.swift; sourceTree = ""; }; + FD2B4AFA29429D1000AB4848 /* ConfigContactsSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigContactsSpec.swift; sourceTree = ""; }; FD37E9C228A1C6F3003AE748 /* ThemeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = ""; }; FD37E9C528A1D4EC003AE748 /* Theme+ClassicDark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+ClassicDark.swift"; sourceTree = ""; }; FD37E9C728A1D73F003AE748 /* Theme+Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+Colors.swift"; sourceTree = ""; }; @@ -4014,6 +4016,7 @@ FD8ECF802934385900C0D1BB /* LibSessionUtil */ = { isa = PBXGroup; children = ( + FD2B4AFA29429D1000AB4848 /* ConfigContactsSpec.swift */, FD8ECF812934387A00C0D1BB /* ConfigUserProfileSpec.swift */, ); path = LibSessionUtil; @@ -6007,6 +6010,7 @@ FD078E5227E1760A000769AF /* OGMDependencyExtensions.swift in Sources */, FD859EFC27C2F60700510D0C /* MockEd25519.swift in Sources */, FDC290A627D860CE005DAE71 /* Mock.swift in Sources */, + FD2B4AFB29429D1000AB4848 /* ConfigContactsSpec.swift in Sources */, FD83B9C027CF2294005E1583 /* TestConstants.swift in Sources */, FD3C906F27E43E8700CD579F /* MockBox.swift in Sources */, FDC4389A27BA002500C60D73 /* OpenGroupAPISpec.swift in Sources */, diff --git a/SessionMessagingKit/Database/Models/SharedConfigDump.swift b/SessionMessagingKit/Database/Models/SharedConfigDump.swift index c5278a68f..ab22c481f 100644 --- a/SessionMessagingKit/Database/Models/SharedConfigDump.swift +++ b/SessionMessagingKit/Database/Models/SharedConfigDump.swift @@ -15,6 +15,7 @@ public struct ConfigDump: Codable, Equatable, Hashable, Identifiable, FetchableR public enum Variant: String, Codable, DatabaseValueConvertible, CaseIterable { case userProfile + case contacts } public var id: Variant { variant } @@ -32,6 +33,7 @@ public extension ConfigDump.Variant { var configMessageKind: SharedConfigMessage.Kind { switch self { case .userProfile: return .userProfile + case .contacts: return .contacts } } } diff --git a/SessionMessagingKit/LibSessionUtil/SessionUtil.swift b/SessionMessagingKit/LibSessionUtil/SessionUtil.swift index 32c29557d..7f295e39a 100644 --- a/SessionMessagingKit/LibSessionUtil/SessionUtil.swift +++ b/SessionMessagingKit/LibSessionUtil/SessionUtil.swift @@ -24,6 +24,7 @@ import SessionUtilitiesKit // MARK: - Configs private static var userProfileConfig: Atomic?> = Atomic(nil) + private static var contactsConfig: Atomic?> = Atomic(nil) // MARK: - Variables @@ -32,6 +33,9 @@ import SessionUtilitiesKit switch variant { case .userProfile: return (userProfileConfig.wrappedValue.map { config_needs_push($0) } ?? false) + + case .contacts: + return (contactsConfig.wrappedValue.map { config_needs_push($0) } ?? false) } } } @@ -40,6 +44,7 @@ import SessionUtilitiesKit private static func config(for variant: ConfigDump.Variant) -> Atomic?> { switch variant { case .userProfile: return SessionUtil.userProfileConfig + case .contacts: return SessionUtil.contactsConfig } } @@ -49,6 +54,7 @@ import SessionUtilitiesKit guard let secretKey: [UInt8] = ed25519SecretKey else { return } SessionUtil.userProfileConfig.mutate { $0 = loadState(for: .userProfile, secretKey: secretKey) } + SessionUtil.contactsConfig.mutate { $0 = loadState(for: .contacts, secretKey: secretKey) } } private static func loadState( @@ -73,17 +79,17 @@ import SessionUtilitiesKit // Setup initial variables (including getting the memory address for any cached data) var conf: UnsafeMutablePointer? = nil let error: UnsafeMutablePointer? = nil - let cachedDump: (data: UnsafePointer, length: Int)? = cachedData?.withUnsafeBytes { unsafeBytes in + let cachedDump: (data: UnsafePointer, length: Int)? = cachedData?.withUnsafeBytes { unsafeBytes in return unsafeBytes.baseAddress.map { ( - $0.assumingMemoryBound(to: CChar.self), + $0.assumingMemoryBound(to: UInt8.self), unsafeBytes.count ) } } // No need to deallocate the `cachedDump.data` as it'll automatically be cleaned up by - // the `cachedData` lifecycle, but need to deallocate the `error` if it gets set + // the `cachedDump` lifecycle, but need to deallocate the `error` if it gets set defer { error?.deallocate() } @@ -94,6 +100,9 @@ import SessionUtilitiesKit switch variant { case .userProfile: return user_profile_init(&conf, &secretKey, cachedDump?.data, (cachedDump?.length ?? 0), error) + + case .contacts: + return contacts_init(&conf, &secretKey, cachedDump?.data, (cachedDump?.length ?? 0), error) } }() diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64/libsession-util.a b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64/libsession-util.a index 5e58f5b33459945aa3200c0bec0d318957eb4ba5..3f991bb6aadfa330a62538ad0adf8abfb0b9eb53 100644 GIT binary patch delta 80499 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 delta 41875 zcmZUc4_uz}{{OG*?ymb@-ED1a+dXUBy~%{4ZK2U^A=4&=5Xu@MWZHy~-HmLqaL?MV zW{oznHX%eI=i~@+=ExC3$kF$l5GUlEj?>}ydSCC?J=yQ_(DQn|-|x@o^Zxv~uKT+G zT-U1BFKPZXB^Wnv@$rk79Gl|v9h-c-&*wYt*kczT>sy$dd|V1_$EPewN!{$9708`$ z7(W??;XceXcACm)hccdo+xCype=cM5YR5|D>mKDttY;GA{;@cq zjwe{)D^`5(EVX+MRbETpnxguj(aJwE?seLaK346$XezvmKDkesUJ8)^+=ZTma~+vo~@jDj?IAa1Xh#G`RnH? z|D2=TdcLyY0_Eu!Dlf`aUVV}B-WcVRla-@+%40b9D>(v=i`9M``J+8`0n=C(r;aOU zD}P}Rwo<>t0+$`4{?q0tf1>><>WH~&_g$iFV7>oztG<-}cc!QwoC*fe^Ex^{Vu6Wl z*qo+;uhO4H{a5zzY_5@8$cM?-$n}S5`NzrkN#_E!KLrM4=(ilMh8M{z5>($$-f)EK z2gz5+L2}ZO>R&=W!2~bi(QyZLGIbtx;gLB0C4qZc@OknJ(sPtnoJtmw_2j!`N}|SH zL0aVUB(*n?_aCkLci}e2q zXF%!T&M4(>9=1nKS6vt2&Y#JfC0Qm+vsSL-zByeY$25i1Xx$9Qtx8#!BR6j_5!~~u#SNpG||8~{s z>y+zocuW23DwKa&uQcvdF0Ew1mb=t&FMD*w2GvO$l_^!qlkQfYagXv(seQL#mt zV=4Dg2MTG}xJ?}w(eYR6G;(;W`YkeroWuARF>=z-yKHa{>m6nJ4C*ql=*bAfc+4=o z&qM@_jWA%xc#Rw)r`D={DS0jV7J0;W_1{5$L0?L+)u`pN!GH|CpN7N981iEd<}UbIgR`~8|)z0v4V5S7n%&C?LwEa z_X*`~vyNqhGoV=dAh+Ix4lV0+lZCd_s z9b!Kg#x z!e?K=@h>~?)9`Qd1O!%Xo?xs2%eGE1vdIU?r^&jPa3LGvsUITe)BX(giPX8|EdlO4 zNIpmYKra0wK6+`MU|c~?BY*gV+TSGW$=k^D$z#Z9vKr-cpPyj-3=Y0K!3aFgo!#V% z4Mtv%|fQ%sj#(LhT{uA}XWCeK{nIR0I;+tQ?;gud~{0jwzvDAym z^T^284I_A3r12B9@FTK=+(*`svl+h_^qmoD{7imAz77V&@gxm3yZI`_NzckYL6cq?xB5x#f$$8`rZ))7xGLL+O43KYwxl^Nzjt_AB%T5D!g!hsq+5 zP4*#9^uNiyy?8B&k2209=aN5uh~r-zZ=#@ZCwU#Yf?Ur6|NaQq1xbh}x05SAQTy@K zMP$pzYTrm+LjIjR=3^ZHQlXOtK0rgFXRyLs$q9WpFPsu(ybLXTpByG1Cl5p1=&~r| zbg~#6Tpnf2{1lhrXGa-7kzbH+2DtMKSxc6a1>|z_c+yG!fCi-EUh)rQBe|8VB1bQKe*+V4jxwsrx5>xJ=l_PGUms=6`%?J}wef{&Kke_+zKHe}Pv!Z6OkMS5^l9lAT@3Cr+_ZXLuvE(b{Lu3j$I;`<;lY7Z~$RcvN&49-^nuckl>j$mi za59rzMGml`ML*(Y_e_t`NZv_a7Sf0p|1RW6gR4EpA^*aOrPO0A{EyN}zCWtE zo-89*kh934e{0-xWF>hXc?kI>+j-_+IR2%<^)zIXLr6&MUF1q6Qnk@z%={Tsv(;nV z3oU$te4qRmxt6>O@!nk?<8iWpOedZH#qnR;=rLxGDZlxzvYp&WUQ8yDAEE*&cLvHO zKj$%4lQp2P4I>Js{=s8>K!*KK^)j&bO^=a8J{rJI+h0A#<>X!DJ7m-^I4VBy7(YS_ zgV4>NdyK2dO!9E@S+MF$kMSLu_p9nuaw2&P8Q9Oj?J#tG<1tP(Orz>Q9^+ba2RXr1 z`vCbzsQ_`WQtu#dC-ccAzBEyQWU$teGwPef73!=C5e;#YS zbF%xCXIFpzK%w=^spj->`8`aEwdRG$8Li0~X6zv}tiLeI+q&vf^V;KSIQh_M@5ZmT zn)u&=|HE3p+UneKKArP?9&dQ^lGM}`>#=W~7f&60)3CRMFeS8g$u`qJy}tH0hXcDY zZ{uhSx1QW)E}9+kRZY;Ct{sLUF>b6|)7pHRIn|1>Oy88`-RiGb7`cyHKMy%in$k`C z+fH?iK59LEy*bBfMxkjt_o#7Kr1G_Wp!LPC(KD==w<4X}YE9ql^k>wUzzT|zrr8r&=9bqvoYLO{3YRUGTzF<}r;&O{}K{ zrctoLFrr|~)*6i0K>5D{|DV?S^mopy7Fs>!Q8Qb2%?m%&ZPn*GJXXxI@FJ_}5m&6$ zz0nb44JSJ?t-L~Wf9vp(;qF;hOtQz@8eA4WYnk;m-WT2@I|c^JHi*6)~}OX z8CF3p!e4TSEp45$GyKOe>+hbhx2%<(u#K$`?hb#<8HTyDE}0oNJFFW!x6KUm2U54A zFW3?>1hV0kO$IZ=7Wk7#zt>F*rh4u;e_6=S3G1Emdw&D{V{uwO6~O3n>I`dcWBAFF z`yW(i(Hv!>^=?zR*Sfnge8zkO%S*PNLzM;LhJjoUB$&O!`cq^0r4vRN*KaM~8$Q>{ z+#8^$SI;Y*^wMv{5U1p8-F8Vd0CPiJb);W72 z8z~#qn|18(yam6Pr3d7iIG5LbK)uq81gYArg%Rk>aavD|wo*u0&8|_#TI;3X$A{Yd z-iKPLMy<(`Zj4MEwpi{5Gy1HjrJKbE z3Qm4J+?zm!ix*iG2j;Y#!&k7xZqxACg^wtv%seQ(j^QozCu&>$l(E+4Ft;~>D)*qm zO)Q*sg0`je;V3x4TCq2L&V)jmot9aOejiYiTf)8bsOnL2{4vwcDm^D!U;A8hi%!&< z8-Ve>+|RIf`sL6Y?;oVUF-^-i9prcKRC|tJ>9bCr7VQmCHKMBVNyRZbZmC9>;20S1 z&!oTlWc7~$M~_F0mq?37_NWztlQXIJVMS0Zyn}6>8o-02>d;cW2@tr*hg&n5*nH?`U6TMK+@7 z2)$lwp~POM6*;w62h{-l$)uYSr_@56M5){d6;5Q~fkG|p z(P0@rAmw~)7FzqB44>=1A`~O({H8YF<@N^X%Q&e1Y}Q|RrIu}gfBbqWpx<4j{(*!1 z)$|Wtt$rW;lg6FJ8t6a5bFBtc9u&~YfMBuuD|o8C?sqasS#7P^hxM~soi%y}Z>G$) z&Ur3;u9<7C4`3Z#uQBn~Ir8#QqRMN(sD=~@$oY7H&SGowli}XL8VzkfDAE3UwR>(* z%1De~gQN5hm#RMtN5=S(OxUINv@&JkLG@?Se?w~GzxueMIQ(6QCVZH5m{cXvP-;P_x-BMtna-TE2=!}T{%{R zA)mYiS_Qr^_sIpw487wy%rF|5+3;qKtpE^mz~4!K$ph+7vnChgxUW&=v0lM^B~aC( z;P|pVEStSeqj8izkE{-;T`yz(Z_{HnNGq;1Ft3irZPr^;U2}hnoh&bbjHx{+$GF{E zf2O4@uvW<`qT*rKLh02P*Zq`PI#N<4Hae3jv#ee3M|<7Bb2@5jt!v_3a|5uxY#2$! z;C-f%6i+SDqC3!l_?y6X!^mn>`h(yj@E3qD!k(w}%Wt$!gFOwD@?NqDofcgK#$djL zC!<_IMobJs82qKwLJ26L7K*b<*3F4fSG>pUha!`Iq9JmHMZP*b<9UVbX>_y<~C}@B)p!@GL zjOkzpI1Q`-cYp);DlCj*rxQSffC=iNg1~Z<1Yp3aBdQZk8HeKbu!tF zQ<*fJ14>-qMr9%C1f`xryi*nb@LkGkGKCzfRC^j2j<{I)@u>8y?@n!?5tNFGL8(Z7 zWx5wV7`wx!zY!FB9yz*Rbt4&1?ypdL7TJw#P8H(jm83*EV!>lDR58l@FJpDD%rfsjn22`f|X4RFsV!Fh31UhE7%HdqJtt2}*^dxCWC7N0j-)pj0>r zN`+mZ)YqxZZw95lCS`s#C~@VW)LVKTj(@4R2nMNGezyJSg}?(0!n>@pwu^@%^W!Nqd?chFD^N~SeRehEW5*yp03>ZJ=C|Re`vK$f{KO zi$Q6)5R`_qMPo{mMa%Io@BG9udYR5qFDMOUgEB`kpzU9!mujP6JQ^6G7DD@bsfFV2 zp%#k28_P0iSRdpJHVklWd+%LiT1D?l!`k+ z%xzLT=maZ4SyQPXPKsG6N`K5HIR0fw4HzV|fs1wShA+|zhCms*4(fKY1(b>#z%5`c z?Ny*8P)O#G$)t}Q&ecn~!CV~wA0eXw7(jnN7-t$;eM)~f7=u|5Cc(a+S_tj$0?$W- z^M*#y8L21|rN`h9419&rZy%2Nluf>kcNi`^QR0+xnq8yaB zN@bvATmpW8g2hUI5hw{4D*Xju56b0%Wr)jF`m;f)*9Y1osr1K!0U7erbJbCz^v6(p zsJqWm|9+*v5&RMjm4No)Mt=eQao}h0_hjpoHiMr+=YaASFI$=K%f|TI8NmSh2(MHz2M;=`RDN!32TIRUKw0&%;5u*!D>M%r z03B$cU+M1zFNM7gG+}R1`kO&1R}V^uOF>Dn_%s~4w}X8dnqUuDjQYEk{uWRYYy_``y&<4G{(4Xf20_WN1e8;3HYih( z3CdKYfs%O&C~GEJ=}!b@%_J!O@t`bPA9yw5;*|baQ0nhL)i6GXKhT67S+zk>8ma=N zp)yb!DgmXTVx_+bl!gkG{sK@M$^)-LT&~ie4N60qpwyEN%1Eav{iz^EI*^s3JN{%) zP9%xol?Y5w`hB3C@hSKnKm6UGEY?a;5-tZN;bKr4Dg-6r0;N9>l!SAY{v1#e&ISwR z^*_trG5i^zG?WI~LkvpB$x44BC~G4@>5m5`VIO!o;^LJ4SWxQeIaw2~0wvK3P!cT! znk zLFumt12Qyq*a7`PP*%H8PBhijLOIb?f#;*50x%EE1?Ql`9Hl=MloL=aSO9yB((eZG z)*;KO^p7po6VUKdjK5?!0E6_PA6$vRKBd106n_^e{!Z{x_&b#THv03xzrpVXWvF97 z8S1f
    JpF0vW4N93dchXe);jlUkWM?~olg7%1j_J~kdtM*rc_K1Me^E6OSy{Srn zGI$YmqS7A^%Bj~2O2=XX*pW=!pvU(psW=8Sj2ku*J#b%S!)Hh|J_9f)=VN!8eqhATlCfeNL+9F!3#Q~FCm8G#ZI zZw0c7mHt9d8qNcyo*Ym*kgfD*fzp9Yr9T6d4y1!Qa{Q;+JBB|6l!^>cGHp9iGi?TC zgz7=BN>rp4WlW^BV9%R1j3=SA%y!S1I!=!DSe^a!`6&1ZIPIpwyQGN`2{| zOl8^jTFb#%>f$26swB$EvObzeUeV$Yf9w^MG~WSc+jh2=;=5h${uf zUkHjnhyG;nEBFT%>lE~X64wsK!X9YhP7^p4h6+#u;z$FO3i}r6nJbSRS*USCp!mB% zNhpu@v1IK~8z>zt1Z556fwBg&K}_+Sq)hBwj75~8%ufe5LZ=C>^LxYRUM*AUg_Hs6 zIY#RXg7whlptK)L?V%n#+Ato5y^E{`rFQa`4Zn zrxff4bEuO+>4ftLV~V}giPoey&ES33w2#9Nw?8i!K3q>weV}x*2b42nw=#b}C}+ej zWqv0pXT%N=%PFf}>2CpLZW}?Vryi7g>XiAlpwv^N%nu%ptNmQ-<&VQ>ERk|$u*=a| z0!sNJP|6o7^9w*JpQp^v1#_+FPr_%|!|R3I4tK0F-vdrXH3r$dKy@{l4gLkgl>oj4 z#(`KCSze{T`Y^*d9pwu^Su^S2e#Cjf<R@*`_TLQAPXU}zOo9G z{%lZspAJfR8Yqh)g-iscTpTEIPU@lgs(V2kLrGbnOiCIkqnoPCPX?tt9_)9qT|q1j z1M@VnUzy(xN(059eXRtJNZquL_zYt*{DYtj|A5lp3EpQVeail2LrVX$l>TH;K7=;F z7_euqe%9OsioF~Zdjak3b2P0sWqu(ja{x?e>7uZYLR}y^6YwEFj~^ zky%=Lymly_pw(rAQjG_c zYKq;;SWu2=4=864gL>4Zx*x=BWp%l5?eHb9 z9U5TV24@|(9))U^`88k#G@b(_%r6GB5LW<7k_n(B=L03V7*J+vC_>pqwvj8}K(ze4G61+`H6TMkNpb3i!@ zB-^ys?Fm~jStg=CO#3UpukNv44mIBfDa|*7A0h1~rN0r3Lo*FZe=R7@3-QK3tA<(# z?GJ*|d=V(kXV|okZe;Ug4sCuI?6Iyp(WS3ib&!8U4dtW{l+pKsGWu>%Mt{Imc91v@ zY;|6)yTIqb4tZd53wGLIcovk2dX6={{+ee z-HD2;z$3s4P#P`+e}TUQTnk+U{t8_HUJsoM8elfK2FwI+0Mo(iz|^n+-c_JLG7SF% z6TlKUeBf%(3z}dI=m6c|Ja7~vbO$Ia;z)21l!W@hYOoi)7wiTlt_{2jYzB+KM%se` z>?Ffb1s(!ckY%)&fD2(S0vCY=;CwI>i~!R?7nn-B53EB+yx_WKW56>&H@E^cz_XC}D3(Mo^f34l*5AZI?BLLv2%{Wc6T3kidK0@q>3Ihz z1>3;)QLq`r;W@Dpl=xckFfa(BmlLbNf5Be?{v9j>hrkkW6f6R35LY0)1UtFd`6nFN z;4qj8{sT-0e*#m%*@#O3@tQZ$2QC1;pd=UrZUo)np&($}t>kaQQ>~6?!eVi&k}=V$ z_$EBrN_aLb)?wi!@NC#238{Fg#Da)O1|>u;d@xKAUfN@5w+fyO^IE5=y0eC_LLr{x+PM`JM}lH7zUu+wd7F zc15^Wu`2?lof1&&1+?eVo=v;e?{_6z9nXb%w4s=jT;7;e4?71+;cefA=UTz%!`v}# zxJI$l2gPHRd>5WPy&sn*(r7Oz-fmC^sx@qSoJ7^rUrT?`_M`OhNiNC$+wbrzdlZq}j8w2|KF?t~aGu3zjp(C&AzEzFxNS1)312JL==(!N}@*m=+| z7b|wD=Hc*0E7|Lw7A9jCb6;et)%JZjhd%hC-J4Ip$Gs<(^}p}Kj{%Z?2tQngX5viu z4C}fdY}dWCtrOeZKj_#!DdK+`F#AWf{qj=SzSjO9!|yW#&alSSO{<$%yBwXXyH-2H z+OBWEzTa;*AHLsa`cw;mAD*LYpd6~9Cd5!@waAeE&jHxZC~ql1aGLh!R@HJ zp&Ea?ZrFc=%h7&A2maQS)|R>)%{R8(=yD9)G<>;v}dxZ__&Bpa$-FmZLSiRm13Om=EUBce=W}mQoz1btI zt2FB?!RAV{Mc7zrHVOAvn%%=O=c zFo%T$8_YprU6om11vXciEyBhsvq{)+x7m0%*nPLzBOJWP9J&YWzsDR9cHd+62wOIp zZJWft$s7=lR-0piYU~VGn* zW|Pe=X44kv_AO?Iuw{$cw#9UYRo0kQHD+}UOe5RO(QROxWwu*jMXgy`i;zG^t=U;? zcGZe)yIHv%tl4hX3ahu9L1FuLvqLz%-5lADv<9}D*mVd8?f4yL?GB0GVFra`51JLi z;7+qUUNw7P z1xH>rM}?Jdm{o6p-EWvZ!p<(Us|&1o)2w|HYi}4)vPDyK{)WSIVkM>#O(S6Z2H7(7LI&kjtaZ`%$`25tznRp^o7u z$A~cagrnvOuPL8>!EQu9eOClv*I78&z17f>ASW5?sd@ezIC7KWYq!VT^fEY*MLhO*ML*0 zTd2!#SO2e!Pn8CcPsK;9^uUDsIRe?cHGah%h{p&NP`BgdUFvIOeHTjk z05aan1RB`GX6jkg?bO$DM0%*t-hc{F{v&F+14#5`do_X8S3x_WA5k5Cr}~E&|IB*1 zfJcF77Wi1_#3-cRT%iS0s3TWH<1(TVZ}wyx2*tC)L}|b@oS{1lpOp?^L_?2^Sb@(d zWduTxjyQ9jT)-oNtJpw-^auq)kB`7xB)h>(Hh3mSz+!x^ODk-u(S#mGkEDF)5fd9Y zf?F8>KbGH0J!b=lY&#AAxC0d+p%p|&I*JuK&R}x)j^`o?J+|S3vk_q8P2dSyVb7VW3#iY{QXP7ZL-z{R zMf9JwTy-(^kxaOq3G6>z{hkx0eq{FySMj^-VIGHY8?~1`2tD0l3gg3{&;-|VksV9D ziBlSS+QYf5@2)2`{u_4Ke%b?k#tl4il}`Y@6`5i}x>@0K%-Eton?2l59VZSqoRx_o`=}ulbW@HCDI^JnD~S%8+ni6%D`MrWsd0tPNgJ2yOoM=!*v~Qyp`$`u|d(I*vMWrRoIg zW%;U8sPDK$b?6xwuW5$@M(A-D3#Vy7%PMW)%$cg&s1KW{x}Ey&YgLa>KM|(dgG6OJ zCQ7y3awOZwC#a64F11Gx^A=lg`;Cm*8c=e%>X&3lo%p2jQPs^J^~+6CvYjkLh&7Uc zk3eNRFJMfWiz3nz$x=70pU}R;J`(GyF>ro^0{EMUuG+>MYSLUb=J^bR~ zb5+M)p%wIItM*X`xkj?<)$gJ%puUi6rkeUhoQeaIVFL}{-+>CDJE`**XanUXhP|y{ zul|1e3o2BPQp>kMvSvaLb+}TF3e=ad%PwxT^RUaLj%H`UK>sn~569ec5nsw4H?Y2=Bmwy2@Ts(H?{fq?>Hp&$%&>=s$zBvN@%p??3tTx% zwLBs~wr{uybE)NKNr?|quSN2r8>tPh8F^-bY)AXm-%Q;aU`C-QHcX$R0ih>1Tzm&K zD!p@$R&ZN18^EW(vfUznEW+I9RcFZvA%PlRfPBjnSLiaoKs4e*k6a6ExLyr0T(w=R zq0!*l2CeWiIWI^7Ht^c5>c8nR^*Xob@;pVC0+AxpiHsyk0t|8fLBqlm5RrLa#xJ0L> zTKn7)a{@Vx`x$FX?ESn_>WWnzdVd@1TR-B|dZmG7j10AsIW5=54 z4?WH3)djXc%*e^GR!oRkFy$sBb8pnBWa<|=OdgzfH`Iv)_NP*DPW&V1I>X_@a!1L2phaT>8-CWh7XW$&YNOgI&#xKD< zNqu80Ro_Kjc%|xD$>I-m7>PKdbaTdz?adK}id$3mZmfY4KZft-LpaU{}Ne2mFoAf zvh4AgIMoGT4QR2(?ToP4!vH?O9;ibUfy|BTiXW$Owfm-wu9!L!u<$tT6g^Yru3#06Ogv)sISpILXbS{=ci#|3Aj3 zNCJqTxJTpVE`QNSJ*xUGX%IT)G1cG7ImH>MFur5JZWty-VCb1~ggqjdO7`#@yT=`d zk0WviN2G-QJJEoYPiKcNWPL@fZyncW9M0CVT_$TY98(v(&bntJrXuuw!J}r|_p%$I z2Mk^xr@HD`Ex0K`b?D)RF2;wRUij}h>UUzT$#xFoL(fp00NW&-D?-m9oT)ls3}dm% z7Rv^6sE=a>F^kkcLVxJ_h_4*36^0(PC_l)Q28!b2nLwVj|W#-ng;#nV5*bIHuf%^NQ3P+Fn@G`oPqPlT33?>$|ZL zSFLT`x-nv7q;*J-bETym?lb3X>))rlXIlUG%sI&#o*RLuoYqEsVvT*_jIrka%{kF>Z;x1H zZU37y*{VO)eT~(zJ>rE+Pt9I_{F0T)Mlp7dJFt@~mK8b67cR2*B-;m^Gk$ww?#$NR_qi@kp}~HpuJ=_}O&ATYyb$FLOF1st+W)$%aOxmlCuNf# z3&;ko&&Vys=}$IsIKY@w@H+|9enxIw$me9CIxJs_Nyx^V$1k$nvHFPI3a)A&T(UJD zYaQ}X#7WcYY2P7buyx7m{p~5ZR)K}*>z>FZeU%-8JF!yj=jhsx$Ze$0$0gg1*9u7` z9qrH4N91PL;!(O?Jj~>~McIb&8X|F-=iuf!d6L*vEBj3MJh?%<098pkUI{xNpY#e> z8io%?94?9E+j)D#1)Tl#GdTdu@PC42wYW8TyP4?PR_gG0S5_F`7vQ(!D-HAeS3rle z;@m@xigT^%Jr1uGw=6v0nzzd_)fyglJIxX6-hB~A`Tlr^mieQ@{C>S@%AP6=psI@_G#EzTSRZvqVS zcCMus>*}9f0W-z=>Sx#eW<%>O|8-qxMxB4d6>GECUVr7)#aFaW-w{4$S~b78O0DJ? zS9q&;iYvjilJ|tin5A1!cF#2%wytt7UFb=cT=9n0r7Q-J9n#hJ>S^3JXBCKKwtvGPxf| zwnCqE&urJ62?_D4J(m4U)dOn%4N=}zR5$>CSBIM?+#33;4)S~HkCW>OY$f=Ad=8=K zKvy58PYn(-jt_Tkpscc_C#NJHZdW3|=QXg@GE`yRu$9IHwY0-Oe$^MyUwEYbXjg|b z(ONMJV~*2`Y`OYK&?3rgm2JP5?xaxgW_gSf zAB%@P^3{-|PUW^v#xnTLx9~MQ`|?1fJk8QP#(MF7JSLjP7)u_k98zveo)R5^4`|N&&f(N(%TuTqPtqK`AhXVU+kmP~zndD~ay|CB7S!_!dy&o4_=%7L@oJ zP~xjW#0Qe{up@yvpaiCa5|{=`U+=nG>z!9((tQQ8bQ%6H4coe?v$x!+eL7bJd5|sWC99o5l9{_P}lhv>E z%Z*lImm8*}e6i9mH%>{M+&BgLv%y;tCs#F+NZ|tc&>K4`*bxrmP?nzbfMr`DJ-whL)T8tVL5V8_rF=m=#veQWG#HBDNCvM03w#=w z2}(ujWE?0JdX;`Bcp3aTbG1hvGJTHf(b>v8Q0mD6?*cPHT)!oy&&K#~$4(*)8DIj4 zY08RM`s2WDu*ZTAf*x=g{BEV+30@5?H(7vw`8rqX83i$GS@M<+^vmr&Qco^uCjiFa z)8@!*v3Z#INc_JtUJs_qpOJ0jW|JW?`%l$y>;2$LSgEG`zp!Bo@ ztOaqOkX>ILh?nN1Kp7pSbQFTJD09Gvz!XqcvD_E(FgSDwuGYa?@I0^*+yRz@*CMep zrN0!sACJi>QRZiYQr-(nIX8&J0!eZs5~e*#Za}gZ4Ja9RgNK40v^UY-0M3U!NP9W$ zrQkf+3u%`dm86H6q>uI(Qf@M;k>kJ9-oa`I&qRR?PkDN)LQ~dy?J1b47J>7Bt4G-rC=J;&IeOK8CtpFNQO2+>5m6xXno3j zxdW*TaUG-ARs2#rW62(S{vi2`J}*MCv%Qe~S8RLGh=6vbd5# zX*dC_1>-=eNA6BSZb|)<)!qk+eLpB2ZULx>O#ZD0nvN&=;3ycTP2V-eBKpBZq z95XT!BTD};C?heX%x?!Jp%zdYtOq5L3hG=?rZ^dt1Y%;4jwH}D$$mUZQWbawbS{|& z-U^)nO3z0pDqBEls0qaSO{xW@!AjcY&L^z1q$1ig$P{ukTH^-;*f|S14wQb8O!7Rk)PwXpYtwO|kJ?Vt>K6LkYwNtS{VU!ppYRD>Owi!AVNP(Bl25-etBQF4fX`E95g_zw+7)F#ErRB3)f)% z`A4yHI|7?QX`o4&-v~+ra?_VIP_OjM&0f-zYH&MP0ook^?G7mOOF+8=%KRb_2aSU;`-m z%FSW+0DzKLA?clt>2D<{GcA<|>T7_@zJ*qx}S2=UTZ=^sK^!;E3xEjP@+vPBa@)#of zwwqhP0_fYoTrdyJ1}_CO!IfY-2p{gcK{qbKPBIMVfC=Empbxwji~)n78!QF^G-%Cf z3!88CPj{DC!);+6N1b8)(iS$|S%(O#e;EH5S6jNv6DQJ#0}Z1S;RP|O5iy`0RzJfX zs|8}dmjW-~z82e&EVArmm&$GTMHX1Yxbwmz&H_{+wORG0W~lTJ;c<3L{cwn{7k{Ls zZqTl<`xF;reb2b!B-zzJgwN6JSVkt+&Ms8OccU4n=>%U2v&%gBQrL96qbMt!^JBPb z+_@3J51%KF;{LOtiNpBYG^u%#%TYh60e{CPRm9+T+c7;ch>01+-=Uac{OybBhkaz` z=uB8<4$s8?ywQ2spEo=YKR`aR=}7$8`pAYOaZ^CbSc=;*oHCMvyFOB@Q}OHX)GquT zSu(l=mJ_N^z&#rkCsdw*`wmWM#NXQVx^(;$JaZruKO|q?yBt4kUeUh-zgjVP z{H#Iz?LT|qY&4VIl#S}LyRvsJ!04j$q3u{qxctB&^pSk>VX8k|q!FLRLu17B6Mwu^ z?U4!&7_g`gg~nMV^jZI3#6M0)NrlisRybuIG#b2iu{JQ^Q~f0M0w!=G7OZSfNd1W4 zEE??_lMYvdJn%-g0cO~M15~zF7Wjk>zBgavr!aoEXmlX-`EF+Y;L!WFtC>=sV_!!r2-}ny`2z_4r0(I!~+3VO~=+mzbsQ?ug;FKlXCdP+8ukDi_ z!XNqoY%j})J}#R}f5*|eCEEpXh$Y9)>=%Jz5&XC>?`ys%&p^NV_@5 z9_m>7H->t=P~)Xp`PGg6P$e;yu?L~gM*lV0?tu~dY;-M7XXufUf}xb{JDkr%r&IqO z`%+=saq9no35Px>ohl>Xz?VT>L#0e85c(kW=2)#zo(v<~ty5KpJ{bLAI&?UGWXc|0 z!GuE}fzF~1edO7qLu_Pn5&uiqfRRGIQb#;sq|uNq6`&Wz)GtU6pcAMY=+C1bpw6ML zYJ{&gkYlqZ09f4L{Fp7?+UP>(&Y_eTR z9r{fCJ5(<5xpCH8XSx;y=*x~IpZW)8+0_{hUeO1>eFzx$qhT>rPSb#e)S>TxuC*(~2uG{`4OArYp>K5Z zsXNh8*&YBT!67zy2(^4XBikwtfPSHYC4MA(6vq)*i6cfTklzf*mS`9F?dz9|7$5ph z;wNf@@p8#535LEzn(op1LSI8%g^`o`3w@|u4zXDw!;ui!N-+`=5c<464m}qg`r@MC z5Y?e?F21sdw8IE}%kaib^@qMySSS97iUVtE#|+g4SW@Vn*I*Ce8RSeIu0Wv+nfp3gY(@-&s#p%zGP7&{>t-5P|bnu4eP7< z?zxWjbF9ci-AB*ghhuBJdl<*Yc>QjKwc=3soO3TdC~hm#8Xxx_(iyMw4$@y9RQ}P4 z){BR_Pda5j#%X*x_d)f_+vD;6(doa}-?^+K-FHx2#zFdT2kD>O)-~vtRU7Y) zng5yV_mys1@Oz!+wpNUW%`rE$eiiQ?h_H69#IGR|qMh+p{gDxF>)S~DmZKukJ<*z; z=x()cU*%fZI*{nbKRny^2iGCi-1LZWYtu3AE3NBKi8y6Ty)|n~OyBC(mC5d-Vz&4` zkN*Guz2b+KxzFDc_4za_djWnQ(tW!t$?90=n!RQ9=W(t0w~Q|ev+5s+m}w=x?~Jw5 zHoDxiQ~$r8xZqbW{3XmnE4#|I6kk#Yt6bBpS3klpI{tE+JHk3;xqGVhpVQn^%!QWu zC#0FY%zc=ZdXMV}%a`e%WYsToS6L5ji#U8s!57ii^{2ZxT6;FR7PS8BbobS!wYAz6 zgF27i9x=1^rseK-(~9^8*(^K5J;l244EMC(ej(y*z55LJOA)qVk%b?G%xEn**PWri zA+ZhK)>p1}zdDf-U%ZBYhIv<+`_%B?ig;Vib?!T(#~XHSa&H-Dn9-WO-Ca82w}=_7 z{kz?Tk>d>oPq_0!2D_DyTHT9Xj9BIH%xF#cqkCYIZCGr{KL@?>h{e19@m2uBdhg>o8w+ z&1g-Hj2iNeuQdJ8gCe{OqCEO9VlT4R{1cg;oF4V($t<$D1BN5cjXE#XecQ18!l)ZU z2D_Dn{HVUD@kQoc9knSm&~}kGOQNQR469Z~dt3i=bJQ6g7O@ShDx;p&slb>@#KM}W z18ZdSau_B*67_g!JnV>%UWf_{)iL?4=$WlQzZNw=G^S67N2B}uqpGzpi>=LjVCere zsxve%c0|WeR85SX2O~=U7d7M@Zzywk8fK3-r20G_?LLbX9OfC)W^IGF_3A{=wYui{aumCTRg9ZDz$&yHN4Z4 zq63CJ11rak^a}SN~3-!%5 zWG6)@hQ{1B)SetYqKk5|Wg8loN4MyiY_YX#+C*>bEi0pEg?bOe%+_bFi{2eF*v+mf zkG?$Ato;wle_s*(V`x0y-D=Kweq(g$lu)AiNObe|=>O`8Y_Wv^Z|mMY(U%;UCiyq% z=RF&Jc_^(v3;tx>OYB2ZtWWUeVB!?(^b diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64_x86_64-simulator/libsession-util.a b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64_x86_64-simulator/libsession-util.a index 8c1e89f238583fdbe03c3619b9ff30608ff576df..4ee48d353b230abc3f18e192c8943c10b7e75bec 100644 GIT binary patch delta 163347 zcmce<34Bav`~QE>WM(p1XCks7B(cU4O{}peB1}jKu|$<5h*Dc*A+0h|TSkPaa;s_^ zrK+kGEuvb)9!oW7EnPIaBDAqQs;c$>T<5+9^*rC_`}@Csuh;Lqn!eAq+}C~G>p5qV zIV!5B7oWBl`vv%H6gXUTLU_`N@Yxt4yrZj@wF(Js->yxYj%`9h+DCK@2?^=YzJ1&F zA(0Ug9on@E35jYK71?Ilo*vbd{Hm#nqh-cDLQu%BL6L&Xx3S=aVKV!;^(y#Z`D)$ zxr1WwAjMZ$UeEGSmW$de`vul}*H`in?dbVa z5sJQ%ib<^R%d#n0=`Z^#?nh5iLr2-!@VbXmtYP^uMw1*4u>9jFC0}J(=cnZG5XCaK z|7KS5VwOiUhqGTem+Q=W`)^*Vz8;@8#Pw)`z!~!D>*q( zaUttRYqaY`M^^YSkBv}{uQRilHf9L(-qXr&0|#zbN6BBZeiL&w>;GnsuB-ea>VbBQ zagkLiMvwyX2R@3wappy=-^-lGfg_k@!?P#?9`j44p{deuVJ>f`8IogeHACgD^9j4dh}5o%l<{o9WCt2aETS3EtMR_ z9Lp?Wo?^CZrTio_gBc&L^y`^RTPr!{DaAF+g}h&ExAVzoamwL?UW#wVDlYG-7$2iJ zBU;hFhvNQjij%r3{+K1iTWho%#1K}@V*bs1ahh_x!0fundO`64v)@c5 zZwBo&Ne;cJ6h5;Q2QUZCR&p^`3HcT0Gv_E)y`)%cp5nmyif=DaoUu@`M}cB*hhowq z#gTN;^_k(!PRxGH;mk=) zJ99DfP3DKp-kXK6?lXy$R}@FQuV^pXqE2d;D7I&ovwkeg%b9gHD8JlN#bV}^6-wU7 z@;>J8A1M6=mM!d8qhz}{vr#z=;0kk@MQUCA{^G%AIS^j~!o5$=2=C;?ApWhpb=T<3BV|p-WvR{Q%_TEfy z42_*Kox}l3nEqVg7|YFg2wL-4e!E%)ir}vG=70;>p2zlRwr^(r(N|Tu{;Xf}D%L+G zFn^^ojAKJnmXGjcnf8vdpW{F?whD1~pQ~8$p`vNKVivP%o03PfT=bEWFL1dJnJqrT z`lmpG0}bK=t(d;fia20AvxWmMfM2D_EI!;Tgw5S7_LM7z>{kpvpcr&eG5u4;uFTW+ zPn2Rj^Bz08v;4{~Wgoy?en`oyK34pg#PemgO@nC$OBye2e)a+3l!c2?rX>9L5EsPY7WyGmCXhKjxt0O8+PP z$ezpkrmVlr{D`@b>H3)v6-UgX9G>3}D7H-OmV7Udv7V0GW83*$4Vnb=|~t<_6~1Ov@L_Ztu<~ zQnWE9|CPuO%k=dC!k_${@&Szd>e#2Zz6}{vk z-e-Q!yutLjpzQ5HvB*PA1T&X*M`<-W& zF;_B2GP^PBG5Nyc>e(K|Y|k9T%x1pA z+`=62tq=ttc!=C@vHs~~H7gDsUXTxtFPD`x{`&T@NvvS;*WD zR^0axpEKLrF9{Kik8btMAm*=dAbk?#h+t3g9Mb`AZ0IRoXO=N5na}@#t7IQf@j3HD z<_hKu-z)nwEZe*BNeELgzhTEunXfXdQ4v*OVmXxAo%sy2264>ko+9x_#a2vj<}^jS z*oBkv&w7e8%-@)XpOij;xrFuWnW@l6P4N^Zep%rBX@nc>U?W{yU?rzrYah^m>M z!oqCC?8Y3%%w%q49%lZ;6u+oAp_eiC^F2j8b24)(?4%#WtVgs%@iHs6GtV*qVlG2~ zRZBg^0dVJ=p5hkM`-+l-nQfS9plzF{=*yfA=I!?s-+|&sPw^YmbQSBL6dPIb8nX@z zq+iN%8S^t{3)sis#2ly=qT~-x5e1nzfSJQw!Q8?8p4o!SC0C<JDAIv zMx;GLJB8v)`XAf6t6! zeI|1m^CPDH9G~1`T5hU}BAA1ilb8#bZ!&i>k2Ak#>TjtyLCki{L~vsVFOjEc7ay=< z88eOf8CcxaONiUpddGN)*O|{Vhccs>zW>6ce9lYsVIF~=Rn33`$@v-~o1C+iQeJe~RUeXM^f5X*|U*zq*;H`ag6 zyvF*a%)gm8{}Li;x0e|Dw_<0eKQoc-E127u&Hlprr-IX1ahmDL%;Nxmf>jlmE6k6W zE197Wa7%UGOQbXVFc*O4OJ3p#^KIrg%!ziLPLx!8iF;5GUlK~*#H?hdGg~oxF>f;) za-6=*j(QcR9`i2iKWCOQ?MwM&C>%rn^b$V!RkGE~TNFbkZe;FZUS!^7&SBoBa<#pM z#h`ea%t75#%kxnb(=8m>)3bGsiJKjXIIi)LUdR)0j492WA7N z#iSGY;ojma$ob)RZ*fFBK}(sExrmv<1$r}^GXG@zYmm*IyhT3q1Lhv)WaddwjPw>( zS4ADs4#jOYTw-p6A$pRxXvut*naCVqR`&MHRjdzU*@bzT^`9~A%xi8sQJUv1Ccw{5 zC$n%u+{A3^uH?E*GxI(dOlM{??aYD99?Yf8Hq5gezah)7yXov&#bvBG#dKj>m@AvlDh_4-z}(85%dF+4{0?%t)83H(^cMT9ii?>;m<^aeT9o}mW{#atx-&hPT{+-1 z=2~WVsu&ebWxmZk%e1iHEmU~k$09;}6bCWqfthtJqS9B;n)^AE209CN3lUA)c;Yi(>m?JQ!VpJFj{Cvz~{^O$RyrmYlLqQCJg4m0h!Mw!w+mORnTSVO;#m`yZ!n^~0#9J28vcBTe%*D)X zwy$9x4Z`}T0=-%BEgZ*}TEs4xft zIm<_QHPMNxmwiN36Wwy3#vPZJI08TNZoFxl44vpYs4TOd&mC`B{G84rS0|fp$Waq? zwadO6V4AEi`=ynS#UlM3wYx7*i=8Ix`})LwWbZviXNek@HFa|8_>8>xq<&-K;^ed# zLmgSN)YQx{I5jy&E=xA}FR$8DPv#GI9kqPbo}Xmtng9>iyyH+}O3u`{L6IHC zOr1i?>{LFHK7$Q)ydU{ZnwmaVK6TQXC@VV~y2!jZA3rUW=R=p+NY1uuh^d(jDK;sG zkjEVc_4^-cdMr$v|1C_Lxc?Mp`K5ie{i9;J8Ih6M89Aw=CruqQF=OoVOXcmB?~nHj zcw}xv=Jd?Wtf|u<*?pg|Pnj@gqC7RtHADe|O2uTn@vdeC3I0uHBpseN4Uf zgrUpwZu=WPrV1@GHCP_9zsF;$wSTz;dOWP8SEST6@M*FcyLKZZ25Q~D&#NepRL zd~(~=xVX5;4ymbAr=-4+ku{Yg#7)=INX}`Sih1%s2Tu9#f!jnr88~%DM*776)9A7P zJ$kQ*|Io-Z>_^W8aZyaPCgjxx>aV@_1h}De{J=a^`0`^kZ)`=^p-WZ ztiLT!nvpKcKQgqIv$ptqEMGakqdmD@YJ74B>c$QeretSi<;0_lBhl~~S?QUnw0N+K zDhI9dj4A1(CuO8g7#E+Co}Qi6rekXA1nhX3nUNh+$4$+ek)DMmqn2D&{9wdwKRta? zZbo)iWC!w_IwdPL1o7l`$?OD`U!-4CJPQ zAc2_F_#~B0yPW4UGvbnarNTM6w;b5t6i5xK{is15vog@2=^1g8<+LCDTgV85Pq*-g zGdGT=$Y8Ygf14!r{>w1RrAD8LPsU9Czl?b!>fd7y{oiBO|1WvsD)?QgHOS6eW_GH! zK?>)UDmxWB!at>lyG+Jo`Uz7eJnr^z*wV*NPscz#?#WXi6q?ej}@4bG2^ica3K(>*%{f{6R`9rV9sTyVxmnLTXv(1&jFXR2R(d-n&fBGT)S%f zyNrI$)m!d-&UJ!Je9rZ`kIwG@YP>X^Gx!_nZ`|8UpPx+uWmPS8*XnAD4l;d%Yn`&I zy>-XBuwqW@x^)(weo2QPJ$_uuPQPU6JD80(&bkL0BRjTf6D7N!Gdx|(hL4r#$a7J& z{V6w{Gj#X&d0)w-H{i4!pZ+W`$o`(@woX$%Zq4afRVuHx()9@XK#6PI6azmLLXXoL z+|KD_Ot{WhCcSA2@$0%%*)O;&Zu8&?VuAj#>Qk5{M7E zL@H(7R#tvx7_9%XOj?^6!}Zt7WRGUXR%LY->RRdb^LAoGG=%FzHpz?M8jk6Q@06P^ zLT>-A+;`n{TL1eF8QH)XB0u}ia9{tzhw^w8j4ypC8#FYA$=Nq`CRx5%S1zNUw;JTN zO9s2X$yRyErf*bM{JmkTNxyfKjJ;y$sb9INY~dBdr#k)ajb(Awh7o%G!VU7R>xK^c zXE(^Nt{dLcSG-*|`&UC#7mrmdg|IZAZ~gnh1F3sk?-U7=}f{&H$_ZW<2j zm#>vwZy7G>KP-{Kw+%7+9wlX?ZyR29(SPxp)P?Fpq;<2&Sk~~ap}kQ*Q_5-Y>qE+( z|I6@$Uccd$?W2qr4EiHWWv0=HkvAF}=wDu124R~{cTr9?8UxF^xf;9b^o5J5U|o6A zY;=`t%tn8G_9IQ8e)c25RAzQFCb;PPJE#xZmlb*%e=_J(=aJM_{@PUUF1Pp?ugHiZ zy)nq<_|rD`UPz7!|BucM+7*KFvN^u{m?pLS;8T5_uz1H|TcHbd50b)cjstt6VZpDX z$StnQ5@uYKOMMboDX(5Mgw(Tr{CrMENOnfXSewK4)Zyrk!d5h^GV+ei@s)h$rml^( z(5RK2TW*banBpQI$OldI{>c^(guWA3s>}7d6IZFr^^cqPAUC?YzO8UpxmFozbFMIbg<7UbSZ|g&4aJW8P z2DQ~km!0USx9ViO`o=i@x#wm2Y-5mq_VaStP<)ELNjqy4*ZMZ_}NU^&1)|>Kjkj;+1V`Xl$;N1r3c2Wo9}CETmL#(l^Lem7)3u zGOn?)NdHBSN^F5fPOMuKV}ZUEYmk_)yw?Pc*q5zV-jQr=<;f458lTbEov4%zCzfG3 zF4OBwpOGs`90 zk>{S)#@hP6!^`eZ*1xDPONum(Ht1Jpr~s|T>%(<}h#Z~Qx=8tKz5M7HPySxJ+qBn^h4`wE1|5l-T1w$TMSJ{935rk zRec{>b5(CGOIv8{VF(TuHcWC;rCtnlKdBdMbl$u4;+U@O1D*IwH%`dag~kDL{_lF< zGS9`v-FmMFI!{qsh}z~hBB;5%cu${G)_JKh*jP4VYQTM6nZJwwEf=|Gg|V01@9xq@ zmgM-ZDr@kX@dKlLWv6GLteWMUSC-`F|EykGUN`1bCT=omy|LV-DabUr5GRD^Vpur7azZXf7;ixy(Cn{4_44A z2x-}9beDg;ZS1J8DwSxOB(nrTB z2Fp1g83XMsN!>puX=C;leUx{y{-5>)wx`7_JN~ZWAKAXb_N2bbPB*TPw`LpL?MeNV z!>%VBzF-Gyg0jcB{4;^e&IAT1d&UzD=(SDhEBSWs^(W$wRrcpZ=0N3m=83H!j{_tP zQ}$m>{~SHTcBPLVrI-gi-oh8zUe0zmZFz*TMBVpOUBEmo4W?#K+Xf9hkMU}OxeTF4ZmHk7B zHbwJ8N!$7!mBSWfXQ|5*8II*zLNZk?3!kv(u)TDevd6>z_)J;Dc9E^@c(VCNE6UmK zmXxC$o|F5Uxdw*iD(U?v+^g6rfo7Jnr0yeQfLj_(ZXAnb%tnYcEWWJ7 z;^&pMoz$TsGfQ3NC2Z@u7v!#wj1Al(W-6a1+OA_}DMs}vOV4PdZhujECO?treP^Px zl>Ocl_S$7ipJ`W|^u&7Z#rCRs%AWK@`LS$o9`$6cMsI9lAX6ho3vVKt7Xh~bX+v;LO3voNlbcEi77I41YN@3Qew91*64Lg z>?b4lImfiOm4@cv5FBd_i>Hg((BQ>jDcvOX^Lygo%f)&WZOf$X%)*vRV|RK=`n zBA1o92D^nNDG!qjo#h_vW_>7LY6U%3c(+LnEo{=~Bn!SVF${&h8yNy9qwqm#{ z>#M?j164VzmdIt-TpM|Lx(E^SwDPL0c^TV56)3lUVtmRik1bjSLFAjsn!1weFhDb2 zMt+LBL_GC;iifk1PA&$c!GJ#|m2*Btgv;!2Q5ie~OC_rYYYj3khDu1c-Nt5acOO=_ zw!2G^Ck!>LIV(du;YPIPfV;uVfJMG>gi1L;!n2-NE2v6Xg_|-`4Th?KRUS(9B2<~I za?@1C^-y6Y?m^{AYzdb8I=R#{-iE~{r;)|_jH)b1vv~EwO@WP7esb#~*B}El(K3Cn zv61l=oQA2>PjPD1PQ8A_=_)SbBYnPe4f4d}Pw^-fSJmj%4ywyDlnS@IX&#zWNS@NP zP-)GBQ<&71qsJrUz;e|11vG(l+Ds=jjaOqc0?ky#2Fp1=A`mnoFDgwJHOe_qMadiG z#;0`XfPKcN^sDB`MWb{LWQfDvXoR)uCHcZWnq@CJbJ9Y&Vg9kkBqPsw26;lWiZ$2~ zo^{tW`K)QBI_)5w3Rs27b;9%yClf8CAWwJ9riX#BmNZTNTvdHNiRGke@>t`gRuoF5 z%~L*BH7lX9$;bm*9|4Vw1`q0^IiwNtT|$aHbpTDbN!>w=aPeYQK1*)xi@Ea!+%L0g zk=(b-SkDW8CNGkTl-J8j)z}jYj8*MaD-)oqVpUg7Wy*o-Zjros5ItG5L^$-0M$n`K{O$rkwn zF@LMvL@eJXzdCGeisx9}5i~SPBcmzFRmUGA<;#1|IR*`YAg*xsO zSp-|ufEw4sVwRy5k6BD|T5F6D43|GqQQXzS3$mi~xOhFM~|DwYzcgyMom-@bUt;#SP`;gO6FA;{6+R88jd#h&n ze7EeE;!?LagaSWhJEKg(d| zFea&pNgb7qKBdrXTlUDA$Bj*53%V)8Vx<@6fW1oLOgnb2VF~(y(x!Lk1a?A3xqv;u$4)YJYWxbQe`o1LtRSxuUtQo$>%pyZ&vNBv#dSSk`Th1gy z@nB`3$6-1s)O%z|8=?&K7)*!x3?>g5YS=)}znYA_bsAXFDJB=L2U z?=`hh6Uk7{271!fN+8s0pJSlMr>V;65mz&KIQug64UZsyXhWV=9(?03%>DMrmIGbt z^m!4+d^X0Z(&%R{{vT%JX82dKKRwlQb@G!`U1)%IX`G~L63%AD?x7Xd#5X!e8R&^m zE7fw3Dm54vz45uqNRM=y(UpyhVa%VdjPxX?88I4EpVvVc3-~I#Q0mSYo5{?>Mx#H} zReY(XhXAe2Cm8coU7dhdxEhKVR6u(4(u~*zv<8fYv4F3w^q{2~u|fUW-z78vU#Jx& zd`Y8+8_hVAOMU@k1z*eP$wo7dU?W{si|4C4>9IyLCbO{@j1`NNk)CNZV;4-4;n+lA zt148shb`%Yc~WWAY~$ZmHhPTE$~A)R8JAH1YbaQ!s1j-d#c?LwMcN@jDfP>(#WBKH}%Dx@M-{gjG z2Jwk(c#C(0ptx^tP(1dwN^C!v2z?ni0NnC6zA=D5##y#uLQ~e6A*to7hcNpLJ*9-C7|&2XjI4D=JYu`=-+Sfus+| zK298mjg9;TNOH;=MSJOM>coTj6CS?FVFpMQ`m#LjRi*!8mEsm=EJ%TWl1jgWxq?Zr zr=WOeam^u4U>X$d;iW5-A(}Z3p9zp-Jktx@g9?6Iu3CN+q{P;PRPjfc82iCB%n=}! z@5mgpOv!%~D;{7D*Ju~vby(rTe08aEoXdQ53CqknOc&;aB4tlzURkWM zJvmx}#}g9zZEF`VP=1jTHYR{xiEjB5N;@+3qF868Km`Jhk0oM zZc-ud25DKg0%@5y2JKWJ2qy_J)M9#pZ6Nu)GUw2l)*UPx|4{ zvON*RpVEi-0wb^-yMcIa57&eEQ{3?L^Hidh^RP%L(>+kQBEZMYZQwY_5*!BmjJc{s zKg>}q2dO20Vhs)jr-CGpv#Y?PK#Cg!QrvKs>w)_q|Ai$+%lpb~C}@3t4F#>wlOV0n zm%u}CFoP$-lklbrR)G{?BuG7$0#XmXiqGHhciQ34gZT65aPtc)fqVE|?UxE`_ zk5j<|U{`i%#|!{#!LJG*Zdbs50z3+?1F76fknA}iRWJgi3LCNAkM%Af`JKx|;^3>G zcKr{)2^Fw_pMh(pszfG&#~{aohrw^A2yu|?ApXWbd;v)1W`k7mB#;ss&2ltI75+V0 zRd^bta@#=av4xY-|3l#LEELJ$V2}bPfE2JZNL?Mt^6!&Wm!AbGz&?=to&l-ro3i}- zMBI=d-dX17Ao=eA$$tgt2F{;oS0|e$s4<_;oG@O9Bd9nEq`5HqS;c$Ul}I)-6Eal7 zy+8`w3A|1TgS$a5kcRa5SUd(Jp*KM4fu$fNlx@ceb#3o4s$h4JdY~;xT~~|s?o2&M zf&VpH-2q(%slv}dN?<#415+}yn9<+>_(g%s5Z@k&le0K!0{#Lv04;F1l`aInsS>^g zq#f;5Fb4J|%(=`wP>)-z$sn~T4Wv7&p&(V%A6$ZReZVilXmBwSjsgvse}1iTLIoOw zR3HeX0)8MB@CK=X2}A+E`)SJmQ;>S-1(3Qn8l(g|fXm_kd#bX30aDLIfxU1siUhqO zw*n2Ae||nVd4K|LU?L2Ek5Y-;0r737Zoh#&z{^DVeG5{;hd_658%RU24y1}+W6lOC z!L}g9Zvj&L1|a$S6YV(hW(Onq8RXj|mBTL}1vms!;7P0>%N)%57?wLR!&qO3dFL5b z{wPR2ybq)v-UZsJi$B20&)_>Cjs1%tKAZ{91gXpWfHVgp!LIOY%B;tf@?rp)+<07+H{bXb$yVA)b4_l&TzOpSn)dZ zdyvNT97tn&45T3`2dSc+AdTTx@JnzLNJID*NJF>^B!34;b6^%o<#ItPHw8pFd;Mo| zLIqPnDmVnBf~`R6^82_jP}fy~)Fb;?UJsr?IXf5yyBoM1^6^1x2ww#$@i_1m=)WDP z=D-q=;!Xe`VE%=ty26uYgo86Qpt(ERO=oZvaT;nkCrP#$kejTKIQA z)kU{JYH2k{L-Qj@L-Q@T6nvB*+p8-;r(<_WAUJsDEybVb4T7VR<5lHcZ z>^Px-wLuE#4pKk?Qh~dDRoC4Bsq3zSi{bx0NdD(R@?QtyaWMQ9un@F?lt45{3A6<% zUQ3V?Xbe&U_WC%X1pGh>=mAnd9Y_WKh*t^R1Sx@PkP`R-B>xK_`L72lfdL>@+y|tJ zJG0z|pai4@R)Lhj5|9#j2_*j)Kx$z& zNG*I8q}7xLT3~Mv(i~|8YS(`gPH1_3-$w;J2hy@S0n)PC3sME8AXU5(q>A5Q{ZiI1 zWc@7GM}n*2f2+6Zk!p~7!yKTZr4EQP- z2fhqi;4luPt5+--jINAghBF(2dW>2CNQwU4Q4Qs7kb3kva2WLMLF)Qb9aKU`K}vKF z=tG(8z)5XXyakMrMb*ZDbea+qAZq17D*t19A)W>+KShj$33lB@perZfs_!wVwcy*bbz;TY<|GO z;aJl`6*vu2fh`~fegmYy#o&C%d8~hy^=aTj=sSQT5XTJCfZc1ZDm)8bhy5r>EALG^ zPHMqn9xKvVP5`N;9Y8A38l;hI3R1zkEW3f^_gk3Ke+iPj8}tRYgVg1No2dj6K`I{w zlApZ^D`J}R+-@So`!IY6lEa(ead0t6EuRQdfH;s6Z^5#O<*SX==SrKvV<<=e=Eq^M zFYDb||4SoHZx4^e35~@U*psP+CqSy;0L!HyEvmOcnl!J2R`{<3yMs$XFX$J71|&EO zq&bs8GB^OFPvAO&0+gY6>;qp0`)Ru@PN?GNL8>^NnZWGGG=Svyb%^p?#hk?)$Lzx_ z4px5Sm|d8M8erEzoOPg$D$KwM1s(}fO9z41A=5t!L?7jM0m;wAyis4tre^8ZQW}Hmh@RTTB#{+OK)QMK0lhH8z6}(jF68$> ze=rNAi_deQ762S7{cakAb@8%2pnY=uwVz5i0o21=54t1T<=QHZ*Fb8{Am&c27K*+Z zq^R@30LV#T2e3BV&1}Dj$xm5Sg5-Y`B)_G#u%}Vr7oi~bVYc*9vL7=K8$1Qb1}VU^ zAO#o*Qn{|o7R+BQYBpQ}$^J339!QB^$DE?_yS(kHCEKB(z|%phXd25I?3fJFZ;0A~ z6wm{7#kA9bZeR^Mp9U-)bOk$tw2-EGD*qU8DCC14%C8#TOa2RM)IQNXy{_%k<|RTEjt_-&;XnR73w77+pNpfpqbB z4WxE218F0e0VaW&t{9mvaGU@|XRtSDf*}f|1YAL?_<>1Pd=sP!FEHtEx=4PXxgI3{ ze2@~#1D9ZmO#{CK$AUkDBS8!7ktVwunJ_3QLjy=7^M_H%H^GjOe*h`-gWzkZU;{|E z*sDRR@MVyCW+BUVmZyM}U<&IKSnkFQB}a1j1GQ7eH$ckxDo7ds04_!aUxWD1?-c$= zi5vpSe>X_}J3#W^1X2svfz-n3AT8Z^W?N7XJAGT6xbluMXgGECZ!Riu6-X7H2jSy) zlJ$E*s;~@PhJvNwS#Ul03-~%n6=s4|VJuNj-s#dzTl}|C8Rh*GNa4Q)Df}7MzXGm? zK8xuMQpcJ=>exSYs-LQurH-eMF z5^yG1Y{yAA1TFx7fFd8Hiu1rS$TVWDAdd%kLQVslLrwudhMWYpglq%D!Dz4<7zK`j zJp$YThJh`h4*}83-htpQ=1KPm7U^G|`MzKBw90@rP zdfs^l{C%{#ZD0!62TTHcgElY@^UpgP zC%wQZFcypeTY_O=b1($NeW`aK*bKCSO+ho*1Qa07z3*a{7elTAu?u)#2C)lxW5&~} z@~#9gf)yYQX*p=8f|yvM2!>J+%g}oxNCj4b-+>6K^-KY{1#&+42ABt~2Q$H~;CQe< z;-xWDK*aJ+0^bE~;G1AH_yHJ&xi8M)Bn%4lf_Dg51vwC;46PuZ1ij7Rx6lKcJ|A}# zXtYU&B>6|kh`O~6a`Ao_v*$qgmf!|Lvlahoj7U1~qrD=-R0)!`93*QAGoP6UVg;Bo zLGnvueG2Q7ST7?ExP(dTK>vs)cP*+2-Y2IOsRAkVdDd5wUe3C245*{!#pYFPF4oNN z!>py9#%5V{-0idMz@45dN-3zN2x?tddI(=SAx*SXQ!~gvCGb4%Ote}-vX+CIe_Aqz;qGA0#v+8xE3Bs4szO(;5Y#FJslpWzCT<`* z$cPG;+6FC`%tZlV5%f>3Y6U>jmV=ae33C>E_nQWyu(Ye9UUF-8sr1Z&1*V6SO&^eAE^t zyRnQHC*TZ6drS*_pd|7qDu->&m0USW$sbXL2$07CKE=3_e=+<2IR!F$KKiKgZ$Jqk z!Ogg`($VcDB^R=sIvz6a312&)>KA>;K_?8+n^l1ru5dk9*cUe*RAEV|vR6}!kx*5s zl5sn&B~VqNkzemaAxCPF43Rs;z!u~tSA3frHXFwXu&UXalbArxS0{lTkOt+2BcL1E`44vQk zzsL#kcl^bYhA4n8w(>0hkSCe*tNHY|m6Y(ha+NUsZ6zh-d>!{z&Az^=n29i!}XL6Y-X-kXl` zCq*$U{zOG2m$2M%l#)wX&ZhfC*sWt#z`r;l=a>2;{kS~u3-r+n`IoZX#9PVbEWa>` zx(E(6ta#r=$<|4#f~P!`9K!PPE=rDIIgt}^eqXFV!D%te1F| z{LXj)C#b#$j%<8zlS*CU{6hG+dP=swrxK|4R&of-12J~wpTu&OLCKjc3+EWK9Nb;m zi&@^l329#uNBzRJfpTzuSNxMXkTGdKJ)#nr>80!uo0a?(*)eBojw^Xarjj@EocVaF zD*xU=WiNeE+0V1TFUg3%y-U&PbJr| zTpFWf=hyZBCDT1j0e0srUg&O)0?XT<Bw>8!5 zF2b51cX^tc)>@OT3e-d^hi=*Orl+a9`|;;F5Z))`W$NHFE{|l)09WPri=5+S3ig?v zrPSpel{!PN^)mJ8S=>p98>TC>^NmfrxZlcimA%l}uhB|w#p6~SEn9e-8tC(*WnXVo zcf0eg3n!bXtgLtnr^B}y%a~tujApqMw+3|Vz?`G*N@KYJ7Mj-GXdNAT++>mLd=bZA z(Moo{$>Nh*nrs&}JU$lO+)x4M`zTJ=RdUq~EuPrnuVm*-K}H3$9V?KI;5JHjzQCeI zw34mQX=X8CiJR`_%M}`ZPLs1k5$WZd}&PpyB&oX+R5_G=9#G`|foi9BZfq~Lm z%$Ea0J0&|`n&R;+mmjO*ZJY>s2(@^WQpC1b4$ilQ7@y(*uPFPU;Yto!rDXcWp_b5U zB_G7zL~{AN@@XqpLgq1!OBI>&h4ZzSZO|hmmsBYG#}i1#7ZzE*j|P*y=7_T2$CMzs zg14M)xVn(Minpj#OhpUi6t*wG6ohOS<(xo$O2C*e{0^vqu9N`eT$Wc-0+>(3SUyV$ zz}|u7mnZ?qeK?`XlmO(hhg7^kN&xcY!%FT#2^j79q8Te{awsDV9Vhg=)&u1tk6ZkW z)&u2YC(GH~BW9LQX)P)j=ehjb9FKlENXP8DNC-Xeddz9}GnaHXAyE z>LU>yF0d?54N`Iy%P)p0+4)K|!TvSaeCW80s|%G+!sbIqPp3cc3U1Jmy%JM_jym{( zj2m+kPI#B>wPl%`Vdin7Zrqo#vMH z3^eu0mQ%m9T9+5U>`5=nk12B$nijgtkzM?JWuuj*+OpqDlb7t&)z2!kNPKmr$zPWs zPu}sbt7{|otu!^2={?}v>4>4Wp-o4TAU&h~>c}3_R7;xg`uof4M+{ckufk9p?gKMj zePwEep_cqanwsd^%S%#=_GwQPdG9Fnbyk`DWzSWna=#93MW5K%)Qr3_8JRh$IrPHl z_HE^KRsz^>KPgN&@)oGl+^VZC6E5)Usx8h#?)FTcMPP)oi+H%w6lh0 z9+B-LoF(MM*G=7|{+z-2y5F_$`2_m5?-&skLPwgr^F7J(es5if^nAnAs#eOYY8>eE zZ8}n3Rqyoe%Uc`iF{Yw?4SqkamwWQ+we?|B3HjsLnMv>T?Owlz_1|E9(~&Yw`z?yF zeuVLU?4`aZaJ8g^e%nDuJKFZ2c&YDJtaCaJ^F*oh!ecM>rEgl%k;%p?T$m{RKvdL1 zRTL8I85;Dw<|WE;V@F5QEaf(S8@>lAL-A7ImGQbz`pO<1rMODbVO^xWQg%_+@7MQi z*9vDBV4+QNBd#1CI8P#190~k09DEx;?H+rJZw|jp^hNb9QF@DSxL!9+y~H=^IrS3X zVbj!0d^0Afm-zl`s(KHo_7Y$BDe5J@wWj>@CBC2bO-{l)X)mKfVVirmXKqyFQM~Vy zL`$%+>@avh8)126e}2~pVOddbuI!3;#@dPo>TGlWh_N|N<$i6Od(b>$B+%`JC9V|rQUeFK|i$6(jL^Sio>Tx0&dh}>Y3 zA3TyRmL0WSHL(KboCA9)bFFgig{ZSCZH|ko(+ov^t0$|@T{Zcio$LOe-@faGx9`q9 zn1WWsq>elwe_gDbFOQ`dJu?0u_nbHNTwuK8Y`o)l44IsL!&DzHD5iH0Cpi92aNOis zaWK%C>RPjbH>|o{g5w9<+yhqI+#hP3?_;JJ z>4>Ye%{{I|ca$eMGAa@rziUJIy%sq3u%W3~lvsYpzE77^-!^Af1zk0>eDSL2@;whw zU*u`ck6iZYaszCruJTI$KAmO9DZJx(r)I1Uu@w%fuoaF(Mf-K6ez*kqz0jIl%$+#1 zF2^mta8{ru<3z+Ci9;o5bTEFSTW zgLmS}bvfQ}tCTZun}T}(*9!hUezgl?Jgmey6yu~hQPd`l|SpU$mx(jt5W1dV3JkY z=9k-Zn%N2yD{S+R<}^U(mfH$*&)Xc?Hk%_Bn{qNXwSH-bW5?^N-ScOT7dZx;Bgyt3 zXV9GbT}yS|Y0HXz2!*XVq@o};6XrGlvw0fK_D5zv@kH?~O!Nr(eyV39S(Rcmbc}c0 zrtWc^)9S*M#Qgjn!$dO<^AoeJQk$b43gUJq6ixm(asC~eqwurSaWGUBT5a*TtR!^$A*a{enQ8jm?l#8-+uVK8k>zt| zrs!;zUi)%eT6UOey4JNhj@eFKwaq`B69_q{wXM*GYejstwL08Zm}G___cFS+@~}xn zmKWf+7Oc;9yPPX_O9 z+NSw?yyI^h4}$8LkF}0Dq%BuP~7lV4G_X5%g;JK7lqzZ(F`STI70HH^LsslI9YnTq2-M%D0avmo}P< zCgn7ZN0=m$6O1rvn4LNPw!#6J*8NjJOzZw>C^){_qV4RxQ*dK29-Su^^~o&gow=yb zw1VE#7WK(0=$(bj>~d8Jw%R=%`sEk#q6+!VRMZQfj#LT_6RL21S4})V&Uu*x-PYgq4KR03&*8l-_B{0 zSU6=ora?}S^P-X9n5ixE1jm?^1jocQTIMl}dS_w@1Jep(rvX_7v9u!1Bjn9I_gZpZ z9HwFG0YOMIt$f!q z?#O(aIr=e&=j%Z}Ck$m{0Gdv^k1wgreP5S~`ELp)A@>L2O0aNz0>qz*Z;{ z3TK)V3e(Km7EqXNP3-i0PCH9*KB~sX_e+A^p#rEaqAI8V+`KfAYr_p*bo|^SxHUP5 z61ljo!BzA?2ue7c?JQ8==GaHONnGTQR5ZcT>(ZV1l!OiwIS^WwaDP#_yT+N^9j$G; z+^6Fb3b#|cvEAj2vlY&#H=iYRs>u!4F1y=F)LKKA?0#3~AnSkJhdR#Favvhg|Ami8 zcPBZ$9|g@_O_Qce{M^5=-+qScBF+M`yYQY;l29~jE$u0Z)gzG}C6eIyXQhX5ncjKW z1+z9!TuGz=MI)cMWx&wHm131@_Y5Toj>#KomK1KMS(4zms^yRShNp0&oLhq7T#Mn{ zh%(p*XnxEpjV{=(jozb?4n-yyX>CS0c2h4LR`cTUd>Vbs4QeZprZ67B%8Dz%`im>Z z`ipy6&}~0m1hoFGcE^m-qMrJdTBId~dq5r3RCj{oXQfgt&7ZYOY^sImi7^C)aZ|OuqQ_M!-akf7!9WU+cr7EX7NdmviS51qx%7Ls_xEwT30%4 zew_bXM&sWHG5gsZ=WTNi2FkTVJbmrhk4v=V=qBbcrIu^Hau~Hix})Wzv&`E88AC{k zUU^&DuDs>+iH25}Q>VIfwJM|LXMl4SHcC)qkL@P+sp_duOjHV~%lQ^s>T@lTNB%iI zos$;#zMbyodQ>+lmPx-GLPFGJ(h}0j{Ii|-S6Gr-8XMTP3*hZ|6fT+(Vk=BlSBp*u zvRh&wv0@*jT^P@O)Za0T7 zi?!cni%;LnxM>DsFWZj9-c>1NU&cdgLZ@GIzlbkPHOCjuz_Y+G{CgXHzyX1_P8V~1 zt6Pk7KEL8NGQQKr9KVF3>GsQadSBM%U`fort6K2`edgNt`;DFRu!It=yQ-o^U1mqPZPW8HFdV4 zq6q{`+~5H~EtaWx--pJZIov8a7qaLZgkcYV0S8Im7)G=Ekx zE=-nQr*P|RgQ~=7Ta`ZxkBrKYq}_lwwavwo7~P(zyKmf{3@O#_xalt3k(`EY!+CoW zi`x_2B2c!8=L`B}E=s&m&~MtJ#IFkaWo^{u7_}zhe$j%*xO$qP{SS9Ta}E}u6hAwU zlS|{g1MDZ`=?aY}j@`7pHP3uXCHenj@6F?)uJ-@&_YA|#u+AXMfPf5Qi3~>At{5@ai zyxzk=-dp#6K9BF?_s8e`P@eNz&g;D1XM3Ob+1{v;R8y()*zEE_L-ohLE+0&ry@t}s zX5UPyWV6p%PwkLukEmTj`y^j-sp-mk5VCo#hY)l2nqTq!purCOQZJKk^wjY-Nqy2Y z)XRrC9U4I*+M<%)v*?8*Uk
    =vSS3?wFy#;{ZL4O#6nc#`VaBtR@hK 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 B((gd5)*cnG;Uev^?pE-3 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 delta 83951 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 V-mQ@Fidr$w(88aSiT7+n(qFijS_l9D diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/module.modulemap b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/module.modulemap index 5580079fa..d5540495e 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/module.modulemap +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/module.modulemap @@ -4,8 +4,10 @@ module SessionUtil { header "session/config.h" header "session/config/error.h" header "session/config/user_profile.h" + header "session/config/contacts.h" header "session/config/encrypt.h" header "session/config/base.h" + header "session/config/profile_pic.h" header "session/xed25519.h" export * } diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.hpp index 86f42533d..be3a4f9cb 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.hpp +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.hpp @@ -237,6 +237,13 @@ class ConfigBase { /// otherwise. const std::string* string() const { return get_clean(); } + /// Returns the value as a ustring_view, if it exists and is a string; nullopt otherwise. + std::optional uview() const { + if (auto* s = get_clean()) + return ustring_view{reinterpret_cast(s->data()), s->size()}; + return std::nullopt; + } + /// returns the value as a string_view or a fallback if the value doesn't exist (or isn't a /// string). The returned view is directly into the value (or fallback) and so mustn't be /// used beyond the validity of either. @@ -278,8 +285,12 @@ class ConfigBase { /// intermediate dicts needed to reach the given key, including replacing non-dict values if /// they currently exist along the path. void operator=(std::string value) { assign_if_changed(std::move(value)); } - /// Same as above, but takes a string_view for convenience. + /// Same as above, but takes a string_view for convenience (this makes a copy). void operator=(std::string_view value) { *this = std::string{value}; } + /// Same as above, but takes a ustring_view + void operator=(ustring_view value) { + *this = std::string{reinterpret_cast(value.data()), value.size()}; + } /// Replace the current value with the given integer. See above. void operator=(int64_t value) { assign_if_changed(value); } /// Replace the current value with the given set. See above. @@ -378,6 +389,14 @@ class ConfigBase { // nothing. virtual void load_extra_data(oxenc::bt_dict extra) {} + // Called to load an ed25519 key for encryption; this is meant for use by single-ownership + // config types, like UserProfile, but not shared config types (closed groups). + // + // Takes a binary string which is either the 32-byte seed, or 64-byte libsodium secret (which is + // just the seed and pubkey concatenated together), and then calls `key(...)` with the seed. + // Throws std::invalid_argument if given something that doesn't match the required input. + void load_key(ustring_view ed25519_secretkey); + public: virtual ~ConfigBase(); diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.h new file mode 100644 index 000000000..a889c7cb9 --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.h @@ -0,0 +1,147 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "base.h" +#include "profile_pic.h" + +typedef struct contacts_contact { + char session_id[67]; // in hex; 66 hex chars + null terminator. + + // These can be NULL. When setting, either NULL or empty string will clear the setting. + const char* name; + const char* nickname; + user_profile_pic profile_pic; + + bool approved; + bool approved_me; + bool blocked; + +} contacts_contact; + +/// Constructs a contacts config object and sets a pointer to it in `conf`. +/// +/// \param ed25519_secretkey must be the 32-byte secret key seed value. (You can also pass the +/// pointer to the beginning of the 64-byte value libsodium calls the "secret key" as the first 32 +/// bytes of that are the seed). This field cannot be null. +/// +/// \param dump - if non-NULL this restores the state from the dumped byte string produced by a past +/// instantiation's call to `dump()`. To construct a new, empty object this should be NULL. +/// +/// \param dumplen - the length of `dump` when restoring from a dump, or 0 when `dump` is NULL. +/// +/// \param error - the pointer to a buffer in which we will write an error string if an error +/// occurs; error messages are discarded if this is given as NULL. If non-NULL this must be a +/// buffer of at least 256 bytes. +/// +/// Returns 0 on success; returns a non-zero error code and write the exception message as a +/// C-string into `error` (if not NULL) on failure. +/// +/// When done with the object the `config_object` must be destroyed by passing the pointer to +/// config_free() (in `session/config/base.h`). +int contacts_init( + config_object** conf, + const unsigned char* ed25519_secretkey, + const unsigned char* dump, + size_t dumplen, + char* error) __attribute__((warn_unused_result)); + +/// Returns true if session_id has the right form (66 hex digits). This is a quick check, not a +/// robust one: it does not check the leading byte prefix, nor the cryptographic properties of the +/// pubkey for actual validity. +bool session_id_is_valid(const char* session_id); + +/// Fills `contact` with the contact info given a session ID (specified as a null-terminated hex +/// string), if the contact exists, and returns true. If the contact does not exist then `contact` +/// is left unchanged and false is returned. +bool contacts_get(const config_object* conf, contacts_contact* contact, const char* session_id) + __attribute__((warn_unused_result)); + +/// Same as the above except that when the contact does not exist, this sets all the contact fields +/// to defaults and loads it with the given session_id. +/// +/// Returns true as long as it is given a valid session_id. A false return is considered an error, +/// and means the session_id was not a valid session_id. +/// +/// This is the method that should usually be used to create or update a contact, followed by +/// setting fields in the contact, and then giving it to contacts_set(). +bool contacts_get_or_create( + const config_object* conf, contacts_contact* contact, const char* session_id) + __attribute__((warn_unused_result)); + +/// Adds or updates a contact from the given contact info struct. +void contacts_set(config_object* conf, const contacts_contact* contact); + +// NB: wrappers for set_name, set_nickname, etc. C++ methods are deliberately omitted as they would +// save very little in actual calling code. The procedure for updating a single field without them +// is simple enough; for example to update `approved` and leave everything else unchanged: +// +// contacts_contact c; +// if (contacts_get_or_create(conf, &c, some_session_id)) { +// const char* new_nickname = "Joe"; +// c.approved = new_nickname; +// contacts_set_or_create(conf, &c); +// } else { +// // some_session_id was invalid! +// } + +/// Erases a contact from the contact list. session_id is in hex. Returns true if the contact was +/// found and removed, false if the contact was not present. You must not call this during +/// iteration; see details below. +bool contacts_erase(config_object* conf, const char* session_id); + +/// Functions for iterating through the entire contact list, in sorted order. Intended use is: +/// +/// contacts_contact c; +/// contacts_iterator *it = contacts_iterator_new(contacts); +/// for (; !contacts_iterator_done(it, &c); contacts_iterator_advance(it)) { +/// // c.session_id, c.nickname, etc. are loaded +/// } +/// contacts_iterator_free(it); +/// +/// It is permitted to modify records (e.g. with a call to `contacts_set`) and add records while +/// iterating. +/// +/// If you need to remove while iterating then usage is slightly different: you must advance the +/// iteration by calling either contacts_iterator_advance if not deleting, or +/// contacts_iterator_erase to erase and advance. Usage looks like this: +/// +/// contacts_contact c; +/// contacts_iterator *it = contacts_iterator_new(contacts); +/// while (!contacts_iterator_done(it, &c)) { +/// // c.session_id, c.nickname, etc. are loaded +/// +/// bool should_delete = /* ... */; +/// +/// if (should_delete) +/// contacts_iterator_erase(it); +/// else +/// contacts_iterator_advance(it); +/// } +/// contacts_iterator_free(it); +/// +/// + +typedef struct contacts_iterator { + void* _internals; +} contacts_iterator; + +// Starts a new iterator. +contacts_iterator* contacts_iterator_new(const config_object* conf); +// Frees an iterator once no longer needed. +void contacts_iterator_free(contacts_iterator* it); + +// Returns true if iteration has reached the end. Otherwise `c` is populated and false is returned. +bool contacts_iterator_done(contacts_iterator* it, contacts_contact* c); + +// Advances the iterator. +void contacts_iterator_advance(contacts_iterator* it); + +// Erases the current contact while advancing the iterator to the next contact in the iteration. +void contacts_iterator_erase(config_object* conf, contacts_iterator* it); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.hpp new file mode 100644 index 000000000..1601c6146 --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.hpp @@ -0,0 +1,187 @@ +#pragma once + +#include +#include +#include +#include + +#include "base.hpp" +#include "namespaces.hpp" +#include "profile_pic.hpp" + +extern "C" struct contacts_contact; + +namespace session::config { + +/// keys used in this config, either currently or in the past (so that we don't reuse): +/// +/// c - dict of contacts; within this dict each key is the session pubkey (binary, 33 bytes) and +/// value is a dict containing keys: +/// +/// ! - dummy value that is always set to an empty string. This ensures that we always have at +/// least one key set, which is required to keep the dict value alive (empty dicts get +/// pruned when serialied). +/// n - contact name (string) +/// N - contact nickname (string) +/// p - profile url (string) +/// q - profile decryption key (binary) +/// a - 1 if approved, omitted otherwise (int) +/// A - 1 if remote has approved me, omitted otherwise (int) +/// b - 1 if contact is blocked, omitted otherwise + +/// Struct containing contact info. Note that data must be copied/used immediately as the data will +/// not remain valid beyond other calls into the library. When settings things in this externally +/// (e.g. to pass into `set()`), take note that the `name` and `nickname` are string_views: that is, +/// they must reference existing string data that remains valid for the duration of the contact_info +/// instance. +struct contact_info { + std::string session_id; // in hex + std::optional name; + std::optional nickname; + std::optional profile_picture; + bool approved = false; + bool approved_me = false; + bool blocked = false; + + contact_info(std::string sid); + + // Internal ctor/method for C API implementations: + contact_info(const struct contacts_contact& c); // From c struct + void into(contacts_contact& c); // Into c struct + + private: + friend class Contacts; + + void load(const dict& info_dict); +}; + +class Contacts : public ConfigBase { + + public: + // No default constructor + Contacts() = delete; + + /// Constructs a contact list from existing data (stored from `dump()`) and the user's secret + /// key for generating the data encryption key. To construct a blank list (i.e. with no + /// pre-existing dumped data to load) pass `std::nullopt` as the second argument. + /// + /// \param ed25519_secretkey - contains the libsodium secret key used to encrypt/decrypt the + /// data when pushing/pulling from the swarm. This can either be the full 64-byte value (which + /// is technically the 32-byte seed followed by the 32-byte pubkey), or just the 32-byte seed of + /// the secret key. + /// + /// \param dumped - either `std::nullopt` to construct a new, empty object; or binary state data + /// that was previously dumped from an instance of this class by calling `dump()`. + Contacts(ustring_view ed25519_secretkey, std::optional dumped); + + Namespace storage_namespace() const override { return Namespace::Contacts; } + + const char* encryption_domain() const override { return "Contacts"; } + + /// Looks up and returns a contact by session ID (hex). Returns nullopt if the session ID was + /// not found, otherwise returns a filled out `contact_info`. + std::optional get(std::string_view pubkey_hex) const; + + /// Similar to get(), but if the session ID does not exist this returns a filled-out + /// contact_info containing the session_id (all other fields will be empty/defaulted). This is + /// intended to be combined with `set` to set-or-create a record. Note that this does not add + /// the session id to the contact list when called: that requires also calling `set` with this + /// value. + contact_info get_or_create(std::string_view pubkey_hex) const; + + /// Sets or updates multiple contact info values at once with the given info. The usual use is + /// to access the current info, change anything desired, then pass it back into set_contact, + /// e.g.: + /// + /// auto c = contacts.get_or_create(pubkey); + /// c.name = "Session User 42"; + /// c.nickname = "BFF"; + /// contacts.set(c); + void set(const contact_info& contact); + + /// Alternative to `set()` for setting individual fields. + void set_name(std::string_view session_id, std::string_view name); + void set_nickname(std::string_view session_id, std::string_view nickname); + void set_profile_pic(std::string_view session_id, profile_pic pic); + void set_approved(std::string_view session_id, bool approved); + void set_approved_me(std::string_view session_id, bool approved_me); + void set_blocked(std::string_view session_id, bool blocked); + + /// Removes a contact, if present. Returns true if it was found and removed, false otherwise. + /// Note that this removes all fields related to a contact, even fields we do not know about. + bool erase(std::string_view session_id); + + struct iterator; + + /// This works like erase, but takes an iterator to the contact to remove. The element is + /// removed and the iterator to the next element after the removed one is returned. This is + /// intended for use where elements are to be removed during iteration: see below for an + /// example. + iterator erase(iterator it); + + /// Iterators for iterating through all contacts. Typically you access this implicit via a for + /// loop over the `Contacts` object: + /// + /// for (auto& contact : contacts) { + /// // use contact.session_id, contact.name, etc. + /// } + /// + /// This iterates in sorted order through the session_ids. + /// + /// It is permitted to modify and add records while iterating (e.g. by modifying `contact` and + /// then calling set()). + /// + /// If you need to erase the current contact during iteration then care is required: you need to + /// advance the iterator via the iterator version of erase when erasing an element rather than + /// incrementing it regularly. For example: + /// + /// for (auto it = contacts.begin(); it != contacts.end(); ) { + /// if (should_remove(*it)) + /// it = contacts.erase(it); + /// else + /// ++it; + /// } + /// + /// Alternatively, you can use the first version with two loops: the first loop through all + /// contacts doesn't erase but just builds a vector of IDs to erase, then the second loops + /// through that vector calling `erase()` for each one. + /// + iterator begin() const { return iterator{data["c"].dict()}; } + iterator end() const { return iterator{nullptr}; } + + using iterator_category = std::input_iterator_tag; + using value_type = contact_info; + using reference = value_type&; + using pointer = value_type*; + using difference_type = std::ptrdiff_t; + + struct iterator { + private: + std::shared_ptr _val; + dict::const_iterator _it; + const dict* _contacts; + void _load_info(); + iterator(const dict* contacts) : _contacts{contacts} { + if (_contacts) { + _it = _contacts->begin(); + _load_info(); + } + } + friend class Contacts; + + public: + bool operator==(const iterator& other) const; + bool operator!=(const iterator& other) const { return !(*this == other); } + bool done() const; // Equivalent to comparing against the end iterator + contact_info& operator*() const { return *_val; } + contact_info* operator->() const { return _val.get(); } + iterator& operator++(); + iterator operator++(int) { + auto copy{*this}; + ++*this; + return copy; + } + }; +}; + +} // namespace session::config diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/namespaces.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/namespaces.hpp index f5ab57c20..1ba0226ca 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/namespaces.hpp +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/namespaces.hpp @@ -6,6 +6,7 @@ namespace session::config { enum class Namespace : std::int16_t { UserProfile = 2, + Contacts = 3, ClosedGroupInfo = 11, }; diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/profile_pic.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/profile_pic.h new file mode 100644 index 000000000..dc9887dd8 --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/profile_pic.h @@ -0,0 +1,21 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef struct user_profile_pic { + // Null-terminated C string containing the uploaded URL of the pic. Will be NULL if there is no + // profile pic. + const char* url; + // The profile pic decryption key, in bytes. This is a byte buffer of length `keylen`, *not* a + // null-terminated C string. Will be NULL if there is no profile pic. + const unsigned char* key; + size_t keylen; +} user_profile_pic; + +#ifdef __cplusplus +} +#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/profile_pic.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/profile_pic.hpp new file mode 100644 index 000000000..00cd90062 --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/profile_pic.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "session/types.hpp" + +namespace session::config { +// Profile pic info. Note that `url` is null terminated (though the null lies just beyond the end +// of the string view: that is, it views into a full std::string). +struct profile_pic { + std::string_view url; + ustring_view key; + + profile_pic(std::string_view url, ustring_view key) : url{url}, key{key} {} + + // Returns true if either url or key are empty + bool empty() const { return url.empty() || key.empty(); } + + // Guard against accidentally passing in a temporary string or ustring: + template < + typename UrlType, + typename KeyType, + std::enable_if_t< + std::is_same_v || std::is_same_v>> + profile_pic(UrlType&& url, KeyType&& key) = delete; +}; + +} // namespace session::config diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.h index 8b438591e..03f382c85 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.h +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.h @@ -5,6 +5,7 @@ extern "C" { #endif #include "base.h" +#include "profile_pic.h" /// Constructs a user profile config object and sets a pointer to it in `conf`. /// @@ -41,16 +42,6 @@ const char* user_profile_get_name(const config_object* conf); /// error (and sets the config_object's error string). int user_profile_set_name(config_object* conf, const char* name); -typedef struct user_profile_pic { - // Null-terminated C string containing the uploaded URL of the pic. Will be NULL if there is no - // profile pic. - const char* url; - // The profile pic decryption key, in bytes. This is a byte buffer of length `keylen`, *not* a - // null-terminated C string. Will be NULL if there is no profile pic. - const unsigned char* key; - size_t keylen; -} user_profile_pic; - // Obtains the current profile pic. The pointers in the returned struct will be NULL if a profile // pic is not currently set, and otherwise should be copied right away (they will not be valid // beyond other API calls on this config object). diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.hpp index f13a83867..cb3b1eb32 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.hpp +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.hpp @@ -5,6 +5,7 @@ #include "base.hpp" #include "namespaces.hpp" +#include "profile_pic.hpp" namespace session::config { @@ -14,13 +15,6 @@ namespace session::config { /// p - user profile url /// q - user profile decryption key (binary) -// Profile pic info. Note that `url` is null terminated (though the null lies just beyond the end -// of the string view: that is, it views into a full std::string). -struct profile_pic { - std::string_view url; - ustring_view key; -}; - class UserProfile final : public ConfigBase { public: @@ -31,12 +25,13 @@ class UserProfile final : public ConfigBase { /// key for generating the data encryption key. To construct a blank profile (i.e. with no /// pre-existing dumped data to load) pass `std::nullopt` as the second argument. /// - /// \param ed25519_secretkey - contains the libsodium secret key used to encrypt/decrypt user - /// profile messages; these can either be the full 64-byte value (which is technically the - /// 32-byte seed followed by the 32-byte pubkey), or just the 32-byte seed of the secret key. + /// \param ed25519_secretkey - contains the libsodium secret key used to encrypt/decrypt the + /// data when pushing/pulling from the swarm. This can either be the full 64-byte value (which + /// is technically the 32-byte seed followed by the 32-byte pubkey), or just the 32-byte seed of + /// the secret key. /// - /// \param dumped - either `std::nullopt` to construct a new, empty user profile; or binary - /// state data that was previously dumped from a UserProfile object by calling `dump()`. + /// \param dumped - either `std::nullopt` to construct a new, empty object; or binary state data + /// that was previously dumped from an instance of this class by calling `dump()`. UserProfile(ustring_view ed25519_secretkey, std::optional dumped); Namespace storage_namespace() const override { return Namespace::UserProfile; } @@ -44,7 +39,7 @@ class UserProfile final : public ConfigBase { const char* encryption_domain() const override { return "UserProfile"; } /// Returns the user profile name, or std::nullopt if there is no profile name set. - const std::optional get_name() const; + std::optional get_name() const; /// Sets the user profile name; if given an empty string then the name is removed. void set_name(std::string_view new_name); @@ -57,9 +52,6 @@ class UserProfile final : public ConfigBase { /// one is empty. void set_profile_pic(std::string_view url, ustring_view key); void set_profile_pic(profile_pic pic); - - private: - void load_key(ustring_view ed25519_secretkey); }; } // namespace session::config diff --git a/SessionMessagingKit/Messages/Control Messages/SharedConfigMessage.swift b/SessionMessagingKit/Messages/Control Messages/SharedConfigMessage.swift index ad4054fb4..96bb65f4c 100644 --- a/SessionMessagingKit/Messages/Control Messages/SharedConfigMessage.swift +++ b/SessionMessagingKit/Messages/Control Messages/SharedConfigMessage.swift @@ -21,10 +21,12 @@ public final class SharedConfigMessage: ControlMessage { public enum Kind: CustomStringConvertible, Codable { case userProfile + case contacts public var description: String { switch self { case .userProfile: return "userProfile" + case .contacts: return "contacts" } } } @@ -74,6 +76,7 @@ public final class SharedConfigMessage: ControlMessage { kind: { switch sharedConfigMessage.kind { case .userProfile: return .userProfile + case .contacts: return .contacts } }(), seqNo: sharedConfigMessage.seqno, @@ -87,6 +90,7 @@ public final class SharedConfigMessage: ControlMessage { kind: { switch self.kind { case .userProfile: return .userProfile + case .contacts: return .contacts } }(), seqno: self.seqNo, @@ -121,6 +125,7 @@ public extension SharedConfigMessage.Kind { var configDumpVariant: ConfigDump.Variant { switch self { case .userProfile: return .userProfile + case .contacts: return .contacts } } } diff --git a/SessionMessagingKit/Protos/Generated/SNProto.swift b/SessionMessagingKit/Protos/Generated/SNProto.swift index 26060b939..b13cf6585 100644 --- a/SessionMessagingKit/Protos/Generated/SNProto.swift +++ b/SessionMessagingKit/Protos/Generated/SNProto.swift @@ -3714,17 +3714,20 @@ extension SNProtoAttachmentPointer.SNProtoAttachmentPointerBuilder { @objc public enum SNProtoSharedConfigMessageKind: Int32 { case userProfile = 1 + case contacts = 2 } private class func SNProtoSharedConfigMessageKindWrap(_ value: SessionProtos_SharedConfigMessage.Kind) -> SNProtoSharedConfigMessageKind { switch value { case .userProfile: return .userProfile + case .contacts: return .contacts } } private class func SNProtoSharedConfigMessageKindUnwrap(_ value: SNProtoSharedConfigMessageKind) -> SessionProtos_SharedConfigMessage.Kind { switch value { case .userProfile: return .userProfile + case .contacts: return .contacts } } diff --git a/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift b/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift index 0d8cd6e48..3dd86bfb7 100644 --- a/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift +++ b/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift @@ -1621,6 +1621,7 @@ struct SessionProtos_SharedConfigMessage { enum Kind: SwiftProtobuf.Enum { typealias RawValue = Int case userProfile // = 1 + case contacts // = 2 init() { self = .userProfile @@ -1629,6 +1630,7 @@ struct SessionProtos_SharedConfigMessage { init?(rawValue: Int) { switch rawValue { case 1: self = .userProfile + case 2: self = .contacts default: return nil } } @@ -1636,6 +1638,7 @@ struct SessionProtos_SharedConfigMessage { var rawValue: Int { switch self { case .userProfile: return 1 + case .contacts: return 2 } } @@ -3332,5 +3335,6 @@ extension SessionProtos_SharedConfigMessage: SwiftProtobuf.Message, SwiftProtobu extension SessionProtos_SharedConfigMessage.Kind: SwiftProtobuf._ProtoNameProviding { static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "USER_PROFILE"), + 2: .same(proto: "CONTACTS"), ] } diff --git a/SessionMessagingKit/Protos/SessionProtos.proto b/SessionMessagingKit/Protos/SessionProtos.proto index 13ec388c2..839dd78b6 100644 --- a/SessionMessagingKit/Protos/SessionProtos.proto +++ b/SessionMessagingKit/Protos/SessionProtos.proto @@ -275,6 +275,7 @@ message AttachmentPointer { message SharedConfigMessage { enum Kind { USER_PROFILE = 1; + CONTACTS = 2; } // @required diff --git a/SessionMessagingKitTests/LibSessionUtil/ConfigContactsSpec.swift b/SessionMessagingKitTests/LibSessionUtil/ConfigContactsSpec.swift new file mode 100644 index 000000000..b2f9c7744 --- /dev/null +++ b/SessionMessagingKitTests/LibSessionUtil/ConfigContactsSpec.swift @@ -0,0 +1,324 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import Sodium +import SessionUtil +import SessionUtilitiesKit + +import Quick +import Nimble + +/// This spec is designed to replicate the initial test cases for the libSession-util to ensure the behaviour matches +class ConfigContactsSpec: QuickSpec { + // MARK: - Spec + + override func spec() { + it("generates Contact configs correctly") { + let seed: Data = Data(hex: "0123456789abcdef0123456789abcdef") + + // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately + let identity = try! Identity.generate(from: seed) + var edSK: [UInt8] = identity.ed25519KeyPair.secretKey + expect(edSK.toHexString().suffix(64)) + .to(equal("4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7")) + expect(identity.x25519KeyPair.publicKey.toHexString()) + .to(equal("d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72")) + expect(String(edSK.toHexString().prefix(32))).to(equal(seed.toHexString())) + + // Initialize a brand new, empty config because we have no dump data to deal with. + let error: UnsafeMutablePointer? = nil + var conf: UnsafeMutablePointer? = nil + expect(contacts_init(&conf, &edSK, nil, 0, error)).to(equal(0)) + error?.deallocate() + + // Empty contacts shouldn't have an existing contact + var definitelyRealId: [CChar] = "050000000000000000000000000000000000000000000000000000000000000000" + .bytes + .map { CChar(bitPattern: $0) } + let contactPtr: UnsafeMutablePointer? = nil + expect(contacts_get(conf, contactPtr, &definitelyRealId)).to(beFalse()) + + var contact2: contacts_contact = contacts_contact() + expect(contacts_get_or_create(conf, &contact2, &definitelyRealId)).to(beTrue()) + expect(contact2.name).to(beNil()) + expect(contact2.nickname).to(beNil()) + expect(contact2.approved).to(beFalse()) + expect(contact2.approved_me).to(beFalse()) + expect(contact2.blocked).to(beFalse()) + expect(contact2.profile_pic).toNot(beNil()) // Creates an empty instance apparently + expect(contact2.profile_pic.url).to(beNil()) + expect(contact2.profile_pic.key).to(beNil()) + expect(contact2.profile_pic.keylen).to(equal(0)) + + // We don't need to push anything, since this is a default contact + expect(config_needs_push(conf)).to(beFalse()) + // And we haven't changed anything so don't need to dump to db + expect(config_needs_dump(conf)).to(beFalse()) + + var toPush: UnsafeMutablePointer? = nil + var toPushLen: Int = 0 + // We don't need to push since we haven't changed anything, so this call is mainly just for + // testing: + let seqno: Int64 = config_push(conf, &toPush, &toPushLen) + expect(toPush).toNot(beNil()) + expect(seqno).to(equal(0)) + expect(toPushLen).to(equal(256)) + + // Update the contact data + let contact2Name: [CChar] = "Joe" + .bytes + .map { CChar(bitPattern: $0) } + let contact2Nickname: [CChar] = "Joey" + .bytes + .map { CChar(bitPattern: $0) } + contact2Name.withUnsafeBufferPointer { contact2.name = $0.baseAddress } + contact2Nickname.withUnsafeBufferPointer { contact2.nickname = $0.baseAddress } + contact2.approved = true + contact2.approved_me = true + + // Update the contact + contacts_set(conf, &contact2) + + // Ensure the contact details were updated + var contact3: contacts_contact = contacts_contact() + expect(contacts_get(conf, &contact3, &definitelyRealId)).to(beTrue()) + expect(String(cString: contact3.name)).to(equal("Joe")) + expect(String(cString: contact3.nickname)).to(equal("Joey")) + expect(contact3.approved).to(beTrue()) + expect(contact3.approved_me).to(beTrue()) + expect(contact3.profile_pic).toNot(beNil()) // Creates an empty instance apparently + expect(contact3.profile_pic.url).to(beNil()) + expect(contact3.profile_pic.key).to(beNil()) + expect(contact3.profile_pic.keylen).to(equal(0)) + expect(contact3.blocked).to(beFalse()) + + let contact3SessionId: [CChar] = withUnsafeBytes(of: contact3.session_id) { [UInt8]($0) } + .map { CChar($0) } + expect(contact3SessionId).to(equal(definitelyRealId.nullTerminated())) + + // Since we've made changes, we should need to push new config to the swarm, *and* should need + // to dump the updated state: + expect(config_needs_push(conf)).to(beTrue()) + expect(config_needs_dump(conf)).to(beTrue()) + + var toPush2: UnsafeMutablePointer? = nil + var toPush2Len: Int = 0 + let seqno2: Int64 = config_push(conf, &toPush2, &toPush2Len); + // incremented since we made changes (this only increments once between + // dumps; even though we changed multiple fields here). + expect(seqno2).to(equal(1)) + toPush2?.deallocate() + + // Pretend we uploaded it + config_confirm_pushed(conf, seqno2) + expect(config_needs_push(conf)).to(beFalse()) + expect(config_needs_dump(conf)).to(beTrue()) + + // NB: Not going to check encrypted data and decryption here because that's general (not + // specific to contacts) and is covered already in the user profile tests. + var dump1: UnsafeMutablePointer? = nil + var dump1Len: Int = 0 + config_dump(conf, &dump1, &dump1Len) + + let error2: UnsafeMutablePointer? = nil + var conf2: UnsafeMutablePointer? = nil + expect(contacts_init(&conf2, &edSK, dump1, dump1Len, error2)).to(equal(0)) + error?.deallocate() + dump1?.deallocate() + + expect(config_needs_push(conf2)).to(beFalse()) + expect(config_needs_dump(conf2)).to(beFalse()) + + var toPush3: UnsafeMutablePointer? = nil + var toPush3Len: Int = 0 + let seqno3: Int64 = config_push(conf, &toPush3, &toPush3Len); + expect(seqno3).to(equal(1)) + toPush3?.deallocate() + + // Because we just called dump() above, to load up contacts2 + expect(config_needs_dump(conf)).to(beFalse()) + + // Ensure the contact details were updated + var contact4: contacts_contact = contacts_contact() + expect(contacts_get(conf2, &contact4, &definitelyRealId)).to(beTrue()) + expect(String(cString: contact4.name)).to(equal("Joe")) + expect(String(cString: contact4.nickname)).to(equal("Joey")) + expect(contact4.approved).to(beTrue()) + expect(contact4.approved_me).to(beTrue()) + expect(contact4.profile_pic).toNot(beNil()) // Creates an empty instance apparently + expect(contact4.profile_pic.url).to(beNil()) + expect(contact4.profile_pic.key).to(beNil()) + expect(contact4.profile_pic.keylen).to(equal(0)) + expect(contact4.blocked).to(beFalse()) + + var anotherId: [CChar] = "051111111111111111111111111111111111111111111111111111111111111111" + .bytes + .map { CChar(bitPattern: $0) } + var contact5: contacts_contact = contacts_contact() + expect(contacts_get_or_create(conf2, &contact5, &anotherId)).to(beTrue()) + expect(contact5.name).to(beNil()) + expect(contact5.nickname).to(beNil()) + expect(contact5.approved).to(beFalse()) + expect(contact5.approved_me).to(beFalse()) + expect(contact5.profile_pic).toNot(beNil()) // Creates an empty instance apparently + expect(contact5.profile_pic.url).to(beNil()) + expect(contact5.profile_pic.key).to(beNil()) + expect(contact5.profile_pic.keylen).to(equal(0)) + expect(contact5.blocked).to(beFalse()) + + // We're not setting any fields, but we should still keep a record of the session id + contacts_set(conf2, &contact5) + expect(config_needs_push(conf2)).to(beTrue()) + + var toPush4: UnsafeMutablePointer? = nil + var toPush4Len: Int = 0 + let seqno4: Int64 = config_push(conf2, &toPush4, &toPush4Len); + expect(seqno4).to(equal(2)) + + // Check the merging + var mergeData: [UnsafePointer?] = [UnsafePointer(toPush4)] + var mergeSize: [Int] = [toPush4Len] + expect(config_merge(conf, &mergeData, &mergeSize, 1)).to(equal(1)) + config_confirm_pushed(conf2, seqno4) + toPush4?.deallocate() + + expect(config_needs_push(conf)).to(beFalse()) + + var toPush5: UnsafeMutablePointer? = nil + var toPush5Len: Int = 0 + let seqno5: Int64 = config_push(conf2, &toPush5, &toPush5Len); + expect(seqno5).to(equal(2)) + toPush5?.deallocate() + + // Iterate through and make sure we got everything we expected + var sessionIds: [String] = [] + var nicknames: [String] = [] + var contact6: contacts_contact = contacts_contact() + let contactIterator: UnsafeMutablePointer = contacts_iterator_new(conf) + while !contacts_iterator_done(contactIterator, &contact6) { + sessionIds.append( + String(cString: withUnsafeBytes(of: contact6.session_id) { [UInt8]($0) } + .map { CChar($0) } + .nullTerminated() + ) + ) + nicknames.append( + contact6.nickname.map { String(cString: $0) } ?? + "(N/A)" + ) + contacts_iterator_advance(contactIterator) + } + contacts_iterator_free(contactIterator) // Need to free the iterator + + expect(sessionIds.count).to(equal(2)) + expect(sessionIds.first).to(equal(String(cString: definitelyRealId.nullTerminated()))) + expect(sessionIds.last).to(equal(String(cString: anotherId.nullTerminated()))) + expect(nicknames.first).to(equal("Joey")) + expect(nicknames.last).to(equal("(N/A)")) + + // Conflict! Oh no! + + // On client 1 delete a contact: + contacts_erase(conf, definitelyRealId) + + // Client 2 adds a new friend: + var thirdId: [CChar] = "052222222222222222222222222222222222222222222222222222222222222222" + .bytes + .map { CChar(bitPattern: $0) } + let nickname7: [CChar] = "Nickname 3" + .bytes + .map { CChar(bitPattern: $0) } + let profileUrl7: [CChar] = "http://example.com/huge.bmp" + .bytes + .map { CChar(bitPattern: $0) } + let profileKey7: [UInt8] = "qwerty".bytes + var contact7: contacts_contact = contacts_contact() + expect(contacts_get_or_create(conf2, &contact7, &thirdId)).to(beTrue()) + nickname7.withUnsafeBufferPointer { contact7.nickname = $0.baseAddress } + contact7.approved = true + contact7.approved_me = true + profileUrl7.withUnsafeBufferPointer { contact7.profile_pic.url = $0.baseAddress } + profileKey7.withUnsafeBufferPointer { contact7.profile_pic.key = $0.baseAddress } + contact7.profile_pic.keylen = 6 + contacts_set(conf2, &contact7) + + expect(config_needs_push(conf)).to(beTrue()) + expect(config_needs_push(conf2)).to(beTrue()) + + var toPush6: UnsafeMutablePointer? = nil + var toPush6Len: Int = 0 + let seqno6: Int64 = config_push(conf, &toPush6, &toPush6Len); + expect(seqno6).to(equal(3)) + + var toPush7: UnsafeMutablePointer? = nil + var toPush7Len: Int = 0 + let seqno7: Int64 = config_push(conf2, &toPush7, &toPush7Len); + expect(seqno7).to(equal(3)) + + expect(String(pointer: toPush6, length: toPush6Len, encoding: .ascii)) + .toNot(equal(String(pointer: toPush7, length: toPush7Len, encoding: .ascii))) + + config_confirm_pushed(conf, seqno6) + config_confirm_pushed(conf2, seqno7) + + var mergeData2: [UnsafePointer?] = [UnsafePointer(toPush7)] + var mergeSize2: [Int] = [toPush7Len] + expect(config_merge(conf, &mergeData2, &mergeSize2, 1)).to(equal(1)) + expect(config_needs_push(conf)).to(beTrue()) + + var mergeData3: [UnsafePointer?] = [UnsafePointer(toPush6)] + var mergeSize3: [Int] = [toPush6Len] + expect(config_merge(conf2, &mergeData3, &mergeSize3, 1)).to(equal(1)) + expect(config_needs_push(conf2)).to(beTrue()) + toPush6?.deallocate() + toPush7?.deallocate() + + var toPush8: UnsafeMutablePointer? = nil + var toPush8Len: Int = 0 + let seqno8: Int64 = config_push(conf, &toPush8, &toPush8Len); + expect(seqno8).to(equal(4)) + + var toPush9: UnsafeMutablePointer? = nil + var toPush9Len: Int = 0 + let seqno9: Int64 = config_push(conf2, &toPush9, &toPush9Len); + expect(seqno9).to(equal(seqno8)) + + expect(String(pointer: toPush8, length: toPush8Len, encoding: .ascii)) + .to(equal(String(pointer: toPush9, length: toPush9Len, encoding: .ascii))) + toPush8?.deallocate() + toPush9?.deallocate() + + config_confirm_pushed(conf, seqno8) + config_confirm_pushed(conf2, seqno9) + + expect(config_needs_push(conf)).to(beFalse()) + expect(config_needs_push(conf2)).to(beFalse()) + + // Validate the changes + var sessionIds2: [String] = [] + var nicknames2: [String] = [] + var contact8: contacts_contact = contacts_contact() + let contactIterator2: UnsafeMutablePointer = contacts_iterator_new(conf) + while !contacts_iterator_done(contactIterator2, &contact8) { + sessionIds2.append( + String(cString: withUnsafeBytes(of: contact8.session_id) { [UInt8]($0) } + .map { CChar($0) } + .nullTerminated() + ) + ) + nicknames2.append( + contact8.nickname.map { String(cString: $0) } ?? + "(N/A)" + ) + contacts_iterator_advance(contactIterator2) + } + contacts_iterator_free(contactIterator2) // Need to free the iterator + + expect(sessionIds2.count).to(equal(2)) + expect(sessionIds2.first).to(equal(String(cString: anotherId.nullTerminated()))) + expect(sessionIds2.last).to(equal(String(cString: thirdId.nullTerminated()))) + expect(nicknames2.first).to(equal("(N/A)")) + expect(nicknames2.last).to(equal("Nickname 3")) + } + } +} diff --git a/SessionMessagingKitTests/LibSessionUtil/ConfigUserProfileSpec.swift b/SessionMessagingKitTests/LibSessionUtil/ConfigUserProfileSpec.swift index 93ba46791..ef9324420 100644 --- a/SessionMessagingKitTests/LibSessionUtil/ConfigUserProfileSpec.swift +++ b/SessionMessagingKitTests/LibSessionUtil/ConfigUserProfileSpec.swift @@ -13,7 +13,7 @@ class ConfigUserProfileSpec: QuickSpec { // MARK: - Spec override func spec() { - it("generates configs correctly") { + it("generates UserProfile configs correctly") { let seed: Data = Data(hex: "0123456789abcdef0123456789abcdef") // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately @@ -309,11 +309,11 @@ class ConfigUserProfileSpec: QuickSpec { // down more than one). var mergeData2: [UnsafePointer?] = [UnsafePointer(toPush3)] var mergeSize2: [Int] = [toPush3Len] - config_merge(conf2, &mergeData2, &mergeSize2, 1) + expect(config_merge(conf2, &mergeData2, &mergeSize2, 1)).to(equal(1)) toPush3?.deallocate() var mergeData3: [UnsafePointer?] = [UnsafePointer(toPush4)] var mergeSize3: [Int] = [toPush4Len] - config_merge(conf, &mergeData3, &mergeSize3, 1) + expect(config_merge(conf, &mergeData3, &mergeSize3, 1)).to(equal(1)) toPush4?.deallocate() // Now after the merge we *will* want to push from both client, since both will have generated a diff --git a/SessionUtilitiesKit/General/Array+Utilities.swift b/SessionUtilitiesKit/General/Array+Utilities.swift index cf350e86d..85e9cd6d6 100644 --- a/SessionUtilitiesKit/General/Array+Utilities.swift +++ b/SessionUtilitiesKit/General/Array+Utilities.swift @@ -63,3 +63,11 @@ public extension Array where Element == String { return self.reversed() } } + +public extension Array where Element == CChar { + func nullTerminated() -> [Element] { + guard self.last != CChar(0) else { return self } + + return self.appending(CChar(0)) + } +} From 8f3dcbc6be09331a9152127fad04cf56019b18d0 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 16 Dec 2022 16:51:08 +1100 Subject: [PATCH 020/135] Sorted out a bunch of the config syncing logic Updated the onboarding to attempt to retrieve the current user profile config and skip display name collection if it already exists Updated the logic to get the snode pool and build paths immediately on launch even if the user hasn't been created yet (faster onboarding) Removed the iOS-specific concurrent dual snode '/store' behaviour Cleaned up the profile updating logic Fixed an issue where the pollers could end up deadlocking the main thread if too many tried to start concurrently --- Session.xcodeproj/project.pbxproj | 24 +- .../ConversationVC+Interaction.swift | 37 +- .../Conversations/ConversationViewModel.swift | 6 +- .../Settings/ThreadSettingsViewModel.swift | 14 +- Session/Home/HomeVC.swift | 35 +- .../MessageRequestsViewModel.swift | 26 +- Session/Meta/AppDelegate.swift | 11 +- Session/Onboarding/DisplayNameVC.swift | 33 +- Session/Onboarding/LinkDeviceVC.swift | 37 +- Session/Onboarding/Onboarding.swift | 120 ++++-- Session/Onboarding/PNModeVC.swift | 86 +++- Session/Onboarding/RegisterVC.swift | 15 +- Session/Onboarding/RestoreVC.swift | 35 +- .../Settings/BlockedContactsViewModel.swift | 5 +- Session/Settings/ImagePickerHandler.swift | 11 +- Session/Settings/NukeDataModal.swift | 3 +- Session/Settings/SettingsViewModel.swift | 62 ++- SessionMessagingKit/Configuration.swift | 3 +- .../Database/LegacyDatabase/SMKLegacy.swift | 4 +- .../Migrations/_003_YDBToGRDBMigration.swift | 12 +- .../Migrations/_011_SharedUtilChanges.swift | 61 ++- .../Database/Models/BlindedIdLookup.swift | 2 +- .../Database/Models/Contact.swift | 25 +- .../Database/Models/Profile.swift | 22 +- .../Database/Models/SharedConfigDump.swift | 51 ++- .../Jobs/Types/ConfigurationSyncJob.swift | 355 +++++++++++++++++ .../Jobs/Types/MessageReceiveJob.swift | 6 +- .../Jobs/Types/SendReadReceiptsJob.swift | 8 +- .../Jobs/Types/UpdateProfilePictureJob.swift | 7 +- .../SessionUtil+Contacts.swift | 366 ++++++++++++++++++ .../SessionUtil+UserProfile.swift | 64 ++- .../QueryInterfaceRequest+Utilities.swift | 73 ++++ .../LibSessionUtil/SessionUtil.swift | 357 +++++++++++++---- .../Messages/Message+Destination.swift | 77 +--- SessionMessagingKit/Messages/Message.swift | 2 +- .../Open Groups/Models/SOGSBatchRequest.swift | 19 +- .../Open Groups/OpenGroupAPI.swift | 47 ++- .../Open Groups/OpenGroupManager.swift | 58 +-- ...essageReceiver+ConfigurationMessages.swift | 102 +++-- .../MessageReceiver+MessageRequests.swift | 44 ++- .../MessageReceiver+VisibleMessages.swift | 19 +- .../MessageSender+ClosedGroups.swift | 11 +- .../Sending & Receiving/MessageReceiver.swift | 78 ---- .../MessageSender+Convenience.swift | 87 +---- .../MessageSender+Encryption.swift | 15 +- .../Sending & Receiving/MessageSender.swift | 171 ++++---- .../Notification+MessageReceiver.swift | 2 + .../Pollers/CurrentUserPoller.swift | 2 +- .../Pollers/OpenGroupPoller.swift | 36 +- .../Sending & Receiving/Pollers/Poller.swift | 18 +- .../Shared Models/MessageViewModel.swift | 14 +- .../Utilities/ProfileManager.swift | 279 +++++++++---- .../Utilities/ProfileManagerError.swift | 2 + .../NotificationServiceExtension.swift | 10 +- .../ShareNavController.swift | 4 +- SessionSnodeKit/Jobs/GetSnodePoolJob.swift | 10 +- .../Models/SendMessageRequest.swift | 2 +- .../Networking/OnionRequestAPI.swift | 2 +- SessionSnodeKit/Networking/SnodeAPI.swift | 160 ++++++-- SessionSnodeKit/Types/SnodeAPINamespace.swift | 6 +- .../Database/Models/Identity.swift | 14 +- SessionUtilitiesKit/Database/Models/Job.swift | 4 + SessionUtilitiesKit/General/Features.swift | 12 +- SessionUtilitiesKit/JobRunner/JobRunner.swift | 3 +- SessionUtilitiesKit/Media/Updatable.swift | 121 ------ .../Networking/BatchResponse.swift | 44 ++- SignalUtilitiesKit/Utilities/AppSetup.swift | 9 +- 67 files changed, 2314 insertions(+), 1146 deletions(-) create mode 100644 SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift create mode 100644 SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift create mode 100644 SessionMessagingKit/LibSessionUtil/Database/QueryInterfaceRequest+Utilities.swift delete mode 100644 SessionUtilitiesKit/Media/Updatable.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 317003eb6..61968cf3f 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -502,7 +502,6 @@ FD09797527FAB64300936362 /* ProfileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09797327FAB3E200936362 /* ProfileManager.swift */; }; FD09797727FAB7A600936362 /* Data+Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09797627FAB7A600936362 /* Data+Image.swift */; }; FD09797927FAB7E800936362 /* ImageFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09797827FAB7E800936362 /* ImageFormat.swift */; }; - FD09797B27FBB25900936362 /* Updatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09797A27FBB25900936362 /* Updatable.swift */; }; FD09797D27FBDB2000936362 /* Notification+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09797C27FBDB2000936362 /* Notification+Utilities.swift */; }; FD09798127FCFEE800936362 /* SessionThread.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09798027FCFEE800936362 /* SessionThread.swift */; }; FD09798327FD1A1500936362 /* ClosedGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09798227FD1A1500936362 /* ClosedGroup.swift */; }; @@ -591,6 +590,9 @@ FD2AAAF128ED57B500A49611 /* SynchronousStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2AAAEF28ED57B500A49611 /* SynchronousStorage.swift */; }; FD2AAAF228ED57B500A49611 /* SynchronousStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2AAAEF28ED57B500A49611 /* SynchronousStorage.swift */; }; FD2B4AFB29429D1000AB4848 /* ConfigContactsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2B4AFA29429D1000AB4848 /* ConfigContactsSpec.swift */; }; + FD2B4AFD294688D000AB4848 /* SessionUtil+Contacts.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2B4AFC294688D000AB4848 /* SessionUtil+Contacts.swift */; }; + FD2B4AFF2946C93200AB4848 /* ConfigurationSyncJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2B4AFE2946C93200AB4848 /* ConfigurationSyncJob.swift */; }; + FD2B4B042949887A00AB4848 /* QueryInterfaceRequest+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2B4B032949887A00AB4848 /* QueryInterfaceRequest+Utilities.swift */; }; FD37E9C328A1C6F3003AE748 /* ThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9C228A1C6F3003AE748 /* ThemeManager.swift */; }; FD37E9C628A1D4EC003AE748 /* Theme+ClassicDark.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9C528A1D4EC003AE748 /* Theme+ClassicDark.swift */; }; FD37E9C828A1D73F003AE748 /* Theme+Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9C728A1D73F003AE748 /* Theme+Colors.swift */; }; @@ -1656,7 +1658,6 @@ FD09797327FAB3E200936362 /* ProfileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileManager.swift; sourceTree = ""; }; FD09797627FAB7A600936362 /* Data+Image.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Image.swift"; sourceTree = ""; }; FD09797827FAB7E800936362 /* ImageFormat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageFormat.swift; sourceTree = ""; }; - FD09797A27FBB25900936362 /* Updatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Updatable.swift; sourceTree = ""; }; FD09797C27FBDB2000936362 /* Notification+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Notification+Utilities.swift"; sourceTree = ""; }; FD09798027FCFEE800936362 /* SessionThread.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionThread.swift; sourceTree = ""; }; FD09798227FD1A1500936362 /* ClosedGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClosedGroup.swift; sourceTree = ""; }; @@ -1707,6 +1708,9 @@ FD28A4F527EAD44C00FF65E7 /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = ""; }; FD2AAAEF28ED57B500A49611 /* SynchronousStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SynchronousStorage.swift; sourceTree = ""; }; FD2B4AFA29429D1000AB4848 /* ConfigContactsSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigContactsSpec.swift; sourceTree = ""; }; + FD2B4AFC294688D000AB4848 /* SessionUtil+Contacts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionUtil+Contacts.swift"; sourceTree = ""; }; + FD2B4AFE2946C93200AB4848 /* ConfigurationSyncJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationSyncJob.swift; sourceTree = ""; }; + FD2B4B032949887A00AB4848 /* QueryInterfaceRequest+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QueryInterfaceRequest+Utilities.swift"; sourceTree = ""; }; FD37E9C228A1C6F3003AE748 /* ThemeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = ""; }; FD37E9C528A1D4EC003AE748 /* Theme+ClassicDark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+ClassicDark.swift"; sourceTree = ""; }; FD37E9C728A1D73F003AE748 /* Theme+Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+Colors.swift"; sourceTree = ""; }; @@ -2550,7 +2554,6 @@ C33FDB22255A580900E217F9 /* OWSMediaUtils.swift */, C33FDB1C255A580900E217F9 /* UIImage+OWS.h */, C33FDB81255A581100E217F9 /* UIImage+OWS.m */, - FD09797A27FBB25900936362 /* Updatable.swift */, ); path = Media; sourceTree = ""; @@ -3719,6 +3722,14 @@ path = Utilities; sourceTree = ""; }; + FD2B4B022949886900AB4848 /* Database */ = { + isa = PBXGroup; + children = ( + FD2B4B032949887A00AB4848 /* QueryInterfaceRequest+Utilities.swift */, + ); + path = Database; + sourceTree = ""; + }; FD37E9C428A1C701003AE748 /* Themes */ = { isa = PBXGroup; children = ( @@ -4005,6 +4016,7 @@ FD8ECF7529340F4800C0D1BB /* LibSessionUtil */ = { isa = PBXGroup; children = ( + FD2B4B022949886900AB4848 /* Database */, FD8ECF8E29381FB200C0D1BB /* Config Handling */, FD8ECF7829340F7100C0D1BB /* libsession-util.xcframework */, FD8ECF882935AB7200C0D1BB /* SessionUtilError.swift */, @@ -4026,6 +4038,7 @@ isa = PBXGroup; children = ( FD8ECF8F29381FC200C0D1BB /* SessionUtil+UserProfile.swift */, + FD2B4AFC294688D000AB4848 /* SessionUtil+Contacts.swift */, ); path = "Config Handling"; sourceTree = ""; @@ -4199,6 +4212,7 @@ FDF0B74E28079E5E004C14C5 /* SendReadReceiptsJob.swift */, C352A348255781F400338F3E /* AttachmentDownloadJob.swift */, C352A35A2557824E00338F3E /* AttachmentUploadJob.swift */, + FD2B4AFE2946C93200AB4848 /* ConfigurationSyncJob.swift */, ); path = Types; sourceTree = ""; @@ -5468,7 +5482,6 @@ FD17D7EA27F6A1C600122BE0 /* SUKLegacy.swift in Sources */, FDA8EB10280F8238002B68E5 /* Codable+Utilities.swift in Sources */, C352A36D2557858E00338F3E /* NSTimer+Proxying.m in Sources */, - FD09797B27FBB25900936362 /* Updatable.swift in Sources */, 7B7CB192271508AD0079FF93 /* CallRingTonePlayer.swift in Sources */, C3C2ABD22553C6C900C340D1 /* Data+SecureRandom.swift in Sources */, FD848B8B283DC509000E298B /* PagedDatabaseObserver.swift in Sources */, @@ -5623,7 +5636,9 @@ FDC4386527B4DE7600C60D73 /* RoomPollInfo.swift in Sources */, FD245C6B2850667400B966DD /* VisibleMessage+Profile.swift in Sources */, FD37EA0F28AB3330003AE748 /* _006_FixHiddenModAdminSupport.swift in Sources */, + FD2B4AFD294688D000AB4848 /* SessionUtil+Contacts.swift in Sources */, 7B81682328A4C1210069F315 /* UpdateTypes.swift in Sources */, + FD2B4AFF2946C93200AB4848 /* ConfigurationSyncJob.swift in Sources */, FDC438A627BB113A00C60D73 /* UserUnbanRequest.swift in Sources */, FD5C72FB284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift in Sources */, FDC4386727B4E10E00C60D73 /* Capabilities.swift in Sources */, @@ -5728,6 +5743,7 @@ C32C5DBF256DD743003C73A2 /* ClosedGroupPoller.swift in Sources */, C352A35B2557824E00338F3E /* AttachmentUploadJob.swift in Sources */, FD5C7305284F0FF30029977D /* MessageReceiver+VisibleMessages.swift in Sources */, + FD2B4B042949887A00AB4848 /* QueryInterfaceRequest+Utilities.swift in Sources */, FD09797027FA6FF300936362 /* Profile.swift in Sources */, FD245C56285065EA00B966DD /* SNProto.swift in Sources */, FD09798B27FD1CFE00936362 /* Capability.swift in Sources */, diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 766689672..92c50bb62 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -1837,7 +1837,7 @@ extension ConversationVC: message: unsendRequest, threadId: threadId, interactionId: nil, - to: .contact(publicKey: userPublicKey, namespace: .default) + to: .contact(publicKey: userPublicKey) ) } return @@ -1856,7 +1856,7 @@ extension ConversationVC: message: unsendRequest, threadId: threadId, interactionId: nil, - to: .contact(publicKey: userPublicKey, namespace: .default) + to: .contact(publicKey: userPublicKey) ) } self?.showInputAccessoryView() @@ -2303,8 +2303,8 @@ extension ConversationVC { return } - Storage.shared.writeAsync( - updates: { db in + Storage.shared + .writePublisher { db in // If we aren't creating a new thread (ie. sending a message request) then send a // messageRequestResponse back to the sender (this allows the sender to know that // they have been approved and can now use this contact in closed groups) @@ -2321,21 +2321,22 @@ extension ConversationVC { } // Default 'didApproveMe' to true for the person approving the message request - try approvalData.contact - .with( - isApproved: true, - didApproveMe: .update(approvalData.contact.didApproveMe || !isNewThread) + try approvalData.contact.save(db) + try Contact + .filter(id: approvalData.contact.id) + .updateAllAndConfig( + db, + Contact.Columns.isApproved.set(to: true), + Contact.Columns.didApproveMe + .set(to: approvalData.contact.didApproveMe || !isNewThread) ) - .save(db) - - - // Update the config with the approved contact - try MessageSender - .syncConfiguration(db, forceSyncNow: true) - .sinkUntilComplete() - }, - completion: { _, _ in updateNavigationBackStack() } - ) + } + .sinkUntilComplete( + receiveCompletion: { _ in + // Update the UI + updateNavigationBackStack() + } + ) } @objc func acceptMessageRequest() { diff --git a/Session/Conversations/ConversationViewModel.swift b/Session/Conversations/ConversationViewModel.swift index 43134d1b3..c5a974041 100644 --- a/Session/Conversations/ConversationViewModel.swift +++ b/Session/Conversations/ConversationViewModel.swift @@ -481,11 +481,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { Storage.shared.writeAsync { db in try Contact .filter(id: threadId) - .updateAll(db, Contact.Columns.isBlocked.set(to: false)) - - try MessageSender - .syncConfiguration(db, forceSyncNow: true) - .sinkUntilComplete() + .updateAllAndConfig(db, Contact.Columns.isBlocked.set(to: false)) } } diff --git a/Session/Conversations/Settings/ThreadSettingsViewModel.swift b/Session/Conversations/Settings/ThreadSettingsViewModel.swift index 2f225fabe..77e4c617f 100644 --- a/Session/Conversations/Settings/ThreadSettingsViewModel.swift +++ b/Session/Conversations/Settings/ThreadSettingsViewModel.swift @@ -152,7 +152,7 @@ class ThreadSettingsViewModel: SessionTableViewModel (7 * 24 * 60 * 60) else { return } // Sync every 2 days Storage.shared - .writePublisherFlatMap { db in try MessageSender.syncConfiguration(db, forceSyncNow: false) } - .sinkUntilComplete( - receiveCompletion: { result in + .writeAsync( + updates: { db in ConfigurationSyncJob.enqueue(db) }, + completion: { _, result in switch result { case .failure: break - case .finished: + case .success: // Only update the 'lastConfigurationSync' timestamp if we have done the // first sync (Don't want a new device config sync to override config // syncs from other devices) diff --git a/Session/Onboarding/DisplayNameVC.swift b/Session/Onboarding/DisplayNameVC.swift index cf0fc7028..462d322d7 100644 --- a/Session/Onboarding/DisplayNameVC.swift +++ b/Session/Onboarding/DisplayNameVC.swift @@ -6,11 +6,25 @@ import SessionMessagingKit import SignalUtilitiesKit final class DisplayNameVC: BaseVC { + private let flow: Onboarding.Flow + private var spacer1HeightConstraint: NSLayoutConstraint! private var spacer2HeightConstraint: NSLayoutConstraint! private var registerButtonBottomOffsetConstraint: NSLayoutConstraint! private var bottomConstraint: NSLayoutConstraint! + // MARK: - Initialization + + init(flow: Onboarding.Flow) { + self.flow = flow + + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + // MARK: - Components private lazy var displayNameTextField: TextField = { @@ -176,11 +190,22 @@ final class DisplayNameVC: BaseVC { // Try to save the user name but ignore the result ProfileManager.updateLocal( queue: DispatchQueue.global(qos: .default), - profileName: displayName, - image: nil, - imageFilePath: nil + profileName: displayName ) - let pnModeVC = PNModeVC() + + // If we are not in the registration flow then we are finished and should go straight + // to the home screen + guard self.flow == .register else { + self.flow.completeRegistration() + + // Go to the home screen + let homeVC: HomeVC = HomeVC() + self.navigationController?.setViewControllers([ homeVC ], animated: true) + return + } + + // Need to get the PN mode if registering + let pnModeVC = PNModeVC(flow: .register) navigationController?.pushViewController(pnModeVC, animated: true) } } diff --git a/Session/Onboarding/LinkDeviceVC.swift b/Session/Onboarding/LinkDeviceVC.swift index 9c25b8c1f..a142b17ff 100644 --- a/Session/Onboarding/LinkDeviceVC.swift +++ b/Session/Onboarding/LinkDeviceVC.swift @@ -91,10 +91,6 @@ final class LinkDeviceVC: BaseVC, UIPageViewControllerDataSource, UIPageViewCont tabBarTopConstraint.constant = navigationController!.navigationBar.height() } - deinit { - NotificationCenter.default.removeObserver(self) - } - // MARK: - General func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { @@ -154,32 +150,17 @@ final class LinkDeviceVC: BaseVC, UIPageViewControllerDataSource, UIPageViewCont return } let (ed25519KeyPair, x25519KeyPair) = try! Identity.generate(from: seed) - Onboarding.Flow.link.preregister(with: seed, ed25519KeyPair: ed25519KeyPair, x25519KeyPair: x25519KeyPair) - Identity.didRegister() + Onboarding.Flow.link + .preregister( + with: seed, + ed25519KeyPair: ed25519KeyPair, + x25519KeyPair: x25519KeyPair + ) - // Now that we have registered get the Snode pool - GetSnodePoolJob.run() - - NotificationCenter.default.addObserver(self, selector: #selector(handleInitialConfigurationMessageReceived), name: .initialConfigurationMessageReceived, object: nil) - - ModalActivityIndicatorViewController - .present( - // There was some crashing here due to force-unwrapping so just falling back to - // using self if there is no nav controller - fromViewController: (self.navigationController ?? self) - ) { [weak self] modal in - self?.activityIndicatorModal = modal - } - } - - @objc private func handleInitialConfigurationMessageReceived(_ notification: Notification) { - DispatchQueue.main.async { - self.navigationController!.dismiss(animated: true) { - let pnModeVC = PNModeVC() - self.navigationController!.setViewControllers([ pnModeVC ], animated: true) - } - } + // Otherwise continue on to request push notifications permissions + let pnModeVC: PNModeVC = PNModeVC(flow: .link) + self.navigationController?.pushViewController(pnModeVC, animated: true) } } diff --git a/Session/Onboarding/Onboarding.swift b/Session/Onboarding/Onboarding.swift index 27c0687fe..0067fb033 100644 --- a/Session/Onboarding/Onboarding.swift +++ b/Session/Onboarding/Onboarding.swift @@ -1,58 +1,124 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import Combine import Sodium import GRDB import SessionUtilitiesKit import SessionMessagingKit enum Onboarding { + private static let profileNameRetrievalPublisher: Atomic> = { + let userPublicKey: String = getUserHexEncodedPublicKey() + + return Atomic( + SnodeAPI.getSwarm(for: userPublicKey) + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) + .flatMap { swarm -> AnyPublisher in + guard let snode = swarm.randomElement() else { + return Fail(error: SnodeAPIError.generic) + .eraseToAnyPublisher() + } + + return CurrentUserPoller.poll( + namespaces: [.configUserProfile], + from: snode, + for: userPublicKey, + on: DispatchQueue.global(qos: .userInitiated), + // Note: These values mean the received messages will be + // processed immediately rather than async as part of a Job + calledFromBackgroundPoller: true, + isBackgroundPollValid: { true } + ) + } + .flatMap { _ -> AnyPublisher in + Storage.shared.readPublisher { db in + try Profile + .filter(id: userPublicKey) + .select(.name) + .asRequest(of: String.self) + .fetchOne(db) + } + } + .shareReplay(1) + .eraseToAnyPublisher() + ) + }() + public static var profileNamePublisher: AnyPublisher { + profileNameRetrievalPublisher.wrappedValue + } enum Flow { case register, recover, link func preregister(with seed: Data, ed25519KeyPair: KeyPair, x25519KeyPair: KeyPair) { - let userDefaults = UserDefaults.standard - Identity.store(seed: seed, ed25519KeyPair: ed25519KeyPair, x25519KeyPair: x25519KeyPair) let x25519PublicKey = x25519KeyPair.hexEncodedPublicKey + // Create the initial shared util state (won't have been created on + // launch due to lack of ed25519 key) + SessionUtil.loadState( + userPublicKey: x25519PublicKey, + ed25519SecretKey: ed25519KeyPair.secretKey + ) + + // Store the user identity information Storage.shared.write { db in - try Contact(id: x25519PublicKey) - .with( - isApproved: true, - didApproveMe: true - ) - .save(db) + try Identity.store( + db, + seed: seed, + ed25519KeyPair: ed25519KeyPair, + x25519KeyPair: x25519KeyPair + ) + // No need to show the seed again if the user is restoring or linking + db[.hasViewedSeed] = (self == .recover || self == .link) + + // Create a contact for the current user and set their approval/trusted statuses so + // they don't get weird behaviours + try Contact + .fetchOrCreate(db, id: x25519PublicKey) + .save(db) + try Contact + .filter(id: x25519PublicKey) + .updateAllAndConfig( + db, + Contact.Columns.isTrusted.set(to: true), // Always trust the current user + Contact.Columns.isApproved.set(to: true), + Contact.Columns.didApproveMe.set(to: true) + ) + // Create the 'Note to Self' thread (not visible by default) try SessionThread .fetchOrCreate(db, id: x25519PublicKey, variant: .contact) .save(db) - - // Create the initial shared util state (won't have been created on - // launch due to lack of ed25519 key) - SessionUtil.loadState(ed25519SecretKey: ed25519KeyPair.secretKey) - - // No need to show the seed again if the user is restoring or linking - db[.hasViewedSeed] = (self == .recover || self == .link) } - + // Set hasSyncedInitialConfiguration to true so that when we hit the // home screen a configuration sync is triggered (yes, the logic is a // bit weird). This is needed so that if the user registers and // immediately links a device, there'll be a configuration in their swarm. - userDefaults[.hasSyncedInitialConfiguration] = (self == .register) + UserDefaults.standard[.hasSyncedInitialConfiguration] = (self == .register) - switch self { - case .register, .recover: - // Set both lastDisplayNameUpdate and lastProfilePictureUpdate to the - // current date, so that we don't overwrite what the user set in the - // display name step with whatever we find in their swarm. - userDefaults[.lastDisplayNameUpdate] = Date() - userDefaults[.lastProfilePictureUpdate] = Date() - - case .link: break - } + // Only continue if this isn't a new account + guard self != .register else { return } + + // Fetch the + Onboarding.profileNamePublisher.sinkUntilComplete() + } + + func completeRegistration() { + // Set the `lastDisplayNameUpdate` to the current date, so that we don't + // overwrite what the user set in the display name step with whatever we + // find in their swarm (otherwise the user could enter a display name and + // have it immediately overwritten due to the config request running slow) + UserDefaults.standard[.lastDisplayNameUpdate] = Date() + + // Notify the app that registration is complete + Identity.didRegister() + + // Now that we have registered get the Snode pool and sync push tokens + GetSnodePoolJob.run() + SyncPushTokensJob.run(uploadOnlyIfStale: false) } } } diff --git a/Session/Onboarding/PNModeVC.swift b/Session/Onboarding/PNModeVC.swift index 83751afed..8906f75cb 100644 --- a/Session/Onboarding/PNModeVC.swift +++ b/Session/Onboarding/PNModeVC.swift @@ -1,13 +1,15 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit +import Combine import SessionUIKit import SessionMessagingKit import SessionSnodeKit import SignalUtilitiesKit final class PNModeVC: BaseVC, OptionViewDelegate { - + private let flow: Onboarding.Flow + private var optionViews: [OptionView] { [ apnsOptionView, backgroundPollingOptionView ] } @@ -15,7 +17,19 @@ final class PNModeVC: BaseVC, OptionViewDelegate { private var selectedOptionView: OptionView? { return optionViews.first { $0.isSelected } } - + + // MARK: - Initialization + + init(flow: Onboarding.Flow) { + self.flow = flow + + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + // MARK: - Components private lazy var apnsOptionView: OptionView = { @@ -128,14 +142,68 @@ final class PNModeVC: BaseVC, OptionViewDelegate { } UserDefaults.standard[.isUsingFullAPNs] = (selectedOptionView == apnsOptionView) - Identity.didRegister() + // If we are registering then we can just continue on + guard flow != .register else { + self.flow.completeRegistration() + + // Go to the home screen + let homeVC: HomeVC = HomeVC() + self.navigationController?.setViewControllers([ homeVC ], animated: true) + return + } - // Go to the home screen - let homeVC: HomeVC = HomeVC() - self.navigationController?.setViewControllers([ homeVC ], animated: true) + // Check if we already have a profile name (ie. profile retrieval completed while waiting on + // this screen) + let existingProfileName: String? = Storage.shared + .read { db in + try Profile + .filter(id: getUserHexEncodedPublicKey(db)) + .select(.name) + .asRequest(of: String.self) + .fetchOne(db) + } - // Now that we have registered get the Snode pool and sync push tokens - GetSnodePoolJob.run() - SyncPushTokensJob.run(uploadOnlyIfStale: false) + guard existingProfileName?.isEmpty != false else { + // If we have one then we can go straight to the home screen + self.flow.completeRegistration() + + // Go to the home screen + let homeVC: HomeVC = HomeVC() + self.navigationController?.setViewControllers([ homeVC ], animated: true) + return + } + + // If we don't have one then show a loading indicator and try to retrieve the existing name + ModalActivityIndicatorViewController.present(fromViewController: self) { viewController in + Onboarding.profileNamePublisher + .timeout(.seconds(10), scheduler: DispatchQueue.main, customError: { HTTPError.timeout }) + .catch { _ -> AnyPublisher in + SNLog("Onboarding failed to retrieve existing profile information") + return Just(nil) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + .receive(on: DispatchQueue.main) + .sinkUntilComplete( + receiveValue: { [weak self, flow = self.flow] value in + // Hide the loading indicator + viewController.dismiss(animated: true) + + // If we have no display name we need to collect one + guard value?.isEmpty == false else { + let displayNameVC: DisplayNameVC = DisplayNameVC(flow: flow) + self?.navigationController?.pushViewController(displayNameVC, animated: true) + return + } + + // Otherwise we are done and can go to the home screen + self?.flow.completeRegistration() + + // Go to the home screen + let homeVC: HomeVC = HomeVC() + self?.navigationController?.setViewControllers([ homeVC ], animated: true) + } + ) + } } } diff --git a/Session/Onboarding/RegisterVC.swift b/Session/Onboarding/RegisterVC.swift index 6d5ab3c94..b72382536 100644 --- a/Session/Onboarding/RegisterVC.swift +++ b/Session/Onboarding/RegisterVC.swift @@ -198,11 +198,18 @@ final class RegisterVC : BaseVC { animate() } - // MARK: Interaction + // MARK: - Interaction + @objc private func register() { - Onboarding.Flow.register.preregister(with: seed, ed25519KeyPair: ed25519KeyPair, x25519KeyPair: x25519KeyPair) - let displayNameVC = DisplayNameVC() - navigationController!.pushViewController(displayNameVC, animated: true) + Onboarding.Flow.register + .preregister( + with: seed, + ed25519KeyPair: ed25519KeyPair, + x25519KeyPair: x25519KeyPair + ) + + let displayNameVC: DisplayNameVC = DisplayNameVC(flow: .register) + self.navigationController?.pushViewController(displayNameVC, animated: true) } @objc private func copyPublicKey() { diff --git a/Session/Onboarding/RestoreVC.swift b/Session/Onboarding/RestoreVC.swift index 0e6496757..85425966e 100644 --- a/Session/Onboarding/RestoreVC.swift +++ b/Session/Onboarding/RestoreVC.swift @@ -194,22 +194,33 @@ final class RestoreVC: BaseVC { present(modal, animated: true) } - let mnemonic = mnemonicTextView.text!.lowercased() + let seed: Data + let keyPairs: (ed25519KeyPair: KeyPair, x25519KeyPair: KeyPair) + do { - let hexEncodedSeed = try Mnemonic.decode(mnemonic: mnemonic) - let seed = Data(hex: hexEncodedSeed) - let (ed25519KeyPair, x25519KeyPair) = try! Identity.generate(from: seed) - Onboarding.Flow.recover.preregister(with: seed, ed25519KeyPair: ed25519KeyPair, x25519KeyPair: x25519KeyPair) - mnemonicTextView.resignFirstResponder() - - Timer.scheduledTimer(withTimeInterval: 0.25, repeats: false) { _ in - let displayNameVC = DisplayNameVC() - self.navigationController!.pushViewController(displayNameVC, animated: true) - } - } catch let error { + let mnemonic: String = mnemonicTextView.text!.lowercased() + let hexEncodedSeed: String = try Mnemonic.decode(mnemonic: mnemonic) + seed = Data(hex: hexEncodedSeed) + keyPairs = try Identity.generate(from: seed) + } + catch let error { let error = error as? Mnemonic.DecodingError ?? Mnemonic.DecodingError.generic showError(title: error.errorDescription!) + return } + + // Load in the user config and progress to the next screen + mnemonicTextView.resignFirstResponder() + + Onboarding.Flow.recover + .preregister( + with: seed, + ed25519KeyPair: keyPairs.ed25519KeyPair, + x25519KeyPair: keyPairs.x25519KeyPair + ) + + let pnModeVC: PNModeVC = PNModeVC(flow: .recover) + self.navigationController?.pushViewController(pnModeVC, animated: true) } @objc private func handleLegalLabelTapped(_ tapGestureRecognizer: UITapGestureRecognizer) { diff --git a/Session/Settings/BlockedContactsViewModel.swift b/Session/Settings/BlockedContactsViewModel.swift index 6c608c2bd..871bae502 100644 --- a/Session/Settings/BlockedContactsViewModel.swift +++ b/Session/Settings/BlockedContactsViewModel.swift @@ -246,10 +246,7 @@ class BlockedContactsViewModel: SessionTableViewModel Void - private let onImagePicked: (UIImage?, String?) -> Void + private let onImagePicked: (UIImage) -> Void + private let onImageFilePicked: (String) -> Void // MARK: - Initialization init( onTransition: @escaping (UIViewController, TransitionType) -> Void, - onImagePicked: @escaping (UIImage?, String?) -> Void + onImagePicked: @escaping (UIImage) -> Void, + onImageFilePicked: @escaping (String) -> Void ) { self.onTransition = onTransition self.onImagePicked = onImagePicked + self.onImageFilePicked = onImageFilePicked } // MARK: - UIImagePickerControllerDelegate @@ -44,14 +47,14 @@ class ImagePickerHandler: NSObject, UIImagePickerControllerDelegate & UINavigati let viewController: CropScaleImageViewController = CropScaleImageViewController( srcImage: rawAvatar, successCompletion: { resultImage in - self?.onImagePicked(resultImage, nil) + self?.onImagePicked(resultImage) } ) self?.onTransition(viewController, .present) return } - self?.onImagePicked(nil, imageUrl.path) + self?.onImageFilePicked(imageUrl.path) } } } diff --git a/Session/Settings/NukeDataModal.swift b/Session/Settings/NukeDataModal.swift index 34b0b84b5..0c7c2adf0 100644 --- a/Session/Settings/NukeDataModal.swift +++ b/Session/Settings/NukeDataModal.swift @@ -149,8 +149,7 @@ final class NukeDataModal: Modal { private func clearDeviceOnly() { ModalActivityIndicatorViewController.present(fromViewController: self, canCancel: false) { [weak self] _ in - Storage.shared - .writePublisherFlatMap { db in try MessageSender.syncConfiguration(db, forceSyncNow: true) } + ConfigurationSyncJob.run() .receive(on: DispatchQueue.main) .sinkUntilComplete( receiveCompletion: { _ in diff --git a/Session/Settings/SettingsViewModel.swift b/Session/Settings/SettingsViewModel.swift index a516d3447..6ea6c166c 100644 --- a/Session/Settings/SettingsViewModel.swift +++ b/Session/Settings/SettingsViewModel.swift @@ -70,13 +70,20 @@ class SettingsViewModel: SessionTableViewModel? = try SessionUtil.loadState( + for: .contacts, + secretKey: secretKey, + cachedData: nil + ) + let contactsConfResult: SessionUtil.ConfResult = try SessionUtil.upsert( + contactData: contactsData.map { ($0.contact.id, $0.contact, $0.profile) }, + in: Atomic(contactsConf) + ) + + if contactsConfResult.needsDump { + try SessionUtil + .createDump( + conf: contactsConf, + for: .contacts, + publicKey: userPublicKey, + messageHashes: nil + )? + .save(db) } Storage.update(progress: 1, for: self, in: target) // In case this is the last migration diff --git a/SessionMessagingKit/Database/Models/BlindedIdLookup.swift b/SessionMessagingKit/Database/Models/BlindedIdLookup.swift index d5e8704c6..3a3d07498 100644 --- a/SessionMessagingKit/Database/Models/BlindedIdLookup.swift +++ b/SessionMessagingKit/Database/Models/BlindedIdLookup.swift @@ -130,7 +130,7 @@ public extension BlindedIdLookup { if isCheckingForOutbox && !contact.isApproved { try Contact .filter(id: contact.id) - .updateAll(db, Contact.Columns.isApproved.set(to: true)) + .updateAllAndConfig(db, Contact.Columns.isApproved.set(to: true)) } break diff --git a/SessionMessagingKit/Database/Models/Contact.swift b/SessionMessagingKit/Database/Models/Contact.swift index 524fd7300..6cc3b4fc5 100644 --- a/SessionMessagingKit/Database/Models/Contact.swift +++ b/SessionMessagingKit/Database/Models/Contact.swift @@ -4,6 +4,8 @@ import Foundation import GRDB import SessionUtilitiesKit +/// This type is duplicate in both the database and within the SessionUtil config so should only ever have it's data changes via the +/// `updateAllAndConfig` function. Updating it elsewhere could result in issues with syncing data between devices public struct Contact: Codable, Identifiable, Equatable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { public static var databaseTableName: String { "contact" } internal static let threadForeignKey = ForeignKey([Columns.id], to: [SessionThread.Columns.id]) @@ -66,29 +68,6 @@ public struct Contact: Codable, Identifiable, Equatable, FetchableRecord, Persis } } -// MARK: - Convenience - -public extension Contact { - func with( - isTrusted: Updatable = .existing, - isApproved: Updatable = .existing, - isBlocked: Updatable = .existing, - didApproveMe: Updatable = .existing - ) -> Contact { - return Contact( - id: id, - isTrusted: ( - (isTrusted ?? self.isTrusted) || - self.id == getUserHexEncodedPublicKey() // Always trust ourselves - ), - isApproved: (isApproved ?? self.isApproved), - isBlocked: (isBlocked ?? self.isBlocked), - didApproveMe: (didApproveMe ?? self.didApproveMe), - hasBeenBlocked: ((isBlocked ?? self.isBlocked) || self.hasBeenBlocked) - ) - } -} - // MARK: - GRDB Interactions public extension Contact { diff --git a/SessionMessagingKit/Database/Models/Profile.swift b/SessionMessagingKit/Database/Models/Profile.swift index a37a7bb2f..533c3657f 100644 --- a/SessionMessagingKit/Database/Models/Profile.swift +++ b/SessionMessagingKit/Database/Models/Profile.swift @@ -5,6 +5,8 @@ import GRDB import DifferenceKit import SessionUtilitiesKit +/// This type is duplicate in both the database and within the SessionUtil config so should only ever have it's data changes via the +/// `updateAllAndConfig` function. Updating it elsewhere could result in issues with syncing data between devices public struct Profile: Codable, Identifiable, Equatable, Hashable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible, CustomStringConvertible, Differentiable { public static var databaseTableName: String { "profile" } internal static let interactionForeignKey = ForeignKey([Columns.id], to: [Interaction.Columns.authorId]) @@ -160,26 +162,6 @@ public extension Profile { } } -// MARK: - Mutation - -public extension Profile { - func with( - name: String? = nil, - profilePictureUrl: Updatable = .existing, - profilePictureFileName: Updatable = .existing, - profileEncryptionKey: Updatable = .existing - ) -> Profile { - return Profile( - id: id, - name: (name ?? self.name), - nickname: self.nickname, - profilePictureUrl: (profilePictureUrl ?? self.profilePictureUrl), - profilePictureFileName: (profilePictureFileName ?? self.profilePictureFileName), - profileEncryptionKey: (profileEncryptionKey ?? self.profileEncryptionKey) - ) - } -} - // MARK: - GRDB Interactions public extension Profile { diff --git a/SessionMessagingKit/Database/Models/SharedConfigDump.swift b/SessionMessagingKit/Database/Models/SharedConfigDump.swift index ab22c481f..90b92befb 100644 --- a/SessionMessagingKit/Database/Models/SharedConfigDump.swift +++ b/SessionMessagingKit/Database/Models/SharedConfigDump.swift @@ -2,38 +2,81 @@ import Foundation import GRDB +import SessionSnodeKit import SessionUtilitiesKit -public struct ConfigDump: Codable, Equatable, Hashable, Identifiable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { +public struct ConfigDump: Codable, Equatable, Hashable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { public static var databaseTableName: String { "configDump" } public typealias Columns = CodingKeys public enum CodingKeys: String, CodingKey, ColumnExpression { case variant + case publicKey case data + case combinedMessageHashes } - public enum Variant: String, Codable, DatabaseValueConvertible, CaseIterable { + public enum Variant: String, Codable, DatabaseValueConvertible { case userProfile case contacts } - public var id: Variant { variant } - /// The type of config this dump is for public let variant: Variant + /// The public key for the swarm this dump is for + /// + /// **Note:** For user config items this will be an empty string + public let publicKey: String + /// The data for this dump public let data: Data + + /// A comma delimited array of message hashes for previously stored messages on the server + private let combinedMessageHashes: String? + + /// An array of message hashes for previously stored messages on the server + var messageHashes: [String]? { ConfigDump.messageHashes(from: combinedMessageHashes) } + + internal init( + variant: Variant, + publicKey: String, + data: Data, + messageHashes: [String]? + ) { + self.variant = variant + self.publicKey = publicKey + self.data = data + self.combinedMessageHashes = ConfigDump.combinedMessageHashes(from: messageHashes) + } } // MARK: - Convenience +public extension ConfigDump { + static func combinedMessageHashes(from messageHashes: [String]?) -> String? { + return messageHashes?.joined(separator: ",") + } + + static func messageHashes(from combinedMessageHashes: String?) -> [String]? { + return combinedMessageHashes?.components(separatedBy: ",") + } +} + public extension ConfigDump.Variant { + static let userVariants: [ConfigDump.Variant] = [ .userProfile, .contacts ] + var configMessageKind: SharedConfigMessage.Kind { switch self { case .userProfile: return .userProfile case .contacts: return .contacts } } + + var namespace: SnodeAPI.Namespace { + switch self { + case .userProfile: return SnodeAPI.Namespace.configUserProfile + case .contacts: return SnodeAPI.Namespace.configContacts + } + } } diff --git a/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift b/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift new file mode 100644 index 000000000..2b734fffc --- /dev/null +++ b/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift @@ -0,0 +1,355 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import Combine +import GRDB +import SessionUtil +import SessionSnodeKit +import SessionUtilitiesKit + +public enum ConfigurationSyncJob: JobExecutor { + public static let maxFailureCount: Int = -1 + public static let requiresThreadId: Bool = false + public static let requiresInteractionId: Bool = false + private static let maxRunFrequency: TimeInterval = 3 + + public static func run( + _ job: Job, + queue: DispatchQueue, + success: @escaping (Job, Bool) -> (), + failure: @escaping (Job, Error?, Bool) -> (), + deferred: @escaping (Job) -> () + ) { + guard Features.useSharedUtilForUserConfig else { + success(job, true) + return + } + + // If we don't have a userKeyPair yet then there is no need to sync the configuration + // as the user doesn't exist yet (this will get triggered on the first launch of a + // fresh install due to the migrations getting run) + guard + let pendingSwarmConfigChanges: [SingleDestinationChanges] = Storage.shared + .read({ db -> [SessionUtil.OutgoingConfResult]? in + guard + Identity.userExists(db), + let ed25519SecretKey: [UInt8] = Identity.fetchUserEd25519KeyPair(db)?.secretKey + else { return nil } + + return try SessionUtil.pendingChanges( + db, + userPublicKey: getUserHexEncodedPublicKey(db), + ed25519SecretKey: ed25519SecretKey + ) + })? + .grouped(by: { $0.destination }) + .map({ (destination: Message.Destination, value: [SessionUtil.OutgoingConfResult]) -> SingleDestinationChanges in + SingleDestinationChanges( + destination: destination, + messages: value, + allOldHashes: value + .map { ($0.oldMessageHashes ?? []) } + .reduce([], +) + .asSet() + ) + }) + else { + failure(job, StorageError.generic, false) + return + } + + // If there are no pending changes then the job can just complete (next time something + // is updated we want to try and run immediately so don't scuedule another run in this case) + guard !pendingSwarmConfigChanges.isEmpty else { + success(job, true) + return + } + + Storage.shared + .readPublisher { db in + try pendingSwarmConfigChanges + .map { (change: SingleDestinationChanges) -> (messages: [TargetedMessage], allOldHashes: Set) in + ( + messages: try change.messages + .map { (outgoingConf: SessionUtil.OutgoingConfResult) -> TargetedMessage in + TargetedMessage( + sendData: try MessageSender.preparedSendData( + db, + message: outgoingConf.message, + to: change.destination, + interactionId: nil + ), + namespace: outgoingConf.namespace, + oldHashes: (outgoingConf.oldMessageHashes ?? []) + ) + }, + allOldHashes: change.allOldHashes + ) + } + } + .subscribe(on: queue) + .receive(on: queue) + .flatMap { (pendingSwarmChange: [(messages: [TargetedMessage], allOldHashes: Set)]) -> AnyPublisher<[HTTP.BatchResponse], Error> in + Publishers + .MergeMany( + pendingSwarmChange + .map { (messages: [TargetedMessage], oldHashes: Set) in + // Note: We do custom sending logic here because we want to batch the + // sending and deletion of messages within the same swarm + SnodeAPI + .sendConfigMessages( + messages + .compactMap { targetedMessage -> SnodeAPI.TargetedMessage? in + targetedMessage.sendData.snodeMessage + .map { ($0, targetedMessage.namespace) } + }, + oldHashes: Array(oldHashes) + ) + } + ) + .collect() + .eraseToAnyPublisher() + } + .map { (responses: [HTTP.BatchResponse]) -> [SuccessfulChange] in + // Process the response data into an easy to understand for (this isn't strictly + // needed but the code gets convoluted without this) + zip(responses, pendingSwarmConfigChanges) + .compactMap { (batchResponse: HTTP.BatchResponse, pendingSwarmChange: SingleDestinationChanges) -> [SuccessfulChange]? in + let maybePublicKey: String? = { + switch pendingSwarmChange.destination { + case .contact(let publicKey), .closedGroup(let publicKey): + return publicKey + + default: return nil + } + }() + + // If we don't have a publicKey then this is an invalid config + guard let publicKey: String = maybePublicKey else { return nil } + + // Need to know if we successfully deleted old messages (if we didn't then + // we want to keep the old hashes so we can delete them the next time) + let didDeleteOldConfigMessages: Bool = { + guard + let subResponse: HTTP.BatchSubResponse = (batchResponse.responses.last as? HTTP.BatchSubResponse), + 200...299 ~= subResponse.code + else { return false } + + return true + }() + + return zip(batchResponse.responses, pendingSwarmChange.messages) + .reduce(into: []) { (result: inout [SuccessfulChange], next: ResponseChange) in + // If the request wasn't successful then just ignore it (the next + // config sync will try make the changes again + guard + let subResponse: HTTP.BatchSubResponse = (next.response as? HTTP.BatchSubResponse), + 200...299 ~= subResponse.code, + let sendMessageResponse: SendMessagesResponse = subResponse.body + else { return } + + result.append( + SuccessfulChange( + message: next.change.message, + publicKey: publicKey, + updatedHashes: (didDeleteOldConfigMessages ? + [sendMessageResponse.hash] : + (next.change.oldMessageHashes ?? []) + .appending(sendMessageResponse.hash) + ) + ) + ) + } + } + .flatMap { $0 } + } + .map { (successfulChanges: [SuccessfulChange]) -> [ConfigDump] in + // Now that we have the successful changes, we need to mark them as pushed and + // generate any config dumps which need to be stored + successfulChanges + .compactMap { successfulChange -> ConfigDump? in + // Updating the pushed state returns a flag indicating whether the config + // needs to be dumped + guard SessionUtil.markAsPushed(message: successfulChange.message, publicKey: successfulChange.publicKey) else { + return nil + } + + let variant: ConfigDump.Variant = successfulChange.message.kind.configDumpVariant + let atomicConf: Atomic?> = SessionUtil.config( + for: variant, + publicKey: successfulChange.publicKey + ) + + return try? SessionUtil.createDump( + conf: atomicConf.wrappedValue, + for: variant, + publicKey: successfulChange.publicKey, + messageHashes: successfulChange.updatedHashes + ) + } + } + .sinkUntilComplete( + receiveValue: { (configDumps: [ConfigDump]) in + // Flag to indicate whether the job should be finished or will run again + var shouldFinishCurrentJob: Bool = false + + // Lastly we need to save the updated dumps to the database + let updatedJob: Job? = Storage.shared.write { db in + // Save the updated dumps to the database + try configDumps.forEach { try $0.save(db) } + + // When we complete the 'ConfigurationSync' job we want to immediately schedule + // another one with a 'nextRunTimestamp' set to the 'maxRunFrequency' value to + // throttle the config sync requests + let nextRunTimestamp: TimeInterval = (Date().timeIntervalSince1970 + maxRunFrequency) + + // If another 'ConfigurationSync' job was scheduled then update that one + // to run at 'nextRunTimestamp' and make the current job stop + if + let existingJob: Job = try? Job + .filter(Job.Columns.id != job.id) + .filter(Job.Columns.variant == Job.Variant.configurationSync) + .fetchOne(db), + !JobRunner.isCurrentlyRunning(existingJob) + { + _ = try existingJob + .with(nextRunTimestamp: nextRunTimestamp) + .saved(db) + shouldFinishCurrentJob = true + return job + } + + return try job + .with(nextRunTimestamp: nextRunTimestamp) + .saved(db) + } + + success((updatedJob ?? job), shouldFinishCurrentJob) + } + ) + } +} + +// MARK: - Convenience Types + +public extension ConfigurationSyncJob { + fileprivate struct SingleDestinationChanges { + let destination: Message.Destination + let messages: [SessionUtil.OutgoingConfResult] + let allOldHashes: Set + } + + fileprivate struct TargetedMessage { + let sendData: MessageSender.PreparedSendData + let namespace: SnodeAPI.Namespace + let oldHashes: [String] + } + + typealias ResponseChange = (response: Codable, change: SessionUtil.OutgoingConfResult) + + fileprivate struct SuccessfulChange { + let message: SharedConfigMessage + let publicKey: String + let updatedHashes: [String] + } +} + +// MARK: - Convenience + +public extension ConfigurationSyncJob { + static func enqueue(_ db: Database? = nil) { + guard let db: Database = db else { + Storage.shared.writeAsync { ConfigurationSyncJob.enqueue($0) } + return + } + + // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent + guard Features.useSharedUtilForUserConfig else { + // If we don't have a userKeyPair yet then there is no need to sync the configuration + // as the user doesn't exist yet (this will get triggered on the first launch of a + // fresh install due to the migrations getting run) + guard + Identity.userExists(db), + let legacyConfigMessage: Message = try? ConfigurationMessage.getCurrent(db) + else { return } + + let publicKey: String = getUserHexEncodedPublicKey(db) + + JobRunner.add( + db, + job: Job( + variant: .messageSend, + threadId: publicKey, + details: MessageSendJob.Details( + destination: Message.Destination.contact(publicKey: publicKey), + message: legacyConfigMessage + ) + ) + ) + return + } + + // Upsert a config sync job (if there is already an pending one then no need + // to add another one) + JobRunner.upsert( + db, + job: ConfigurationSyncJob.createOrUpdateIfNeeded(db) + ) + } + + @discardableResult static func createOrUpdateIfNeeded(_ db: Database) -> Job { + // Try to get an existing job (if there is one that's not running) + if + let existingJob: Job = try? Job + .filter(Job.Columns.variant == Job.Variant.configurationSync) + .fetchOne(db), + !JobRunner.isCurrentlyRunning(existingJob) + { + return existingJob + } + + // Otherwise create a new job + return Job( + variant: .configurationSync, + behaviour: .recurring + ) + } + + static func run() -> AnyPublisher { + // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent + guard Features.useSharedUtilForUserConfig else { + return Storage.shared + .writePublisher { db -> MessageSender.PreparedSendData in + // If we don't have a userKeyPair yet then there is no need to sync the configuration + // as the user doesn't exist yet (this will get triggered on the first launch of a + // fresh install due to the migrations getting run) + guard Identity.userExists(db) else { throw StorageError.generic } + + let publicKey: String = getUserHexEncodedPublicKey(db) + + return try MessageSender.preparedSendData( + db, + message: try ConfigurationMessage.getCurrent(db), + to: Message.Destination.contact(publicKey: publicKey), + interactionId: nil + ) + } + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) + .receive(on: DispatchQueue.global(qos: .userInitiated)) + .flatMap { MessageSender.sendImmediate(preparedSendData: $0) } + .eraseToAnyPublisher() + } + + // Trigger the job emitting the result when completed + return Future { resolver in + ConfigurationSyncJob.run( + Job(variant: .configurationSync), + queue: DispatchQueue.global(qos: .userInitiated), + success: { _, _ in resolver(Result.success(())) }, + failure: { _, error, _ in resolver(Result.failure(error ?? HTTPError.generic)) }, + deferred: { _ in } + ) + } + .eraseToAnyPublisher() + } +} diff --git a/SessionMessagingKit/Jobs/Types/MessageReceiveJob.swift b/SessionMessagingKit/Jobs/Types/MessageReceiveJob.swift index d845f4eb5..e3ac101c8 100644 --- a/SessionMessagingKit/Jobs/Types/MessageReceiveJob.swift +++ b/SessionMessagingKit/Jobs/Types/MessageReceiveJob.swift @@ -33,7 +33,11 @@ public enum MessageReceiveJob: JobExecutor { Storage.shared.write { db in // Send any SharedConfigMessages to the SessionUtil to handle it - try SessionUtil.handleConfigMessages(db, messages: sharedConfigMessages) + try SessionUtil.handleConfigMessages( + db, + messages: sharedConfigMessages, + publicKey: (job.threadId ?? "") + ) // Handle the remaining messages var remainingMessagesToProcess: [Details.MessageInfo] = [] diff --git a/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift b/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift index e2fd0f7a2..020aa27fc 100644 --- a/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift +++ b/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift @@ -9,7 +9,7 @@ public enum SendReadReceiptsJob: JobExecutor { public static let maxFailureCount: Int = -1 public static let requiresThreadId: Bool = false public static let requiresInteractionId: Bool = false - private static let minRunFrequency: TimeInterval = 3 + private static let maxRunFrequency: TimeInterval = 3 public static func run( _ job: Job, @@ -56,9 +56,9 @@ public enum SendReadReceiptsJob: JobExecutor { case .finished: // When we complete the 'SendReadReceiptsJob' we want to immediately schedule // another one for the same thread but with a 'nextRunTimestamp' set to the - // 'minRunFrequency' value to throttle the read receipt requests + // 'maxRunFrequency' value to throttle the read receipt requests var shouldFinishCurrentJob: Bool = false - let nextRunTimestamp: TimeInterval = (Date().timeIntervalSince1970 + minRunFrequency) + let nextRunTimestamp: TimeInterval = (Date().timeIntervalSince1970 + maxRunFrequency) let updatedJob: Job? = Storage.shared.write { db in // If another 'sendReadReceipts' job was scheduled then update that one @@ -163,7 +163,7 @@ public extension SendReadReceiptsJob { behaviour: .recurring, threadId: threadId, details: Details( - destination: .contact(publicKey: threadId, namespace: .default), + destination: .contact(publicKey: threadId), timestampMsValues: timestampMsValues.asSet() ) ) diff --git a/SessionMessagingKit/Jobs/Types/UpdateProfilePictureJob.swift b/SessionMessagingKit/Jobs/Types/UpdateProfilePictureJob.swift index 5050c035a..43bab45c1 100644 --- a/SessionMessagingKit/Jobs/Types/UpdateProfilePictureJob.swift +++ b/SessionMessagingKit/Jobs/Types/UpdateProfilePictureJob.swift @@ -49,11 +49,8 @@ public enum UpdateProfilePictureJob: JobExecutor { ProfileManager.updateLocal( queue: queue, profileName: profile.name, - image: nil, - imageFilePath: profileFilePath, - success: { db, _ in - try MessageSender.syncConfiguration(db, forceSyncNow: true).sinkUntilComplete() - + avatarUpdate: (profileFilePath.map { .uploadFilePath($0) } ?? .none), + success: { db in // Need to call the 'success' closure asynchronously on the queue to prevent a reentrancy // issue as it will write to the database and this closure is already called within // another database write diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift new file mode 100644 index 000000000..26ce1fce0 --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift @@ -0,0 +1,366 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import GRDB +import SessionUtil +import SessionUtilitiesKit + +internal extension SessionUtil { + // MARK: - Incoming Changes + + static func handleContactsUpdate( + _ db: Database, + in atomicConf: Atomic?>, + needsDump: Bool + ) throws { + typealias ContactData = [String: (contact: Contact, profile: Profile)] + + guard needsDump else { return } + guard atomicConf.wrappedValue != nil else { throw SessionUtilError.nilConfigObject } + + // Since we are doing direct memory manipulation we are using an `Atomic` type which has + // blocking access in it's `mutate` closure + let contactData: ContactData = atomicConf.mutate { conf -> ContactData in + var contactData: ContactData = [:] + var contact: contacts_contact = contacts_contact() + let contactIterator: UnsafeMutablePointer = contacts_iterator_new(conf) + + while !contacts_iterator_done(contactIterator, &contact) { + let contactId: String = String(cString: withUnsafeBytes(of: contact.session_id) { [UInt8]($0) } + .map { CChar($0) } + .nullTerminated() + ) + let contactResult: Contact = Contact( + id: contactId, + isApproved: contact.approved, + isBlocked: contact.blocked, + didApproveMe: contact.approved_me + ) + let profileResult: Profile = Profile( + id: contactId, + name: (contact.name.map { String(cString: $0) } ?? ""), + nickname: contact.nickname.map { String(cString: $0) }, + profilePictureUrl: contact.profile_pic.url.map { String(cString: $0) }, + profileEncryptionKey: (contact.profile_pic.key != nil && contact.profile_pic.keylen > 0 ? + Data(bytes: contact.profile_pic.key, count: contact.profile_pic.keylen) : + nil + ) + ) + + contactData[contactId] = (contactResult, profileResult) + contacts_iterator_advance(contactIterator) + } + contacts_iterator_free(contactIterator) // Need to free the iterator + + return contactData + } + + // The current users contact data is handled separately so exclude it if it's present (as that's + // actually a bug) + let userPublicKey: String = getUserHexEncodedPublicKey() + let targetContactData: ContactData = contactData.filter { $0.key != userPublicKey } + + // If we only updated the current user contact then no need to continue + guard !targetContactData.isEmpty else { return } + + // Since we don't sync 100% of the data stored against the contact and profile objects we + // need to only update the data we do have to ensure we don't overwrite anything that doesn't + // get synced + try targetContactData + .forEach { sessionId, data in + // Note: We only update the contact and profile records if the data has actually changed + // in order to avoid triggering UI updates for every thread on the home screen (the DB + // observation system can't differ between update calls which do and don't change anything) + let contact: Contact = Contact.fetchOrCreate(db, id: sessionId) + let profile: Profile = Profile.fetchOrCreate(db, id: sessionId) + + if + (!data.profile.name.isEmpty && profile.name != data.profile.name) || + profile.nickname != data.profile.nickname || + profile.profilePictureUrl != data.profile.profilePictureUrl || + profile.profileEncryptionKey != data.profile.profileEncryptionKey + { + try profile.save(db) + try Profile + .filter(id: sessionId) + .updateAll( // Handling a config update so don't use `updateAllAndConfig` + db, + [ + (data.profile.name.isEmpty || profile.name == data.profile.name ? nil : + Profile.Columns.name.set(to: data.profile.name) + ), + (profile.nickname == data.profile.nickname ? nil : + Profile.Columns.nickname.set(to: data.profile.nickname) + ), + (profile.profilePictureUrl != data.profile.profilePictureUrl ? nil : + Profile.Columns.profilePictureUrl.set(to: data.profile.profilePictureUrl) + ), + (profile.profileEncryptionKey != data.profile.profileEncryptionKey ? nil : + Profile.Columns.profileEncryptionKey.set(to: data.profile.profileEncryptionKey) + ) + ].compactMap { $0 } + ) + } + + /// Since message requests have no reverse, we should only handle setting `isApproved` + /// and `didApproveMe` to `true`. This may prevent some weird edge cases where a config message + /// swapping `isApproved` and `didApproveMe` to `false` + if + (contact.isApproved != data.contact.isApproved) || + (contact.isBlocked != data.contact.isBlocked) || + (contact.didApproveMe != data.contact.didApproveMe) + { + try contact.save(db) + try Contact + .filter(id: sessionId) + .updateAll( // Handling a config update so don't use `updateAllAndConfig` + db, + [ + (!data.contact.isApproved ? nil : + Contact.Columns.isApproved.set(to: true) + ), + Contact.Columns.isBlocked.set(to: data.contact.isBlocked), + (!data.contact.didApproveMe ? nil : + Contact.Columns.didApproveMe.set(to: true) + ) + ].compactMap { $0 } + ) + } + } + } + + // MARK: - Outgoing Changes + + static func upsert( + contactData: [(id: String, contact: Contact?, profile: Profile?)], + in atomicConf: Atomic?> + ) throws -> ConfResult { + guard atomicConf.wrappedValue != nil else { throw SessionUtilError.nilConfigObject } + + // The current users contact data doesn't need to sync so exclude it + let userPublicKey: String = getUserHexEncodedPublicKey() + let targetContacts: [(id: String, contact: Contact?, profile: Profile?)] = contactData + .filter { $0.id != userPublicKey } + + // If we only updated the current user contact then no need to continue + guard !targetContacts.isEmpty else { return ConfResult(needsPush: false, needsDump: false) } + + // Since we are doing direct memory manipulation we are using an `Atomic` type which has + // blocking access in it's `mutate` closure + return atomicConf.mutate { conf in + // Update the name + targetContacts + .forEach { (id, maybeContact, maybeProfile) in + var sessionId: [CChar] = id + .bytes + .map { CChar(bitPattern: $0) } + var contact: contacts_contact = contacts_contact() + guard contacts_get_or_create(conf, &contact, &sessionId) else { + SNLog("Unable to upsert contact from Config Message") + return + } + + // Assign all properties to match the updated contact (if there is one) + if let updatedContact: Contact = maybeContact { + contact.approved = updatedContact.isApproved + contact.approved_me = updatedContact.didApproveMe + contact.blocked = updatedContact.isBlocked + } + + // Update the profile data (if there is one) + if let updatedProfile: Profile = maybeProfile { + /// Users we have sent a message request to may not have profile info in certain situations + /// + /// Note: We **MUST** store these in local variables rather than access them directly or they won't + /// exist in memory long enough to actually be assigned in the C type + let updatedName: [CChar]? = (updatedProfile.name.isEmpty ? + nil : + updatedProfile.name + .bytes + .map { CChar(bitPattern: $0) } + ) + let updatedNickname: [CChar]? = updatedProfile.nickname? + .bytes + .map { CChar(bitPattern: $0) } + let updatedAvatarUrl: [CChar]? = updatedProfile.profilePictureUrl? + .bytes + .map { CChar(bitPattern: $0) } + let updatedAvatarKey: [UInt8]? = updatedProfile.profileEncryptionKey? + .bytes + let oldAvatarUrl: String? = contact.profile_pic.url.map { String(cString: $0) } + let oldAvatarKey: Data? = (contact.profile_pic.key != nil && contact.profile_pic.keylen > 0 ? + Data(bytes: contact.profile_pic.key, count: contact.profile_pic.keylen) : + nil + ) + updatedName?.withUnsafeBufferPointer { contact.name = $0.baseAddress } + (updatedNickname == nil ? + contact.nickname = nil : + updatedNickname?.withUnsafeBufferPointer { contact.nickname = $0.baseAddress } + ) + (updatedAvatarUrl == nil ? + contact.profile_pic.url = nil : + updatedAvatarUrl?.withUnsafeBufferPointer { + contact.profile_pic.url = $0.baseAddress + } + ) + (updatedAvatarKey == nil ? + contact.profile_pic.key = nil : + updatedAvatarKey?.withUnsafeBufferPointer { + contact.profile_pic.key = $0.baseAddress + } + ) + contact.profile_pic.keylen = (updatedAvatarKey?.count ?? 0) + + // Download the profile picture if needed + if oldAvatarUrl != updatedProfile.profilePictureUrl || oldAvatarKey != updatedProfile.profileEncryptionKey { + ProfileManager.downloadAvatar(for: updatedProfile) + } + } + + // Store the updated contact + contacts_set(conf, &contact) + } + + return ConfResult( + needsPush: config_needs_push(conf), + needsDump: config_needs_dump(conf) + ) + } + } +} + +// MARK: - Convenience + +internal extension SessionUtil { + static func updatingContacts(_ db: Database, _ updated: [T]) throws -> [T] { + guard let updatedContacts: [Contact] = updated as? [Contact] else { throw StorageError.generic } + + // The current users contact data doesn't need to sync so exclude it + let userPublicKey: String = getUserHexEncodedPublicKey(db) + let targetContacts: [Contact] = updatedContacts.filter { $0.id != userPublicKey } + + // If we only updated the current user contact then no need to continue + guard !targetContacts.isEmpty else { return updated } + + db.afterNextTransaction { db in + do { + let atomicConf: Atomic?> = SessionUtil.config( + for: .contacts, + publicKey: userPublicKey + ) + let result: ConfResult = try SessionUtil + .upsert( + contactData: targetContacts.map { (id: $0.id, contact: $0, profile: nil) }, + in: atomicConf + ) + + // If we don't need to dump the data the we can finish early + guard result.needsDump else { return } + + try SessionUtil.saveState( + db, + keepingExistingMessageHashes: true, + configDump: try atomicConf.mutate { conf in + try SessionUtil.createDump( + conf: conf, + for: .contacts, + publicKey: userPublicKey, + messageHashes: nil + ) + } + ) + } + catch { + SNLog("[libSession-util] Failed to dump updated data") + } + } + + return updated + } + + static func updatingProfiles(_ db: Database, _ updated: [T]) throws -> [T] { + guard let updatedProfiles: [Profile] = updated as? [Profile] else { throw StorageError.generic } + + // We should only sync profiles which are associated to contact data to avoid including profiles + // for random people in community conversations so filter out any profiles which don't have an + // associated contact + let existingContactIds: [String] = (try? Contact + .filter(ids: updatedProfiles.map { $0.id }) + .select(.id) + .asRequest(of: String.self) + .fetchAll(db)) + .defaulting(to: []) + + // If none of the profiles are associated with existing contacts then ignore the changes (no need + // to do a config sync) + guard !existingContactIds.isEmpty else { return updated } + + // Get the user public key (updating their profile is handled separately + let userPublicKey: String = getUserHexEncodedPublicKey(db) + + db.afterNextTransaction { db in + do { + // Update the user profile first (if needed) + if let updatedUserProfile: Profile = updatedProfiles.first(where: { $0.id == userPublicKey }) { + let atomicConf: Atomic?> = SessionUtil.config( + for: .userProfile, + publicKey: userPublicKey + ) + let result: ConfResult = try SessionUtil.update( + profile: updatedUserProfile, + in: atomicConf + ) + + if result.needsDump { + try SessionUtil.saveState( + db, + keepingExistingMessageHashes: true, + configDump: try atomicConf.mutate { conf in + try SessionUtil.createDump( + conf: conf, + for: .userProfile, + publicKey: userPublicKey, + messageHashes: nil + ) + } + ) + } + } + + // Then update other contacts + let atomicConf: Atomic?> = SessionUtil.config( + for: .contacts, + publicKey: userPublicKey + ) + let result: ConfResult = try SessionUtil + .upsert( + contactData: updatedProfiles + .filter { $0.id != userPublicKey } + .map { (id: $0.id, contact: nil, profile: $0) }, + in: atomicConf + ) + + // If we don't need to dump the data the we can finish early + guard result.needsDump else { return } + + try SessionUtil.saveState( + db, + keepingExistingMessageHashes: true, + configDump: try atomicConf.mutate { conf in + try SessionUtil.createDump( + conf: conf, + for: .contacts, + publicKey: userPublicKey, + messageHashes: nil + ) + } + ) + } + catch { + SNLog("[libSession-util] Failed to dump updated data") + } + } + + return updated + } +} diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift index 35fa57e27..d89132c58 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift @@ -6,22 +6,24 @@ import SessionUtil import SessionUtilitiesKit internal extension SessionUtil { + // MARK: - Incoming Changes + static func handleUserProfileUpdate( _ db: Database, - in target: Target, + in atomicConf: Atomic?>, needsDump: Bool, latestConfigUpdateSentTimestamp: TimeInterval ) throws { typealias ProfileData = (profileName: String, profilePictureUrl: String?, profilePictureKey: Data?) guard needsDump else { return } - guard target.conf.wrappedValue != nil else { throw SessionUtilError.nilConfigObject } + guard atomicConf.wrappedValue != nil else { throw SessionUtilError.nilConfigObject } let userPublicKey: String = getUserHexEncodedPublicKey(db) // Since we are doing direct memory manipulation we are using an `Atomic` type which has // blocking access in it's `mutate` closure - let maybeProfileData: ProfileData? = target.conf.mutate { conf -> ProfileData? in + let maybeProfileData: ProfileData? = atomicConf.mutate { conf -> ProfileData? in // A profile must have a name so if this is null then it's invalid and can be ignored guard let profileNamePtr: UnsafePointer = user_profile_get_name(conf) else { return nil @@ -52,33 +54,55 @@ internal extension SessionUtil { // Only save the data in the database if it's valid guard let profileData: ProfileData = maybeProfileData else { return } - // Profile (also force-approve the current user in case the account got into a weird state or - // restored directly from a migration) - try MessageReceiver.updateProfileIfNeeded( + // Handle user profile changes + try ProfileManager.updateProfileIfNeeded( db, publicKey: userPublicKey, name: profileData.profileName, - profilePictureUrl: profileData.profilePictureUrl, - profileKey: profileData.profilePictureKey, - sentTimestamp: latestConfigUpdateSentTimestamp + avatarUpdate: { + guard + let profilePictureUrl: String = profileData.profilePictureUrl, + let profileKey: Data = profileData.profilePictureKey + else { return .none } + + return .updateTo( + url: profilePictureUrl, + key: profileKey, + fileName: nil + ) + }(), + sentTimestamp: latestConfigUpdateSentTimestamp, + calledFromConfigHandling: true ) - try Contact(id: userPublicKey) - .with( - isApproved: true, - didApproveMe: true - ) - .save(db) + + // Create a contact for the current user if needed (also force-approve the current user + // in case the account got into a weird state or restored directly from a migration) + let userContact: Contact = Contact.fetchOrCreate(db, id: userPublicKey) + + if !userContact.isTrusted || !userContact.isApproved || !userContact.didApproveMe { + try userContact.save(db) + try Contact + .filter(id: userPublicKey) + .updateAll( // Handling a config update so don't use `updateAllAndConfig` + db, + Contact.Columns.isTrusted.set(to: true), // Always trust the current user + Contact.Columns.isApproved.set(to: true), + Contact.Columns.didApproveMe.set(to: true) + ) + } } - @discardableResult static func update( + // MARK: - Outgoing Changes + + static func update( profile: Profile, - in target: Target + in atomicConf: Atomic?> ) throws -> ConfResult { - guard target.conf.wrappedValue != nil else { throw SessionUtilError.nilConfigObject } + guard atomicConf.wrappedValue != nil else { throw SessionUtilError.nilConfigObject } // Since we are doing direct memory manipulation we are using an `Atomic` type which has // blocking access in it's `mutate` closure - return target.conf.mutate { conf in + return atomicConf.mutate { conf in // Update the name user_profile_set_name(conf, profile.name) @@ -101,7 +125,7 @@ internal extension SessionUtil { user_profile_set_pic(conf, profilePic) } - return ( + return ConfResult( needsPush: config_needs_push(conf), needsDump: config_needs_dump(conf) ) diff --git a/SessionMessagingKit/LibSessionUtil/Database/QueryInterfaceRequest+Utilities.swift b/SessionMessagingKit/LibSessionUtil/Database/QueryInterfaceRequest+Utilities.swift new file mode 100644 index 000000000..2caf9c03c --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/Database/QueryInterfaceRequest+Utilities.swift @@ -0,0 +1,73 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import GRDB +import SessionUtilitiesKit + +// MARK: - GRDB + +public extension QueryInterfaceRequest { + @discardableResult + func updateAllAndConfig( + _ db: Database, + _ assignments: ColumnAssignment... + ) throws -> Int { + return try updateAllAndConfig(db, assignments) + } + + @discardableResult + func updateAllAndConfig( + _ db: Database, + _ assignments: [ColumnAssignment] + ) throws -> Int { + switch self { + case let contactRequest as QueryInterfaceRequest: + return try contactRequest.updateAndFetchAllAndUpdateConfig(db, assignments).count + + case let profileRequest as QueryInterfaceRequest: + return try profileRequest.updateAndFetchAllAndUpdateConfig(db, assignments).count + + default: return try self.updateAll(db, assignments) + } + } +} + +public extension QueryInterfaceRequest where RowDecoder: FetchableRecord & TableRecord { + @discardableResult + func updateAndFetchAllAndUpdateConfig( + _ db: Database, + _ assignments: ColumnAssignment... + ) throws -> [RowDecoder] { + return try updateAndFetchAllAndUpdateConfig(db, assignments) + } + + @discardableResult + func updateAndFetchAllAndUpdateConfig( + _ db: Database, + _ assignments: [ColumnAssignment] + ) throws -> [RowDecoder] { + defer { + db.afterNextTransaction { db in + guard + self is QueryInterfaceRequest || + self is QueryInterfaceRequest || + self is QueryInterfaceRequest + else { return } + + // If we change one of these types then we may as well automatically enqueue + // a new config sync job once the transaction completes + ConfigurationSyncJob.enqueue(db) + } + } + + switch self { + case is QueryInterfaceRequest: + return try SessionUtil.updatingContacts(db, try updateAndFetchAll(db, assignments)) + + case is QueryInterfaceRequest: + return try SessionUtil.updatingProfiles(db, try updateAndFetchAll(db, assignments)) + + default: return try self.updateAndFetchAll(db, assignments) + } + } +} diff --git a/SessionMessagingKit/LibSessionUtil/SessionUtil.swift b/SessionMessagingKit/LibSessionUtil/SessionUtil.swift index 7f295e39a..d995a571f 100644 --- a/SessionMessagingKit/LibSessionUtil/SessionUtil.swift +++ b/SessionMessagingKit/LibSessionUtil/SessionUtil.swift @@ -2,73 +2,94 @@ import Foundation import GRDB +import SessionSnodeKit import SessionUtil import SessionUtilitiesKit -/*internal*/public enum SessionUtil { - public typealias ConfResult = (needsPush: Bool, needsDump: Bool) - public typealias IncomingConfResult = (needsPush: Bool, needsDump: Bool, latestSentTimestamp: TimeInterval) +public enum SessionUtil { + public struct ConfResult { + let needsPush: Bool + let needsDump: Bool + } - enum Target { - case global(variant: ConfigDump.Variant) - case custom(conf: Atomic?>) - - var conf: Atomic?> { - switch self { - case .global(let variant): return SessionUtil.config(for: variant) - case .custom(let conf): return conf - } - } + public struct IncomingConfResult { + let needsPush: Bool + let needsDump: Bool + let messageHashes: [String] + let latestSentTimestamp: TimeInterval + } + + public struct OutgoingConfResult { + let message: SharedConfigMessage + let namespace: SnodeAPI.Namespace + let destination: Message.Destination + let oldMessageHashes: [String]? } // MARK: - Configs - private static var userProfileConfig: Atomic?> = Atomic(nil) - private static var contactsConfig: Atomic?> = Atomic(nil) + fileprivate static var configStore: Atomic<[ConfigKey: Atomic?>]> = Atomic([:]) + + public static func config(for variant: ConfigDump.Variant, publicKey: String) -> Atomic?> { + let key: ConfigKey = ConfigKey(variant: variant, publicKey: publicKey) + + return ( + SessionUtil.configStore.wrappedValue[key] ?? + Atomic(nil) + ) + } // MARK: - Variables + /// Returns `true` if there is a config which needs to be pushed, but returns `false` if the configs are all up to date or haven't been + /// loaded yet (eg. fresh install) public static var needsSync: Bool { - return ConfigDump.Variant.allCases.contains { variant in - switch variant { - case .userProfile: - return (userProfileConfig.wrappedValue.map { config_needs_push($0) } ?? false) - - case .contacts: - return (contactsConfig.wrappedValue.map { config_needs_push($0) } ?? false) - } - } - } - - // MARK: - Convenience - private static func config(for variant: ConfigDump.Variant) -> Atomic?> { - switch variant { - case .userProfile: return SessionUtil.userProfileConfig - case .contacts: return SessionUtil.contactsConfig - } + return configStore + .wrappedValue + .contains { _, atomicConf in config_needs_push(atomicConf.wrappedValue) } } // MARK: - Loading - /*internal*/public static func loadState(ed25519SecretKey: [UInt8]?) { + public static func loadState( + userPublicKey: String, + ed25519SecretKey: [UInt8]? + ) { guard let secretKey: [UInt8] = ed25519SecretKey else { return } - SessionUtil.userProfileConfig.mutate { $0 = loadState(for: .userProfile, secretKey: secretKey) } - SessionUtil.contactsConfig.mutate { $0 = loadState(for: .contacts, secretKey: secretKey) } - } - - private static func loadState( - for variant: ConfigDump.Variant, - secretKey ed25519SecretKey: [UInt8]? - ) -> UnsafeMutablePointer? { - guard let secretKey: [UInt8] = ed25519SecretKey else { return nil } + // Retrieve the existing dumps from the database + let existingDumps: Set = Storage.shared + .read { db in try ConfigDump.fetchSet(db) } + .defaulting(to: []) + let existingDumpVariants: Set = existingDumps + .map { $0.variant } + .asSet() + let missingRequiredVariants: Set = ConfigDump.Variant.userVariants + .asSet() + .subtracting(existingDumpVariants) - // Load any - let storedDump: Data? = Storage.shared - .read { db in try ConfigDump.fetchOne(db, id: variant) }? - .data - - return try? loadState(for: variant, secretKey: secretKey, cachedData: storedDump) + // Create the 'config_object' records for each dump + SessionUtil.configStore.mutate { confStore in + existingDumps.forEach { dump in + confStore[ConfigKey(variant: dump.variant, publicKey: dump.publicKey)] = Atomic( + try? SessionUtil.loadState( + for: dump.variant, + secretKey: secretKey, + cachedData: dump.data + ) + ) + } + + missingRequiredVariants.forEach { variant in + confStore[ConfigKey(variant: variant, publicKey: userPublicKey)] = Atomic( + try? SessionUtil.loadState( + for: variant, + secretKey: secretKey, + cachedData: nil + ) + ) + } + } } internal static func loadState( @@ -117,87 +138,165 @@ import SessionUtilitiesKit internal static func saveState( _ db: Database, - conf: UnsafeMutablePointer?, - for variant: ConfigDump.Variant + keepingExistingMessageHashes: Bool, + configDump: ConfigDump? ) throws { + guard let configDump: ConfigDump = configDump else { return } + + // If we want to keep the existing message hashes then we need + // to fetch them from the db and create a new 'ConfigDump' instance + let targetDump: ConfigDump = try { + guard keepingExistingMessageHashes else { return configDump } + + let existingCombinedMessageHashes: String? = try ConfigDump + .filter( + ConfigDump.Columns.variant == configDump.variant && + ConfigDump.Columns.publicKey == configDump.publicKey + ) + .select(.combinedMessageHashes) + .asRequest(of: String.self) + .fetchOne(db) + + return ConfigDump( + variant: configDump.variant, + publicKey: configDump.publicKey, + data: configDump.data, + messageHashes: ConfigDump.messageHashes(from: existingCombinedMessageHashes) + ) + }() + + // Actually save the dump + try targetDump.save(db) + } + + internal static func createDump( + conf: UnsafeMutablePointer?, + for variant: ConfigDump.Variant, + publicKey: String, + messageHashes: [String]? + ) throws -> ConfigDump? { guard conf != nil else { throw SessionUtilError.nilConfigObject } // If it doesn't need a dump then do nothing - guard config_needs_dump(conf) else { return } + guard config_needs_dump(conf) else { return nil } var dumpResult: UnsafeMutablePointer? = nil var dumpResultLen: Int = 0 config_dump(conf, &dumpResult, &dumpResultLen) - guard let dumpResult: UnsafeMutablePointer = dumpResult else { return } + guard let dumpResult: UnsafeMutablePointer = dumpResult else { return nil } let dumpData: Data = Data(bytes: dumpResult, count: dumpResultLen) dumpResult.deallocate() - try ConfigDump( + return ConfigDump( variant: variant, - data: dumpData + publicKey: publicKey, + data: dumpData, + messageHashes: messageHashes ) - .save(db) } // MARK: - Pushes - public static func getChanges( - for variants: [ConfigDump.Variant] = ConfigDump.Variant.allCases, + public static func pendingChanges( + _ db: Database, + userPublicKey: String, ed25519SecretKey: [UInt8] - ) -> [SharedConfigMessage] { - return variants - .compactMap { variant -> SharedConfigMessage? in - let conf = SessionUtil.config(for: variant) + ) throws -> [OutgoingConfResult] { + let existingDumpInfo: Set = try ConfigDump + .select(.variant, .publicKey, .combinedMessageHashes) + .asRequest(of: DumpInfo.self) + .fetchSet(db) + + // Ensure we always check the required user config types for changes even if there is no dump + // data yet (to deal with first launch cases) + return existingDumpInfo + .inserting( + contentsOf: DumpInfo.requiredUserConfigDumpInfo(userPublicKey: userPublicKey) + .filter { requiredInfo -> Bool in + !existingDumpInfo.contains(where: { + $0.variant == requiredInfo.variant && + $0.publicKey == requiredInfo.publicKey + }) + } + ) + .compactMap { dumpInfo -> OutgoingConfResult? in + let key: ConfigKey = ConfigKey(variant: dumpInfo.variant, publicKey: dumpInfo.publicKey) + let atomicConf: Atomic?> = ( + SessionUtil.configStore.wrappedValue[key] ?? + Atomic(nil) + ) // Check if the config needs to be pushed - guard config_needs_push(conf.wrappedValue) else { return nil } + guard config_needs_push(atomicConf.wrappedValue) else { return nil } var toPush: UnsafeMutablePointer? = nil var toPushLen: Int = 0 - let seqNo: Int64 = conf.mutate { config_push($0, &toPush, &toPushLen) } + let seqNo: Int64 = atomicConf.mutate { config_push($0, &toPush, &toPushLen) } guard let toPush: UnsafeMutablePointer = toPush else { return nil } let pushData: Data = Data(bytes: toPush, count: toPushLen) toPush.deallocate() - return SharedConfigMessage( - kind: variant.configMessageKind, - seqNo: seqNo, - data: pushData + return OutgoingConfResult( + message: SharedConfigMessage( + kind: dumpInfo.variant.configMessageKind, + seqNo: seqNo, + data: pushData + ), + namespace: dumpInfo.variant.namespace, + destination: (dumpInfo.publicKey == userPublicKey ? + Message.Destination.contact(publicKey: userPublicKey) : + Message.Destination.closedGroup(groupPublicKey: dumpInfo.publicKey) + ), + oldMessageHashes: dumpInfo.messageHashes ) } } - public static func markAsPushed(messages: [SharedConfigMessage]) -> [ConfigDump.Variant: Bool] { - messages.reduce(into: [:]) { result, message in - let conf = SessionUtil.config(for: message.kind.configDumpVariant) - - // Mark the config as pushed - config_confirm_pushed(conf.wrappedValue, message.seqNo) - - // Update the result to indicate whether the config needs to be dumped - result[message.kind.configDumpVariant] = config_needs_dump(conf.wrappedValue) - } + public static func markAsPushed( + message: SharedConfigMessage, + publicKey: String + ) -> Bool { + let key: ConfigKey = ConfigKey(variant: message.kind.configDumpVariant, publicKey: publicKey) + let atomicConf: Atomic?> = ( + SessionUtil.configStore.wrappedValue[key] ?? + Atomic(nil) + ) + + // Mark the config as pushed + config_confirm_pushed(atomicConf.wrappedValue, message.seqNo) + + // Update the result to indicate whether the config needs to be dumped + return config_needs_dump(atomicConf.wrappedValue) } // MARK: - Receiving public static func handleConfigMessages( _ db: Database, - messages: [SharedConfigMessage] + messages: [SharedConfigMessage], + publicKey: String ) throws { + guard !messages.isEmpty else { return } + guard !publicKey.isEmpty else { throw MessageReceiverError.noThread } + let groupedMessages: [SharedConfigMessage.Kind: [SharedConfigMessage]] = messages .grouped(by: \.kind) // Merge the config messages into the current state let results: [ConfigDump.Variant: IncomingConfResult] = groupedMessages .reduce(into: [:]) { result, next in - let atomicConf = SessionUtil.config(for: next.key.configDumpVariant) + let key: ConfigKey = ConfigKey(variant: next.key.configDumpVariant, publicKey: publicKey) + let atomicConf: Atomic?> = ( + SessionUtil.configStore.wrappedValue[key] ?? + Atomic(nil) + ) var needsPush: Bool = false var needsDump: Bool = false + let messageHashes: [String] = next.value.compactMap { $0.serverHash } let messageSentTimestamp: TimeInterval = TimeInterval( (next.value.compactMap { $0.sentTimestamp }.max() ?? 0) / 1000 ) @@ -217,25 +316,119 @@ import SessionUtilitiesKit } // Return the current state of the config - result[next.key.configDumpVariant] = ( + result[next.key.configDumpVariant] = IncomingConfResult( needsPush: needsPush, needsDump: needsDump, + messageHashes: messageHashes, latestSentTimestamp: messageSentTimestamp ) } - // If the data needs to be dumped then apply the relevant local changes + // Process the results from the merging try results.forEach { variant, result in + let key: ConfigKey = ConfigKey(variant: variant, publicKey: publicKey) + let atomicConf: Atomic?> = ( + SessionUtil.configStore.wrappedValue[key] ?? + Atomic(nil) + ) + + // Apply the updated states to the database switch variant { case .userProfile: try SessionUtil.handleUserProfileUpdate( db, - in: .global(variant: variant), + in: atomicConf, needsDump: result.needsDump, latestConfigUpdateSentTimestamp: result.latestSentTimestamp ) + + case .contacts: + try SessionUtil.handleContactsUpdate( + db, + in: atomicConf, + needsDump: result.needsDump + ) } + + // We need to get the existing message hashes and combine them with the latest from the + // service node to ensure the next push will properly clean up old messages + let oldMessageHashes: Set = try ConfigDump + .filter( + ConfigDump.Columns.variant == variant && + ConfigDump.Columns.publicKey == publicKey + ) + .select(.combinedMessageHashes) + .asRequest(of: String.self) + .fetchOne(db) + .map { ConfigDump.messageHashes(from: $0) } + .defaulting(to: []) + .asSet() + let allMessageHashes: [String] = Array(oldMessageHashes + .inserting(contentsOf: result.messageHashes.asSet())) + let messageHashesChanged: Bool = (oldMessageHashes != result.messageHashes.asSet()) + + // Now that the changes are applied, update the cached dumps + switch (result.needsDump, messageHashesChanged) { + case (true, _): + // The config data had changes so regenerate the dump and save it + try atomicConf + .mutate { conf -> ConfigDump? in + try SessionUtil.createDump( + conf: conf, + for: variant, + publicKey: publicKey, + messageHashes: allMessageHashes + ) + }? + .save(db) + + case (false, true): + // The config data didn't change but there were different messages on the service node + // so just update the message hashes so the next sync can properly remove any old ones + try ConfigDump + .filter( + ConfigDump.Columns.variant == variant && + ConfigDump.Columns.publicKey == publicKey + ) + .updateAll( + db, + ConfigDump.Columns.combinedMessageHashes + .set(to: ConfigDump.combinedMessageHashes(from: allMessageHashes)) + ) + + default: break + } + + // Now that the local state has been updated, trigger a config sync (this will push any + // pending updates and properly update the state) + if results.contains(where: { $0.value.needsPush }) { + ConfigurationSyncJob.enqueue(db) } } } + +// MARK: - Internal Convenience + +fileprivate extension SessionUtil { + struct ConfigKey: Hashable { + let variant: ConfigDump.Variant + let publicKey: String + } + + struct DumpInfo: FetchableRecord, Decodable, Hashable { + let variant: ConfigDump.Variant + let publicKey: String + private let combinedMessageHashes: String? + + var messageHashes: [String]? { ConfigDump.messageHashes(from: combinedMessageHashes) } + + // MARK: - Convenience + + static func requiredUserConfigDumpInfo(userPublicKey: String) -> Set { + return ConfigDump.Variant.userVariants + .map { DumpInfo(variant: $0, publicKey: userPublicKey, combinedMessageHashes: nil) } + .asSet() + } + } +} diff --git a/SessionMessagingKit/Messages/Message+Destination.swift b/SessionMessagingKit/Messages/Message+Destination.swift index 40211e90f..3714b06c6 100644 --- a/SessionMessagingKit/Messages/Message+Destination.swift +++ b/SessionMessagingKit/Messages/Message+Destination.swift @@ -6,15 +6,9 @@ import SessionSnodeKit import SessionUtilitiesKit public extension Message { - enum Destination: Codable { - case contact( - publicKey: String, - namespace: SnodeAPI.Namespace - ) - case closedGroup( - groupPublicKey: String, - namespace: SnodeAPI.Namespace - ) + enum Destination: Codable, Hashable { + case contact(publicKey: String) + case closedGroup(groupPublicKey: String) case openGroup( roomToken: String, server: String, @@ -23,13 +17,6 @@ public extension Message { fileIds: [String]? = nil ) case openGroupInbox(server: String, openGroupPublicKey: String, blindedPublicKey: String) - - var namespace: SnodeAPI.Namespace { - switch self { - case .contact(_, let namespace), .closedGroup(_, let namespace): return namespace - default: preconditionFailure("Attepted to retrieve namespace for invalid destination") - } - } public static func from( _ db: Database, @@ -50,10 +37,10 @@ public extension Message { ) } - return .contact(publicKey: thread.id, namespace: .default) + return .contact(publicKey: thread.id) case .closedGroup: - return .closedGroup(groupPublicKey: thread.id, namespace: .legacyClosedGroup) + return .closedGroup(groupPublicKey: thread.id) case .openGroup: guard let openGroup: OpenGroup = try thread.openGroup.fetchOne(db) else { @@ -79,59 +66,5 @@ public extension Message { default: return self } } - - // MARK: - Codable - - // FIXME: Remove this custom implementation after enough time has passed (added the 'namespace' properties) - public init(from decoder: Decoder) throws { - let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) - - // Should only have a single root key so we can just switch on it to have cleaner code - switch container.allKeys.first { - case .contact: - let childContainer: KeyedDecodingContainer = try container.nestedContainer(keyedBy: ContactCodingKeys.self, forKey: .contact) - - self = .contact( - publicKey: try childContainer.decode(String.self, forKey: .publicKey), - namespace: ( - (try? childContainer.decode(SnodeAPI.Namespace.self, forKey: .namespace)) ?? - .default - ) - ) - - case .closedGroup: - let childContainer: KeyedDecodingContainer = try container.nestedContainer(keyedBy: ClosedGroupCodingKeys.self, forKey: .closedGroup) - - self = .closedGroup( - groupPublicKey: try childContainer.decode(String.self, forKey: .groupPublicKey), - namespace: ( - (try? childContainer.decode(SnodeAPI.Namespace.self, forKey: .namespace)) ?? - .legacyClosedGroup - ) - ) - - case .openGroup: - let childContainer: KeyedDecodingContainer = try container.nestedContainer(keyedBy: OpenGroupCodingKeys.self, forKey: .openGroup) - - self = .openGroup( - roomToken: try childContainer.decode(String.self, forKey: .roomToken), - server: try childContainer.decode(String.self, forKey: .server), - whisperTo: try? childContainer.decode(String.self, forKey: .whisperTo), - whisperMods: try childContainer.decode(Bool.self, forKey: .whisperMods), - fileIds: try? childContainer.decode([String].self, forKey: .fileIds) - ) - - case .openGroupInbox: - let childContainer: KeyedDecodingContainer = try container.nestedContainer(keyedBy: OpenGroupInboxCodingKeys.self, forKey: .openGroupInbox) - - self = .openGroupInbox( - server: try childContainer.decode(String.self, forKey: .server), - openGroupPublicKey: try childContainer.decode(String.self, forKey: .openGroupPublicKey), - blindedPublicKey: try childContainer.decode(String.self, forKey: .blindedPublicKey) - ) - - default: throw MessageReceiverError.invalidMessage - } - } } } diff --git a/SessionMessagingKit/Messages/Message.swift b/SessionMessagingKit/Messages/Message.swift index dc31d502e..3ea92ad34 100644 --- a/SessionMessagingKit/Messages/Message.swift +++ b/SessionMessagingKit/Messages/Message.swift @@ -183,7 +183,7 @@ public extension Message { default: return false } - case is ConfigurationMessage: return true + case is ConfigurationMessage, is SharedConfigMessage: return true case is UnsendRequest: return true default: return false } diff --git a/SessionMessagingKit/Open Groups/Models/SOGSBatchRequest.swift b/SessionMessagingKit/Open Groups/Models/SOGSBatchRequest.swift index 5d97b576f..c44fe7aee 100644 --- a/SessionMessagingKit/Open Groups/Models/SOGSBatchRequest.swift +++ b/SessionMessagingKit/Open Groups/Models/SOGSBatchRequest.swift @@ -108,15 +108,18 @@ internal extension AnyPublisher where Output == HTTP.BatchResponse, Failure == E func map( requests: [OpenGroupAPI.BatchRequest.Info], toHashMapFor endpointType: E.Type - ) -> AnyPublisher<[E: (ResponseInfoType, Codable?)], Error> { + ) -> AnyPublisher<(info: ResponseInfoType, data: [E: Codable]), Error> { return self - .map { result in - result.enumerated() - .reduce(into: [:]) { prev, next in - guard let endpoint: E = requests[next.offset].endpoint as? E else { return } - - prev[endpoint] = next.element - } + .map { result -> (info: ResponseInfoType, data: [E: Codable]) in + ( + info: result.info, + data: result.responses.enumerated() + .reduce(into: [:]) { prev, next in + guard let endpoint: E = requests[next.offset].endpoint as? E else { return } + + prev[endpoint] = next.element + } + ) } .eraseToAnyPublisher() } diff --git a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift index 688ec61fb..69224a0ea 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift @@ -32,7 +32,7 @@ public enum OpenGroupAPI { hasPerformedInitialPoll: Bool, timeSinceLastPoll: TimeInterval, using dependencies: SMKDependencies = SMKDependencies() - ) -> AnyPublisher<[Endpoint: (ResponseInfoType, Codable?)], Error> { + ) -> AnyPublisher<(info: ResponseInfoType, data: [Endpoint: Codable]), Error> { let lastInboxMessageId: Int64 = (try? OpenGroup .select(.inboxLatestMessageId) .filter(OpenGroup.Columns.server == server) @@ -152,7 +152,7 @@ public enum OpenGroupAPI { server: String, requests: [BatchRequest.Info], using dependencies: SMKDependencies = SMKDependencies() - ) -> AnyPublisher<[Endpoint: (ResponseInfoType, Codable?)], Error> { + ) -> AnyPublisher<(info: ResponseInfoType, data: [Endpoint: Codable]), Error> { let responseTypes = requests.map { $0.responseType } return OpenGroupAPI @@ -184,7 +184,7 @@ public enum OpenGroupAPI { server: String, requests: [BatchRequest.Info], using dependencies: SMKDependencies = SMKDependencies() - ) -> AnyPublisher<[Endpoint: (ResponseInfoType, Codable?)], Error> { + ) -> AnyPublisher<(info: ResponseInfoType, data: [Endpoint: Codable]), Error> { let responseTypes = requests.map { $0.responseType } return OpenGroupAPI @@ -339,10 +339,9 @@ public enum OpenGroupAPI { requests: requestResponseType, using: dependencies ) - .flatMap { (response: [Endpoint: (ResponseInfoType, Codable?)]) -> AnyPublisher<(capabilities: (info: ResponseInfoType, data: Capabilities), room: (info: ResponseInfoType, data: Room)), Error> in - let maybeCapabilities: (info: ResponseInfoType, data: Capabilities?)? = response[.capabilities] - .map { info, data in (info, (data as? HTTP.BatchSubResponse)?.body) } - let maybeRoomResponse: (ResponseInfoType, Codable?)? = response + .flatMap { (info: ResponseInfoType, data: [Endpoint: Codable]) -> AnyPublisher<(capabilities: (info: ResponseInfoType, data: Capabilities), room: (info: ResponseInfoType, data: Room)), Error> in + let maybeCapabilities: HTTP.BatchSubResponse? = (data[.capabilities] as? HTTP.BatchSubResponse) + let maybeRoomResponse: Codable? = data .first(where: { key, _ in switch key { case .room: return true @@ -350,14 +349,13 @@ public enum OpenGroupAPI { } }) .map { _, value in value } - let maybeRoom: (info: ResponseInfoType, data: Room?)? = maybeRoomResponse - .map { info, data in (info, (data as? HTTP.BatchSubResponse)?.body) } + let maybeRoom: HTTP.BatchSubResponse? = (maybeRoomResponse as? HTTP.BatchSubResponse) guard - let capabilitiesInfo: ResponseInfoType = maybeCapabilities?.info, - let capabilities: Capabilities = maybeCapabilities?.data, - let roomInfo: ResponseInfoType = maybeRoom?.info, - let room: Room = maybeRoom?.data + let capabilitiesInfo: ResponseInfoType = maybeCapabilities?.responseInfo, + let capabilities: Capabilities = maybeCapabilities?.body, + let roomInfo: ResponseInfoType = maybeRoom?.responseInfo, + let room: Room = maybeRoom?.body else { return Fail(error: HTTPError.parsingFailed) .eraseToAnyPublisher() @@ -407,25 +405,22 @@ public enum OpenGroupAPI { requests: requestResponseType, using: dependencies ) - .flatMap { (response: [Endpoint: (ResponseInfoType, Codable?)]) -> AnyPublisher<(capabilities: (info: ResponseInfoType, data: Capabilities), rooms: (info: ResponseInfoType, data: [Room])), Error> in - let maybeCapabilities: (info: ResponseInfoType, data: Capabilities?)? = response[.capabilities] - .map { info, data in (info, (data as? HTTP.BatchSubResponse)?.body) } - let maybeRoomResponse: (ResponseInfoType, Codable?)? = response + .flatMap { (info: ResponseInfoType, data: [Endpoint: Codable]) -> AnyPublisher<(capabilities: (info: ResponseInfoType, data: Capabilities), rooms: (info: ResponseInfoType, data: [Room])), Error> in + let maybeCapabilities: HTTP.BatchSubResponse? = (data[.capabilities] as? HTTP.BatchSubResponse) + let maybeRooms: HTTP.BatchSubResponse<[Room]>? = data .first(where: { key, _ in switch key { case .rooms: return true default: return false } }) - .map { _, value in value } - let maybeRooms: (info: ResponseInfoType, data: [Room]?)? = maybeRoomResponse - .map { info, data in (info, (data as? HTTP.BatchSubResponse<[Room]>)?.body) } + .map { _, value in value as? HTTP.BatchSubResponse<[Room]> } guard - let capabilitiesInfo: ResponseInfoType = maybeCapabilities?.info, - let capabilities: Capabilities = maybeCapabilities?.data, - let roomsInfo: ResponseInfoType = maybeRooms?.info, - let rooms: [Room] = maybeRooms?.data + let capabilitiesInfo: ResponseInfoType = maybeCapabilities?.responseInfo, + let capabilities: Capabilities = maybeCapabilities?.body, + let roomsInfo: ResponseInfoType = maybeRooms?.responseInfo, + let rooms: [Room] = maybeRooms?.body else { return Fail(error: HTTPError.parsingFailed) .eraseToAnyPublisher() @@ -1263,7 +1258,9 @@ public enum OpenGroupAPI { requests: requestResponseType, using: dependencies ) - .map { $0.values.map { responseInfo, _ in responseInfo } } + .map { _, data -> [ResponseInfoType] in + data.values.compactMap { ($0 as? BatchSubResponseType)?.responseInfo } + } .eraseToAnyPublisher() } diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index 845f461a5..ad963705b 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -68,36 +68,40 @@ public final class OpenGroupManager { // MARK: - Polling public func startPolling(using dependencies: OGMDependencies = OGMDependencies()) { - guard !dependencies.cache.isPolling else { return } + // Run on the 'workQueue' to ensure any 'Atomic' access doesn't block the main thread + // on startup + OpenGroupAPI.workQueue.async { + guard !dependencies.cache.isPolling else { return } - let servers: Set = dependencies.storage - .read { db in - // The default room promise creates an OpenGroup with an empty `roomToken` value, - // we don't want to start a poller for this as the user hasn't actually joined a room - try OpenGroup - .select(.server) - .filter(OpenGroup.Columns.isActive == true) - .filter(OpenGroup.Columns.roomToken != "") - .distinct() - .asRequest(of: String.self) - .fetchSet(db) - } - .defaulting(to: []) - - dependencies.mutableCache.mutate { cache in - cache.isPolling = true - cache.pollers = servers - .reduce(into: [:]) { result, server in - result[server.lowercased()]?.stop() // Should never occur - result[server.lowercased()] = OpenGroupAPI.Poller(for: server.lowercased()) + let servers: Set = dependencies.storage + .read { db in + // The default room promise creates an OpenGroup with an empty `roomToken` value, + // we don't want to start a poller for this as the user hasn't actually joined a room + try OpenGroup + .select(.server) + .filter(OpenGroup.Columns.isActive == true) + .filter(OpenGroup.Columns.roomToken != "") + .distinct() + .asRequest(of: String.self) + .fetchSet(db) } + .defaulting(to: []) - // Note: We loop separately here because when the cache is mocked-out for tests it - // doesn't actually store the value (meaning the pollers won't be started), but if - // we do it in the 'reduce' function, the 'reduce' result will actually store the - // poller value resulting in a bunch of OpenGroup pollers running in a way that can't - // be stopped during unit tests - cache.pollers.forEach { _, poller in poller.startIfNeeded(using: dependencies) } + dependencies.mutableCache.mutate { cache in + cache.isPolling = true + cache.pollers = servers + .reduce(into: [:]) { result, server in + result[server.lowercased()]?.stop() // Should never occur + result[server.lowercased()] = OpenGroupAPI.Poller(for: server.lowercased()) + } + + // Note: We loop separately here because when the cache is mocked-out for tests it + // doesn't actually store the value (meaning the pollers won't be started), but if + // we do it in the 'reduce' function, the 'reduce' result will actually store the + // poller value resulting in a bunch of OpenGroup pollers running in a way that can't + // be stopped during unit tests + cache.pollers.forEach { _, poller in poller.startIfNeeded(using: dependencies) } + } } } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift index 9fe36f054..2e4203966 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift @@ -7,6 +7,11 @@ import SessionUtilitiesKit extension MessageReceiver { internal static func handleConfigurationMessage(_ db: Database, message: ConfigurationMessage) throws { + guard !Features.useSharedUtilForUserConfig else { + // TODO: Show warning prompt for X days + return + } + let userPublicKey = getUserHexEncodedPublicKey(db) guard message.sender == userPublicKey else { return } @@ -21,22 +26,41 @@ extension MessageReceiver { .defaulting(to: Date(timeIntervalSince1970: 0)) .timeIntervalSince1970 - // Profile (also force-approve the current user in case the account got into a weird state or - // restored directly from a migration) - try MessageReceiver.updateProfileIfNeeded( + // Handle user profile changes + try ProfileManager.updateProfileIfNeeded( db, publicKey: userPublicKey, name: message.displayName, - profilePictureUrl: message.profilePictureUrl, - profileKey: message.profileKey, + avatarUpdate: { + guard + let profilePictureUrl: String = message.profilePictureUrl, + let profileKey: Data = message.profileKey + else { return .none } + + return .updateTo( + url: profilePictureUrl, + key: profileKey, + fileName: nil + ) + }(), sentTimestamp: messageSentTimestamp ) - try Contact(id: userPublicKey) - .with( - isApproved: true, - didApproveMe: true - ) - .save(db) + + // Create a contact for the current user if needed (also force-approve the current user + // in case the account got into a weird state or restored directly from a migration) + let userContact: Contact = Contact.fetchOrCreate(db, id: userPublicKey) + + if !userContact.isTrusted || !userContact.isApproved || !userContact.didApproveMe { + try userContact.save(db) + try Contact + .filter(id: userPublicKey) + .updateAll( // Handling a config update so don't use `updateAllAndConfig` + db, + Contact.Columns.isTrusted.set(to: true), + Contact.Columns.isApproved.set(to: true), + Contact.Columns.didApproveMe.set(to: true) + ) + } if isInitialSync || messageSentTimestamp > lastConfigTimestamp { if isInitialSync { @@ -53,11 +77,10 @@ extension MessageReceiver { // If the contact is a blinded contact then only add them if they haven't already been // unblinded if SessionId.Prefix(from: sessionId) == .blinded { - let hasUnblindedContact: Bool = (try? BlindedIdLookup + let hasUnblindedContact: Bool = BlindedIdLookup .filter(BlindedIdLookup.Columns.blindedId == sessionId) .filter(BlindedIdLookup.Columns.sessionId != nil) - .isNotEmpty(db)) - .defaulting(to: false) + .isNotEmpty(db) if hasUnblindedContact { return @@ -74,13 +97,21 @@ extension MessageReceiver { profile.profilePictureUrl != contactInfo.profilePictureUrl || profile.profileEncryptionKey != contactInfo.profileKey { - try profile - .with( - name: contactInfo.displayName, - profilePictureUrl: .updateIf(contactInfo.profilePictureUrl), - profileEncryptionKey: .updateIf(contactInfo.profileKey) + try profile.save(db) + try Profile + .filter(id: sessionId) + .updateAll( // Handling a config update so don't use `updateAllAndConfig` + db, + [ + Profile.Columns.name.set(to: contactInfo.displayName), + (contactInfo.profilePictureUrl == nil ? nil : + Profile.Columns.profilePictureUrl.set(to: contactInfo.profilePictureUrl) + ), + (contactInfo.profileKey == nil ? nil : + Profile.Columns.profileEncryptionKey.set(to: contactInfo.profileKey) + ) + ].compactMap { $0 } ) - .save(db) } /// We only update these values if the proto actually has values for them (this is to prevent an @@ -94,22 +125,23 @@ extension MessageReceiver { (contactInfo.hasIsBlocked && (contact.isBlocked != contactInfo.isBlocked)) || (contactInfo.hasDidApproveMe && (contact.didApproveMe != contactInfo.didApproveMe)) { - try contact - .with( - isApproved: (contactInfo.hasIsApproved && contactInfo.isApproved ? - true : - .existing - ), - isBlocked: (contactInfo.hasIsBlocked ? - .update(contactInfo.isBlocked) : - .existing - ), - didApproveMe: (contactInfo.hasDidApproveMe && contactInfo.didApproveMe ? - true : - .existing - ) + try contact.save(db) + try Contact + .filter(id: sessionId) + .updateAll( // Handling a config update so don't use `updateAllAndConfig` + db, + [ + (!contactInfo.hasIsApproved || !contactInfo.isApproved ? nil : + Contact.Columns.isApproved.set(to: true) + ), + (!contactInfo.hasIsBlocked ? nil : + Contact.Columns.isBlocked.set(to: contactInfo.isBlocked) + ), + (!contactInfo.hasDidApproveMe || !contactInfo.didApproveMe ? nil : + Contact.Columns.didApproveMe.set(to: contactInfo.didApproveMe) + ) + ].compactMap { $0 } ) - .save(db) } // If the contact is blocked diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift index 179d6a5b3..66320c92a 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift @@ -23,12 +23,22 @@ extension MessageReceiver { if let profile = message.profile { let messageSentTimestamp: TimeInterval = (TimeInterval(message.sentTimestamp ?? 0) / 1000) - try MessageReceiver.updateProfileIfNeeded( + try ProfileManager.updateProfileIfNeeded( db, publicKey: senderId, name: profile.displayName, - profilePictureUrl: profile.profilePictureUrl, - profileKey: profile.profileKey, + avatarUpdate: { + guard + let profilePictureUrl: String = profile.profilePictureUrl, + let profileKey: Data = profile.profileKey + else { return .none } + + return .updateTo( + url: profilePictureUrl, + key: profileKey, + fileName: nil + ) + }(), sentTimestamp: messageSentTimestamp ) } @@ -88,8 +98,7 @@ extension MessageReceiver { try updateContactApprovalStatusIfNeeded( db, senderSessionId: senderId, - threadId: nil, - forceConfigSync: blindedContactIds.isEmpty // Sync here if there were no blinded contacts + threadId: nil ) // If there were blinded contacts which have now been resolved to this contact then we should remove @@ -103,8 +112,7 @@ extension MessageReceiver { try updateContactApprovalStatusIfNeeded( db, senderSessionId: userPublicKey, - threadId: unblindedThread.id, - forceConfigSync: true + threadId: unblindedThread.id ) } @@ -128,8 +136,7 @@ extension MessageReceiver { internal static func updateContactApprovalStatusIfNeeded( _ db: Database, senderSessionId: String, - threadId: String?, - forceConfigSync: Bool + threadId: String? ) throws { let userPublicKey: String = getUserHexEncodedPublicKey(db) @@ -149,9 +156,10 @@ extension MessageReceiver { guard !contact.isApproved else { return } - _ = try? contact - .with(isApproved: true) - .saved(db) + try? contact.save(db) + _ = try? Contact + .filter(id: threadId) + .updateAllAndConfig(db, Contact.Columns.isApproved.set(to: true)) } else { // The message was sent to the current user so flag their 'didApproveMe' as true (can't send a message to @@ -160,14 +168,10 @@ extension MessageReceiver { guard !contact.didApproveMe else { return } - _ = try? contact - .with(didApproveMe: true) - .saved(db) + try? contact.save(db) + _ = try? Contact + .filter(id: senderSessionId) + .updateAllAndConfig(db, Contact.Columns.didApproveMe.set(to: true)) } - - // Force a config sync to ensure all devices know the contact approval state if desired - guard forceConfigSync else { return } - - try MessageSender.syncConfiguration(db, forceSyncNow: true).sinkUntilComplete() } } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift index 8e6963210..31045cd8d 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift @@ -25,12 +25,22 @@ extension MessageReceiver { // Update profile if needed (want to do this regardless of whether the message exists or // not to ensure the profile info gets sync between a users devices at every chance) if let profile = message.profile { - try MessageReceiver.updateProfileIfNeeded( + try ProfileManager.updateProfileIfNeeded( db, publicKey: sender, name: profile.displayName, - profilePictureUrl: profile.profilePictureUrl, - profileKey: profile.profileKey, + avatarUpdate: { + guard + let profilePictureUrl: String = profile.profilePictureUrl, + let profileKey: Data = profile.profileKey + else { return .none } + + return .updateTo( + url: profilePictureUrl, + key: profileKey, + fileName: nil + ) + }(), sentTimestamp: messageSentTimestamp ) } @@ -272,8 +282,7 @@ extension MessageReceiver { try MessageReceiver.updateContactApprovalStatusIfNeeded( db, senderSessionId: sender, - threadId: thread.id, - forceConfigSync: false + threadId: thread.id ) } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift index e3fa2ec73..5f7c64244 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift @@ -103,7 +103,7 @@ extension MessageSender { // the 'ClosedGroup' object we created sentTimestampMs: UInt64(floor(formationTimestamp * 1000)) ), - to: .contact(publicKey: memberId, namespace: .default), + to: .contact(publicKey: memberId), interactionId: nil ) } @@ -197,7 +197,8 @@ extension MessageSender { ClosedGroupControlMessage.KeyPairWrapper( publicKey: memberPublicKey, encryptedKeyPair: try MessageSender.encryptWithSessionProtocol( - plaintext, + db, + plaintext: plaintext, for: memberPublicKey ) ) @@ -645,7 +646,11 @@ extension MessageSender { let plaintext = try proto.serializedData() let thread: SessionThread = try SessionThread .fetchOrCreate(db, id: publicKey, variant: .contact) - let ciphertext = try MessageSender.encryptWithSessionProtocol(plaintext, for: publicKey) + let ciphertext = try MessageSender.encryptWithSessionProtocol( + db, + plaintext: plaintext, + for: publicKey + ) SNLog("Sending latest encryption key pair to: \(publicKey).") try MessageSender.send( diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index 357a9b915..d117f399e 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -305,82 +305,4 @@ public enum MessageReceiver { return (contactId, .contact) } - - internal static func updateProfileIfNeeded( - _ db: Database, - publicKey: String, - name: String?, - profilePictureUrl: String?, - profileKey: Data?, - sentTimestamp: TimeInterval, - dependencies: Dependencies = Dependencies() - ) throws { - let isCurrentUser = (publicKey == getUserHexEncodedPublicKey(db, dependencies: dependencies)) - let profile: Profile = Profile.fetchOrCreate(id: publicKey) - var updatedProfile: Profile = profile - - // Name - if let name = name, name != profile.name { - let shouldUpdate: Bool - if isCurrentUser { - shouldUpdate = given(UserDefaults.standard[.lastDisplayNameUpdate]) { - sentTimestamp > $0.timeIntervalSince1970 - } - .defaulting(to: true) - } - else { - shouldUpdate = true - } - - if shouldUpdate { - if isCurrentUser { - UserDefaults.standard[.lastDisplayNameUpdate] = Date(timeIntervalSince1970: sentTimestamp) - } - - updatedProfile = updatedProfile.with(name: name) - } - } - - // Profile picture & profile key - if - let profileKey: Data = profileKey, - let profilePictureUrl: String = profilePictureUrl, - profileKey.count == ProfileManager.avatarAES256KeyByteLength, - profileKey != profile.profileEncryptionKey - { - let shouldUpdate: Bool - if isCurrentUser { - shouldUpdate = given(UserDefaults.standard[.lastProfilePictureUpdate]) { - sentTimestamp > $0.timeIntervalSince1970 - } - .defaulting(to: true) - } - else { - shouldUpdate = true - } - - if shouldUpdate { - if isCurrentUser { - UserDefaults.standard[.lastProfilePictureUpdate] = Date(timeIntervalSince1970: sentTimestamp) - } - - updatedProfile = updatedProfile.with( - profilePictureUrl: .update(profilePictureUrl), - profileEncryptionKey: .update(profileKey) - ) - } - } - - // Persist any changes - if updatedProfile != profile { - try updatedProfile.save(db) - } - - // Download the profile picture if needed - if updatedProfile.profilePictureUrl != profile.profilePictureUrl || updatedProfile.profileEncryptionKey != profile.profileEncryptionKey { - db.afterNextTransaction { _ in - ProfileManager.downloadAvatar(for: updatedProfile) - } - } - } } diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift b/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift index 0aa47a509..b3db30c7e 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift @@ -85,8 +85,8 @@ extension MessageSender { let threadId: String = { switch destination { - case .contact(let publicKey, _): return publicKey - case .closedGroup(let groupPublicKey, _): return groupPublicKey + case .contact(let publicKey): return publicKey + case .closedGroup(let groupPublicKey): return groupPublicKey case .openGroup(let roomToken, let server, _, _, _): return OpenGroup.idFor(roomToken: roomToken, server: server) @@ -152,87 +152,4 @@ extension MessageSender { } .eraseToAnyPublisher() } - - /// This method requires the `db` value to be passed in because if it's called within a `writeAsync` completion block - /// it will throw a "re-entrant" fatal error when attempting to write again - public static func syncConfiguration( - _ db: Database, - forceSyncNow: Bool = true - ) throws -> AnyPublisher { - // If we don't have a userKeyPair yet then there is no need to sync the configuration - // as the user doesn't exist yet (this will get triggered on the first launch of a - // fresh install due to the migrations getting run) - guard - Identity.userExists(db), - let ed25519SecretKey: [UInt8] = Identity.fetchUserEd25519KeyPair(db)?.secretKey - else { - return Fail(error: StorageError.generic) - .eraseToAnyPublisher() - } - - let publicKey: String = getUserHexEncodedPublicKey(db) - let legacyDestination: Message.Destination = Message.Destination.contact( - publicKey: publicKey, - namespace: .default - ) - let legacyConfigurationMessage = try ConfigurationMessage.getCurrent(db) - let userConfigMessageChanges: [SharedConfigMessage] = SessionUtil.getChanges( - ed25519SecretKey: ed25519SecretKey - ) - let destination: Message.Destination = Message.Destination.contact( - publicKey: publicKey, - namespace: .userProfileConfig - ) - - guard forceSyncNow else { - JobRunner.add( - db, - job: Job( - variant: .messageSend, - threadId: publicKey, - details: MessageSendJob.Details( - destination: legacyDestination, - message: legacyConfigurationMessage - ) - ) - ) - - return Just(()) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - } - - let sendData: PreparedSendData = try MessageSender.preparedSendData( - db, - message: legacyConfigurationMessage, - to: legacyDestination, - interactionId: nil - ) - - let userConfigSendData: [PreparedSendData] = try userConfigMessageChanges - .map { message in - try MessageSender.preparedSendData( - db, - message: message, - to: destination, - interactionId: nil - ) - } - - /// We want to avoid blocking the db write thread so we dispatch the API call to a different thread - return Just(()) - .setFailureType(to: Error.self) - .receive(on: DispatchQueue.global(qos: .userInitiated)) - .flatMap { _ -> AnyPublisher in - Publishers - .MergeMany( - ([sendData] + userConfigSendData) - .map { MessageSender.sendImmediate(preparedSendData: $0) } - ) - .collect() - .map { _ in () } - .eraseToAnyPublisher() - } - .eraseToAnyPublisher() - } } diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender+Encryption.swift b/SessionMessagingKit/Sending & Receiving/MessageSender+Encryption.swift index 99f4e6765..0517705ab 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender+Encryption.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender+Encryption.swift @@ -1,16 +1,18 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import GRDB import Sodium import SessionUtilitiesKit extension MessageSender { internal static func encryptWithSessionProtocol( - _ plaintext: Data, + _ db: Database, + plaintext: Data, for recipientHexEncodedX25519PublicKey: String, using dependencies: SMKDependencies = SMKDependencies() ) throws -> Data { - guard let userEd25519KeyPair: Box.KeyPair = dependencies.storage.read({ db in Identity.fetchUserEd25519KeyPair(db) }) else { + guard let userEd25519KeyPair: Box.KeyPair = Identity.fetchUserEd25519KeyPair(db) else { throw MessageSenderError.noUserED25519KeyPair } @@ -30,13 +32,16 @@ extension MessageSender { } internal static func encryptWithSessionBlindingProtocol( - _ plaintext: Data, + _ db: Database, + plaintext: Data, for recipientBlindedId: String, openGroupPublicKey: String, using dependencies: SMKDependencies = SMKDependencies() ) throws -> Data { - guard SessionId.Prefix(from: recipientBlindedId) == .blinded else { throw MessageSenderError.signingFailed } - guard let userEd25519KeyPair: Box.KeyPair = dependencies.storage.read({ db in Identity.fetchUserEd25519KeyPair(db) }) else { + guard SessionId.Prefix(from: recipientBlindedId) == .blinded else { + throw MessageSenderError.signingFailed + } + guard let userEd25519KeyPair: Box.KeyPair = Identity.fetchUserEd25519KeyPair(db) else { throw MessageSenderError.noUserED25519KeyPair } guard let blindedKeyPair = dependencies.sodium.blindedKeyPair(serverPublicKey: openGroupPublicKey, edKeyPair: userEd25519KeyPair, genericHash: dependencies.genericHash) else { diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index f60609b0d..0ccb31ce3 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -206,8 +206,8 @@ public final class MessageSender { message.sender = userPublicKey message.recipient = { switch destination { - case .contact(let publicKey, _): return publicKey - case .closedGroup(let groupPublicKey, _): return groupPublicKey + case .contact(let publicKey): return publicKey + case .closedGroup(let groupPublicKey): return groupPublicKey case .openGroup, .openGroupInbox: preconditionFailure() } }() @@ -283,16 +283,17 @@ public final class MessageSender { let ciphertext: Data do { switch destination { - case .contact(let publicKey, _): - ciphertext = try encryptWithSessionProtocol(plaintext, for: publicKey) + case .contact(let publicKey): + ciphertext = try encryptWithSessionProtocol(db, plaintext: plaintext, for: publicKey) - case .closedGroup(let groupPublicKey, _): + case .closedGroup(let groupPublicKey): guard let encryptionKeyPair: ClosedGroupKeyPair = try? ClosedGroupKeyPair.fetchLatestKeyPair(db, threadId: groupPublicKey) else { throw MessageSenderError.noKeyPair } ciphertext = try encryptWithSessionProtocol( - plaintext, + db, + plaintext: plaintext, for: SessionId(.standard, publicKey: encryptionKeyPair.publicKey.bytes).hexString ) @@ -319,7 +320,7 @@ public final class MessageSender { kind = .sessionMessage senderPublicKey = "" - case .closedGroup(let groupPublicKey, _): + case .closedGroup(let groupPublicKey): kind = .closedGroupMessage senderPublicKey = groupPublicKey @@ -553,7 +554,8 @@ public final class MessageSender { do { ciphertext = try encryptWithSessionBlindingProtocol( - plaintext, + db, + plaintext: plaintext, for: recipientBlindedPublicKey, openGroupPublicKey: openGroupPublicKey, using: dependencies @@ -636,107 +638,86 @@ public final class MessageSender { let isMainAppActive: Bool = (UserDefaults.sharedLokiProject?[.isMainAppActive]) .defaulting(to: false) - var isSuccess = false - var errorCount = 0 return SnodeAPI .sendMessage( snodeMessage, - in: destination.namespace + in: { + switch destination { + case .closedGroup: return .legacyClosedGroup + default: return .`default` + } + }() ) .subscribe(on: DispatchQueue.global(qos: .default)) - .flatMap { result, totalCount -> AnyPublisher in - switch result { - case .success(let response): - // Don't emit if we've already succeeded - guard !isSuccess else { - return Just(false) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - } - isSuccess = true - - let updatedMessage: Message = message - updatedMessage.serverHash = response.1.hash - - let job: Job? = Job( - variant: .notifyPushServer, - behaviour: .runOnce, - details: NotifyPushServerJob.Details(message: snodeMessage) - ) - let shouldNotify: Bool = { - switch updatedMessage { - case is VisibleMessage, is UnsendRequest: return !isSyncMessage - case let callMessage as CallMessage: - switch callMessage.kind { - case .preOffer: return true - default: return false - } + .flatMap { response -> AnyPublisher in + let updatedMessage: Message = message + updatedMessage.serverHash = response.1.hash + let job: Job? = Job( + variant: .notifyPushServer, + behaviour: .runOnce, + details: NotifyPushServerJob.Details(message: snodeMessage) + ) + let shouldNotify: Bool = { + switch updatedMessage { + case is VisibleMessage, is UnsendRequest: return !isSyncMessage + case let callMessage as CallMessage: + switch callMessage.kind { + case .preOffer: return true default: return false } - }() - return dependencies.storage - .writePublisher { db -> Void in - try MessageSender.handleSuccessfulMessageSend( - db, - message: updatedMessage, - to: destination, - interactionId: data.interactionId, - isSyncMessage: isSyncMessage, - using: dependencies - ) + default: return false + } + }() - guard shouldNotify && isMainAppActive else { return () } + return dependencies.storage + .writePublisher { db -> Void in + try MessageSender.handleSuccessfulMessageSend( + db, + message: updatedMessage, + to: destination, + interactionId: data.interactionId, + isSyncMessage: isSyncMessage, + using: dependencies + ) - JobRunner.add(db, job: job) - return () - } - .flatMap { _ -> AnyPublisher in - guard shouldNotify && !isMainAppActive else { - return Just(true) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - } - guard let job: Job = job else { - return Just(true) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - } + guard shouldNotify else { return () } - return Future { resolver in - NotifyPushServerJob.run( - job, - queue: DispatchQueue.global(qos: .default), - success: { _, _ in resolver(Result.success(true)) }, - failure: { _, _, _ in - // Always fulfill because the notify PN server job isn't critical. - resolver(Result.success(true)) - }, - deferred: { _ in - // Always fulfill because the notify PN server job isn't critical. - resolver(Result.success(true)) - } - ) - } + JobRunner.add(db, job: job) + return () + } + .flatMap { _ -> AnyPublisher in + guard shouldNotify && !isMainAppActive else { + return Just(true) + .setFailureType(to: Error.self) .eraseToAnyPublisher() - } - .eraseToAnyPublisher() - - case .failure(let error): - errorCount += 1 - - // Only process the error if all promises failed - guard errorCount == totalCount else { - return Just(false) + } + guard let job: Job = job else { + return Just(true) .setFailureType(to: Error.self) .eraseToAnyPublisher() } - return Fail(error: error) - .eraseToAnyPublisher() - } + return Future { resolver in + NotifyPushServerJob.run( + job, + queue: DispatchQueue.global(qos: .default), + success: { _, _ in resolver(Result.success(true)) }, + failure: { _, _, _ in + // Always fulfill because the notify PN server job isn't critical. + resolver(Result.success(true)) + }, + deferred: { _ in + // Always fulfill because the notify PN server job isn't critical. + resolver(Result.success(true)) + } + ) + } + .eraseToAnyPublisher() + } + .eraseToAnyPublisher() } .filter { $0 } .handleEvents( @@ -960,8 +941,8 @@ public final class MessageSender { try? ControlMessageProcessRecord( threadId: { switch destination { - case .contact(let publicKey, _): return publicKey - case .closedGroup(let groupPublicKey, _): return groupPublicKey + case .contact(let publicKey): return publicKey + case .closedGroup(let groupPublicKey): return groupPublicKey case .openGroup(let roomToken, let server, _, _, _): return OpenGroup.idFor(roomToken: roomToken, server: server) @@ -977,7 +958,7 @@ public final class MessageSender { // • the destination was a contact // • we didn't sync it already let userPublicKey = getUserHexEncodedPublicKey(db) - if case .contact(let publicKey, let namespace) = destination, !isSyncMessage { + if case .contact(let publicKey) = destination, !isSyncMessage { if let message = message as? VisibleMessage { message.syncTarget = publicKey } if let message = message as? ExpirationTimerUpdate { message.syncTarget = publicKey } @@ -986,7 +967,7 @@ public final class MessageSender { data: try prepareSendToSnodeDestination( db, message: message, - to: .contact(publicKey: userPublicKey, namespace: namespace), + to: .contact(publicKey: userPublicKey), interactionId: interactionId, userPublicKey: userPublicKey, messageSendTimestamp: Int64(floor(Date().timeIntervalSince1970 * 1000)), diff --git a/SessionMessagingKit/Sending & Receiving/Notification+MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/Notification+MessageReceiver.swift index 5c6ffe962..d5c5b48fa 100644 --- a/SessionMessagingKit/Sending & Receiving/Notification+MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/Notification+MessageReceiver.swift @@ -4,6 +4,7 @@ import Foundation public extension Notification.Name { + // FIXME: Remove once `useSharedUtilForUserConfig` is permanent static let initialConfigurationMessageReceived = Notification.Name("initialConfigurationMessageReceived") static let missedCall = Notification.Name("missedCall") } @@ -14,5 +15,6 @@ public extension Notification.Key { @objc public extension NSNotification { + // FIXME: Remove once `useSharedUtilForUserConfig` is permanent @objc static let initialConfigurationMessageReceived = Notification.Name.initialConfigurationMessageReceived.rawValue as NSString } diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift index ebaaba9e1..ed88e7edc 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift @@ -8,7 +8,7 @@ import SessionSnodeKit import SessionUtilitiesKit public final class CurrentUserPoller: Poller { - public static var namespaces: [SnodeAPI.Namespace] = [.default, .userProfileConfig] + public static var namespaces: [SnodeAPI.Namespace] = [.default, .configUserProfile, .configContacts] private var targetSnode: Atomic = Atomic(nil) private var usedSnodes: Atomic> = Atomic([]) diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift index 317c8c99d..557642d1d 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift @@ -8,7 +8,7 @@ import SessionUtilitiesKit extension OpenGroupAPI { public final class Poller { - typealias PollResponse = [OpenGroupAPI.Endpoint: (info: ResponseInfoType, data: Codable?)] + typealias PollResponse = (info: ResponseInfoType, data: [OpenGroupAPI.Endpoint: Codable]) private let server: String private var timer: Timer? = nil @@ -279,11 +279,11 @@ extension OpenGroupAPI { using dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies() ) { let server: String = self.server - let validResponses: PollResponse = response - .filter { endpoint, endpointResponse in + let validResponses: [OpenGroupAPI.Endpoint: Codable] = response.data + .filter { endpoint, data in switch endpoint { case .capabilities: - guard (endpointResponse.data as? HTTP.BatchSubResponse)?.body != nil else { + guard (data as? HTTP.BatchSubResponse)?.body != nil else { SNLog("Open group polling failed due to invalid capability data.") return false } @@ -291,8 +291,8 @@ extension OpenGroupAPI { return true case .roomPollInfo(let roomToken, _): - guard (endpointResponse.data as? HTTP.BatchSubResponse)?.body != nil else { - switch (endpointResponse.data as? HTTP.BatchSubResponse)?.code { + guard (data as? HTTP.BatchSubResponse)?.body != nil else { + switch (data as? HTTP.BatchSubResponse)?.code { case 404: SNLog("Open group polling failed to retrieve info for unknown room '\(roomToken)'.") default: SNLog("Open group polling failed due to invalid room info data.") } @@ -303,10 +303,10 @@ extension OpenGroupAPI { case .roomMessagesRecent(let roomToken), .roomMessagesBefore(let roomToken, _), .roomMessagesSince(let roomToken, _): guard - let responseData: HTTP.BatchSubResponse<[Failable]> = endpointResponse.data as? HTTP.BatchSubResponse<[Failable]>, + let responseData: HTTP.BatchSubResponse<[Failable]> = data as? HTTP.BatchSubResponse<[Failable]>, let responseBody: [Failable] = responseData.body else { - switch (endpointResponse.data as? HTTP.BatchSubResponse<[Failable]>)?.code { + switch (data as? HTTP.BatchSubResponse<[Failable]>)?.code { case 404: SNLog("Open group polling failed to retrieve messages for unknown room '\(roomToken)'.") default: SNLog("Open group polling failed due to invalid messages data.") } @@ -325,7 +325,7 @@ extension OpenGroupAPI { case .inbox, .inboxSince, .outbox, .outboxSince: guard - let responseData: HTTP.BatchSubResponse<[DirectMessage]?> = endpointResponse.data as? HTTP.BatchSubResponse<[DirectMessage]?>, + let responseData: HTTP.BatchSubResponse<[DirectMessage]?> = data as? HTTP.BatchSubResponse<[DirectMessage]?>, !responseData.failedToParseBody else { SNLog("Open group polling failed due to invalid inbox/outbox data.") @@ -378,12 +378,12 @@ extension OpenGroupAPI { return (capabilities, groups) } - let changedResponses: PollResponse = validResponses - .filter { endpoint, endpointResponse in + let changedResponses: [OpenGroupAPI.Endpoint: Codable] = validResponses + .filter { endpoint, data in switch endpoint { case .capabilities: guard - let responseData: HTTP.BatchSubResponse = endpointResponse.data as? HTTP.BatchSubResponse, + let responseData: HTTP.BatchSubResponse = data as? HTTP.BatchSubResponse, let responseBody: Capabilities = responseData.body else { return false } @@ -391,7 +391,7 @@ extension OpenGroupAPI { case .roomPollInfo(let roomToken, _): guard - let responseData: HTTP.BatchSubResponse = endpointResponse.data as? HTTP.BatchSubResponse, + let responseData: HTTP.BatchSubResponse = data as? HTTP.BatchSubResponse, let responseBody: RoomPollInfo = responseData.body else { return false } guard let existingOpenGroup: OpenGroup = currentInfo?.groups.first(where: { $0.roomToken == roomToken }) else { @@ -424,11 +424,11 @@ extension OpenGroupAPI { .updateAll(db, OpenGroup.Columns.pollFailureCount.set(to: 0)) } - try changedResponses.forEach { endpoint, endpointResponse in + try changedResponses.forEach { endpoint, data in switch endpoint { case .capabilities: guard - let responseData: HTTP.BatchSubResponse = endpointResponse.data as? HTTP.BatchSubResponse, + let responseData: HTTP.BatchSubResponse = data as? HTTP.BatchSubResponse, let responseBody: Capabilities = responseData.body else { return } @@ -440,7 +440,7 @@ extension OpenGroupAPI { case .roomPollInfo(let roomToken, _): guard - let responseData: HTTP.BatchSubResponse = endpointResponse.data as? HTTP.BatchSubResponse, + let responseData: HTTP.BatchSubResponse = data as? HTTP.BatchSubResponse, let responseBody: RoomPollInfo = responseData.body else { return } @@ -455,7 +455,7 @@ extension OpenGroupAPI { case .roomMessagesRecent(let roomToken), .roomMessagesBefore(let roomToken, _), .roomMessagesSince(let roomToken, _): guard - let responseData: HTTP.BatchSubResponse<[Failable]> = endpointResponse.data as? HTTP.BatchSubResponse<[Failable]>, + let responseData: HTTP.BatchSubResponse<[Failable]> = data as? HTTP.BatchSubResponse<[Failable]>, let responseBody: [Failable] = responseData.body else { return } @@ -469,7 +469,7 @@ extension OpenGroupAPI { case .inbox, .inboxSince, .outbox, .outboxSince: guard - let responseData: HTTP.BatchSubResponse<[DirectMessage]?> = endpointResponse.data as? HTTP.BatchSubResponse<[DirectMessage]?>, + let responseData: HTTP.BatchSubResponse<[DirectMessage]?> = data as? HTTP.BatchSubResponse<[DirectMessage]?>, !responseData.failedToParseBody else { return } diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift index 8a5ab36a4..5bca3274e 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift @@ -66,13 +66,17 @@ public class Poller { // MARK: - Private API internal func startIfNeeded(for publicKey: String) { - guard isPolling.wrappedValue[publicKey] != true else { return } - - // Might be a race condition that the setUpPolling finishes too soon, - // and the timer is not created, if we mark the group as is polling - // after setUpPolling. So the poller may not work, thus misses messages - isPolling.mutate { $0[publicKey] = true } - setUpPolling(for: publicKey) + // Run on the 'pollerQueue' to ensure any 'Atomic' access doesn't block the main thread + // on startup + Threading.pollerQueue.async { [weak self] in + guard self?.isPolling.wrappedValue[publicKey] != true else { return } + + // Might be a race condition that the setUpPolling finishes too soon, + // and the timer is not created, if we mark the group as is polling + // after setUpPolling. So the poller may not work, thus misses messages + self?.isPolling.mutate { $0[publicKey] = true } + self?.setUpPolling(for: publicKey) + } } /// We want to initially trigger a poll against the target service node and then run the recursive polling, diff --git a/SessionMessagingKit/Shared Models/MessageViewModel.swift b/SessionMessagingKit/Shared Models/MessageViewModel.swift index 81c452039..99664cdeb 100644 --- a/SessionMessagingKit/Shared Models/MessageViewModel.swift +++ b/SessionMessagingKit/Shared Models/MessageViewModel.swift @@ -146,8 +146,8 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable, // MARK: - Mutation public func with( - attachments: Updatable<[Attachment]> = .existing, - reactionInfo: Updatable<[ReactionInfo]> = .existing + attachments: [Attachment]? = nil, + reactionInfo: [ReactionInfo]? = nil ) -> MessageViewModel { return MessageViewModel( threadId: self.threadId, @@ -845,11 +845,9 @@ public extension MessageViewModel.AttachmentInteractionInfo { updatedPagedDataCache = updatedPagedDataCache.upserting( dataToUpdate.with( - attachments: .update( - attachments - .sorted() - .map { $0.attachment } - ) + attachments: attachments + .sorted() + .map { $0.attachment } ) ) } @@ -927,7 +925,7 @@ public extension MessageViewModel.ReactionInfo { else { return } updatedPagedDataCache = updatedPagedDataCache.upserting( - dataToUpdate.with(reactionInfo: .update(reactionInfo.sorted())) + dataToUpdate.with(reactionInfo: reactionInfo.sorted()) ) pagedRowIdsWithNoReactions.remove(interactionRowId) } diff --git a/SessionMessagingKit/Utilities/ProfileManager.swift b/SessionMessagingKit/Utilities/ProfileManager.swift index a4c671ccf..9cf23b523 100644 --- a/SessionMessagingKit/Utilities/ProfileManager.swift +++ b/SessionMessagingKit/Utilities/ProfileManager.swift @@ -8,6 +8,28 @@ import SignalCoreKit import SessionUtilitiesKit public struct ProfileManager { + public enum AvatarUpdate { + case none + case remove + case uploadImage(UIImage) + case uploadFilePath(String) + case updateTo(url: String, key: Data, fileName: String?) + + var image: UIImage? { + switch self { + case .uploadImage(let image): return image + default: return nil + } + } + + var filePath: String? { + switch self { + case .uploadFilePath(let filePath): return filePath + default: return nil + } + } + } + // The max bytes for a user's profile name, encoded in UTF8. // Before encrypting and submitting we NULL pad the name data to this length. private static let nameDataLength: UInt = 64 @@ -263,77 +285,85 @@ public struct ProfileManager { public static func updateLocal( queue: DispatchQueue, profileName: String, - image: UIImage?, - imageFilePath: String?, - success: ((Database, Profile) throws -> ())? = nil, + avatarUpdate: AvatarUpdate = .none, + success: ((Database) throws -> ())? = nil, failure: ((ProfileManagerError) -> ())? = nil ) { - prepareAndUploadAvatarImage( - queue: queue, - image: image, - imageFilePath: imageFilePath, - success: { fileInfo, newProfileKey in - // If we have no download url the we are removing the profile image - guard let (downloadUrl, fileName): (String, String) = fileInfo else { - Storage.shared.writeAsync { db in - let existingProfile: Profile = Profile.fetchOrCreateCurrentUser(db) + let userPublicKey: String = getUserHexEncodedPublicKey() + let isRemovingAvatar: Bool = { + switch avatarUpdate { + case .remove: return true + default: return false + } + }() + + switch avatarUpdate { + case .none, .remove, .updateTo: + Storage.shared.writeAsync { db in + if isRemovingAvatar { + let existingProfileUrl: String? = try Profile + .filter(id: userPublicKey) + .select(.profilePictureUrl) + .asRequest(of: String.self) + .fetchOne(db) + let existingProfileFileName: String? = try Profile + .filter(id: userPublicKey) + .select(.profilePictureFileName) + .asRequest(of: String.self) + .fetchOne(db) - OWSLogger.verbose(existingProfile.profilePictureUrl != nil ? + // Remove any cached avatar image value + if let fileName: String = existingProfileFileName { + profileAvatarCache.mutate { $0[fileName] = nil } + } + + OWSLogger.verbose(existingProfileUrl != nil ? "Updating local profile on service with cleared avatar." : "Updating local profile on service with no avatar." ) - - let updatedProfile: Profile = try existingProfile - .with( - name: profileName, - profilePictureUrl: nil, - profilePictureFileName: nil, - profileEncryptionKey: (existingProfile.profilePictureUrl != nil ? - .update(newProfileKey) : - .existing - ) - ) - .saved(db) - - try SessionUtil.update( - profile: updatedProfile, - in: .global(variant: .userProfile) - ) - - SNLog("Successfully updated service with profile.") - try success?(db, updatedProfile) } - return - } - - // Update user defaults - UserDefaults.standard[.lastProfilePictureUpload] = Date() - - // Update the profile - Storage.shared.writeAsync { db in - let profile: Profile = try Profile - .fetchOrCreateCurrentUser(db) - .with( - name: profileName, - profilePictureUrl: .update(downloadUrl), - profilePictureFileName: .update(fileName), - profileEncryptionKey: .update(newProfileKey) - ) - .saved(db) + + try ProfileManager.updateProfileIfNeeded( + db, + publicKey: userPublicKey, + name: profileName, + avatarUpdate: avatarUpdate, + sentTimestamp: Date().timeIntervalSince1970 + ) SNLog("Successfully updated service with profile.") - try success?(db, profile) + try success?(db) } - }, - failure: failure - ) + + case .uploadFilePath, .uploadImage: + prepareAndUploadAvatarImage( + queue: queue, + image: avatarUpdate.image, + imageFilePath: avatarUpdate.filePath, + success: { downloadUrl, fileName, newProfileKey in + Storage.shared.writeAsync { db in + try ProfileManager.updateProfileIfNeeded( + db, + publicKey: userPublicKey, + name: profileName, + avatarUpdate: .updateTo(url: downloadUrl, key: newProfileKey, fileName: fileName), + sentTimestamp: Date().timeIntervalSince1970 + ) + + SNLog("Successfully updated service with profile.") + try success?(db) + } + }, + failure: failure + ) + } } - public static func prepareAndUploadAvatarImage( + private static func prepareAndUploadAvatarImage( queue: DispatchQueue, image: UIImage?, imageFilePath: String?, - success: @escaping ((downloadUrl: String, fileName: String)?, Data) -> (), + success: @escaping ((downloadUrl: String, fileName: String, profileKey: Data)) -> (), failure: ((ProfileManagerError) -> ())? = nil ) { queue.async { @@ -348,7 +378,9 @@ public struct ProfileManager { avatarImageData = try { guard var image: UIImage = image else { - guard let imageFilePath: String = imageFilePath else { return nil } + guard let imageFilePath: String = imageFilePath else { + throw ProfileManagerError.invalidCall + } let data: Data = try Data(contentsOf: URL(fileURLWithPath: imageFilePath)) @@ -397,20 +429,8 @@ public struct ProfileManager { // If we have no image then we should succeed (database changes happen in the callback) guard let data: Data = avatarImageData else { - // Remove any cached avatar image value - let maybeExistingFileName: String? = Storage.shared - .read { db in - try Profile - .select(.profilePictureFileName) - .asRequest(of: String.self) - .fetchOne(db) - } - - if let fileName: String = maybeExistingFileName { - profileAvatarCache.mutate { $0[fileName] = nil } - } - - return success(nil, newProfileKey) + failure?(ProfileManagerError.invalidCall) + return } // If we have a new avatar image, we must first: @@ -469,9 +489,124 @@ public struct ProfileManager { profileAvatarCache.mutate { $0[fileName] = data } SNLog("Successfully uploaded avatar image.") - success((downloadUrl, fileName), newProfileKey) + success((downloadUrl, fileName, newProfileKey)) } ) } } + + public static func updateProfileIfNeeded( + _ db: Database, + publicKey: String, + name: String?, + avatarUpdate: AvatarUpdate, + sentTimestamp: TimeInterval, + calledFromConfigHandling: Bool = false, + dependencies: Dependencies = Dependencies() + ) throws { + let isCurrentUser = (publicKey == getUserHexEncodedPublicKey(db, dependencies: dependencies)) + let profile: Profile = Profile.fetchOrCreate(id: publicKey) + var profileChanges: [ColumnAssignment] = [] + + // Name + if let name: String = name, !name.isEmpty, name != profile.name { + let shouldUpdate: Bool + if isCurrentUser { + shouldUpdate = given(UserDefaults.standard[.lastDisplayNameUpdate]) { + sentTimestamp > $0.timeIntervalSince1970 + } + .defaulting(to: true) + } + else { + shouldUpdate = true + } + + if shouldUpdate { + if isCurrentUser { + UserDefaults.standard[.lastDisplayNameUpdate] = Date(timeIntervalSince1970: sentTimestamp) + } + + profileChanges.append(Profile.Columns.name.set(to: name)) + } + } + + // Profile picture & profile key + var avatarNeedsDownload: Bool = false + let shouldUpdateAvatar: Bool = { + guard isCurrentUser else { return true } + + return given(UserDefaults.standard[.lastProfilePictureUpdate]) { + sentTimestamp > $0.timeIntervalSince1970 + } + .defaulting(to: true) + }() + + if shouldUpdateAvatar { + switch avatarUpdate { + case .none: break + case .uploadImage, .uploadFilePath: preconditionFailure("Invalid options for this function") + + case .remove: + if isCurrentUser { + UserDefaults.standard[.lastProfilePictureUpdate] = Date(timeIntervalSince1970: sentTimestamp) + } + + profileChanges.append(Profile.Columns.profilePictureUrl.set(to: nil)) + profileChanges.append(Profile.Columns.profileEncryptionKey.set(to: nil)) + + // Profile filename (this isn't synchronized between devices so can be immediately saved) + _ = try? Profile + .filter(id: publicKey) + .updateAll(db, Profile.Columns.profilePictureFileName.set(to: nil)) + + case .updateTo(let url, let key, let fileName): + if + ( + url != profile.profilePictureUrl || + key != profile.profileEncryptionKey + ) && + key.count == ProfileManager.avatarAES256KeyByteLength && + key != profile.profileEncryptionKey + { + profileChanges.append(Profile.Columns.profilePictureUrl.set(to: url)) + profileChanges.append(Profile.Columns.profileEncryptionKey.set(to: key)) + avatarNeedsDownload = true + } + + // Profile filename (this isn't synchronized between devices so can be immediately saved) + if let fileName: String = fileName { + _ = try? Profile + .filter(id: publicKey) + .updateAll(db, Profile.Columns.profilePictureFileName.set(to: fileName)) + } + } + } + + // Persist any changes + if !profileChanges.isEmpty { + try profile.save(db) + + if calledFromConfigHandling { + try Profile + .filter(id: publicKey) + .updateAll( // Handling a config update so don't use `updateAllAndConfig` + db, + profileChanges + ) + } + else { + try Profile + .filter(id: publicKey) + .updateAllAndConfig(db, profileChanges) + } + } + + // Download the profile picture if needed + guard avatarNeedsDownload else { return } + + db.afterNextTransaction { db in + // Need to refetch to ensure the db changes have occurred + ProfileManager.downloadAvatar(for: Profile.fetchOrCreate(id: publicKey)) + } + } } diff --git a/SessionMessagingKit/Utilities/ProfileManagerError.swift b/SessionMessagingKit/Utilities/ProfileManagerError.swift index 1be60fbad..a522e492e 100644 --- a/SessionMessagingKit/Utilities/ProfileManagerError.swift +++ b/SessionMessagingKit/Utilities/ProfileManagerError.swift @@ -8,6 +8,7 @@ public enum ProfileManagerError: LocalizedError { case avatarEncryptionFailed case avatarUploadFailed case avatarUploadMaxFileSizeExceeded + case invalidCall var localizedDescription: String { switch self { @@ -16,6 +17,7 @@ public enum ProfileManagerError: LocalizedError { case .avatarEncryptionFailed: return "Avatar encryption failed." case .avatarUploadFailed: return "Avatar upload failed." case .avatarUploadMaxFileSizeExceeded: return "Maximum file size exceeded." + case .invalidCall: return "Attempted to remove avatar using the wrong method." } } } diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index c83962cec..7dd880554 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -143,7 +143,11 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension self.handleSuccessForIncomingCall(db, for: callMessage) case let sharedConfigMessage as SharedConfigMessage: - try SessionUtil.handleConfigMessages(db, messages: [sharedConfigMessage]) + try SessionUtil.handleConfigMessages( + db, + messages: [sharedConfigMessage], + publicKey: (processedMessage.threadId ?? "") + ) default: break } @@ -214,9 +218,7 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension // If we need a config sync then trigger it now if needsConfigSync { - Storage.shared.write { db in - try MessageSender.syncConfiguration(db, forceSyncNow: true).sinkUntilComplete() - } + ConfigurationSyncJob.enqueue() } checkIsAppReady() diff --git a/SessionShareExtension/ShareNavController.swift b/SessionShareExtension/ShareNavController.swift index 5edc9cedc..c5eb6ce7e 100644 --- a/SessionShareExtension/ShareNavController.swift +++ b/SessionShareExtension/ShareNavController.swift @@ -92,9 +92,7 @@ final class ShareNavController: UINavigationController, ShareViewDelegate { // If we need a config sync then trigger it now if needsConfigSync { - Storage.shared.write { db in - try? MessageSender.syncConfiguration(db, forceSyncNow: true).sinkUntilComplete() - } + ConfigurationSyncJob.enqueue() } checkIsAppReady() diff --git a/SessionSnodeKit/Jobs/GetSnodePoolJob.swift b/SessionSnodeKit/Jobs/GetSnodePoolJob.swift index 71156a4c7..45a0f1d5c 100644 --- a/SessionSnodeKit/Jobs/GetSnodePoolJob.swift +++ b/SessionSnodeKit/Jobs/GetSnodePoolJob.swift @@ -18,13 +18,6 @@ public enum GetSnodePoolJob: JobExecutor { failure: @escaping (Job, Error?, Bool) -> (), deferred: @escaping (Job) -> () ) { - // If the user doesn't exist then don't do anything (when the user registers we run this - // job directly) - guard Identity.userExists() else { - deferred(job) - return - } - // If we already have cached Snodes then we still want to trigger the 'SnodeAPI.getSnodePool' // but we want to succeed this job immediately (since it's marked as blocking), this allows us // to block if we have no Snode pool and prevent other jobs from failing but avoids having to @@ -35,7 +28,10 @@ public enum GetSnodePoolJob: JobExecutor { return } + // If we don't have the snode pool cached then we should also try to build the path (this will + // speed up the onboarding process for new users because it can run before the user is created) SnodeAPI.getSnodePool() + .flatMap { _ in OnionRequestAPI.getPath(excluding: nil) } .subscribe(on: queue) .receive(on: queue) .sinkUntilComplete( diff --git a/SessionSnodeKit/Models/SendMessageRequest.swift b/SessionSnodeKit/Models/SendMessageRequest.swift index b64748ddc..91831f155 100644 --- a/SessionSnodeKit/Models/SendMessageRequest.swift +++ b/SessionSnodeKit/Models/SendMessageRequest.swift @@ -6,7 +6,7 @@ extension SnodeAPI { public class SendMessageRequest: SnodeAuthenticatedRequestBody { enum CodingKeys: String, CodingKey { case namespace - case signatureTimestamp = "sig_timestamp" + case signatureTimestamp = "timestamp"//"sig_timestamp" // TODO: Add this back once the snodes are fixed!! } let message: SnodeMessage diff --git a/SessionSnodeKit/Networking/OnionRequestAPI.swift b/SessionSnodeKit/Networking/OnionRequestAPI.swift index e078abe1e..d98c3212e 100644 --- a/SessionSnodeKit/Networking/OnionRequestAPI.swift +++ b/SessionSnodeKit/Networking/OnionRequestAPI.swift @@ -209,7 +209,7 @@ public enum OnionRequestAPI: OnionRequestAPIType { } /// Returns a `Path` to be used for building an onion request. Builds new paths as needed. - private static func getPath(excluding snode: Snode?) -> AnyPublisher<[Snode], Error> { + internal static func getPath(excluding snode: Snode?) -> AnyPublisher<[Snode], Error> { guard pathSize >= 1 else { preconditionFailure("Can't build path of size zero.") } let paths: [[Snode]] = OnionRequestAPI.paths diff --git a/SessionSnodeKit/Networking/SnodeAPI.swift b/SessionSnodeKit/Networking/SnodeAPI.swift index 3c88ada34..694213d93 100644 --- a/SessionSnodeKit/Networking/SnodeAPI.swift +++ b/SessionSnodeKit/Networking/SnodeAPI.swift @@ -7,6 +7,8 @@ import GRDB import SessionUtilitiesKit public final class SnodeAPI { + public typealias TargetedMessage = (message: SnodeMessage, namespace: Namespace) + internal static let sodium: Atomic = Atomic(Sodium()) private static var hasLoadedSnodePool: Atomic = Atomic(false) @@ -47,7 +49,6 @@ public final class SnodeAPI { ] }() private static let snodeFailureThreshold: Int = 3 - private static let targetSwarmSnodeCount: Int = 2 private static let minSnodePoolCount: Int = 12 private static func offsetTimestampMsNow() -> UInt64 { @@ -269,13 +270,6 @@ public final class SnodeAPI { .eraseToAnyPublisher() } - public static func getTargetSnodes(for publicKey: String) -> AnyPublisher<[Snode], Error> { - // shuffled() uses the system's default random generator, which is cryptographically secure - return getSwarm(for: publicKey) - .map { Array($0.shuffled().prefix(targetSwarmSnodeCount)) } - .eraseToAnyPublisher() - } - public static func getSwarm( for publicKey: String, using dependencies: SSKDependencies = SSKDependencies() @@ -422,19 +416,21 @@ public final class SnodeAPI { ) .decoded(as: responseTypes, using: dependencies) .map { batchResponse -> [SnodeAPI.Namespace: (info: ResponseInfoType, data: (messages: [SnodeReceivedMessage], lastHash: String?)?)] in - zip(namespaces, batchResponse) + zip(namespaces, batchResponse.responses) .reduce(into: [:]) { result, next in - guard let messageResponse: GetMessagesResponse = (next.1.1 as? HTTP.BatchSubResponse)?.body else { + guard + let subResponse: HTTP.BatchSubResponse = (next.1 as? HTTP.BatchSubResponse), + let messageResponse: GetMessagesResponse = subResponse.body + else { return } let namespace: SnodeAPI.Namespace = next.0 - let requestInfo: ResponseInfoType = next.1.0 - + result[namespace] = ( - requestInfo, - ( - messageResponse.messages + info: subResponse.responseInfo, + data: ( + messages: messageResponse.messages .compactMap { rawMessage -> SnodeReceivedMessage? in SnodeReceivedMessage( snode: snode, @@ -443,7 +439,7 @@ public final class SnodeAPI { rawMessage: rawMessage ) }, - namespaceLastHash[namespace] + lastHash: namespaceLastHash[namespace] ) ) } @@ -453,13 +449,13 @@ public final class SnodeAPI { .eraseToAnyPublisher() } - // MARK: Store + // MARK: - Store public static func sendMessage( _ message: SnodeMessage, in namespace: Namespace, using dependencies: SSKDependencies = SSKDependencies() - ) -> AnyPublisher<(Result<(ResponseInfoType, SendMessagesResponse), Error>, Int), Error> { + ) -> AnyPublisher<(ResponseInfoType, SendMessagesResponse), Error> { let publicKey: String = (Features.useTestnet ? message.recipient.removingIdPrefixIfNeeded() : message.recipient @@ -511,27 +507,125 @@ public final class SnodeAPI { .eraseToAnyPublisher() } - return getTargetSnodes(for: publicKey) + return getSwarm(for: publicKey) .subscribe(on: Threading.workQueue) - .flatMap { targetSnodes -> AnyPublisher<(Result<(ResponseInfoType, SendMessagesResponse), Error>, Int), Error> in - Publishers - .MergeMany( - targetSnodes - .map { targetSnode -> AnyPublisher, Never> in - return sendMessage(to: targetSnode) - .retry(maxRetryCount) - .eraseToAnyPublisher() - .asResult() - } - ) - .map { result in (result, targetSnodes.count) } - .setFailureType(to: Error.self) + .flatMap { swarm -> AnyPublisher<(ResponseInfoType, SendMessagesResponse), Error> in + guard let snode: Snode = swarm.randomElement() else { + return Fail(error: SnodeAPIError.generic) + .eraseToAnyPublisher() + } + + return sendMessage(to: snode) + .retry(maxRetryCount) .eraseToAnyPublisher() } + .retry(maxRetryCount) .eraseToAnyPublisher() } - // MARK: Edit + public static func sendConfigMessages( + _ targetedMessages: [TargetedMessage], + oldHashes: [String], + using dependencies: SSKDependencies = SSKDependencies() + ) -> AnyPublisher { + guard + !targetedMessages.isEmpty, + let recipient: String = targetedMessages.first?.message.recipient + else { + return Fail(error: SnodeAPIError.generic) + .eraseToAnyPublisher() + } + // TODO: Need to get either the closed group subKey or the userEd25519 key for auth + guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else { + return Fail(error: SnodeAPIError.noKeyPair) + .eraseToAnyPublisher() + } + + let userX25519PublicKey: String = getUserHexEncodedPublicKey() + let publicKey: String = (Features.useTestnet ? + recipient.removingIdPrefixIfNeeded() : + recipient + ) + var requests: [SnodeAPI.BatchRequest.Info] = targetedMessages + .map { message, namespace in + // Check if this namespace requires authentication + guard namespace.requiresReadAuthentication else { + return BatchRequest.Info( + request: SnodeRequest( + endpoint: .sendMessage, + body: LegacySendMessagesRequest( + message: message, + namespace: namespace + ) + ), + responseType: SendMessagesResponse.self + ) + } + + return BatchRequest.Info( + request: SnodeRequest( + endpoint: .sendMessage, + body: SendMessageRequest( + message: message, + namespace: namespace, + subkey: nil, // TODO: Need to get this + timestampMs: SnodeAPI.offsetTimestampMsNow(), + ed25519PublicKey: userED25519KeyPair.publicKey, + ed25519SecretKey: userED25519KeyPair.secretKey + ) + ), + responseType: SendMessagesResponse.self + ) + } + + // If we had any previous config messages then we should delete them + if !oldHashes.isEmpty { + requests.append( + BatchRequest.Info( + request: SnodeRequest( + endpoint: .deleteMessages, + body: DeleteMessagesRequest( + messageHashes: oldHashes, + requireSuccessfulDeletion: false, + pubkey: userX25519PublicKey, + ed25519PublicKey: userED25519KeyPair.publicKey, + ed25519SecretKey: userED25519KeyPair.secretKey + ) + ), + responseType: DeleteMessagesResponse.self + ) + ) + } + + let responseTypes = requests.map { $0.responseType } + + return getSwarm(for: publicKey) + .subscribe(on: Threading.workQueue) + .flatMap { swarm -> AnyPublisher in + guard let snode: Snode = swarm.randomElement() else { + return Fail(error: SnodeAPIError.generic) + .eraseToAnyPublisher() + } + + return SnodeAPI + .send( + request: SnodeRequest( + endpoint: .sequence, + body: BatchRequest(requests: requests) + ), + to: snode, + associatedWith: publicKey, + using: dependencies + ) + .eraseToAnyPublisher() + .decoded(as: responseTypes, using: dependencies) + .eraseToAnyPublisher() + } + .retry(maxRetryCount) + .eraseToAnyPublisher() + } + + // MARK: - Edit public static func updateExpiry( publicKey: String, diff --git a/SessionSnodeKit/Types/SnodeAPINamespace.swift b/SessionSnodeKit/Types/SnodeAPINamespace.swift index da038aeca..17fc93056 100644 --- a/SessionSnodeKit/Types/SnodeAPINamespace.swift +++ b/SessionSnodeKit/Types/SnodeAPINamespace.swift @@ -3,10 +3,12 @@ import Foundation public extension SnodeAPI { - enum Namespace: Int, Codable { + enum Namespace: Int, Codable, Hashable { case `default` = 0 - case userProfileConfig = 2 + case configUserProfile = 2 + case configContacts = 3 + case configClosedGroupInfo = 11 case legacyClosedGroup = -10 diff --git a/SessionUtilitiesKit/Database/Models/Identity.swift b/SessionUtilitiesKit/Database/Models/Identity.swift index 4eb224273..b4412595d 100644 --- a/SessionUtilitiesKit/Database/Models/Identity.swift +++ b/SessionUtilitiesKit/Database/Models/Identity.swift @@ -65,14 +65,12 @@ public extension Identity { ) } - static func store(seed: Data, ed25519KeyPair: KeyPair, x25519KeyPair: KeyPair) { - Storage.shared.write { db in - try Identity(variant: .seed, data: seed).save(db) - try Identity(variant: .ed25519SecretKey, data: Data(ed25519KeyPair.secretKey)).save(db) - try Identity(variant: .ed25519PublicKey, data: Data(ed25519KeyPair.publicKey)).save(db) - try Identity(variant: .x25519PrivateKey, data: Data(x25519KeyPair.secretKey)).save(db) - try Identity(variant: .x25519PublicKey, data: Data(x25519KeyPair.publicKey)).save(db) - } + static func store(_ db: Database, seed: Data, ed25519KeyPair: KeyPair, x25519KeyPair: KeyPair) throws { + try Identity(variant: .seed, data: seed).save(db) + try Identity(variant: .ed25519SecretKey, data: Data(ed25519KeyPair.secretKey)).save(db) + try Identity(variant: .ed25519PublicKey, data: Data(ed25519KeyPair.publicKey)).save(db) + try Identity(variant: .x25519PrivateKey, data: Data(x25519KeyPair.secretKey)).save(db) + try Identity(variant: .x25519PublicKey, data: Data(x25519KeyPair.publicKey)).save(db) } static func userExists(_ db: Database? = nil) -> Bool { diff --git a/SessionUtilitiesKit/Database/Models/Job.swift b/SessionUtilitiesKit/Database/Models/Job.swift index 037d83fc3..84bd5b970 100644 --- a/SessionUtilitiesKit/Database/Models/Job.swift +++ b/SessionUtilitiesKit/Database/Models/Job.swift @@ -102,6 +102,10 @@ public struct Job: Codable, Equatable, Identifiable, FetchableRecord, MutablePer /// This is a job that runs once whenever an attachment is downloaded to attempt to decode and properly /// download the attachment case attachmentDownload + + /// This is a job that runs once whenever the user config or a closed group config changes, it retrieves the + /// state of all config objects and syncs any that are flagged as needing to be synced + case configurationSync } public enum Behaviour: Int, Codable, DatabaseValueConvertible, CaseIterable { diff --git a/SessionUtilitiesKit/General/Features.swift b/SessionUtilitiesKit/General/Features.swift index d23dabef6..0584e131c 100644 --- a/SessionUtilitiesKit/General/Features.swift +++ b/SessionUtilitiesKit/General/Features.swift @@ -1,6 +1,10 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. -@objc(SNFeatures) -public final class Features : NSObject { - public static let useOnionRequests = true - public static let useTestnet = false +import Foundation + +public final class Features { + public static let useOnionRequests: Bool = true + public static let useTestnet: Bool = false + + public static let useSharedUtilForUserConfig: Bool = true } diff --git a/SessionUtilitiesKit/JobRunner/JobRunner.swift b/SessionUtilitiesKit/JobRunner/JobRunner.swift index 71e4d7b3a..a09c282c6 100644 --- a/SessionUtilitiesKit/JobRunner/JobRunner.swift +++ b/SessionUtilitiesKit/JobRunner/JobRunner.swift @@ -65,7 +65,8 @@ public final class JobRunner { jobVariants.remove(.attachmentUpload), jobVariants.remove(.messageSend), jobVariants.remove(.notifyPushServer), - jobVariants.remove(.sendReadReceipts) + jobVariants.remove(.sendReadReceipts), + jobVariants.remove(.configurationSync) ].compactMap { $0 } ) let messageReceiveQueue: JobQueue = JobQueue( diff --git a/SessionUtilitiesKit/Media/Updatable.swift b/SessionUtilitiesKit/Media/Updatable.swift deleted file mode 100644 index 4a4a39495..000000000 --- a/SessionUtilitiesKit/Media/Updatable.swift +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import Foundation - -public enum Updatable: ExpressibleByNilLiteral { - /// A cleared value. - /// - /// In code, the cleared of a value is typically written using the `nil` - /// literal rather than the explicit `.remove` enumeration case. - case remove - - /// The existing value, this will leave whatever value is currently available. - case existing - - /// An updated value, stored as `Wrapped`. - case update(Wrapped) - - // MARK: - ExpressibleByNilLiteral - - public init(nilLiteral: ()) { - self = .remove - } - - public static func updateIf(_ maybeValue: Wrapped?) -> Updatable { - switch maybeValue { - case .some(let value): return .update(value) - default: return .existing - } - } - - public static func updateTo(_ maybeValue: Wrapped?) -> Updatable { - switch maybeValue { - case .some(let value): return .update(value) - default: return .remove - } - } - - // MARK: - Functions - - public func value(existing: Wrapped) -> Wrapped? { - switch self { - case .remove: return nil - case .existing: return existing - case .update(let newValue): return newValue - } - } - - public func value(existing: Wrapped) -> Wrapped { - switch self { - case .remove: fatalError("Attempted to assign a 'removed' value to a non-null") - case .existing: return existing - case .update(let newValue): return newValue - } - } -} - -// MARK: - Coalesing-nil operator - -public func ?? (updatable: Updatable, existingValue: @autoclosure () throws -> T) rethrows -> T { - switch updatable { - case .remove: fatalError("Attempted to assign a 'removed' value to a non-null") - case .existing: return try existingValue() - case .update(let newValue): return newValue - } -} - -public func ?? (updatable: Updatable, existingValue: @autoclosure () throws -> T?) rethrows -> T? { - switch updatable { - case .remove: return nil - case .existing: return try existingValue() - case .update(let newValue): return newValue - } -} - -public func ?? (updatable: Updatable>, existingValue: @autoclosure () throws -> T?) rethrows -> T? { - switch updatable { - case .remove: return nil - case .existing: return try existingValue() - case .update(let newValue): return newValue - } -} - -// MARK: - ExpressibleBy Conformance - -extension Updatable { - public init(_ value: Wrapped) { - self = .update(value) - } -} - -extension Updatable: ExpressibleByUnicodeScalarLiteral, ExpressibleByExtendedGraphemeClusterLiteral, ExpressibleByStringLiteral where Wrapped == String { - public init(stringLiteral value: Wrapped) { - self = .update(value) - } - - public init(extendedGraphemeClusterLiteral value: Wrapped) { - self = .update(value) - } - - public init(unicodeScalarLiteral value: Wrapped) { - self = .update(value) - } -} - -extension Updatable: ExpressibleByIntegerLiteral where Wrapped == Int { - public init(integerLiteral value: Int) { - self = .update(value) - } -} - -extension Updatable: ExpressibleByFloatLiteral where Wrapped == Double { - public init(floatLiteral value: Double) { - self = .update(value) - } -} - -extension Updatable: ExpressibleByBooleanLiteral where Wrapped == Bool { - public init(booleanLiteral value: Bool) { - self = .update(value) - } -} diff --git a/SessionUtilitiesKit/Networking/BatchResponse.swift b/SessionUtilitiesKit/Networking/BatchResponse.swift index da3ccdbfa..8b83fc6c2 100644 --- a/SessionUtilitiesKit/Networking/BatchResponse.swift +++ b/SessionUtilitiesKit/Networking/BatchResponse.swift @@ -4,16 +4,20 @@ import Foundation import Combine public extension HTTP { - // MARK: - Convenience Aliases - - typealias BatchResponse = [(ResponseInfoType, Codable?)] typealias BatchResponseTypes = [Codable.Type] + // MARK: - BatchResponse + + struct BatchResponse { + public let info: ResponseInfoType + public let responses: [Codable] + } + // MARK: - BatchSubResponse - struct BatchSubResponse: Codable { + struct BatchSubResponse: BatchSubResponseType { /// The numeric http response code (e.g. 200 for success) - public let code: Int32 + public let code: Int /// Any headers returned by the request public let headers: [String: String] @@ -25,7 +29,7 @@ public extension HTTP { public let failedToParseBody: Bool public init( - code: Int32, + code: Int, headers: [String: String] = [:], body: T? = nil, failedToParseBody: Bool = false @@ -38,13 +42,23 @@ public extension HTTP { } } +public protocol BatchSubResponseType: Codable { + var code: Int { get } + var headers: [String: String] { get } + var failedToParseBody: Bool { get } +} + +extension BatchSubResponseType { + public var responseInfo: ResponseInfoType { HTTP.ResponseInfo(code: code, headers: headers) } +} + public extension HTTP.BatchSubResponse { init(from decoder: Decoder) throws { let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) let body: T? = try? container.decode(T.self, forKey: .body) self = HTTP.BatchSubResponse( - code: try container.decode(Int32.self, forKey: .code), + code: try container.decode(Int.self, forKey: .code), headers: ((try? container.decode([String: String].self, forKey: .headers)) ?? [:]), body: body, failedToParseBody: ( @@ -111,13 +125,15 @@ public extension AnyPublisher where Output == (ResponseInfoType, Data?), Failure do { // TODO: Remove the 'Swift.' - let result: HTTP.BatchResponse = try Swift.zip(dataArray, types) - .map { data, type in try type.decoded(from: data, using: dependencies) } - .map { data in (responseInfo, data) } - - return Just(result) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() + return Just( + HTTP.BatchResponse( + info: responseInfo, + responses: try Swift.zip(dataArray, types) + .map { data, type in try type.decoded(from: data, using: dependencies) } + ) + ) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } catch { return Fail(error: HTTPError.parsingFailed) diff --git a/SignalUtilitiesKit/Utilities/AppSetup.swift b/SignalUtilitiesKit/Utilities/AppSetup.swift index a0d5b7e23..641e7726a 100644 --- a/SignalUtilitiesKit/Utilities/AppSetup.swift +++ b/SignalUtilitiesKit/Utilities/AppSetup.swift @@ -77,9 +77,12 @@ public enum AppSetup { // After the migrations have run but before the migration completion we load the // SessionUtil state and update the 'needsConfigSync' flag based on whether the // configs also need to be sync'ed - SessionUtil.loadState( - ed25519SecretKey: Identity.fetchUserEd25519KeyPair()?.secretKey - ) + if Identity.userExists() { + SessionUtil.loadState( + userPublicKey: getUserHexEncodedPublicKey(), + ed25519SecretKey: Identity.fetchUserEd25519KeyPair()?.secretKey + ) + } DispatchQueue.main.async { migrationsCompletion(result, (needsConfigSync || SessionUtil.needsSync)) From 14174e3fbdcec4dbbf62a39238b07ae9742d1a3b Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 16 Dec 2022 17:02:40 +1100 Subject: [PATCH 021/135] Fixed the build errors caused by removing the old 'syncConfiguration' function --- Session/Closed Groups/NewClosedGroupVC.swift | 7 +------ Session/Conversations/ConversationVC+Interaction.swift | 8 +------- Session/Meta/AppDelegate.swift | 4 +--- Session/Open Groups/JoinOpenGroupVC.swift | 7 +------ SessionMessagingKit/Open Groups/OpenGroupManager.swift | 3 +++ 5 files changed, 7 insertions(+), 22 deletions(-) diff --git a/Session/Closed Groups/NewClosedGroupVC.swift b/Session/Closed Groups/NewClosedGroupVC.swift index 3e799825d..c98e35227 100644 --- a/Session/Closed Groups/NewClosedGroupVC.swift +++ b/Session/Closed Groups/NewClosedGroupVC.swift @@ -343,12 +343,7 @@ final class NewClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegate } }, receiveValue: { thread in - Storage.shared.writeAsync { db in - try? MessageSender - .syncConfiguration(db, forceSyncNow: true) - .sinkUntilComplete() - } - + ConfigurationSyncJob.enqueue() self?.presentingViewController?.dismiss(animated: true, completion: nil) SessionApp.presentConversation(for: thread.id, action: .compose, animated: false) } diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 92c50bb62..446ef1c8d 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -1562,13 +1562,7 @@ extension ConversationVC: .sinkUntilComplete( receiveCompletion: { result in switch result { - case .finished: - Storage.shared.writeAsync { db in - try MessageSender - .syncConfiguration(db, forceSyncNow: true) - .sinkUntilComplete() // FIXME: It's probably cleaner to do this inside addOpenGroup(...) - } - + case .finished: break case .failure(let error): let errorModal: ConfirmationModal = ConfirmationModal( info: ConfirmationModal.Info( diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index ec164de32..aa186b1a9 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -295,9 +295,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD appVersion.lastAppVersion != appVersion.currentAppVersion ) { - try MessageSender - .syncConfiguration(db, forceSyncNow: true) - .sinkUntilComplete() + ConfigurationSyncJob.enqueue(db) } } } diff --git a/Session/Open Groups/JoinOpenGroupVC.swift b/Session/Open Groups/JoinOpenGroupVC.swift index 60923ce50..648363152 100644 --- a/Session/Open Groups/JoinOpenGroupVC.swift +++ b/Session/Open Groups/JoinOpenGroupVC.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit +import Combine import AVFoundation import GRDB import SessionUIKit @@ -189,12 +190,6 @@ final class JoinOpenGroupVC: BaseVC, UIPageViewControllerDataSource, UIPageViewC self?.showError(title: title, message: message) case .finished: - Storage.shared.writeAsync { db in - try MessageSender - .syncConfiguration(db, forceSyncNow: true) - .sinkUntilComplete() // FIXME: It's probably cleaner to do this inside addOpenGroup(...) - } - self?.presentingViewController?.dismiss(animated: true, completion: nil) if shouldOpenCommunity { diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index ad963705b..26f4a290e 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -275,6 +275,9 @@ public final class OpenGroupManager { .flatMap { response -> Future in Future { resolver in dependencies.storage.write { db in + // Enqueue a config sync job (have a newly added open group to sync) + ConfigurationSyncJob.enqueue(db) + // Store the capabilities first OpenGroupManager.handleCapabilities( db, From e3a080dd5bc802b2a67ff9d1fc7ec052b0349a40 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 21 Dec 2022 18:29:57 +1100 Subject: [PATCH 022/135] Updated to the latest version of the libSession-utl (contacts size) Fixed some broken unit tests (still some broken ones) --- .../SessionUtil+Contacts.swift | 2 +- .../ios-arm64/libsession-util.a | Bin 934024 -> 935072 bytes .../libsession-util.a | Bin 2091280 -> 2093456 bytes .../session/config/contacts.h | 7 +- .../session/config/contacts.hpp | 17 +- .../LibSessionUtil/ConfigContactsSpec.swift | 13 +- .../Open Groups/OpenGroupAPISpec.swift | 48 ++-- .../MessageSenderEncryptionSpec.swift | 217 +++++++++++------- .../Networking/BatchResponseSpec.swift | 6 +- 9 files changed, 184 insertions(+), 126 deletions(-) diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift index 26ce1fce0..703d6fe63 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift @@ -155,7 +155,7 @@ internal extension SessionUtil { .bytes .map { CChar(bitPattern: $0) } var contact: contacts_contact = contacts_contact() - guard contacts_get_or_create(conf, &contact, &sessionId) else { + guard contacts_get_or_construct(conf, &contact, &sessionId) else { SNLog("Unable to upsert contact from Config Message") return } diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64/libsession-util.a b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64/libsession-util.a index 3f991bb6aadfa330a62538ad0adf8abfb0b9eb53..16f752d7cad87a57c2693076c1a1ffb407bae5a1 100644 GIT binary patch delta 27623 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 diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.h index a889c7cb9..ba27f79b9 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.h +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.h @@ -67,7 +67,7 @@ bool contacts_get(const config_object* conf, contacts_contact* contact, const ch /// /// This is the method that should usually be used to create or update a contact, followed by /// setting fields in the contact, and then giving it to contacts_set(). -bool contacts_get_or_create( +bool contacts_get_or_construct( const config_object* conf, contacts_contact* contact, const char* session_id) __attribute__((warn_unused_result)); @@ -79,7 +79,7 @@ void contacts_set(config_object* conf, const contacts_contact* contact); // is simple enough; for example to update `approved` and leave everything else unchanged: // // contacts_contact c; -// if (contacts_get_or_create(conf, &c, some_session_id)) { +// if (contacts_get_or_construct(conf, &c, some_session_id)) { // const char* new_nickname = "Joe"; // c.approved = new_nickname; // contacts_set_or_create(conf, &c); @@ -92,6 +92,9 @@ void contacts_set(config_object* conf, const contacts_contact* contact); /// iteration; see details below. bool contacts_erase(config_object* conf, const char* session_id); +/// Returns the number of contacts. +size_t contacts_size(const config_object* conf); + /// Functions for iterating through the entire contact list, in sorted order. Intended use is: /// /// contacts_contact c; diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.hpp index 1601c6146..f97199c53 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.hpp +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.hpp @@ -84,16 +84,17 @@ class Contacts : public ConfigBase { /// Similar to get(), but if the session ID does not exist this returns a filled-out /// contact_info containing the session_id (all other fields will be empty/defaulted). This is - /// intended to be combined with `set` to set-or-create a record. Note that this does not add - /// the session id to the contact list when called: that requires also calling `set` with this - /// value. - contact_info get_or_create(std::string_view pubkey_hex) const; + /// intended to be combined with `set` to set-or-create a record. + /// + /// NB: calling this does *not* add the session id to the contact list when called: that + /// requires also calling `set` with this value. + contact_info get_or_construct(std::string_view pubkey_hex) const; /// Sets or updates multiple contact info values at once with the given info. The usual use is /// to access the current info, change anything desired, then pass it back into set_contact, /// e.g.: /// - /// auto c = contacts.get_or_create(pubkey); + /// auto c = contacts.get_or_construct(pubkey); /// c.name = "Session User 42"; /// c.nickname = "BFF"; /// contacts.set(c); @@ -119,6 +120,12 @@ class Contacts : public ConfigBase { /// example. iterator erase(iterator it); + /// Returns the number of contacts. + size_t size() const; + + /// Returns true if the contact list is empty. + bool empty() const { return size() == 0; } + /// Iterators for iterating through all contacts. Typically you access this implicit via a for /// loop over the `Contacts` object: /// diff --git a/SessionMessagingKitTests/LibSessionUtil/ConfigContactsSpec.swift b/SessionMessagingKitTests/LibSessionUtil/ConfigContactsSpec.swift index b2f9c7744..f1426c85b 100644 --- a/SessionMessagingKitTests/LibSessionUtil/ConfigContactsSpec.swift +++ b/SessionMessagingKitTests/LibSessionUtil/ConfigContactsSpec.swift @@ -38,8 +38,10 @@ class ConfigContactsSpec: QuickSpec { let contactPtr: UnsafeMutablePointer? = nil expect(contacts_get(conf, contactPtr, &definitelyRealId)).to(beFalse()) + expect(contacts_size(conf)).to(equal(0)) + var contact2: contacts_contact = contacts_contact() - expect(contacts_get_or_create(conf, &contact2, &definitelyRealId)).to(beTrue()) + expect(contacts_get_or_construct(conf, &contact2, &definitelyRealId)).to(beTrue()) expect(contact2.name).to(beNil()) expect(contact2.nickname).to(beNil()) expect(contact2.approved).to(beFalse()) @@ -155,7 +157,7 @@ class ConfigContactsSpec: QuickSpec { .bytes .map { CChar(bitPattern: $0) } var contact5: contacts_contact = contacts_contact() - expect(contacts_get_or_create(conf2, &contact5, &anotherId)).to(beTrue()) + expect(contacts_get_or_construct(conf2, &contact5, &anotherId)).to(beTrue()) expect(contact5.name).to(beNil()) expect(contact5.nickname).to(beNil()) expect(contact5.approved).to(beFalse()) @@ -193,6 +195,8 @@ class ConfigContactsSpec: QuickSpec { // Iterate through and make sure we got everything we expected var sessionIds: [String] = [] var nicknames: [String] = [] + expect(contacts_size(conf)).to(equal(2)) + var contact6: contacts_contact = contacts_contact() let contactIterator: UnsafeMutablePointer = contacts_iterator_new(conf) while !contacts_iterator_done(contactIterator, &contact6) { @@ -211,6 +215,7 @@ class ConfigContactsSpec: QuickSpec { contacts_iterator_free(contactIterator) // Need to free the iterator expect(sessionIds.count).to(equal(2)) + expect(sessionIds.count).to(equal(contacts_size(conf))) expect(sessionIds.first).to(equal(String(cString: definitelyRealId.nullTerminated()))) expect(sessionIds.last).to(equal(String(cString: anotherId.nullTerminated()))) expect(nicknames.first).to(equal("Joey")) @@ -233,7 +238,7 @@ class ConfigContactsSpec: QuickSpec { .map { CChar(bitPattern: $0) } let profileKey7: [UInt8] = "qwerty".bytes var contact7: contacts_contact = contacts_contact() - expect(contacts_get_or_create(conf2, &contact7, &thirdId)).to(beTrue()) + expect(contacts_get_or_construct(conf2, &contact7, &thirdId)).to(beTrue()) nickname7.withUnsafeBufferPointer { contact7.nickname = $0.baseAddress } contact7.approved = true contact7.approved_me = true @@ -297,6 +302,8 @@ class ConfigContactsSpec: QuickSpec { // Validate the changes var sessionIds2: [String] = [] var nicknames2: [String] = [] + expect(contacts_size(conf)).to(equal(2)) + var contact8: contacts_contact = contacts_contact() let contactIterator2: UnsafeMutablePointer = contacts_iterator_new(conf) while !contacts_iterator_done(contactIterator2, &contact8) { diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift b/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift index d0bee4fff..d97f36586 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift +++ b/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift @@ -27,9 +27,9 @@ class OpenGroupAPISpec: QuickSpec { var dependencies: SMKDependencies! var response: (ResponseInfoType, Codable)? = nil - var pollResponse: [OpenGroupAPI.Endpoint: (ResponseInfoType, Codable?)]? + var pollResponse: (info: ResponseInfoType, data: [OpenGroupAPI.Endpoint: Codable])? var error: Error? - + describe("an OpenGroupAPI") { // MARK: - Configuration @@ -205,14 +205,16 @@ class OpenGroupAPISpec: QuickSpec { expect(error?.localizedDescription).to(beNil()) // Validate the response data - expect(pollResponse?.values).to(haveCount(3)) - expect(pollResponse?.keys).to(contain(.capabilities)) - expect(pollResponse?.keys).to(contain(.roomPollInfo("testRoom", 0))) - expect(pollResponse?.keys).to(contain(.roomMessagesRecent("testRoom"))) - expect(pollResponse?[.capabilities]?.0).to(beAKindOf(TestOnionRequestAPI.ResponseInfo.self)) + + expect(pollResponse?.data.count).to(equal(3)) + expect(pollResponse?.data.keys).to(contain(.capabilities)) + expect(pollResponse?.data.keys).to(contain(.roomPollInfo("testRoom", 0))) + expect(pollResponse?.data.keys).to(contain(.roomMessagesRecent("testRoom"))) + expect(pollResponse?.data[.capabilities]) + .to(beAKindOf(TestOnionRequestAPI.ResponseInfo.self)) // Validate request data - let requestData: TestOnionRequestAPI.RequestData? = (pollResponse?[.capabilities]?.0 as? TestOnionRequestAPI.ResponseInfo)?.requestData + let requestData: TestOnionRequestAPI.RequestData? = (pollResponse?.data[.capabilities] as? TestOnionRequestAPI.ResponseInfo)?.requestData expect(requestData?.urlString).to(equal("testserver/batch")) expect(requestData?.httpMethod).to(equal("POST")) expect(requestData?.publicKey).to(equal("88672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b")) @@ -241,7 +243,7 @@ class OpenGroupAPISpec: QuickSpec { timeout: .milliseconds(100) ) expect(error?.localizedDescription).to(beNil()) - expect(pollResponse?.keys).to(contain(.roomMessagesRecent("testRoom"))) + expect(pollResponse?.data.keys).to(contain(.roomMessagesRecent("testRoom"))) } it("retrieves recent messages if there was a last message and it has not performed the initial poll and the last message was too long ago") { @@ -272,7 +274,7 @@ class OpenGroupAPISpec: QuickSpec { timeout: .milliseconds(100) ) expect(error?.localizedDescription).to(beNil()) - expect(pollResponse?.keys).to(contain(.roomMessagesRecent("testRoom"))) + expect(pollResponse?.data.keys).to(contain(.roomMessagesRecent("testRoom"))) } it("retrieves recent messages if there was a last message and it has performed an initial poll but it was not too long ago") { @@ -303,7 +305,7 @@ class OpenGroupAPISpec: QuickSpec { timeout: .milliseconds(100) ) expect(error?.localizedDescription).to(beNil()) - expect(pollResponse?.keys).to(contain(.roomMessagesSince("testRoom", seqNo: 123))) + expect(pollResponse?.data.keys).to(contain(.roomMessagesSince("testRoom", seqNo: 123))) } it("retrieves recent messages if there was a last message and there has already been a poll this session") { @@ -334,7 +336,7 @@ class OpenGroupAPISpec: QuickSpec { timeout: .milliseconds(100) ) expect(error?.localizedDescription).to(beNil()) - expect(pollResponse?.keys).to(contain(.roomMessagesSince("testRoom", seqNo: 123))) + expect(pollResponse?.data.keys).to(contain(.roomMessagesSince("testRoom", seqNo: 123))) } context("when unblinded") { @@ -370,8 +372,8 @@ class OpenGroupAPISpec: QuickSpec { expect(error?.localizedDescription).to(beNil()) // Validate the response data - expect(pollResponse?.keys).toNot(contain(.inbox)) - expect(pollResponse?.keys).toNot(contain(.outbox)) + expect(pollResponse?.data.keys).toNot(contain(.inbox)) + expect(pollResponse?.data.keys).toNot(contain(.outbox)) } } @@ -471,8 +473,8 @@ class OpenGroupAPISpec: QuickSpec { expect(error?.localizedDescription).to(beNil()) // Validate the response data - expect(pollResponse?.keys).to(contain(.inbox)) - expect(pollResponse?.keys).to(contain(.outbox)) + expect(pollResponse?.data.keys).to(contain(.inbox)) + expect(pollResponse?.data.keys).to(contain(.outbox)) } it("retrieves recent inbox messages if there was no last message") { @@ -498,7 +500,7 @@ class OpenGroupAPISpec: QuickSpec { timeout: .milliseconds(100) ) expect(error?.localizedDescription).to(beNil()) - expect(pollResponse?.keys).to(contain(.inbox)) + expect(pollResponse?.data.keys).to(contain(.inbox)) } it("retrieves inbox messages since the last message if there was one") { @@ -529,7 +531,7 @@ class OpenGroupAPISpec: QuickSpec { timeout: .milliseconds(100) ) expect(error?.localizedDescription).to(beNil()) - expect(pollResponse?.keys).to(contain(.inboxSince(id: 124))) + expect(pollResponse?.data.keys).to(contain(.inboxSince(id: 124))) } it("retrieves recent outbox messages if there was no last message") { @@ -555,7 +557,7 @@ class OpenGroupAPISpec: QuickSpec { timeout: .milliseconds(100) ) expect(error?.localizedDescription).to(beNil()) - expect(pollResponse?.keys).to(contain(.outbox)) + expect(pollResponse?.data.keys).to(contain(.outbox)) } it("retrieves outbox messages since the last message if there was one") { @@ -586,7 +588,7 @@ class OpenGroupAPISpec: QuickSpec { timeout: .milliseconds(100) ) expect(error?.localizedDescription).to(beNil()) - expect(pollResponse?.keys).to(contain(.outboxSince(id: 125))) + expect(pollResponse?.data.keys).to(contain(.outboxSince(id: 125))) } } } @@ -650,9 +652,9 @@ class OpenGroupAPISpec: QuickSpec { ) expect(error?.localizedDescription).to(beNil()) - let capabilitiesResponse: HTTP.BatchSubResponse? = (pollResponse?[.capabilities]?.1 as? HTTP.BatchSubResponse) - let pollInfoResponse: HTTP.BatchSubResponse? = (pollResponse?[.roomPollInfo("testRoom", 0)]?.1 as? HTTP.BatchSubResponse) - let messagesResponse: HTTP.BatchSubResponse<[Failable]>? = (pollResponse?[.roomMessagesRecent("testRoom")]?.1 as? HTTP.BatchSubResponse<[Failable]>) + let capabilitiesResponse: HTTP.BatchSubResponse? = (pollResponse?.data[.capabilities] as? HTTP.BatchSubResponse) + let pollInfoResponse: HTTP.BatchSubResponse? = (pollResponse?.data[.roomPollInfo("testRoom", 0)] as? HTTP.BatchSubResponse) + let messagesResponse: HTTP.BatchSubResponse<[Failable]>? = (pollResponse?.data[.roomMessagesRecent("testRoom")] as? HTTP.BatchSubResponse<[Failable]>) expect(capabilitiesResponse?.failedToParseBody).to(beFalse()) expect(pollInfoResponse?.failedToParseBody).to(beTrue()) expect(messagesResponse?.failedToParseBody).to(beTrue()) diff --git a/SessionMessagingKitTests/Sending & Receiving/MessageSenderEncryptionSpec.swift b/SessionMessagingKitTests/Sending & Receiving/MessageSenderEncryptionSpec.swift index aa3de4a23..514bb6af9 100644 --- a/SessionMessagingKitTests/Sending & Receiving/MessageSenderEncryptionSpec.swift +++ b/SessionMessagingKitTests/Sending & Receiving/MessageSenderEncryptionSpec.swift @@ -56,11 +56,14 @@ class MessageSenderEncryptionSpec: QuickSpec { } it("can encrypt correctly") { - let result = try? MessageSender.encryptWithSessionProtocol( - "TestMessage".data(using: .utf8)!, - for: "05\(TestConstants.publicKey)", - using: SMKDependencies(storage: mockStorage) - ) + let result = mockStorage.write { db in + try? MessageSender.encryptWithSessionProtocol( + db, + plaintext: "TestMessage".data(using: .utf8)!, + for: "05\(TestConstants.publicKey)", + using: SMKDependencies(storage: mockStorage) + ) + } // Note: A Nonce is used for this so we can't compare the exact value when not mocked expect(result).toNot(beNil()) @@ -68,11 +71,14 @@ class MessageSenderEncryptionSpec: QuickSpec { } it("returns the correct value when mocked") { - let result = try? MessageSender.encryptWithSessionProtocol( - "TestMessage".data(using: .utf8)!, - for: "05\(TestConstants.publicKey)", - using: dependencies - ) + let result = mockStorage.write { db in + try? MessageSender.encryptWithSessionProtocol( + db, + plaintext: "TestMessage".data(using: .utf8)!, + for: "05\(TestConstants.publicKey)", + using: dependencies + ) + } expect(result?.bytes).to(equal([1, 2, 3])) } @@ -83,51 +89,63 @@ class MessageSenderEncryptionSpec: QuickSpec { _ = try Identity.filter(id: .ed25519SecretKey).deleteAll(db) } - expect { - try MessageSender.encryptWithSessionProtocol( - "TestMessage".data(using: .utf8)!, - for: "05\(TestConstants.publicKey)", - using: dependencies - ) + mockStorage.write { db in + expect { + try MessageSender.encryptWithSessionProtocol( + db, + plaintext: "TestMessage".data(using: .utf8)!, + for: "05\(TestConstants.publicKey)", + using: dependencies + ) + } + .to(throwError(MessageSenderError.noUserED25519KeyPair)) } - .to(throwError(MessageSenderError.noUserED25519KeyPair)) } it("throws an error if the signature generation fails") { mockSign.when { $0.signature(message: anyArray(), secretKey: anyArray()) }.thenReturn(nil) - expect { - try MessageSender.encryptWithSessionProtocol( - "TestMessage".data(using: .utf8)!, - for: "05\(TestConstants.publicKey)", - using: dependencies - ) + mockStorage.write { db in + expect { + try MessageSender.encryptWithSessionProtocol( + db, + plaintext: "TestMessage".data(using: .utf8)!, + for: "05\(TestConstants.publicKey)", + using: dependencies + ) + } + .to(throwError(MessageSenderError.signingFailed)) } - .to(throwError(MessageSenderError.signingFailed)) } it("throws an error if the encryption fails") { mockBox.when { $0.seal(message: anyArray(), recipientPublicKey: anyArray()) }.thenReturn(nil) - expect { - try MessageSender.encryptWithSessionProtocol( - "TestMessage".data(using: .utf8)!, - for: "05\(TestConstants.publicKey)", - using: dependencies - ) + mockStorage.write { db in + expect { + try MessageSender.encryptWithSessionProtocol( + db, + plaintext: "TestMessage".data(using: .utf8)!, + for: "05\(TestConstants.publicKey)", + using: dependencies + ) + } + .to(throwError(MessageSenderError.encryptionFailed)) } - .to(throwError(MessageSenderError.encryptionFailed)) } } context("when encrypting with the blinded session protocol") { it("successfully encrypts") { - let result = try? MessageSender.encryptWithSessionBlindingProtocol( - "TestMessage".data(using: .utf8)!, - for: "15\(TestConstants.blindedPublicKey)", - openGroupPublicKey: TestConstants.serverPublicKey, - using: dependencies - ) + let result = mockStorage.write { db in + try? MessageSender.encryptWithSessionBlindingProtocol( + db, + plaintext: "TestMessage".data(using: .utf8)!, + for: "15\(TestConstants.blindedPublicKey)", + openGroupPublicKey: TestConstants.serverPublicKey, + using: dependencies + ) + } expect(result?.toHexString()) .to(equal( @@ -138,23 +156,29 @@ class MessageSenderEncryptionSpec: QuickSpec { } it("includes a version at the start of the encrypted value") { - let result = try? MessageSender.encryptWithSessionBlindingProtocol( - "TestMessage".data(using: .utf8)!, - for: "15\(TestConstants.blindedPublicKey)", - openGroupPublicKey: TestConstants.serverPublicKey, - using: dependencies - ) + let result = mockStorage.write { db in + try? MessageSender.encryptWithSessionBlindingProtocol( + db, + plaintext: "TestMessage".data(using: .utf8)!, + for: "15\(TestConstants.blindedPublicKey)", + openGroupPublicKey: TestConstants.serverPublicKey, + using: dependencies + ) + } expect(result?.toHexString().prefix(2)).to(equal("00")) } it("includes the nonce at the end of the encrypted value") { - let maybeResult = try? MessageSender.encryptWithSessionBlindingProtocol( - "TestMessage".data(using: .utf8)!, - for: "15\(TestConstants.blindedPublicKey)", - openGroupPublicKey: TestConstants.serverPublicKey, - using: dependencies - ) + let maybeResult = mockStorage.write { db in + try? MessageSender.encryptWithSessionBlindingProtocol( + db, + plaintext: "TestMessage".data(using: .utf8)!, + for: "15\(TestConstants.blindedPublicKey)", + openGroupPublicKey: TestConstants.serverPublicKey, + using: dependencies + ) + } let result: [UInt8] = (maybeResult?.bytes ?? []) let nonceBytes: [UInt8] = Array(result[max(0, (result.count - 24))..)?.body) + expect((result?.responses[0] as? HTTP.BatchSubResponse)?.body) .to(equal(testType)) - expect((result?[1].1 as? HTTP.BatchSubResponse)?.body) + expect((result?.responses[1] as? HTTP.BatchSubResponse)?.body) .to(equal(testType2)) } From 4d487f268639acdb63ecf53154d3f0325e5e8f84 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 5 Jan 2023 12:36:47 +1100 Subject: [PATCH 023/135] Fixed the broken unit tests --- Session/Utilities/BackgroundPoller.swift | 6 +- .../Open Groups/OpenGroupAPI.swift | 187 ++++++++--- .../Open Groups/OpenGroupManager.swift | 30 +- SessionMessagingKit/SMKDependencies.swift | 2 + .../Open Groups/OpenGroupAPISpec.swift | 317 +++++------------- .../Open Groups/OpenGroupManagerSpec.swift | 110 +++--- SessionSnodeKit/SSKDependencies.swift | 2 + .../Combine/Publisher+Utilities.swift | 25 ++ .../General/Dependencies.swift | 8 + SessionUtilitiesKit/JobRunner/JobRunner.swift | 6 +- _SharedTestUtilities/CombineExtensions.swift | 9 +- 11 files changed, 349 insertions(+), 353 deletions(-) diff --git a/Session/Utilities/BackgroundPoller.swift b/Session/Utilities/BackgroundPoller.swift index 5b5745127..5d4a6adf6 100644 --- a/Session/Utilities/BackgroundPoller.swift +++ b/Session/Utilities/BackgroundPoller.swift @@ -43,7 +43,7 @@ public final class BackgroundPoller { } ) ) - .subscribe(on: DispatchQueue.main) + .subscribeOnMain(immediately: true) .receiveOnMain(immediately: true) .collect() .sinkUntilComplete( @@ -65,7 +65,7 @@ public final class BackgroundPoller { let userPublicKey: String = getUserHexEncodedPublicKey() return SnodeAPI.getSwarm(for: userPublicKey) - .subscribe(on: DispatchQueue.main) + .subscribeOnMain(immediately: true) .receiveOnMain(immediately: true) .flatMap { swarm -> AnyPublisher in guard let snode = swarm.randomElement() else { @@ -102,7 +102,7 @@ public final class BackgroundPoller { .defaulting(to: []) .map { groupPublicKey in SnodeAPI.getSwarm(for: groupPublicKey) - .subscribe(on: DispatchQueue.main) + .subscribeOnMain(immediately: true) .receiveOnMain(immediately: true) .flatMap { swarm -> AnyPublisher in guard let snode: Snode = swarm.randomElement() else { diff --git a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift index 69224a0ea..ae249bfb3 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift @@ -31,7 +31,9 @@ public enum OpenGroupAPI { server: String, hasPerformedInitialPoll: Bool, timeSinceLastPoll: TimeInterval, - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> AnyPublisher<(info: ResponseInfoType, data: [Endpoint: Codable]), Error> { let lastInboxMessageId: Int64 = (try? OpenGroup .select(.inboxLatestMessageId) @@ -151,7 +153,9 @@ public enum OpenGroupAPI { _ db: Database, server: String, requests: [BatchRequest.Info], - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> AnyPublisher<(info: ResponseInfoType, data: [Endpoint: Codable]), Error> { let responseTypes = requests.map { $0.responseType } @@ -183,7 +187,9 @@ public enum OpenGroupAPI { _ db: Database, server: String, requests: [BatchRequest.Info], - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> AnyPublisher<(info: ResponseInfoType, data: [Endpoint: Codable]), Error> { let responseTypes = requests.map { $0.responseType } @@ -215,7 +221,9 @@ public enum OpenGroupAPI { _ db: Database, server: String, forceBlinded: Bool = false, - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> AnyPublisher<(ResponseInfoType, Capabilities), Error> { return OpenGroupAPI .send( @@ -238,7 +246,9 @@ public enum OpenGroupAPI { public static func rooms( _ db: Database, server: String, - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> AnyPublisher<(ResponseInfoType, [Room]), Error> { return OpenGroupAPI .send( @@ -262,7 +272,9 @@ public enum OpenGroupAPI { _ db: Database, for roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> AnyPublisher<(ResponseInfoType, Room), Error> { return OpenGroupAPI .send( @@ -290,7 +302,9 @@ public enum OpenGroupAPI { lastUpdated: Int64, for roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> AnyPublisher<(ResponseInfoType, RoomPollInfo), Error> { return OpenGroupAPI .send( @@ -304,14 +318,24 @@ public enum OpenGroupAPI { .decoded(as: RoomPollInfo.self, using: dependencies) } + public typealias CapabilitiesAndRoomResponse = ( + info: ResponseInfoType, + data: ( + capabilities: (info: ResponseInfoType, data: Capabilities), + room: (info: ResponseInfoType, data: Room) + ) + ) + /// This is a convenience method which constructs a `/sequence` of the `capabilities` and `room` requests, refer to those /// methods for the documented behaviour of each method public static func capabilitiesAndRoom( _ db: Database, for roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies() - ) -> AnyPublisher<(capabilities: (info: ResponseInfoType, data: Capabilities), room: (info: ResponseInfoType, data: Room)), Error> { + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) + ) -> AnyPublisher { let requestResponseType: [BatchRequest.Info] = [ // Get the latest capabilities for the server (in case it's a new server or the cached ones are stale) BatchRequest.Info( @@ -339,7 +363,7 @@ public enum OpenGroupAPI { requests: requestResponseType, using: dependencies ) - .flatMap { (info: ResponseInfoType, data: [Endpoint: Codable]) -> AnyPublisher<(capabilities: (info: ResponseInfoType, data: Capabilities), room: (info: ResponseInfoType, data: Room)), Error> in + .flatMap { (info: ResponseInfoType, data: [Endpoint: Codable]) -> AnyPublisher in let maybeCapabilities: HTTP.BatchSubResponse? = (data[.capabilities] as? HTTP.BatchSubResponse) let maybeRoomResponse: Codable? = data .first(where: { key, _ in @@ -362,8 +386,11 @@ public enum OpenGroupAPI { } return Just(( - capabilities: (info: capabilitiesInfo, data: capabilities), - room: (info: roomInfo, data: room) + info: info, + data: ( + capabilities: (info: capabilitiesInfo, data: capabilities), + room: (info: roomInfo, data: room) + ) )) .setFailureType(to: Error.self) .eraseToAnyPublisher() @@ -376,7 +403,9 @@ public enum OpenGroupAPI { public static func capabilitiesAndRooms( _ db: Database, on server: String, - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> AnyPublisher<(capabilities: (info: ResponseInfoType, data: Capabilities), rooms: (info: ResponseInfoType, data: [Room])), Error> { let requestResponseType: [BatchRequest.Info] = [ // Get the latest capabilities for the server (in case it's a new server or the cached ones are stale) @@ -447,7 +476,9 @@ public enum OpenGroupAPI { whisperTo: String?, whisperMods: Bool, fileIds: [String]?, - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> AnyPublisher<(ResponseInfoType, Message), Error> { guard let signResult: (publicKey: String, signature: Bytes) = sign(db, messageBytes: plaintext.bytes, for: server, fallbackSigningType: .standard, using: dependencies) else { return Fail(error: OpenGroupAPIError.signingFailed) @@ -480,7 +511,9 @@ public enum OpenGroupAPI { id: Int64, in roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> AnyPublisher<(ResponseInfoType, Message), Error> { return OpenGroupAPI .send( @@ -504,7 +537,9 @@ public enum OpenGroupAPI { fileIds: [Int64]?, in roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { guard let signResult: (publicKey: String, signature: Bytes) = sign(db, messageBytes: plaintext.bytes, for: server, fallbackSigningType: .standard, using: dependencies) else { return Fail(error: OpenGroupAPIError.signingFailed) @@ -533,7 +568,9 @@ public enum OpenGroupAPI { id: Int64, in roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { return OpenGroupAPI .send( @@ -555,7 +592,9 @@ public enum OpenGroupAPI { _ db: Database, in roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> AnyPublisher<(ResponseInfoType, [Message]), Error> { return OpenGroupAPI .send( @@ -578,7 +617,9 @@ public enum OpenGroupAPI { messageId: Int64, in roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> AnyPublisher<(ResponseInfoType, [Message]), Error> { return OpenGroupAPI .send( @@ -601,7 +642,9 @@ public enum OpenGroupAPI { seqNo: Int64, in roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> AnyPublisher<(ResponseInfoType, [Message]), Error> { return OpenGroupAPI .send( @@ -637,7 +680,9 @@ public enum OpenGroupAPI { sessionId: String, in roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { return OpenGroupAPI .send( @@ -659,7 +704,9 @@ public enum OpenGroupAPI { id: Int64, in roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> AnyPublisher { /// URL(String:) won't convert raw emojis, so need to do a little encoding here. /// The raw emoji will come back when calling url.path @@ -688,7 +735,9 @@ public enum OpenGroupAPI { id: Int64, in roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> AnyPublisher<(ResponseInfoType, ReactionAddResponse), Error> { /// URL(String:) won't convert raw emojis, so need to do a little encoding here. /// The raw emoji will come back when calling url.path @@ -716,7 +765,9 @@ public enum OpenGroupAPI { id: Int64, in roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> AnyPublisher<(ResponseInfoType, ReactionRemoveResponse), Error> { /// URL(String:) won't convert raw emojis, so need to do a little encoding here. /// The raw emoji will come back when calling url.path @@ -744,7 +795,9 @@ public enum OpenGroupAPI { id: Int64, in roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> AnyPublisher<(ResponseInfoType, ReactionRemoveAllResponse), Error> { /// URL(String:) won't convert raw emojis, so need to do a little encoding here. /// The raw emoji will come back when calling url.path @@ -783,7 +836,9 @@ public enum OpenGroupAPI { id: Int64, in roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> AnyPublisher { return OpenGroupAPI .send( @@ -807,7 +862,9 @@ public enum OpenGroupAPI { id: Int64, in roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> AnyPublisher { return OpenGroupAPI .send( @@ -830,7 +887,9 @@ public enum OpenGroupAPI { _ db: Database, in roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> AnyPublisher { return OpenGroupAPI .send( @@ -854,7 +913,9 @@ public enum OpenGroupAPI { fileName: String? = nil, to roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> AnyPublisher<(ResponseInfoType, FileUploadResponse), Error> { return OpenGroupAPI .send( @@ -881,8 +942,11 @@ public enum OpenGroupAPI { fileId: String, from roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> AnyPublisher<(ResponseInfoType, Data), Error> { + print("Download File") return OpenGroupAPI .send( db, @@ -893,6 +957,7 @@ public enum OpenGroupAPI { using: dependencies ) .flatMap { responseInfo, maybeData -> AnyPublisher<(ResponseInfoType, Data), Error> in + print("Download File FlatMap") guard let data: Data = maybeData else { return Fail(error: HTTPError.parsingFailed) .eraseToAnyPublisher() @@ -916,7 +981,9 @@ public enum OpenGroupAPI { public static func inbox( _ db: Database, on server: String, - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> AnyPublisher<(ResponseInfoType, [DirectMessage]?), Error> { return OpenGroupAPI .send( @@ -940,7 +1007,9 @@ public enum OpenGroupAPI { _ db: Database, id: Int64, on server: String, - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> AnyPublisher<(ResponseInfoType, [DirectMessage]?), Error> { return OpenGroupAPI .send( @@ -962,7 +1031,9 @@ public enum OpenGroupAPI { ciphertext: Data, toInboxFor blindedSessionId: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> AnyPublisher<(ResponseInfoType, SendDirectMessageResponse), Error> { return OpenGroupAPI .send( @@ -989,7 +1060,9 @@ public enum OpenGroupAPI { public static func outbox( _ db: Database, on server: String, - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> AnyPublisher<(ResponseInfoType, [DirectMessage]?), Error> { return OpenGroupAPI .send( @@ -1013,7 +1086,9 @@ public enum OpenGroupAPI { _ db: Database, id: Int64, on server: String, - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> AnyPublisher<(ResponseInfoType, [DirectMessage]?), Error> { return OpenGroupAPI .send( @@ -1066,7 +1141,9 @@ public enum OpenGroupAPI { for timeout: TimeInterval? = nil, from roomTokens: [String]? = nil, on server: String, - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { return OpenGroupAPI .send( @@ -1114,7 +1191,9 @@ public enum OpenGroupAPI { sessionId: String, from roomTokens: [String]?, on server: String, - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { return OpenGroupAPI .send( @@ -1191,7 +1270,9 @@ public enum OpenGroupAPI { visible: Bool, for roomTokens: [String]?, on server: String, - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { guard (moderator != nil && admin == nil) || (moderator == nil && admin != nil) else { return Fail(error: HTTPError.generic) @@ -1224,8 +1305,10 @@ public enum OpenGroupAPI { sessionId: String, in roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies() - ) -> AnyPublisher<[ResponseInfoType], Error> { + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) + ) -> AnyPublisher<(info: ResponseInfoType, data: [Endpoint: ResponseInfoType]), Error> { let banRequestBody: UserBanRequest = UserBanRequest( rooms: [roomToken], global: nil, @@ -1258,8 +1341,11 @@ public enum OpenGroupAPI { requests: requestResponseType, using: dependencies ) - .map { _, data -> [ResponseInfoType] in - data.values.compactMap { ($0 as? BatchSubResponseType)?.responseInfo } + .map { info, data -> (info: ResponseInfoType, data: [Endpoint: ResponseInfoType]) in + ( + info, + data.compactMapValues { ($0 as? BatchSubResponseType)?.responseInfo } + ) } .eraseToAnyPublisher() } @@ -1273,7 +1359,9 @@ public enum OpenGroupAPI { for serverName: String, fallbackSigningType signingType: SessionId.Prefix, forceBlinded: Bool = false, - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> (publicKey: String, signature: Bytes)? { guard let userEdKeyPair: Box.KeyPair = Identity.fetchUserEd25519KeyPair(db), @@ -1340,7 +1428,9 @@ public enum OpenGroupAPI { for serverName: String, with serverPublicKey: String, forceBlinded: Bool = false, - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> URLRequest? { guard let url: URL = request.url else { return nil } @@ -1401,7 +1491,9 @@ public enum OpenGroupAPI { _ db: Database, request: Request, forceBlinded: Bool = false, - using dependencies: SMKDependencies = SMKDependencies() + using dependencies: SMKDependencies = SMKDependencies( + queue: OpenGroupAPI.workQueue + ) ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { let urlRequest: URLRequest @@ -1430,11 +1522,10 @@ public enum OpenGroupAPI { .eraseToAnyPublisher() } - // We was to avoid blocking the db write thread so we dispatch the API call to a different thread + // We want to avoid blocking the db write thread so we dispatch the API call to a different thread return Just(()) .setFailureType(to: Error.self) - .subscribe(on: OpenGroupAPI.workQueue) - .receive(on: OpenGroupAPI.workQueue) + .subscribe(on: dependencies.queue) .flatMap { dependencies.onionApi.sendOnionRequest(signedRequest, to: request.server, with: publicKey) } .eraseToAnyPublisher() } diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index 26f4a290e..200cc60ea 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -281,14 +281,14 @@ public final class OpenGroupManager { // Store the capabilities first OpenGroupManager.handleCapabilities( db, - capabilities: response.capabilities.data, + capabilities: response.data.capabilities.data, on: targetServer ) // Then the room try OpenGroupManager.handlePollInfo( db, - pollInfo: OpenGroupAPI.RoomPollInfo(room: response.room.data), + pollInfo: OpenGroupAPI.RoomPollInfo(room: response.data.room.data), publicKey: publicKey, for: roomToken, on: targetServer, @@ -1051,7 +1051,9 @@ public final class OpenGroupManager { fileId: String, for roomToken: String, on server: String, - using dependencies: OGMDependencies = OGMDependencies() + using dependencies: OGMDependencies = OGMDependencies( + queue: DispatchQueue.global(qos: .background) + ) ) -> AnyPublisher { // Normally the image for a given group is stored with the group thread, so it's only // fetched once. However, on the join open group screen we show images for groups the @@ -1087,18 +1089,14 @@ public final class OpenGroupManager { } // Trigger the download on a background queue - let publisher: AnyPublisher = dependencies.storage - .readPublisherFlatMap { db in - OpenGroupAPI - .downloadFile( - db, - fileId: fileId, - from: roomToken, - on: server, - using: dependencies - ) - } - .subscribe(on: DispatchQueue.global(qos: .background)) + let publisher: AnyPublisher = OpenGroupAPI + .downloadFile( + db, + fileId: fileId, + from: roomToken, + on: server, + using: dependencies + ) .map { _, imageData in if server.lowercased() == OpenGroupAPI.defaultServer { dependencies.storage.write { db in @@ -1167,6 +1165,7 @@ extension OpenGroupManager { public var cache: OGMCacheType { return mutableCache.wrappedValue } public init( + queue: DispatchQueue? = nil, cache: Atomic? = nil, onionApi: OnionRequestAPIType.Type? = nil, generalCache: Atomic? = nil, @@ -1186,6 +1185,7 @@ extension OpenGroupManager { _mutableCache = Atomic(cache) super.init( + queue: queue, onionApi: onionApi, generalCache: generalCache, storage: storage, diff --git a/SessionMessagingKit/SMKDependencies.swift b/SessionMessagingKit/SMKDependencies.swift index c8d9c18e0..fa66b4b6a 100644 --- a/SessionMessagingKit/SMKDependencies.swift +++ b/SessionMessagingKit/SMKDependencies.swift @@ -57,6 +57,7 @@ public class SMKDependencies: SSKDependencies { // MARK: - Initialization public init( + queue: DispatchQueue? = nil, onionApi: OnionRequestAPIType.Type? = nil, generalCache: Atomic? = nil, storage: Storage? = nil, @@ -82,6 +83,7 @@ public class SMKDependencies: SSKDependencies { _nonceGenerator24 = Atomic(nonceGenerator24) super.init( + queue: queue, onionApi: onionApi, generalCache: generalCache, storage: storage, diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift b/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift index d97f36586..384e46edc 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift +++ b/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift @@ -25,6 +25,7 @@ class OpenGroupAPISpec: QuickSpec { var mockNonce16Generator: MockNonce16Generator! var mockNonce24Generator: MockNonce24Generator! var dependencies: SMKDependencies! + var disposables: [AnyCancellable] = [] var response: (ResponseInfoType, Codable)? = nil var pollResponse: (info: ResponseInfoType, data: [OpenGroupAPI.Endpoint: Codable])? @@ -115,6 +116,8 @@ class OpenGroupAPISpec: QuickSpec { } afterEach { + disposables.forEach { $0.cancel() } + mockStorage = nil mockSodium = nil mockAeadXChaCha20Poly1305Ietf = nil @@ -122,6 +125,7 @@ class OpenGroupAPISpec: QuickSpec { mockGenericHash = nil mockEd25519 = nil dependencies = nil + disposables = [] response = nil pollResponse = nil @@ -191,11 +195,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in pollResponse = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(pollResponse) .toEventuallyNot( @@ -205,16 +207,13 @@ class OpenGroupAPISpec: QuickSpec { expect(error?.localizedDescription).to(beNil()) // Validate the response data - expect(pollResponse?.data.count).to(equal(3)) expect(pollResponse?.data.keys).to(contain(.capabilities)) expect(pollResponse?.data.keys).to(contain(.roomPollInfo("testRoom", 0))) expect(pollResponse?.data.keys).to(contain(.roomMessagesRecent("testRoom"))) - expect(pollResponse?.data[.capabilities]) - .to(beAKindOf(TestOnionRequestAPI.ResponseInfo.self)) // Validate request data - let requestData: TestOnionRequestAPI.RequestData? = (pollResponse?.data[.capabilities] as? TestOnionRequestAPI.ResponseInfo)?.requestData + let requestData: TestOnionRequestAPI.RequestData? = (pollResponse?.info as? TestOnionRequestAPI.ResponseInfo)?.requestData expect(requestData?.urlString).to(equal("testserver/batch")) expect(requestData?.httpMethod).to(equal("POST")) expect(requestData?.publicKey).to(equal("88672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b")) @@ -231,11 +230,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in pollResponse = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(pollResponse) .toEventuallyNot( @@ -262,11 +259,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in pollResponse = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(pollResponse) .toEventuallyNot( @@ -293,11 +288,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in pollResponse = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(pollResponse) .toEventuallyNot( @@ -324,11 +317,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in pollResponse = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(pollResponse) .toEventuallyNot( @@ -358,11 +349,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in pollResponse = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(pollResponse) .toEventuallyNot( @@ -459,11 +448,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in pollResponse = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(pollResponse) .toEventuallyNot( @@ -488,11 +475,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in pollResponse = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(pollResponse) .toEventuallyNot( @@ -519,11 +504,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in pollResponse = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(pollResponse) .toEventuallyNot( @@ -545,11 +528,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in pollResponse = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(pollResponse) .toEventuallyNot( @@ -576,11 +557,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in pollResponse = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(pollResponse) .toEventuallyNot( @@ -639,11 +618,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in pollResponse = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(pollResponse) .toEventuallyNot( @@ -671,11 +648,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in pollResponse = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(error?.localizedDescription) .toEventually( @@ -702,11 +677,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in pollResponse = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(error?.localizedDescription) .toEventually( @@ -733,11 +706,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in pollResponse = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(error?.localizedDescription) .toEventually( @@ -764,11 +735,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in pollResponse = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(error?.localizedDescription) .toEventually( @@ -827,11 +796,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in pollResponse = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(error?.localizedDescription) .toEventually( @@ -865,11 +832,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(response) .toEventuallyNot( @@ -937,11 +902,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(response) .toEventuallyNot( @@ -1020,7 +983,7 @@ class OpenGroupAPISpec: QuickSpec { } dependencies = dependencies.with(onionApi: TestApi.self) - var response: (capabilities: (info: ResponseInfoType, data: OpenGroupAPI.Capabilities?), room: (info: ResponseInfoType, data: OpenGroupAPI.Room?))? + var response: OpenGroupAPI.CapabilitiesAndRoomResponse? mockStorage .readPublisherFlatMap { db in @@ -1031,11 +994,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(response) .toEventuallyNot( @@ -1045,11 +1006,11 @@ class OpenGroupAPISpec: QuickSpec { expect(error?.localizedDescription).to(beNil()) // Validate the response data - expect(response?.capabilities.data).to(equal(TestApi.capabilitiesData)) - expect(response?.room.data).to(equal(TestApi.roomData)) + expect(response?.data.capabilities.data).to(equal(TestApi.capabilitiesData)) + expect(response?.data.room.data).to(equal(TestApi.roomData)) // Validate request data - let requestData: TestOnionRequestAPI.RequestData? = (response?.capabilities.info as? TestOnionRequestAPI.ResponseInfo)?.requestData + let requestData: TestOnionRequestAPI.RequestData? = (response?.info as? TestOnionRequestAPI.ResponseInfo)?.requestData expect(requestData?.httpMethod).to(equal("POST")) expect(requestData?.urlString).to(equal("testserver/sequence")) } @@ -1077,7 +1038,7 @@ class OpenGroupAPISpec: QuickSpec { } dependencies = dependencies.with(onionApi: TestApi.self) - var response: (capabilities: (info: ResponseInfoType, data: OpenGroupAPI.Capabilities?), room: (info: ResponseInfoType, data: OpenGroupAPI.Room?))? + var response: OpenGroupAPI.CapabilitiesAndRoomResponse? mockStorage .readPublisherFlatMap { db in @@ -1089,11 +1050,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(error?.localizedDescription) .toEventually( @@ -1151,7 +1110,7 @@ class OpenGroupAPISpec: QuickSpec { } dependencies = dependencies.with(onionApi: TestApi.self) - var response: (capabilities: (info: ResponseInfoType, data: OpenGroupAPI.Capabilities?), room: (info: ResponseInfoType, data: OpenGroupAPI.Room?))? + var response: OpenGroupAPI.CapabilitiesAndRoomResponse? mockStorage .readPublisherFlatMap { db in @@ -1163,11 +1122,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(error?.localizedDescription) .toEventually( @@ -1242,7 +1199,7 @@ class OpenGroupAPISpec: QuickSpec { } dependencies = dependencies.with(onionApi: TestApi.self) - var response: (capabilities: (info: ResponseInfoType, data: OpenGroupAPI.Capabilities?), room: (info: ResponseInfoType, data: OpenGroupAPI.Room?))? + var response: OpenGroupAPI.CapabilitiesAndRoomResponse? mockStorage .readPublisherFlatMap { db in @@ -1253,11 +1210,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(error?.localizedDescription) .toEventually( @@ -1319,11 +1274,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(response) .toEventuallyNot( @@ -1366,11 +1319,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(response) .toEventuallyNot( @@ -1408,11 +1359,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(error?.localizedDescription) .toEventually( @@ -1445,11 +1394,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(error?.localizedDescription) .toEventually( @@ -1480,11 +1427,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(error?.localizedDescription) .toEventually( @@ -1522,11 +1467,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(response) .toEventuallyNot( @@ -1564,11 +1507,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(error?.localizedDescription) .toEventually( @@ -1601,11 +1542,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(error?.localizedDescription) .toEventually( @@ -1644,11 +1583,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(error?.localizedDescription) .toEventually( @@ -1696,11 +1633,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(response) .toEventuallyNot( @@ -1752,11 +1687,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(response) .toEventuallyNot( @@ -1795,11 +1728,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(response) .toEventuallyNot( @@ -1836,11 +1767,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(error?.localizedDescription) .toEventually( @@ -1872,11 +1801,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(error?.localizedDescription) .toEventually( @@ -1906,11 +1833,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(error?.localizedDescription) .toEventually( @@ -1947,11 +1872,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(response) .toEventuallyNot( @@ -1988,11 +1911,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(error?.localizedDescription) .toEventually( @@ -2024,11 +1945,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(error?.localizedDescription) .toEventually( @@ -2066,11 +1985,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(error?.localizedDescription) .toEventually( @@ -2103,11 +2020,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(response) .toEventuallyNot( @@ -2149,11 +2064,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(response) .toEventuallyNot( @@ -2191,11 +2104,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(response) .toEventuallyNot( @@ -2231,11 +2142,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(response) .toEventuallyNot( @@ -2270,11 +2179,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(response) .toEventuallyNot( @@ -2312,11 +2219,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(response) .toEventuallyNot( @@ -2350,11 +2255,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(response) .toEventuallyNot( @@ -2389,11 +2292,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(response) .toEventuallyNot( @@ -2428,11 +2329,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(response) .toEventuallyNot( @@ -2487,11 +2386,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(response) .toEventuallyNot( @@ -2539,11 +2436,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(response) .toEventuallyNot( @@ -2571,11 +2466,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(response) .toEventuallyNot( @@ -2605,11 +2498,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(response) .toEventuallyNot( @@ -2653,11 +2544,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(response) .toEventuallyNot( @@ -2684,11 +2573,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(response) .toEventuallyNot( @@ -2717,11 +2604,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(response) .toEventuallyNot( @@ -2768,11 +2653,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(response) .toEventuallyNot( @@ -2802,11 +2685,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(response) .toEventuallyNot( @@ -2838,11 +2719,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(response) .toEventuallyNot( @@ -2874,11 +2753,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(error?.localizedDescription) .toEventually( @@ -2891,7 +2768,7 @@ class OpenGroupAPISpec: QuickSpec { } context("when banning and deleting all messages for a user") { - var response: [ResponseInfoType]? + var response: (info: ResponseInfoType, data: [OpenGroupAPI.Endpoint: ResponseInfoType])? beforeEach { class TestApi: TestOnionRequestAPI { @@ -2937,11 +2814,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(response) .toEventuallyNot( @@ -2951,7 +2826,7 @@ class OpenGroupAPISpec: QuickSpec { expect(error?.localizedDescription).to(beNil()) // Validate request data - let requestData: TestOnionRequestAPI.RequestData? = (response?.first as? TestOnionRequestAPI.ResponseInfo)?.requestData + let requestData: TestOnionRequestAPI.RequestData? = (response?.info as? TestOnionRequestAPI.ResponseInfo)?.requestData expect(requestData?.httpMethod).to(equal("POST")) expect(requestData?.urlString).to(equal("testserver/sequence")) } @@ -2968,11 +2843,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(response) .toEventuallyNot( @@ -2982,7 +2855,7 @@ class OpenGroupAPISpec: QuickSpec { expect(error?.localizedDescription).to(beNil()) // Validate request data - let requestData: TestOnionRequestAPI.RequestData? = (response?.first as? TestOnionRequestAPI.ResponseInfo)?.requestData + let requestData: TestOnionRequestAPI.RequestData? = (response?.info as? TestOnionRequestAPI.ResponseInfo)?.requestData let jsonObject: Any = try! JSONSerialization.jsonObject( with: requestData!.body!, options: [.fragmentsAllowed] @@ -3025,11 +2898,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(error?.localizedDescription) .toEventually( @@ -3054,11 +2925,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(error?.localizedDescription) .toEventually( @@ -3083,11 +2952,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(error?.localizedDescription) .toEventually( @@ -3116,11 +2983,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(response) .toEventuallyNot( @@ -3154,11 +3019,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(error?.localizedDescription) .toEventually( @@ -3189,11 +3052,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(response) .toEventuallyNot( @@ -3228,11 +3089,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(error?.localizedDescription) .toEventually( @@ -3257,11 +3116,9 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(error?.localizedDescription) .toEventually( diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift index 5a9b19206..26d5cc290 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift +++ b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift @@ -116,6 +116,7 @@ class OpenGroupManagerSpec: QuickSpec { mockNonce24Generator = MockNonce24Generator() mockUserDefaults = MockUserDefaults() dependencies = OpenGroupManager.OGMDependencies( + queue: DispatchQueue.main, cache: Atomic(mockOGMCache), onionApi: TestCapabilitiesAndRoomApi.self, generalCache: Atomic(mockGeneralCache), @@ -380,18 +381,25 @@ class OpenGroupManagerSpec: QuickSpec { openGroupManager.startPolling(using: dependencies) expect(mockOGMCache) - .to(call(matchingParameters: true) { - $0.pollers = [ - "testserver": OpenGroupAPI.Poller(for: "testserver"), - "testserver1": OpenGroupAPI.Poller(for: "testserver1") - ] - }) + .toEventually( + call(matchingParameters: true) { + $0.pollers = [ + "testserver": OpenGroupAPI.Poller(for: "testserver"), + "testserver1": OpenGroupAPI.Poller(for: "testserver1") + ] + }, + timeout: .milliseconds(50) + ) } it("updates the isPolling flag") { openGroupManager.startPolling(using: dependencies) - expect(mockOGMCache).to(call(matchingParameters: true) { $0.isPolling = true }) + expect(mockOGMCache) + .toEventually( + call(matchingParameters: true) { $0.isPolling = true }, + timeout: .milliseconds(50) + ) } it("does nothing if already polling") { @@ -399,7 +407,10 @@ class OpenGroupManagerSpec: QuickSpec { openGroupManager.startPolling(using: dependencies) - expect(mockOGMCache).toNot(call { $0.pollers }) + expect(mockOGMCache).toEventuallyNot( + call { $0.pollers }, + timeout: .milliseconds(50) + ) } } @@ -815,9 +826,8 @@ class OpenGroupManagerSpec: QuickSpec { dependencies: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) - .sinkUntilComplete(receiveCompletion: { _ in didComplete = true }) + .handleEvents(receiveCompletion: { _ in didComplete = true }) + .sinkAndStore(in: &disposables) expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50)) expect( @@ -847,9 +857,8 @@ class OpenGroupManagerSpec: QuickSpec { dependencies: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) - .sinkUntilComplete(receiveCompletion: { _ in didComplete = true }) + .handleEvents(receiveCompletion: { _ in didComplete = true }) + .sinkAndStore(in: &disposables) expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50)) expect(mockOGMCache) @@ -887,9 +896,8 @@ class OpenGroupManagerSpec: QuickSpec { dependencies: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) - .sinkUntilComplete(receiveCompletion: { _ in didComplete = true }) + .handleEvents(receiveCompletion: { _ in didComplete = true }) + .sinkAndStore(in: &disposables) expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50)) expect( @@ -942,10 +950,8 @@ class OpenGroupManagerSpec: QuickSpec { dependencies: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) .mapError { result -> Error in error.setting(to: result) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(error?.localizedDescription) .toEventually( @@ -3365,9 +3371,8 @@ class OpenGroupManagerSpec: QuickSpec { var response: [OpenGroupAPI.Room]? OpenGroupManager.getDefaultRoomsIfNeeded(using: dependencies) - .sinkUntilComplete(receiveValue: { (data: [OpenGroupAPI.Room]) in - response = data - }) + .handleEvents(receiveOutput: { (data: [OpenGroupAPI.Room]) in response = data }) + .sinkAndStore(in: &disposables) expect(response) .toEventually( @@ -3422,7 +3427,7 @@ class OpenGroupManagerSpec: QuickSpec { OpenGroupManager.getDefaultRoomsIfNeeded(using: dependencies) .mapError { result -> Error in error.setting(to: result) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(error?.localizedDescription) .toEventually( @@ -3442,7 +3447,7 @@ class OpenGroupManagerSpec: QuickSpec { OpenGroupManager.getDefaultRoomsIfNeeded(using: dependencies) .mapError { result -> Error in error.setting(to: result) } - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(error?.localizedDescription) .toEventually( @@ -3519,8 +3524,8 @@ class OpenGroupManagerSpec: QuickSpec { OpenGroupManager .getDefaultRoomsIfNeeded(using: dependencies) - .sinkUntilComplete() - + .sinkAndStore(in: &disposables) + expect(mockUserDefaults) .toEventually( call(matchingParameters: true) { @@ -3596,7 +3601,8 @@ class OpenGroupManagerSpec: QuickSpec { using: dependencies ) } - .sinkUntilComplete(receiveValue: { result = $0 }) + .handleEvents(receiveOutput: { result = $0 }) + .sinkAndStore(in: &disposables) expect(result).toEventually(equal(publisher.firstValue()), timeout: .milliseconds(50)) } @@ -3614,7 +3620,8 @@ class OpenGroupManagerSpec: QuickSpec { using: dependencies ) } - .sinkUntilComplete(receiveCompletion: { _ in didComplete = true }) + .handleEvents(receiveCompletion: { _ in didComplete = true }) + .sinkAndStore(in: &disposables) expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50)) expect( @@ -3644,7 +3651,8 @@ class OpenGroupManagerSpec: QuickSpec { using: dependencies ) } - .sinkUntilComplete(receiveCompletion: { _ in didComplete = true }) + .handleEvents(receiveCompletion: { _ in didComplete = true }) + .sinkAndStore(in: &disposables) expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50)) expect(mockUserDefaults) @@ -3676,16 +3684,17 @@ class OpenGroupManagerSpec: QuickSpec { let publisher = mockStorage .readPublisherFlatMap { (db: Database) -> AnyPublisher in - OpenGroupManager.roomImage( - db, - fileId: "1", - for: "testRoom", - on: "testServer", - using: dependencies - ) + OpenGroupManager + .roomImage( + db, + fileId: "1", + for: "testRoom", + on: "testServer", + using: dependencies + ) } publisher.sinkAndStore(in: &disposables) - + expect(mockOGMCache) .toEventually( call(matchingParameters: true) { @@ -3710,9 +3719,8 @@ class OpenGroupManagerSpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) - .sinkUntilComplete(receiveValue: { (data: Data) in result = data }) + .handleEvents(receiveOutput: { (data: Data) in result = data }) + .sinkAndStore(in: &disposables) expect(result).toEventually(equal(Data([1, 2, 3])), timeout: .milliseconds(50)) } @@ -3731,9 +3739,8 @@ class OpenGroupManagerSpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) - .sinkUntilComplete(receiveCompletion: { _ in didComplete = true }) + .handleEvents(receiveCompletion: { _ in didComplete = true }) + .sinkAndStore(in: &disposables) expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50)) expect( @@ -3764,9 +3771,8 @@ class OpenGroupManagerSpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) - .sinkUntilComplete(receiveCompletion: { _ in didComplete = true }) + .handleEvents(receiveCompletion: { _ in didComplete = true }) + .sinkAndStore(in: &disposables) expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50)) expect(mockUserDefaults) @@ -3813,9 +3819,8 @@ class OpenGroupManagerSpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) - .sinkUntilComplete(receiveValue: { (data: Data) in result = data }) + .handleEvents(receiveOutput: { (data: Data) in result = data }) + .sinkAndStore(in: &disposables) expect(result).toEventually(equal(Data([2, 3, 4])), timeout: .milliseconds(50)) } @@ -3844,9 +3849,8 @@ class OpenGroupManagerSpec: QuickSpec { using: dependencies ) } - .subscribe(on: DispatchQueue.main) - .receiveOnMain(immediately: true) - .sinkUntilComplete(receiveValue: { (data: Data) in result = data }) + .handleEvents(receiveOutput: { (data: Data) in result = data }) + .sinkAndStore(in: &disposables) expect(result).toEventually(equal(Data([1, 2, 3])), timeout: .milliseconds(50)) } diff --git a/SessionSnodeKit/SSKDependencies.swift b/SessionSnodeKit/SSKDependencies.swift index 8f271429f..e61cb7311 100644 --- a/SessionSnodeKit/SSKDependencies.swift +++ b/SessionSnodeKit/SSKDependencies.swift @@ -14,6 +14,7 @@ open class SSKDependencies: Dependencies { // MARK: - Initialization public init( + queue: DispatchQueue? = nil, onionApi: OnionRequestAPIType.Type? = nil, generalCache: Atomic? = nil, storage: Storage? = nil, @@ -24,6 +25,7 @@ open class SSKDependencies: Dependencies { _onionApi = Atomic(onionApi) super.init( + queue: queue, generalCache: generalCache, storage: storage, scheduler: scheduler, diff --git a/SessionUtilitiesKit/Combine/Publisher+Utilities.swift b/SessionUtilitiesKit/Combine/Publisher+Utilities.swift index 4e0f1ee27..9aef2ea5c 100644 --- a/SessionUtilitiesKit/Combine/Publisher+Utilities.swift +++ b/SessionUtilitiesKit/Combine/Publisher+Utilities.swift @@ -25,6 +25,31 @@ public extension Publisher { ) } + /// The standard `.subscribe(on: DispatchQueue.main)` seems to ocassionally dispatch to the + /// next run loop before actually subscribing, this method checks if it's running on the main thread already and + /// if so just subscribes directly rather than routing via `.receive(on:)` + func subscribeOnMain(immediately receiveImmediately: Bool = false, options: DispatchQueue.SchedulerOptions? = nil) -> AnyPublisher { + guard receiveImmediately else { + return self.subscribe(on: DispatchQueue.main, options: options) + .eraseToAnyPublisher() + } + + return self + .flatMap { value -> AnyPublisher in + guard Thread.isMainThread else { + return Just(value) + .setFailureType(to: Failure.self) + .subscribe(on: DispatchQueue.main, options: options) + .eraseToAnyPublisher() + } + + return Just(value) + .setFailureType(to: Failure.self) + .eraseToAnyPublisher() + } + .eraseToAnyPublisher() + } + /// The standard `.receive(on: DispatchQueue.main)` seems to ocassionally dispatch to the /// next run loop before emitting data, this method checks if it's running on the main thread already and /// if so just emits directly rather than routing via `.receive(on:)` diff --git a/SessionUtilitiesKit/General/Dependencies.swift b/SessionUtilitiesKit/General/Dependencies.swift index e9c576639..30e443be1 100644 --- a/SessionUtilitiesKit/General/Dependencies.swift +++ b/SessionUtilitiesKit/General/Dependencies.swift @@ -4,6 +4,12 @@ import Foundation import GRDB open class Dependencies { + public var _queue: Atomic + public var queue: DispatchQueue { + get { Dependencies.getValueSettingIfNull(&_queue) { DispatchQueue.global(qos: .default) } } + set { _queue.mutate { $0 = newValue } } + } + public var _generalCache: Atomic?> public var generalCache: Atomic { get { Dependencies.getValueSettingIfNull(&_generalCache) { General.cache } } @@ -37,12 +43,14 @@ open class Dependencies { // MARK: - Initialization public init( + queue: DispatchQueue? = nil, generalCache: Atomic? = nil, storage: Storage? = nil, scheduler: ValueObservationScheduler? = nil, standardUserDefaults: UserDefaultsType? = nil, date: Date? = nil ) { + _queue = Atomic(queue) _generalCache = Atomic(generalCache) _storage = Atomic(storage) _scheduler = Atomic(scheduler) diff --git a/SessionUtilitiesKit/JobRunner/JobRunner.swift b/SessionUtilitiesKit/JobRunner/JobRunner.swift index a09c282c6..8c0f1512b 100644 --- a/SessionUtilitiesKit/JobRunner/JobRunner.swift +++ b/SessionUtilitiesKit/JobRunner/JobRunner.swift @@ -631,7 +631,11 @@ private final class JobQueue { fileprivate func start(force: Bool = false) { // We only want the JobRunner to run in the main app - guard CurrentAppContext().isMainApp else { return } + guard + HasAppContext() && + CurrentAppContext().isMainApp && + !CurrentAppContext().isRunningTests + else { return } guard force || !isRunning.wrappedValue else { return } // The JobRunner runs synchronously we need to ensure this doesn't start diff --git a/_SharedTestUtilities/CombineExtensions.swift b/_SharedTestUtilities/CombineExtensions.swift index cb59fd653..1c8ac8505 100644 --- a/_SharedTestUtilities/CombineExtensions.swift +++ b/_SharedTestUtilities/CombineExtensions.swift @@ -4,9 +4,10 @@ import Foundation import Combine import SessionUtilitiesKit -public extension AnyPublisher { - func sinkAndStore(in storage: inout C) where C : RangeReplaceableCollection, C.Element == AnyCancellable { +public extension Publisher { + func sinkAndStore(in storage: inout C) where C: RangeReplaceableCollection, C.Element == AnyCancellable { self + .subscribeOnMain(immediately: true) .receiveOnMain(immediately: true) .sink( receiveCompletion: { _ in }, @@ -14,7 +15,9 @@ public extension AnyPublisher { ) .store(in: &storage) } - +} + +public extension AnyPublisher { func firstValue() -> Output? { var value: Output? From 29ba25916cc3fcf675abb103e29e6b275d739304 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 5 Jan 2023 13:40:33 +1100 Subject: [PATCH 024/135] Fixed a few issues uncovered when testing the shared util feature flag Added code to handle an edge-case where an account could exist without a display name if crashing at the right time when onboarding/linking (now request a display name on start up) Fixed a crash which could occur when downloading new avatars Fixed a minor layout issue with the seed reminder view --- .../ConversationVC+Interaction.swift | 3 +-- Session/Meta/AppDelegate.swift | 15 +++++++++---- Session/Onboarding/Onboarding.swift | 9 ++++++++ Session/Onboarding/SeedReminderView.swift | 3 ++- Session/Open Groups/JoinOpenGroupVC.swift | 3 +-- .../Jobs/Types/ConfigurationSyncJob.swift | 8 ++++--- .../QueryInterfaceRequest+Utilities.swift | 6 ++++++ .../LibSessionUtil/SessionUtil.swift | 2 ++ .../Open Groups/OpenGroupAPI.swift | 2 -- .../Open Groups/OpenGroupManager.swift | 8 ++++--- ...essageReceiver+ConfigurationMessages.swift | 11 ++++++++-- .../Utilities/ProfileManager.swift | 21 ++++++++++++++++++- .../Open Groups/OpenGroupManagerSpec.swift | 4 ---- 13 files changed, 71 insertions(+), 24 deletions(-) diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 446ef1c8d..a04745d20 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -1554,8 +1554,7 @@ extension ConversationVC: db, roomToken: room, server: server, - publicKey: publicKey, - isConfigMessage: false + publicKey: publicKey ) } .receive(on: DispatchQueue.main) diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index aa186b1a9..23e9894bb 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -405,10 +405,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD self.hasInitialRootViewController = true self.window?.rootViewController = StyledNavigationController( - rootViewController: (Identity.userExists() ? - HomeVC() : - LandingVC() - ) + rootViewController: { + guard Identity.userExists() else { return LandingVC() } + guard !Profile.fetchOrCreateCurrentUser().name.isEmpty else { + // If we have no display name then collect one (this can happen if the + // app crashed during onboarding which would leave the user in an invalid + // state with no display name) + return DisplayNameVC(flow: .register) + } + + return HomeVC() + }() ) UIViewController.attemptRotationToDeviceOrientation() diff --git a/Session/Onboarding/Onboarding.swift b/Session/Onboarding/Onboarding.swift index 0067fb033..948893aeb 100644 --- a/Session/Onboarding/Onboarding.swift +++ b/Session/Onboarding/Onboarding.swift @@ -9,6 +9,15 @@ import SessionMessagingKit enum Onboarding { private static let profileNameRetrievalPublisher: Atomic> = { + // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent + guard Features.useSharedUtilForUserConfig else { + return Atomic( + Just(nil) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + ) + } + let userPublicKey: String = getUserHexEncodedPublicKey() return Atomic( diff --git a/Session/Onboarding/SeedReminderView.swift b/Session/Onboarding/SeedReminderView.swift index 83b0034c0..5570cc578 100644 --- a/Session/Onboarding/SeedReminderView.swift +++ b/Session/Onboarding/SeedReminderView.swift @@ -82,10 +82,11 @@ final class SeedReminderView: UIView { // Set up button let button = SessionButton(style: .bordered, size: .small) + button.setContentCompressionResistancePriority(.required, for: .horizontal) button.accessibilityLabel = "Continue" button.isAccessibilityElement = true button.setTitle("continue_2".localized(), for: UIControl.State.normal) - button.set(.width, to: 96) + button.set(.width, greaterThanOrEqualTo: 96) button.addTarget(self, action: #selector(handleContinueButtonTapped), for: UIControl.Event.touchUpInside) // Set up content stack view diff --git a/Session/Open Groups/JoinOpenGroupVC.swift b/Session/Open Groups/JoinOpenGroupVC.swift index 648363152..f32320f16 100644 --- a/Session/Open Groups/JoinOpenGroupVC.swift +++ b/Session/Open Groups/JoinOpenGroupVC.swift @@ -174,8 +174,7 @@ final class JoinOpenGroupVC: BaseVC, UIPageViewControllerDataSource, UIPageViewC db, roomToken: roomToken, server: server, - publicKey: publicKey, - isConfigMessage: false + publicKey: publicKey ) } .receive(on: DispatchQueue.main) diff --git a/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift b/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift index 2b734fffc..563c34db2 100644 --- a/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift +++ b/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift @@ -265,11 +265,13 @@ public extension ConfigurationSyncJob { // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent guard Features.useSharedUtilForUserConfig else { - // If we don't have a userKeyPair yet then there is no need to sync the configuration - // as the user doesn't exist yet (this will get triggered on the first launch of a - // fresh install due to the migrations getting run) + // If we don't have a userKeyPair (or name) yet then there is no need to sync the + // configuration as the user doesn't fully exist yet (this will get triggered on + // the first launch of a fresh install due to the migrations getting run and a few + // times during onboarding) guard Identity.userExists(db), + !Profile.fetchOrCreateCurrentUser(db).name.isEmpty, let legacyConfigMessage: Message = try? ConfigurationMessage.getCurrent(db) else { return } diff --git a/SessionMessagingKit/LibSessionUtil/Database/QueryInterfaceRequest+Utilities.swift b/SessionMessagingKit/LibSessionUtil/Database/QueryInterfaceRequest+Utilities.swift index 2caf9c03c..e186e0027 100644 --- a/SessionMessagingKit/LibSessionUtil/Database/QueryInterfaceRequest+Utilities.swift +++ b/SessionMessagingKit/LibSessionUtil/Database/QueryInterfaceRequest+Utilities.swift @@ -60,6 +60,12 @@ public extension QueryInterfaceRequest where RowDecoder: FetchableRecord & Table } } + // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent + guard Features.useSharedUtilForUserConfig else { + return try self.updateAndFetchAll(db, assignments) + } + + // Update the config dump state where needed switch self { case is QueryInterfaceRequest: return try SessionUtil.updatingContacts(db, try updateAndFetchAll(db, assignments)) diff --git a/SessionMessagingKit/LibSessionUtil/SessionUtil.swift b/SessionMessagingKit/LibSessionUtil/SessionUtil.swift index d995a571f..6d95d9a5a 100644 --- a/SessionMessagingKit/LibSessionUtil/SessionUtil.swift +++ b/SessionMessagingKit/LibSessionUtil/SessionUtil.swift @@ -280,6 +280,8 @@ public enum SessionUtil { messages: [SharedConfigMessage], publicKey: String ) throws { + // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent + guard Features.useSharedUtilForUserConfig else { return } guard !messages.isEmpty else { return } guard !publicKey.isEmpty else { throw MessageReceiverError.noThread } diff --git a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift index ae249bfb3..46a96f7c8 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift @@ -946,7 +946,6 @@ public enum OpenGroupAPI { queue: OpenGroupAPI.workQueue ) ) -> AnyPublisher<(ResponseInfoType, Data), Error> { - print("Download File") return OpenGroupAPI .send( db, @@ -957,7 +956,6 @@ public enum OpenGroupAPI { using: dependencies ) .flatMap { responseInfo, maybeData -> AnyPublisher<(ResponseInfoType, Data), Error> in - print("Download File FlatMap") guard let data: Data = maybeData else { return Fail(error: HTTPError.parsingFailed) .eraseToAnyPublisher() diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index 200cc60ea..d8d87e567 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -207,12 +207,12 @@ public final class OpenGroupManager { roomToken: String, server: String, publicKey: String, - isConfigMessage: Bool, + calledFromConfigHandling: Bool = false, dependencies: OGMDependencies = OGMDependencies() ) -> AnyPublisher { // If we are currently polling for this server and already have a TSGroupThread for this room the do nothing if hasExistingOpenGroup(db, roomToken: roomToken, server: server, publicKey: publicKey, dependencies: dependencies) { - SNLog("Ignoring join open group attempt (already joined), user initiated: \(!isConfigMessage)") + SNLog("Ignoring join open group attempt (already joined), user initiated: \(!calledFromConfigHandling)") return Just(()) .setFailureType(to: Error.self) .eraseToAnyPublisher() @@ -276,7 +276,9 @@ public final class OpenGroupManager { Future { resolver in dependencies.storage.write { db in // Enqueue a config sync job (have a newly added open group to sync) - ConfigurationSyncJob.enqueue(db) + if !calledFromConfigHandling { + ConfigurationSyncJob.enqueue(db) + } // Store the capabilities first OpenGroupManager.handleCapabilities( diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift index 2e4203966..c3304e9ea 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift @@ -43,7 +43,8 @@ extension MessageReceiver { fileName: nil ) }(), - sentTimestamp: messageSentTimestamp + sentTimestamp: messageSentTimestamp, + calledFromConfigHandling: true ) // Create a contact for the current user if needed (also force-approve the current user @@ -197,7 +198,13 @@ extension MessageReceiver { for openGroupURL in message.openGroups { if let (room, server, publicKey) = OpenGroupManager.parseOpenGroup(from: openGroupURL) { OpenGroupManager.shared - .add(db, roomToken: room, server: server, publicKey: publicKey, isConfigMessage: true) + .add( + db, + roomToken: room, + server: server, + publicKey: publicKey, + calledFromConfigHandling: true + ) .sinkUntilComplete() } } diff --git a/SessionMessagingKit/Utilities/ProfileManager.swift b/SessionMessagingKit/Utilities/ProfileManager.swift index 9cf23b523..458ee0209 100644 --- a/SessionMessagingKit/Utilities/ProfileManager.swift +++ b/SessionMessagingKit/Utilities/ProfileManager.swift @@ -594,6 +594,25 @@ public struct ProfileManager { profileChanges ) } + // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent + else if !Features.useSharedUtilForUserConfig { + // If we have a contact record for the profile (ie. it's a synced profile) then + // should should send an updated config message, otherwise we should just update + // the local state (the shared util has this logic build in to it's handling) + if (try? Contact.exists(db, id: publicKey)) == true { + try Profile + .filter(id: publicKey) + .updateAllAndConfig(db, profileChanges) + } + else { + try Profile + .filter(id: publicKey) + .updateAll( + db, + profileChanges + ) + } + } else { try Profile .filter(id: publicKey) @@ -606,7 +625,7 @@ public struct ProfileManager { db.afterNextTransaction { db in // Need to refetch to ensure the db changes have occurred - ProfileManager.downloadAvatar(for: Profile.fetchOrCreate(id: publicKey)) + ProfileManager.downloadAvatar(for: Profile.fetchOrCreate(db, id: publicKey)) } } } diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift index 26d5cc290..65e1a5a71 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift +++ b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift @@ -822,7 +822,6 @@ class OpenGroupManagerSpec: QuickSpec { roomToken: "testRoom", server: "testServer", publicKey: TestConstants.serverPublicKey, - isConfigMessage: false, dependencies: dependencies ) } @@ -853,7 +852,6 @@ class OpenGroupManagerSpec: QuickSpec { roomToken: "testRoom", server: "testServer", publicKey: TestConstants.serverPublicKey, - isConfigMessage: false, dependencies: dependencies ) } @@ -892,7 +890,6 @@ class OpenGroupManagerSpec: QuickSpec { publicKey: TestConstants.serverPublicKey .replacingOccurrences(of: "c3", with: "00") .replacingOccurrences(of: "b3", with: "00"), - isConfigMessage: false, dependencies: dependencies ) } @@ -946,7 +943,6 @@ class OpenGroupManagerSpec: QuickSpec { roomToken: "testRoom", server: "testServer", publicKey: TestConstants.serverPublicKey, - isConfigMessage: false, dependencies: dependencies ) } From af9a135c08f9e715546b4960ac248c1433ce9b49 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Mon, 16 Jan 2023 09:19:56 +1100 Subject: [PATCH 025/135] Fixed an issue where open group info could incorrectly get removed --- SessionMessagingKit/Open Groups/OpenGroupManager.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index d8d87e567..0f48ab8d8 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -419,23 +419,23 @@ public final class OpenGroupManager { maybePublicKey.map { OpenGroup.Columns.publicKey.set(to: $0) } : nil ), - (openGroup.name != pollInfo.details?.name ? + (pollInfo.details != nil && openGroup.name != pollInfo.details?.name ? (pollInfo.details?.name).map { OpenGroup.Columns.name.set(to: $0) } : nil ), - (openGroup.roomDescription != pollInfo.details?.roomDescription ? + (pollInfo.details != nil && openGroup.roomDescription != pollInfo.details?.roomDescription ? (pollInfo.details?.roomDescription).map { OpenGroup.Columns.roomDescription.set(to: $0) } : nil ), - (openGroup.imageId != pollInfo.details?.imageId.map { "\($0)" } ? - (pollInfo.details?.imageId).map { OpenGroup.Columns.imageId.set(to: "\($0)") } : + (pollInfo.details != nil && openGroup.imageId != pollInfo.details?.imageId ? + (pollInfo.details?.imageId).map { OpenGroup.Columns.imageId.set(to: $0) } : nil ), (openGroup.userCount != pollInfo.activeUsers ? OpenGroup.Columns.userCount.set(to: pollInfo.activeUsers) : nil ), - (openGroup.infoUpdates != pollInfo.details?.infoUpdates ? + (pollInfo.details != nil && openGroup.infoUpdates != pollInfo.details?.infoUpdates ? (pollInfo.details?.infoUpdates).map { OpenGroup.Columns.infoUpdates.set(to: $0) } : nil ), From 0abb09c0cfebad4699585ab155e4303627e6e587 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 18 Jan 2023 10:56:18 +1100 Subject: [PATCH 026/135] Fixed a few small issues found when testing Fixed a couple of issues with the ConfigurationSyncJob logic Moved the proto parsing out of the MessageReceiveJob write block (to reduce time blocking writes) Removed difficulty from the SendMessageResponse (deprecated and removed) --- .../Conversations/ConversationViewModel.swift | 3 +- .../Jobs/Types/ConfigurationSyncJob.swift | 44 ++++++++++++++----- .../Jobs/Types/MessageReceiveJob.swift | 31 +++++++++---- .../Models/SendMessageResponse.swift | 3 -- SessionSnodeKit/Networking/SnodeAPI.swift | 4 +- .../Networking/BatchResponse.swift | 8 +++- 6 files changed, 66 insertions(+), 27 deletions(-) diff --git a/Session/Conversations/ConversationViewModel.swift b/Session/Conversations/ConversationViewModel.swift index c5a974041..113a6b148 100644 --- a/Session/Conversations/ConversationViewModel.swift +++ b/Session/Conversations/ConversationViewModel.swift @@ -104,10 +104,11 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { public private(set) lazy var threadData: SessionThreadViewModel = SessionThreadViewModel( threadId: self.threadId, threadVariant: self.initialThreadVariant, + threadIsNoteToSelf: (self.threadId == getUserHexEncodedPublicKey()), currentUserIsClosedGroupMember: (self.initialThreadVariant != .closedGroup ? nil : Storage.shared.read { db in - try GroupMember + GroupMember .filter(GroupMember.Columns.groupId == self.threadId) .filter(GroupMember.Columns.profileId == getUserHexEncodedPublicKey(db)) .filter(GroupMember.Columns.role == GroupMember.Role.standard) diff --git a/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift b/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift index 563c34db2..0ff78309d 100644 --- a/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift +++ b/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift @@ -110,10 +110,17 @@ public enum ConfigurationSyncJob: JobExecutor { .collect() .eraseToAnyPublisher() } - .map { (responses: [HTTP.BatchResponse]) -> [SuccessfulChange] in + .flatMap { (responses: [HTTP.BatchResponse]) -> AnyPublisher<[SuccessfulChange], Error> in + // We make a sequence call for this so it's possible to get fewer responses than + // expected so if that happens fail and re-run later + guard responses.count == pendingSwarmConfigChanges.count else { + return Fail(error: HTTPError.invalidResponse) + .eraseToAnyPublisher() + } + // Process the response data into an easy to understand for (this isn't strictly // needed but the code gets convoluted without this) - zip(responses, pendingSwarmConfigChanges) + let successfulChanges: [SuccessfulChange] = zip(responses, pendingSwarmConfigChanges) .compactMap { (batchResponse: HTTP.BatchResponse, pendingSwarmChange: SingleDestinationChanges) -> [SuccessfulChange]? in let maybePublicKey: String? = { switch pendingSwarmChange.destination { @@ -145,6 +152,7 @@ public enum ConfigurationSyncJob: JobExecutor { guard let subResponse: HTTP.BatchSubResponse = (next.response as? HTTP.BatchSubResponse), 200...299 ~= subResponse.code, + !subResponse.failedToParseBody, let sendMessageResponse: SendMessagesResponse = subResponse.body else { return } @@ -162,6 +170,10 @@ public enum ConfigurationSyncJob: JobExecutor { } } .flatMap { $0 } + + return Just(successfulChanges) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } .map { (successfulChanges: [SuccessfulChange]) -> [ConfigDump] in // Now that we have the successful changes, we need to mark them as pushed and @@ -189,6 +201,13 @@ public enum ConfigurationSyncJob: JobExecutor { } } .sinkUntilComplete( + receiveCompletion: { result in + switch result { + case .finished: break + case .failure(let error): + failure(job, error, false) + } + }, receiveValue: { (configDumps: [ConfigDump]) in // Flag to indicate whether the job should be finished or will run again var shouldFinishCurrentJob: Bool = false @@ -209,12 +228,17 @@ public enum ConfigurationSyncJob: JobExecutor { let existingJob: Job = try? Job .filter(Job.Columns.id != job.id) .filter(Job.Columns.variant == Job.Variant.configurationSync) - .fetchOne(db), - !JobRunner.isCurrentlyRunning(existingJob) + .fetchOne(db) { - _ = try existingJob - .with(nextRunTimestamp: nextRunTimestamp) - .saved(db) + // If the next job isn't currently running then delay it's start time + // until the 'nextRunTimestamp' + if !JobRunner.isCurrentlyRunning(existingJob) { + _ = try existingJob + .with(nextRunTimestamp: nextRunTimestamp) + .saved(db) + } + + // If there is another job then we should finish this one shouldFinishCurrentJob = true return job } @@ -302,10 +326,10 @@ public extension ConfigurationSyncJob { @discardableResult static func createOrUpdateIfNeeded(_ db: Database) -> Job { // Try to get an existing job (if there is one that's not running) if - let existingJob: Job = try? Job + let existingJobs: [Job] = try? Job .filter(Job.Columns.variant == Job.Variant.configurationSync) - .fetchOne(db), - !JobRunner.isCurrentlyRunning(existingJob) + .fetchAll(db), + let existingJob: Job = existingJobs.first(where: { !JobRunner.isCurrentlyRunning($0) }) { return existingJob } diff --git a/SessionMessagingKit/Jobs/Types/MessageReceiveJob.swift b/SessionMessagingKit/Jobs/Types/MessageReceiveJob.swift index e3ac101c8..b928b9e85 100644 --- a/SessionMessagingKit/Jobs/Types/MessageReceiveJob.swift +++ b/SessionMessagingKit/Jobs/Types/MessageReceiveJob.swift @@ -25,9 +25,24 @@ public enum MessageReceiveJob: JobExecutor { } var updatedJob: Job = job - var leastSevereError: Error? - let nonConfigMessages: [Details.MessageInfo] = details.messages + var lastError: Error? + var remainingMessagesToProcess: [Details.MessageInfo] = [] + let nonConfigMessages: [(info: Details.MessageInfo, proto: SNProtoContent)] = details.messages .filter { $0.variant != .sharedConfigMessage } + .compactMap { messageInfo -> (info: Details.MessageInfo, proto: SNProtoContent)? in + do { + return (messageInfo, try SNProtoContent.parseData(messageInfo.serializedProtoData)) + } + catch { + SNLog("Couldn't receive message due to error: \(error)") + lastError = error + + // We failed to process this message but it is a retryable error + // so add it to the list to re-process + remainingMessagesToProcess.append(messageInfo) + return nil + } + } let sharedConfigMessages: [SharedConfigMessage] = details.messages .compactMap { $0.message as? SharedConfigMessage } @@ -40,14 +55,12 @@ public enum MessageReceiveJob: JobExecutor { ) // Handle the remaining messages - var remainingMessagesToProcess: [Details.MessageInfo] = [] - - for messageInfo in nonConfigMessages { + for (messageInfo, protoContent) in nonConfigMessages { do { try MessageReceiver.handle( db, message: messageInfo.message, - associatedWithProto: try SNProtoContent.parseData(messageInfo.serializedProtoData), + associatedWithProto: protoContent, openGroupId: nil ) } @@ -71,7 +84,7 @@ public enum MessageReceiveJob: JobExecutor { default: SNLog("Couldn't receive message due to error: \(error)") - leastSevereError = error + lastError = error // We failed to process this message but it is a retryable error // so add it to the list to re-process @@ -94,12 +107,12 @@ public enum MessageReceiveJob: JobExecutor { } // Handle the result - switch leastSevereError { + switch lastError { case let error as MessageReceiverError where !error.isRetryable: failure(updatedJob, error, true) case .some(let error): - failure(updatedJob, error, false) // TODO: Confirm the 'noKeyPair' errors here aren't an issue + failure(updatedJob, error, false) case .none: success(updatedJob, false) diff --git a/SessionSnodeKit/Models/SendMessageResponse.swift b/SessionSnodeKit/Models/SendMessageResponse.swift index d49dd9b55..052188a0e 100644 --- a/SessionSnodeKit/Models/SendMessageResponse.swift +++ b/SessionSnodeKit/Models/SendMessageResponse.swift @@ -4,12 +4,10 @@ import Foundation public class SendMessagesResponse: SnodeRecursiveResponse { private enum CodingKeys: String, CodingKey { - case difficulty case hash case swarm } - public let difficulty: Int64 public let hash: String // MARK: - Initialization @@ -17,7 +15,6 @@ public class SendMessagesResponse: SnodeRecursiveResponse = try decoder.container(keyedBy: CodingKeys.self) - difficulty = try container.decode(Int64.self, forKey: .difficulty) hash = try container.decode(String.self, forKey: .hash) try super.init(from: decoder) diff --git a/SessionSnodeKit/Networking/SnodeAPI.swift b/SessionSnodeKit/Networking/SnodeAPI.swift index 694213d93..19531ca46 100644 --- a/SessionSnodeKit/Networking/SnodeAPI.swift +++ b/SessionSnodeKit/Networking/SnodeAPI.swift @@ -549,7 +549,7 @@ public final class SnodeAPI { var requests: [SnodeAPI.BatchRequest.Info] = targetedMessages .map { message, namespace in // Check if this namespace requires authentication - guard namespace.requiresReadAuthentication else { + guard namespace.requiresWriteAuthentication else { return BatchRequest.Info( request: SnodeRequest( endpoint: .sendMessage, @@ -618,7 +618,7 @@ public final class SnodeAPI { using: dependencies ) .eraseToAnyPublisher() - .decoded(as: responseTypes, using: dependencies) + .decoded(as: responseTypes, requireAllResults: false, using: dependencies) .eraseToAnyPublisher() } .retry(maxRetryCount) diff --git a/SessionUtilitiesKit/Networking/BatchResponse.swift b/SessionUtilitiesKit/Networking/BatchResponse.swift index 8b83fc6c2..05c8a77fe 100644 --- a/SessionUtilitiesKit/Networking/BatchResponse.swift +++ b/SessionUtilitiesKit/Networking/BatchResponse.swift @@ -81,6 +81,7 @@ public extension Decodable { public extension AnyPublisher where Output == (ResponseInfoType, Data?), Failure == Error { func decoded( as types: HTTP.BatchResponseTypes, + requireAllResults: Bool = true, using dependencies: Dependencies = Dependencies() ) -> AnyPublisher { self @@ -101,7 +102,7 @@ public extension AnyPublisher where Output == (ResponseInfoType, Data?), Failure case let anyArray as [Any]: dataArray = anyArray.compactMap { try? JSONSerialization.data(withJSONObject: $0) } - guard dataArray.count == types.count else { + guard !requireAllResults || dataArray.count == types.count else { return Fail(error: HTTPError.parsingFailed) .eraseToAnyPublisher() } @@ -110,7 +111,10 @@ public extension AnyPublisher where Output == (ResponseInfoType, Data?), Failure guard let resultsArray: [Data] = (anyDict["results"] as? [Any])? .compactMap({ try? JSONSerialization.data(withJSONObject: $0) }), - resultsArray.count == types.count + ( + !requireAllResults || + resultsArray.count == types.count + ) else { return Fail(error: HTTPError.parsingFailed) .eraseToAnyPublisher() From 349dc03e17e8df07c45ef978cb7752453176911b Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 18 Jan 2023 13:21:40 +1100 Subject: [PATCH 027/135] Added a secondary fallback for the timestamp in the context menu --- Session/Conversations/Context Menu/ContextMenuVC.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Session/Conversations/Context Menu/ContextMenuVC.swift b/Session/Conversations/Context Menu/ContextMenuVC.swift index d68851588..3b4b1bbd2 100644 --- a/Session/Conversations/Context Menu/ContextMenuVC.swift +++ b/Session/Conversations/Context Menu/ContextMenuVC.swift @@ -71,10 +71,12 @@ final class ContextMenuVC: UIViewController { private lazy var fallbackTimestampLabel: UILabel = { let result: UILabel = UILabel() + result.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) result.font = .systemFont(ofSize: Values.verySmallFontSize) result.text = cellViewModel.dateForUI.formattedForDisplay result.themeTextColor = .textPrimary result.alpha = 0 + result.numberOfLines = 2 return result }() @@ -187,10 +189,14 @@ final class ContextMenuVC: UIViewController { fallbackTimestampLabel.set(.height, to: ContextMenuVC.actionViewHeight) if cellViewModel.variant == .standardOutgoing { + fallbackTimestampLabel.textAlignment = .right fallbackTimestampLabel.pin(.right, to: .left, of: menuView, withInset: -Values.mediumSpacing) + fallbackTimestampLabel.pin(.left, to: .left, of: view, withInset: Values.mediumSpacing) } else { + fallbackTimestampLabel.textAlignment = .left fallbackTimestampLabel.pin(.left, to: .right, of: menuView, withInset: Values.mediumSpacing) + fallbackTimestampLabel.pin(.right, to: .right, of: view, withInset: -Values.mediumSpacing) } // Constrains From 07046db4b689e838008f0e1c65a781cbf7af50da Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 27 Jan 2023 14:51:04 +1100 Subject: [PATCH 028/135] Updated to the latest libSession, fixed a few bugs Added the logic to sync the last read state for a conversation Added the legacyClosedGroup thread variant Updated the config handling to be able to update the 'mergeResult' and require a dump/push due to local changes Fixed an issue where the name on the CallVC could go off the screen Fixed an issue where OpenGroup info could sometimes incorrectly get deleted Fixed an issue where the ConfirmationModal on a SessionTableViewController wouldn't trigger it's action Fixed an issue where the config handling could incorrectly trigger a contacts update when there were no changes --- Session.xcodeproj/project.pbxproj | 8 + .../Calls/Call Management/SessionCall.swift | 10 +- Session/Calls/CallVC.swift | 3 +- .../ConversationVC+Interaction.swift | 5 +- .../Conversations/ConversationViewModel.swift | 4 +- .../Content Views/QuoteView.swift | 2 +- .../Message Cells/VisibleMessageCell.swift | 8 +- .../Settings/ThreadSettingsViewModel.swift | 21 +- .../ConversationTitleView.swift | 2 +- Session/Home/HomeViewModel.swift | 2 +- .../MessageRequestsViewController.swift | 4 +- .../MessageRequestsViewModel.swift | 2 +- Session/Notifications/AppNotifications.swift | 16 +- Session/Shared/FullConversationCell.swift | 13 +- .../Shared/SessionTableViewController.swift | 1 + Session/Utilities/MockDataGenerator.swift | 2 +- .../Migrations/_003_YDBToGRDBMigration.swift | 2 +- .../Migrations/_011_SharedUtilChanges.swift | 28 + .../DisappearingMessageConfiguration.swift | 9 +- .../Database/Models/Interaction.swift | 54 +- .../Database/Models/Profile.swift | 2 +- .../Database/Models/SessionThread.swift | 16 +- .../Database/Models/SharedConfigDump.swift | 8 +- .../Jobs/Types/SendReadReceiptsJob.swift | 1 + .../SessionUtil+Contacts.swift | 36 +- .../SessionUtil+ConvoInfoVolatile.swift | 614 ++++++++++++++++++ .../Config Handling/SessionUtil+Groups.swift | 19 + .../SessionUtil+UserProfile.swift | 10 +- .../QueryInterfaceRequest+Utilities.swift | 7 + .../LibSessionUtil/SessionUtil.swift | 61 +- .../libsession-util.xcframework/Info.plist | 24 +- .../ios-arm64/libsession-util.a | Bin 935072 -> 1065320 bytes .../libsession-util.a | Bin 2093456 -> 2357680 bytes .../module.modulemap | 2 + .../session/config/base.hpp | 41 +- .../session/config/contacts.h | 6 +- .../session/config/contacts.hpp | 19 +- .../session/config/convo_info_volatile.h | 219 +++++++ .../session/config/convo_info_volatile.hpp | 380 +++++++++++ .../session/config/namespaces.hpp | 1 + .../session/config/profile_pic.hpp | 27 +- .../session/config/util.h | 16 + .../session/util.hpp | 17 + .../ClosedGroupControlMessage.swift | 4 +- .../SharedConfigMessage.swift | 10 + .../Messages/Message+Destination.swift | 2 +- .../Visible Messages/VisibleMessage.swift | 5 +- .../Open Groups/OpenGroupManager.swift | 12 +- .../Protos/Generated/SNProto.swift | 6 + .../Protos/Generated/SessionProtos.pb.swift | 8 + .../Protos/SessionProtos.proto | 2 + .../MessageReceiver+ClosedGroups.swift | 2 +- ...essageReceiver+ConfigurationMessages.swift | 2 +- .../MessageReceiver+ReadReceipts.swift | 2 +- .../MessageReceiver+UnsendRequests.swift | 8 +- .../MessageReceiver+VisibleMessages.swift | 19 +- .../MessageSender+ClosedGroups.swift | 6 +- .../Sending & Receiving/MessageReceiver.swift | 2 +- .../Sending & Receiving/MessageSender.swift | 2 +- .../Pollers/CurrentUserPoller.swift | 4 +- .../Typing Indicators/TypingIndicators.swift | 6 +- .../Shared Models/MessageViewModel.swift | 11 +- .../SessionThreadViewModel.swift | 14 +- .../NSENotificationPresenter.swift | 14 +- SessionSnodeKit/Types/SnodeAPINamespace.swift | 2 + .../ThreadSettingsViewModelSpec.swift | 4 +- .../Profile Pictures/ProfilePictureView.swift | 2 +- 67 files changed, 1716 insertions(+), 155 deletions(-) create mode 100644 SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift create mode 100644 SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Groups.swift create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.h create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.hpp create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/util.h diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 61968cf3f..f3aa34610 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -631,6 +631,8 @@ FD3C907127E445E500CD579F /* MessageReceiverDecryptionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C907027E445E500CD579F /* MessageReceiverDecryptionSpec.swift */; }; FD3E0C84283B5835002A425C /* SessionThreadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3E0C83283B5835002A425C /* SessionThreadViewModel.swift */; }; FD42F9A8285064B800A0C77D /* PushNotificationAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBDE255A581900E217F9 /* PushNotificationAPI.swift */; }; + FD43EE9D297A5190009C87C5 /* SessionUtil+Groups.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD43EE9C297A5190009C87C5 /* SessionUtil+Groups.swift */; }; + FD43EE9F297E2EE0009C87C5 /* SessionUtil+ConvoInfoVolatile.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD43EE9E297E2EE0009C87C5 /* SessionUtil+ConvoInfoVolatile.swift */; }; FD4B200E283492210034334B /* InsetLockableTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD4B200D283492210034334B /* InsetLockableTableView.swift */; }; FD52090028AF6153006098F6 /* OWSBackgroundTask.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDC1B255A581F00E217F9 /* OWSBackgroundTask.m */; }; FD52090128AF61BA006098F6 /* OWSBackgroundTask.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDB38255A580B00E217F9 /* OWSBackgroundTask.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -1751,6 +1753,8 @@ FD3C907027E445E500CD579F /* MessageReceiverDecryptionSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageReceiverDecryptionSpec.swift; sourceTree = ""; }; FD3C907427E83AC200CD579F /* OpenGroupServerIdLookup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupServerIdLookup.swift; sourceTree = ""; }; FD3E0C83283B5835002A425C /* SessionThreadViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionThreadViewModel.swift; sourceTree = ""; }; + FD43EE9C297A5190009C87C5 /* SessionUtil+Groups.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionUtil+Groups.swift"; sourceTree = ""; }; + FD43EE9E297E2EE0009C87C5 /* SessionUtil+ConvoInfoVolatile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionUtil+ConvoInfoVolatile.swift"; sourceTree = ""; }; FD4B200D283492210034334B /* InsetLockableTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsetLockableTableView.swift; sourceTree = ""; }; FD52090228B4680F006098F6 /* RadioButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButton.swift; sourceTree = ""; }; FD52090428B4915F006098F6 /* PrivacySettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacySettingsViewModel.swift; sourceTree = ""; }; @@ -4039,6 +4043,8 @@ children = ( FD8ECF8F29381FC200C0D1BB /* SessionUtil+UserProfile.swift */, FD2B4AFC294688D000AB4848 /* SessionUtil+Contacts.swift */, + FD43EE9E297E2EE0009C87C5 /* SessionUtil+ConvoInfoVolatile.swift */, + FD43EE9C297A5190009C87C5 /* SessionUtil+Groups.swift */, ); path = "Config Handling"; sourceTree = ""; @@ -5674,6 +5680,7 @@ C3C2A7852553AAF300C340D1 /* SessionProtos.pb.swift in Sources */, FDF0B74F28079E5E004C14C5 /* SendReadReceiptsJob.swift in Sources */, FDF0B7422804EA4F004C14C5 /* _002_SetupStandardJobs.swift in Sources */, + FD43EE9F297E2EE0009C87C5 /* SessionUtil+ConvoInfoVolatile.swift in Sources */, B8EB20EE2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift in Sources */, FDF8487F29405994007DCAE5 /* HTTPHeader+OpenGroup.swift in Sources */, FD8ECF7D2934293A00C0D1BB /* _011_SharedUtilChanges.swift in Sources */, @@ -5688,6 +5695,7 @@ FD245C692850666800B966DD /* ExpirationTimerUpdate.swift in Sources */, FD42F9A8285064B800A0C77D /* PushNotificationAPI.swift in Sources */, FD245C6C2850669200B966DD /* MessageReceiveJob.swift in Sources */, + FD43EE9D297A5190009C87C5 /* SessionUtil+Groups.swift in Sources */, FD83B9CC27D179BC005E1583 /* FSEndpoint.swift in Sources */, FD7115F228C6CB3900B47552 /* _010_AddThreadIdToFTS.swift in Sources */, FD716E6428502DDD00C96BF4 /* CallManagerProtocol.swift in Sources */, diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index 07b01a114..3c4901fc7 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -324,12 +324,20 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate { } }() - guard shouldMarkAsRead else { return } + guard + shouldMarkAsRead, + let threadVariant: SessionThread.Variant = try SessionThread + .filter(id: interaction.threadId) + .select(.variant) + .asRequest(of: SessionThread.Variant.self) + .fetchOne(db) + else { return } try Interaction.markAsRead( db, interactionId: interaction.id, threadId: interaction.threadId, + threadVariant: threadVariant, includingOlder: false, trySendReadReceipt: false ) diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index 49152e981..a568f4088 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -398,7 +398,8 @@ final class CallVC: UIViewController, VideoPreviewDelegate { view.addSubview(titleLabel) titleLabel.translatesAutoresizingMaskIntoConstraints = false titleLabel.center(.vertical, in: minimizeButton) - titleLabel.center(.horizontal, in: view) + titleLabel.pin(.left, to: .left, of: view, withInset: Values.largeSpacing) + titleLabel.pin(.right, to: .right, of: view, withInset: Values.largeSpacing) // Response Panel view.addSubview(responsePanel) diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 531d3bb7d..bfe97c1dc 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -1108,6 +1108,7 @@ extension ConversationVC: guard cellViewModel.reactionInfo?.isEmpty == false && ( + self.viewModel.threadData.threadVariant == .legacyClosedGroup || self.viewModel.threadData.threadVariant == .closedGroup || self.viewModel.threadData.threadVariant == .openGroup ), @@ -1797,7 +1798,7 @@ extension ConversationVC: self?.showInputAccessoryView() } - case .contact, .closedGroup: + case .contact, .legacyClosedGroup, .closedGroup: let serverHash: String? = Storage.shared.read { db -> String? in try Interaction .select(.serverHash) @@ -1856,7 +1857,7 @@ extension ConversationVC: }) actionSheet.addAction(UIAlertAction( - title: (cellViewModel.threadVariant == .closedGroup ? + title: (cellViewModel.threadVariant == .legacyClosedGroup || cellViewModel.threadVariant == .closedGroup ? "delete_message_for_everyone".localized() : String(format: "delete_message_for_me_and_recipient".localized(), threadName) ), diff --git a/Session/Conversations/ConversationViewModel.swift b/Session/Conversations/ConversationViewModel.swift index 113a6b148..9cf2ba174 100644 --- a/Session/Conversations/ConversationViewModel.swift +++ b/Session/Conversations/ConversationViewModel.swift @@ -105,7 +105,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { threadId: self.threadId, threadVariant: self.initialThreadVariant, threadIsNoteToSelf: (self.threadId == getUserHexEncodedPublicKey()), - currentUserIsClosedGroupMember: (self.initialThreadVariant != .closedGroup ? + currentUserIsClosedGroupMember: ((self.initialThreadVariant != .legacyClosedGroup && self.initialThreadVariant != .closedGroup) ? nil : Storage.shared.read { db in GroupMember @@ -406,6 +406,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { else { return } let threadId: String = self.threadData.threadId + let threadVariant: SessionThread.Variant = self.threadData.threadVariant let trySendReadReceipt: Bool = (self.threadData.threadIsMessageRequest == false) self.lastInteractionIdMarkedAsRead = targetInteractionId @@ -414,6 +415,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { db, interactionId: targetInteractionId, threadId: threadId, + threadVariant: threadVariant, includingOlder: true, trySendReadReceipt: trySendReadReceipt ) diff --git a/Session/Conversations/Message Cells/Content Views/QuoteView.swift b/Session/Conversations/Message Cells/Content Views/QuoteView.swift index 2ee2f9068..c3730fe65 100644 --- a/Session/Conversations/Message Cells/Content Views/QuoteView.swift +++ b/Session/Conversations/Message Cells/Content Views/QuoteView.swift @@ -269,7 +269,7 @@ final class QuoteView: UIView { contentView.addSubview(mainStackView) mainStackView.pin(to: contentView) - if threadVariant != .openGroup && threadVariant != .closedGroup { + if threadVariant == .contact { bodyLabel.set(.width, to: bodyLabelSize.width) } diff --git a/Session/Conversations/Message Cells/VisibleMessageCell.swift b/Session/Conversations/Message Cells/VisibleMessageCell.swift index 728a98d55..49f688752 100644 --- a/Session/Conversations/Message Cells/VisibleMessageCell.swift +++ b/Session/Conversations/Message Cells/VisibleMessageCell.swift @@ -305,7 +305,11 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { cellViewModel.isOnlyMessageInCluster ) ) - let isGroupThread: Bool = (cellViewModel.threadVariant == .openGroup || cellViewModel.threadVariant == .closedGroup) + let isGroupThread: Bool = ( + cellViewModel.threadVariant == .openGroup || + cellViewModel.threadVariant == .legacyClosedGroup || + cellViewModel.threadVariant == .closedGroup + ) // Profile picture view profilePictureViewLeadingConstraint.constant = (isGroupThread ? VisibleMessageCell.groupThreadHSpacing : 0) @@ -706,6 +710,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { maxWidth: maxWidth, showingAllReactions: showExpandedReactions, showNumbers: ( + cellViewModel.threadVariant == .legacyClosedGroup || cellViewModel.threadVariant == .closedGroup || cellViewModel.threadVariant == .openGroup ) @@ -1066,6 +1071,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { case .standardIncoming, .standardIncomingDeleted: let isGroupThread = ( cellViewModel.threadVariant == .openGroup || + cellViewModel.threadVariant == .legacyClosedGroup || cellViewModel.threadVariant == .closedGroup ) let leftGutterSize = (isGroupThread ? leftGutterSize : contactThreadHSpacing) diff --git a/Session/Conversations/Settings/ThreadSettingsViewModel.swift b/Session/Conversations/Settings/ThreadSettingsViewModel.swift index 8ecf5ef77..612bfb602 100644 --- a/Session/Conversations/Settings/ThreadSettingsViewModel.swift +++ b/Session/Conversations/Settings/ThreadSettingsViewModel.swift @@ -180,7 +180,7 @@ class ThreadSettingsViewModel: SessionTableViewModel (thread.mutedUntilTimestamp ?? 0) else { return } - guard thread.variant != .closedGroup && thread.variant != .openGroup else { return } + guard + thread.variant != .legacyClosedGroup && + thread.variant != .closedGroup && + thread.variant != .openGroup + else { return } guard interaction.variant == .infoCall, let infoMessageData: Data = (interaction.body ?? "").data(using: .utf8), @@ -342,7 +346,11 @@ public class NotificationPresenter: NSObject, NotificationsProtocol { // No reaction notifications for muted, group threads or message requests guard Date().timeIntervalSince1970 > (thread.mutedUntilTimestamp ?? 0) else { return } - guard thread.variant != .closedGroup && thread.variant != .openGroup else { return } + guard + thread.variant != .legacyClosedGroup && + thread.variant != .closedGroup && + thread.variant != .openGroup + else { return } guard !isMessageRequest else { return } let senderName: String = Profile.displayName(db, id: reaction.authorId, threadVariant: thread.variant) @@ -551,6 +559,7 @@ class NotificationActionHandler { db, interactionId: interaction.id, threadId: thread.id, + threadVariant: thread.variant, includingOlder: true, trySendReadReceipt: true ) @@ -607,6 +616,7 @@ class NotificationActionHandler { .asRequest(of: Int64.self) .fetchOne(db), threadId: thread.id, + threadVariant: thread.variant, includingOlder: true, trySendReadReceipt: true ) diff --git a/Session/Shared/FullConversationCell.swift b/Session/Shared/FullConversationCell.swift index e14d31b6a..01197d547 100644 --- a/Session/Shared/FullConversationCell.swift +++ b/Session/Shared/FullConversationCell.swift @@ -308,12 +308,12 @@ public final class FullConversationCell: UITableViewCell { switch cellViewModel.threadVariant { case .contact, .openGroup: bottomLabelStackView.isHidden = true - case .closedGroup: + case .legacyClosedGroup, .closedGroup: bottomLabelStackView.isHidden = (cellViewModel.threadMemberNames ?? "").isEmpty ThemeManager.onThemeChange(observer: displayNameLabel) { [weak self, weak snippetLabel] theme, _ in guard let textColor: UIColor = theme.color(for: .textPrimary) else { return } - if cellViewModel.threadVariant == .closedGroup { + if cellViewModel.threadVariant == .legacyClosedGroup || cellViewModel.threadVariant == .closedGroup { snippetLabel?.attributedText = self?.getHighlightedSnippet( content: (cellViewModel.threadMemberNames ?? ""), currentUserPublicKey: cellViewModel.currentUserPublicKey, @@ -354,8 +354,11 @@ public final class FullConversationCell: UITableViewCell { ofSize: (unreadCount < 10000 ? Values.verySmallFontSize : 8) ) hasMentionView.isHidden = !( - ((cellViewModel.threadUnreadMentionCount ?? 0) > 0) && - (cellViewModel.threadVariant == .closedGroup || cellViewModel.threadVariant == .openGroup) + ((cellViewModel.threadUnreadMentionCount ?? 0) > 0) && ( + cellViewModel.threadVariant == .legacyClosedGroup || + cellViewModel.threadVariant == .closedGroup || + cellViewModel.threadVariant == .openGroup + ) ) profilePictureView.update( publicKey: cellViewModel.threadId, @@ -454,7 +457,7 @@ public final class FullConversationCell: UITableViewCell { )) } - if cellViewModel.threadVariant == .closedGroup || cellViewModel.threadVariant == .openGroup { + if cellViewModel.threadVariant == .legacyClosedGroup || cellViewModel.threadVariant == .closedGroup || cellViewModel.threadVariant == .openGroup { let authorName: String = cellViewModel.authorName(for: cellViewModel.threadVariant) result.append(NSAttributedString( diff --git a/Session/Shared/SessionTableViewController.swift b/Session/Shared/SessionTableViewController.swift index 81669ce34..665a14db4 100644 --- a/Session/Shared/SessionTableViewController.swift +++ b/Session/Shared/SessionTableViewController.swift @@ -609,6 +609,7 @@ class SessionTableViewController? = try SessionUtil.loadState( + for: .convoInfoVolatile, + secretKey: secretKey, + cachedData: nil + ) + let convoInfoVolatileConfResult: SessionUtil.ConfResult = try SessionUtil.upsert( + convoInfoVolatileChanges: volatileThreadInfo, + in: Atomic(convoInfoVolatileConf) + ) + + if convoInfoVolatileConfResult.needsDump { + try SessionUtil + .createDump( + conf: contactsConf, + for: .convoInfoVolatile, + publicKey: userPublicKey, + messageHashes: nil + )? + .save(db) + } Storage.update(progress: 1, for: self, in: target) // In case this is the last migration } } diff --git a/SessionMessagingKit/Database/Models/DisappearingMessageConfiguration.swift b/SessionMessagingKit/Database/Models/DisappearingMessageConfiguration.swift index 30ccf219c..5c18b9417 100644 --- a/SessionMessagingKit/Database/Models/DisappearingMessageConfiguration.swift +++ b/SessionMessagingKit/Database/Models/DisappearingMessageConfiguration.swift @@ -17,11 +17,17 @@ public struct DisappearingMessagesConfiguration: Codable, Identifiable, Equatabl case durationSeconds } + public enum DisappearingMessageType: Int, Codable, Hashable, DatabaseValueConvertible { + case disappearAfterRead + case disappearAfterSend + } + public var id: String { threadId } // Identifiable public let threadId: String public let isEnabled: Bool public let durationSeconds: TimeInterval + public var type: DisappearingMessageType? { return nil } // TODO: Add as part of Disappearing Message Rebuild // MARK: - Relationships @@ -45,7 +51,8 @@ public extension DisappearingMessagesConfiguration { func with( isEnabled: Bool? = nil, - durationSeconds: TimeInterval? = nil + durationSeconds: TimeInterval? = nil, + type: DisappearingMessageType? = nil ) -> DisappearingMessagesConfiguration { return DisappearingMessagesConfiguration( threadId: threadId, diff --git a/SessionMessagingKit/Database/Models/Interaction.swift b/SessionMessagingKit/Database/Models/Interaction.swift index 83782c4b7..f90b7ae9d 100644 --- a/SessionMessagingKit/Database/Models/Interaction.swift +++ b/SessionMessagingKit/Database/Models/Interaction.swift @@ -85,6 +85,10 @@ public struct Interaction: Codable, Identifiable, Equatable, FetchableRecord, Mu // MARK: - Convenience + public static let variantsToIncrementUnreadCount: [Variant] = [ + .standardIncoming, .infoCall + ] + public var isInfoMessage: Bool { switch self { case .infoClosedGroupCreated, .infoClosedGroupUpdated, .infoClosedGroupCurrentUserLeft, @@ -349,7 +353,7 @@ public struct Interaction: Codable, Identifiable, Equatable, FetchableRecord, Mu state: .sending ).insert(db) - case .closedGroup: + case .legacyClosedGroup, .closedGroup: let closedGroupMemberIds: Set = (try? GroupMember .select(.profileId) .filter(GroupMember.Columns.groupId == threadId) @@ -445,13 +449,28 @@ public extension Interaction { _ db: Database, interactionId: Int64?, threadId: String, + threadVariant: SessionThread.Variant, includingOlder: Bool, trySendReadReceipt: Bool ) throws { guard let interactionId: Int64 = interactionId else { return } // Once all of the below is done schedule the jobs - func scheduleJobs(interactionIds: [Int64]) { + func scheduleJobs( + _ db: Database, + threadId: String, + threadVariant: SessionThread.Variant, + interactionIds: [Int64], + lastReadTimestampMs: Int64 + ) throws { + // Update the last read timestamp if needed + try SessionUtil.syncThreadLastReadIfNeeded( + db, + threadId: threadId, + threadVariant: threadVariant, + lastReadTimestampMs: lastReadTimestampMs + ) + // Add the 'DisappearingMessagesJob' if needed - this will update any expiring // messages `expiresStartedAtMs` values JobRunner.upsert( @@ -510,13 +529,22 @@ public extension Interaction { guard includingOlder, let interactionInfo: InteractionReadInfo = maybeInteractionInfo else { // Only mark as read and trigger the subsequent jobs if the interaction is // actually not read (no point updating and triggering db changes otherwise) - guard maybeInteractionInfo?.wasRead == false else { return } + guard + maybeInteractionInfo?.wasRead == false, + let timestampMs: Int64 = maybeInteractionInfo?.timestampMs + else { return } _ = try Interaction .filter(id: interactionId) .updateAll(db, Columns.wasRead.set(to: true)) - scheduleJobs(interactionIds: [interactionId]) + try scheduleJobs( + db, + threadId: threadId, + threadVariant: threadVariant, + interactionIds: [interactionId], + lastReadTimestampMs: timestampMs + ) return } @@ -533,7 +561,13 @@ public extension Interaction { // for this interaction (need to ensure the disapeparing messages run for sync'ed // outgoing messages which will always have 'wasRead' as false) guard !interactionIdsToMarkAsRead.isEmpty else { - scheduleJobs(interactionIds: [interactionId]) + try scheduleJobs( + db, + threadId: threadId, + threadVariant: threadVariant, + interactionIds: [interactionId], + lastReadTimestampMs: interactionInfo.timestampMs + ) return } @@ -541,13 +575,19 @@ public extension Interaction { try interactionQuery.updateAll(db, Columns.wasRead.set(to: true)) // Retrieve the interaction ids we want to update - scheduleJobs(interactionIds: interactionIdsToMarkAsRead) + try scheduleJobs( + db, + threadId: threadId, + threadVariant: threadVariant, + interactionIds: interactionIdsToMarkAsRead, + lastReadTimestampMs: interactionInfo.timestampMs + ) } /// This method flags sent messages as read for the specified recipients /// /// **Note:** This method won't update the 'wasRead' flag (it will be updated via the above method) - static func markAsRead(_ db: Database, recipientId: String, timestampMsValues: [Double], readTimestampMs: Double) throws { + static func markAsRecipientRead(_ db: Database, recipientId: String, timestampMsValues: [Double], readTimestampMs: Double) throws { guard db[.areReadReceiptsEnabled] == true else { return } try RecipientState diff --git a/SessionMessagingKit/Database/Models/Profile.swift b/SessionMessagingKit/Database/Models/Profile.swift index 533c3657f..737d78cd6 100644 --- a/SessionMessagingKit/Database/Models/Profile.swift +++ b/SessionMessagingKit/Database/Models/Profile.swift @@ -324,7 +324,7 @@ public extension Profile { } switch threadVariant { - case .contact, .closedGroup: return name + case .contact, .legacyClosedGroup, .closedGroup: return name case .openGroup: // In open groups, where it's more likely that multiple users have the same name, diff --git a/SessionMessagingKit/Database/Models/SessionThread.swift b/SessionMessagingKit/Database/Models/SessionThread.swift index ca08aa182..b5ddd28be 100644 --- a/SessionMessagingKit/Database/Models/SessionThread.swift +++ b/SessionMessagingKit/Database/Models/SessionThread.swift @@ -32,12 +32,14 @@ public struct SessionThread: Codable, Identifiable, Equatable, FetchableRecord, case notificationSound case mutedUntilTimestamp case onlyNotifyForMentions + case markedAsUnread } public enum Variant: Int, Codable, Hashable, DatabaseValueConvertible { case contact - case closedGroup + case legacyClosedGroup case openGroup + case closedGroup } /// Unique identifier for a thread (formerly known as uniqueId) @@ -74,6 +76,9 @@ public struct SessionThread: Codable, Identifiable, Equatable, FetchableRecord, /// A flag indicating whether the thread should only notify for mentions public let onlyNotifyForMentions: Bool + /// A flag indicating whether this thread has been manually marked as unread by the user + public let markedAsUnread: Bool? + // MARK: - Relationships public var contact: QueryInterfaceRequest { @@ -111,7 +116,8 @@ public struct SessionThread: Codable, Identifiable, Equatable, FetchableRecord, messageDraft: String? = nil, notificationSound: Preferences.Sound? = nil, mutedUntilTimestamp: TimeInterval? = nil, - onlyNotifyForMentions: Bool = false + onlyNotifyForMentions: Bool = false, + markedAsUnread: Bool? = false ) { self.id = id self.variant = variant @@ -122,6 +128,7 @@ public struct SessionThread: Codable, Identifiable, Equatable, FetchableRecord, self.notificationSound = notificationSound self.mutedUntilTimestamp = mutedUntilTimestamp self.onlyNotifyForMentions = onlyNotifyForMentions + self.markedAsUnread = markedAsUnread } // MARK: - Custom Database Interaction @@ -147,7 +154,8 @@ public extension SessionThread { messageDraft: messageDraft, notificationSound: notificationSound, mutedUntilTimestamp: mutedUntilTimestamp, - onlyNotifyForMentions: onlyNotifyForMentions + onlyNotifyForMentions: onlyNotifyForMentions, + markedAsUnread: markedAsUnread ) } } @@ -304,7 +312,7 @@ public extension SessionThread { profile: Profile? = nil ) -> String { switch variant { - case .closedGroup: return (closedGroupName ?? "Unknown Group") + case .legacyClosedGroup, .closedGroup: return (closedGroupName ?? "Unknown Group") case .openGroup: return (openGroupName ?? "Unknown Group") case .contact: guard !isNoteToSelf else { return "NOTE_TO_SELF".localized() } diff --git a/SessionMessagingKit/Database/Models/SharedConfigDump.swift b/SessionMessagingKit/Database/Models/SharedConfigDump.swift index 90b92befb..5b424eb8d 100644 --- a/SessionMessagingKit/Database/Models/SharedConfigDump.swift +++ b/SessionMessagingKit/Database/Models/SharedConfigDump.swift @@ -19,6 +19,8 @@ public struct ConfigDump: Codable, Equatable, Hashable, FetchableRecord, Persist public enum Variant: String, Codable, DatabaseValueConvertible { case userProfile case contacts + case convoInfoVolatile + case groups } /// The type of config this dump is for @@ -64,12 +66,14 @@ public extension ConfigDump { } public extension ConfigDump.Variant { - static let userVariants: [ConfigDump.Variant] = [ .userProfile, .contacts ] + static let userVariants: [ConfigDump.Variant] = [ .userProfile, .contacts, .convoInfoVolatile, .groups ] var configMessageKind: SharedConfigMessage.Kind { switch self { case .userProfile: return .userProfile case .contacts: return .contacts + case .convoInfoVolatile: return .convoInfoVolatile + case .groups: return .groups } } @@ -77,6 +81,8 @@ public extension ConfigDump.Variant { switch self { case .userProfile: return SnodeAPI.Namespace.configUserProfile case .contacts: return SnodeAPI.Namespace.configContacts + case .convoInfoVolatile: return SnodeAPI.Namespace.configConvoInfoVolatile + case .groups: return SnodeAPI.Namespace.configGroups } } } diff --git a/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift b/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift index 020aa27fc..0bbbc6bc8 100644 --- a/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift +++ b/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift @@ -120,6 +120,7 @@ public extension SendReadReceiptsJob { .joining( // Don't send read receipts in group threads required: Interaction.thread + .filter(SessionThread.Columns.variant != SessionThread.Variant.legacyClosedGroup) .filter(SessionThread.Columns.variant != SessionThread.Variant.closedGroup) .filter(SessionThread.Columns.variant != SessionThread.Variant.openGroup) ) diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift index 703d6fe63..b9cf7951f 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift @@ -11,11 +11,17 @@ internal extension SessionUtil { static func handleContactsUpdate( _ db: Database, in atomicConf: Atomic?>, - needsDump: Bool - ) throws { - typealias ContactData = [String: (contact: Contact, profile: Profile)] + mergeResult: ConfResult + ) throws -> ConfResult { + typealias ContactData = [ + String: ( + contact: Contact, + profile: Profile, + isHiddenConversation: Bool + ) + ] - guard needsDump else { return } + guard mergeResult.needsDump else { return mergeResult } guard atomicConf.wrappedValue != nil else { throw SessionUtilError.nilConfigObject } // Since we are doing direct memory manipulation we are using an `Atomic` type which has @@ -47,7 +53,11 @@ internal extension SessionUtil { ) ) - contactData[contactId] = (contactResult, profileResult) + contactData[contactId] = ( + contactResult, + profileResult, + false + ) contacts_iterator_advance(contactIterator) } contacts_iterator_free(contactIterator) // Need to free the iterator @@ -61,7 +71,7 @@ internal extension SessionUtil { let targetContactData: ContactData = contactData.filter { $0.key != userPublicKey } // If we only updated the current user contact then no need to continue - guard !targetContactData.isEmpty else { return } + guard !targetContactData.isEmpty else { return mergeResult } // Since we don't sync 100% of the data stored against the contact and profile objects we // need to only update the data we do have to ensure we don't overwrite anything that doesn't @@ -107,8 +117,8 @@ internal extension SessionUtil { /// swapping `isApproved` and `didApproveMe` to `false` if (contact.isApproved != data.contact.isApproved) || - (contact.isBlocked != data.contact.isBlocked) || - (contact.didApproveMe != data.contact.didApproveMe) + (contact.isBlocked != data.contact.isBlocked) || + (contact.didApproveMe != data.contact.didApproveMe) { try contact.save(db) try Contact @@ -116,17 +126,21 @@ internal extension SessionUtil { .updateAll( // Handling a config update so don't use `updateAllAndConfig` db, [ - (!data.contact.isApproved ? nil : + (!data.contact.isApproved || contact.isApproved == data.contact.isApproved ? nil : Contact.Columns.isApproved.set(to: true) ), - Contact.Columns.isBlocked.set(to: data.contact.isBlocked), - (!data.contact.didApproveMe ? nil : + (contact.isBlocked == data.contact.isBlocked ? nil : + Contact.Columns.isBlocked.set(to: data.contact.isBlocked) + ), + (!data.contact.didApproveMe || contact.didApproveMe == data.contact.didApproveMe ? nil : Contact.Columns.didApproveMe.set(to: true) ) ].compactMap { $0 } ) } } + + return mergeResult } // MARK: - Outgoing Changes diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift new file mode 100644 index 000000000..8119f3b24 --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift @@ -0,0 +1,614 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import GRDB +import SessionUtil +import SessionUtilitiesKit + +internal extension SessionUtil { + // MARK: - Incoming Changes + + static func handleConvoInfoVolatileUpdate( + _ db: Database, + in atomicConf: Atomic?>, + mergeResult: ConfResult + ) throws -> ConfResult { + guard mergeResult.needsDump else { return mergeResult } + guard atomicConf.wrappedValue != nil else { throw SessionUtilError.nilConfigObject } + + // Since we are doing direct memory manipulation we are using an `Atomic` type which has + // blocking access in it's `mutate` closure + let volatileThreadInfo: [VolatileThreadInfo] = atomicConf.mutate { conf -> [VolatileThreadInfo] in + var volatileThreadInfo: [VolatileThreadInfo] = [] + var oneToOne: convo_info_volatile_1to1 = convo_info_volatile_1to1() + var openGroup: convo_info_volatile_open = convo_info_volatile_open() + var legacyClosedGroup: convo_info_volatile_legacy_closed = convo_info_volatile_legacy_closed() + let convoIterator: OpaquePointer = convo_info_volatile_iterator_new(conf) + + while !convo_info_volatile_iterator_done(convoIterator) { + if convo_info_volatile_it_is_1to1(convoIterator, &oneToOne) { + let sessionId: String = String(cString: withUnsafeBytes(of: oneToOne.session_id) { [UInt8]($0) } + .map { CChar($0) } + .nullTerminated() + ) + + volatileThreadInfo.append( + VolatileThreadInfo( + threadId: sessionId, + variant: .contact, + changes: [ + .markedAsUnread(oneToOne.unread), + .lastReadTimestampMs(oneToOne.last_read) + ] + ) + ) + } + else if convo_info_volatile_it_is_open(convoIterator, &openGroup) { + let server: String = String(cString: withUnsafeBytes(of: openGroup.base_url) { [UInt8]($0) } + .map { CChar($0) } + .nullTerminated() + ) + let roomToken: String = String(cString: withUnsafeBytes(of: openGroup.room) { [UInt8]($0) } + .map { CChar($0) } + .nullTerminated() + ) + let publicKey: String = String(cString: withUnsafeBytes(of: openGroup.pubkey) { [UInt8]($0) } + .map { CChar($0) } + .nullTerminated() + ) + + // Note: A normal 'openGroupId' isn't lowercased but the volatile conversation + // info will always be lowercase so we force everything to lowercase to simplify + // the code + volatileThreadInfo.append( + VolatileThreadInfo( + threadId: OpenGroup.idFor(roomToken: roomToken, server: server), + variant: .openGroup, + openGroupUrlInfo: VolatileThreadInfo.OpenGroupUrlInfo( + threadId: OpenGroup.idFor(roomToken: roomToken, server: server), + server: server, + roomToken: roomToken, + publicKey: publicKey + ), + changes: [ + .markedAsUnread(openGroup.unread), + .lastReadTimestampMs(openGroup.last_read) + ] + ) + ) + } + else if convo_info_volatile_it_is_legacy_closed(convoIterator, &legacyClosedGroup) { + let groupId: String = String(cString: withUnsafeBytes(of: legacyClosedGroup.group_id) { [UInt8]($0) } + .map { CChar($0) } + .nullTerminated() + ) + + volatileThreadInfo.append( + VolatileThreadInfo( + threadId: groupId, + variant: .legacyClosedGroup, + changes: [ + .markedAsUnread(legacyClosedGroup.unread), + .lastReadTimestampMs(legacyClosedGroup.last_read) + ] + ) + ) + } + else { + SNLog("Ignoring unknown conversation type when iterating through volatile conversation info update") + } + + convo_info_volatile_iterator_advance(convoIterator) + } + convo_info_volatile_iterator_free(convoIterator) // Need to free the iterator + + return volatileThreadInfo + } + + // If we don't have any conversations then no need to continue + guard !volatileThreadInfo.isEmpty else { return mergeResult } + + // Get the local volatile thread info from all conversations + let localVolatileThreadInfo: [String: VolatileThreadInfo] = VolatileThreadInfo.fetchAll(db) + .reduce(into: [:]) { result, next in result[next.threadId] = next } + + // Map the volatileThreadInfo, upserting any changes and returning a list of local changes + // which should override any synced changes (eg. 'lastReadTimestampMs') + let newerLocalChanges: [VolatileThreadInfo] = try volatileThreadInfo + .compactMap { threadInfo -> VolatileThreadInfo? in + // Fetch the "proper" threadId (we need the correct casing for updating the database) + guard + let threadId: String = try? SessionThread + .select(.id) + .filter(SessionThread.Columns.id.lowercased == threadInfo.threadId) + .asRequest(of: String.self) + .fetchOne(db) + else { return nil } + + + // Get the existing local state for the thread + let localThreadInfo: VolatileThreadInfo? = localVolatileThreadInfo[threadId] + + // Update the thread 'markedAsUnread' state + if + let markedAsUnread: Bool = threadInfo.changes.markedAsUnread, + markedAsUnread != (localThreadInfo?.changes.markedAsUnread ?? false) + { + try SessionThread + .filter(id: threadId) + .updateAll( // Handling a config update so don't use `updateAllAndConfig` + db, + SessionThread.Columns.markedAsUnread.set(to: markedAsUnread) + ) + } + + // If the device has a more recent read interaction then return the info so we can + // update the cached config state accordingly + guard + let lastReadTimestampMs: Int64 = threadInfo.changes.lastReadTimestampMs, + lastReadTimestampMs > (localThreadInfo?.changes.lastReadTimestampMs ?? 0) + else { + // We only want to return the 'lastReadTimestampMs' change, since the local state + // should win in that case, so ignore all others + return localThreadInfo? + .filterChanges { change in + switch change { + case .lastReadTimestampMs: return true + default: return false + } + } + } + + // Mark all older interactions as read + try Interaction + .filter( + Interaction.Columns.threadId == threadId && + Interaction.Columns.timestampMs <= lastReadTimestampMs + ) + .updateAll( // Handling a config update so don't use `updateAllAndConfig` + db, + Interaction.Columns.wasRead.set(to: true) + ) + return nil + } + + // If there are no newer local last read timestamps then just return the mergeResult + guard !newerLocalChanges.isEmpty else { return mergeResult } + + return try upsert( + convoInfoVolatileChanges: newerLocalChanges, + in: atomicConf + ) + } + + static func upsert( + convoInfoVolatileChanges: [VolatileThreadInfo], + in atomicConf: Atomic?> + ) throws -> ConfResult { + // Since we are doing direct memory manipulation we are using an `Atomic` type which has + // blocking access in it's `mutate` closure + return atomicConf.mutate { conf in + convoInfoVolatileChanges.forEach { threadInfo in + var cThreadId: [CChar] = threadInfo.cThreadId + + switch threadInfo.variant { + case .contact: + var oneToOne: convo_info_volatile_1to1 = convo_info_volatile_1to1() + + guard convo_info_volatile_get_or_construct_1to1(conf, &oneToOne, &cThreadId) else { + SNLog("Unable to create contact conversation when updating last read timestamp") + return + } + + threadInfo.changes.forEach { change in + switch change { + case .lastReadTimestampMs(let lastReadMs): + oneToOne.last_read = lastReadMs + + case .markedAsUnread(let unread): + oneToOne.unread = unread + } + } + convo_info_volatile_set_1to1(conf, &oneToOne) + + case .legacyClosedGroup: + var legacyClosedGroup: convo_info_volatile_legacy_closed = convo_info_volatile_legacy_closed() + + guard convo_info_volatile_get_or_construct_legacy_closed(conf, &legacyClosedGroup, &cThreadId) else { + SNLog("Unable to create legacy group conversation when updating last read timestamp") + return + } + + threadInfo.changes.forEach { change in + switch change { + case .lastReadTimestampMs(let lastReadMs): + legacyClosedGroup.last_read = lastReadMs + + case .markedAsUnread(let unread): + legacyClosedGroup.unread = unread + } + } + convo_info_volatile_set_legacy_closed(conf, &legacyClosedGroup) + + case .openGroup: + guard + var cBaseUrl: [CChar] = threadInfo.cBaseUrl, + var cRoomToken: [CChar] = threadInfo.cRoomToken, + var cPubkey: [CChar] = threadInfo.cPubkey + else { + SNLog("Unable to create community conversation when updating last read timestamp due to missing URL info") + return + } + + var openGroup: convo_info_volatile_open = convo_info_volatile_open() + + guard convo_info_volatile_get_or_construct_open(conf, &openGroup, &cBaseUrl, &cRoomToken, &cPubkey) else { + SNLog("Unable to create legacy group conversation when updating last read timestamp") + return + } + + threadInfo.changes.forEach { change in + switch change { + case .lastReadTimestampMs(let lastReadMs): + openGroup.last_read = lastReadMs + + case .markedAsUnread(let unread): + openGroup.unread = unread + } + } + convo_info_volatile_set_open(conf, &openGroup) + + case .closedGroup: return // TODO: Need to add when the type is added to the lib + } + } + + return ConfResult( + needsPush: config_needs_push(conf), + needsDump: config_needs_dump(conf) + ) + } + } +} + +// MARK: - Convenience + +internal extension SessionUtil { + static func updatingThreads(_ db: Database, _ updated: [T]) throws -> [T] { + guard let updatedThreads: [SessionThread] = updated as? [SessionThread] else { + throw StorageError.generic + } + + // If we have no updated threads then no need to continue + guard !updatedThreads.isEmpty else { return updated } + + let userPublicKey: String = getUserHexEncodedPublicKey(db) + let changes: [VolatileThreadInfo] = try updatedThreads.map { thread in + VolatileThreadInfo( + threadId: thread.id, + variant: thread.variant, + openGroupUrlInfo: (thread.variant != .openGroup ? nil : + try VolatileThreadInfo.OpenGroupUrlInfo.fetchOne(db, id: thread.id) + ), + changes: [.markedAsUnread(thread.markedAsUnread ?? false)] + ) + } + + db.afterNextTransaction { db in + do { + let atomicConf: Atomic?> = SessionUtil.config( + for: .convoInfoVolatile, + publicKey: userPublicKey + ) + let result: ConfResult = try upsert( + convoInfoVolatileChanges: changes, + in: atomicConf + ) + + // If we don't need to dump the data the we can finish early + guard result.needsDump else { return } + + try SessionUtil.saveState( + db, + keepingExistingMessageHashes: true, + configDump: try atomicConf.mutate { conf in + try SessionUtil.createDump( + conf: conf, + for: .convoInfoVolatile, + publicKey: userPublicKey, + messageHashes: nil + ) + } + ) + } + catch { + SNLog("[libSession-util] Failed to dump updated data") + } + } + + return updated + } + + static func syncThreadLastReadIfNeeded( + _ db: Database, + threadId: String, + threadVariant: SessionThread.Variant, + lastReadTimestampMs: Int64 + ) throws { + let userPublicKey: String = getUserHexEncodedPublicKey(db) + let atomicConf: Atomic?> = SessionUtil.config( + for: .convoInfoVolatile, + publicKey: userPublicKey + ) + let change: VolatileThreadInfo = VolatileThreadInfo( + threadId: threadId, + variant: threadVariant, + openGroupUrlInfo: (threadVariant != .openGroup ? nil : + try VolatileThreadInfo.OpenGroupUrlInfo.fetchOne(db, id: threadId) + ), + changes: [.lastReadTimestampMs(lastReadTimestampMs)] + ) + + // Update the conf + let result: ConfResult = try upsert( + convoInfoVolatileChanges: [change], + in: atomicConf + ) + + // If we need to dump then do so here + if result.needsDump { + try SessionUtil.saveState( + db, + keepingExistingMessageHashes: true, + configDump: try atomicConf.mutate { conf in + try SessionUtil.createDump( + conf: conf, + for: .contacts, + publicKey: userPublicKey, + messageHashes: nil + ) + } + ) + } + + // If we need to push then enqueue a 'ConfigurationSyncJob' + if result.needsPush { + ConfigurationSyncJob.enqueue(db) + } + } + + static func timestampAlreadyRead( + threadId: String, + threadVariant: SessionThread.Variant, + timestampMs: Int64, + userPublicKey: String, + openGroup: OpenGroup? + ) -> Bool { + let atomicConf: Atomic?> = SessionUtil.config( + for: .convoInfoVolatile, + publicKey: userPublicKey + ) + + // If we don't have a config then just assume it's unread + guard atomicConf.wrappedValue != nil else { return false } + + // Since we are doing direct memory manipulation we are using an `Atomic` type which has + // blocking access in it's `mutate` closure + return atomicConf.mutate { conf in + switch threadVariant { + case .contact: + var cThreadId: [CChar] = threadId + .bytes + .map { CChar(bitPattern: $0) } + var oneToOne: convo_info_volatile_1to1 = convo_info_volatile_1to1() + guard convo_info_volatile_get_1to1(conf, &oneToOne, &cThreadId) else { return false } + + return (oneToOne.last_read > timestampMs) + + case .legacyClosedGroup: + var cThreadId: [CChar] = threadId + .bytes + .map { CChar(bitPattern: $0) } + var legacyClosedGroup: convo_info_volatile_legacy_closed = convo_info_volatile_legacy_closed() + + guard convo_info_volatile_get_legacy_closed(conf, &legacyClosedGroup, &cThreadId) else { + return false + } + + return (legacyClosedGroup.last_read > timestampMs) + + case .openGroup: + guard let openGroup: OpenGroup = openGroup else { return false } + + var cBaseUrl: [CChar] = openGroup.server + .bytes + .map { CChar(bitPattern: $0) } + var cRoomToken: [CChar] = openGroup.roomToken + .bytes + .map { CChar(bitPattern: $0) } + var cPubKey: [CChar] = openGroup.publicKey + .bytes + .map { CChar(bitPattern: $0) } + var convoOpenGroup: convo_info_volatile_open = convo_info_volatile_open() + + guard convo_info_volatile_get_open(conf, &convoOpenGroup, &cBaseUrl, &cRoomToken, &cPubKey) else { + return false + } + + return (convoOpenGroup.last_read > timestampMs) + + case .closedGroup: return false // TODO: Need to add when the type is added to the lib + } + } + } +} + +// MARK: - VolatileThreadInfo + +public extension SessionUtil { + struct VolatileThreadInfo { + enum Change { + case markedAsUnread(Bool) + case lastReadTimestampMs(Int64) + } + + fileprivate struct OpenGroupUrlInfo: FetchableRecord, Codable, Hashable { + let threadId: String + let server: String + let roomToken: String + let publicKey: String + + static func fetchOne(_ db: Database, id: String) throws -> OpenGroupUrlInfo? { + return try OpenGroup + .filter(id: id) + .select(.threadId, .server, .roomToken, .publicKey) + .asRequest(of: OpenGroupUrlInfo.self) + .fetchOne(db) + } + } + + let threadId: String + let variant: SessionThread.Variant + private let openGroupUrlInfo: OpenGroupUrlInfo? + let changes: [Change] + + var cThreadId: [CChar] { + threadId.bytes.map { CChar(bitPattern: $0) } + } + var cBaseUrl: [CChar]? { + (openGroupUrlInfo?.server).map { + $0.bytes.map { CChar(bitPattern: $0) } + } + } + var cRoomToken: [CChar]? { + (openGroupUrlInfo?.roomToken).map { + $0.bytes.map { CChar(bitPattern: $0) } + } + } + var cPubkey: [CChar]? { + (openGroupUrlInfo?.publicKey).map { + $0.bytes.map { CChar(bitPattern: $0) } + } + } + + fileprivate init( + threadId: String, + variant: SessionThread.Variant, + openGroupUrlInfo: OpenGroupUrlInfo? = nil, + changes: [Change] + ) { + self.threadId = threadId + self.variant = variant + self.openGroupUrlInfo = openGroupUrlInfo + self.changes = changes + } + + // MARK: - Convenience + + func filterChanges(isIncluded: (Change) -> Bool) -> VolatileThreadInfo { + return VolatileThreadInfo( + threadId: threadId, + variant: variant, + openGroupUrlInfo: openGroupUrlInfo, + changes: changes.filter(isIncluded) + ) + } + + static func fetchAll(_ db: Database? = nil, ids: [String]? = nil) -> [VolatileThreadInfo] { + guard let db: Database = db else { + return Storage.shared + .read { db in fetchAll(db, ids: ids) } + .defaulting(to: []) + } + + struct FetchedInfo: FetchableRecord, Codable, Hashable { + let id: String + let variant: SessionThread.Variant + let markedAsUnread: Bool? + let timestampMs: Int64? + let server: String? + let roomToken: String? + let publicKey: String? + } + + let thread: TypedTableAlias = TypedTableAlias() + let interaction: TypedTableAlias = TypedTableAlias() + let openGroup: TypedTableAlias = TypedTableAlias() + let request: SQLRequest = """ + SELECT + \(thread[.id]), + \(thread[.variant]), + \(thread[.markedAsUnread]), + MAX(\(interaction[.timestampMs])), + \(openGroup[.server]), + \(openGroup[.roomToken]), + \(openGroup[.publicKey]) + + FROM \(SessionThread.self) + LEFT JOIN \(Interaction.self) ON ( + \(interaction[.threadId]) = \(thread[.id]) AND + \(interaction[.wasRead]) = true AND + -- Note: Due to the complexity of how call messages are handled and the short + -- duration they exist in the swarm, we have decided to exclude trying to + -- include them when syncing the read status of conversations (they are also + -- implemented differently between platforms so including them could be a + -- significant amount of work) + \(SQL("\(interaction[.variant]) IN \(Interaction.Variant.variantsToIncrementUnreadCount.filter { $0 != .infoCall })")) + ) + LEFT JOIN \(OpenGroup.self) ON \(openGroup[.threadId]) = \(thread[.id]) + \(ids == nil ? SQL("") : + "WHERE \(SQL("\(thread[.id]) IN \(ids ?? [])"))" + ) + """ + + return ((try? request.fetchAll(db)) ?? []) + .map { threadInfo in + VolatileThreadInfo( + threadId: threadInfo.id, + variant: threadInfo.variant, + openGroupUrlInfo: { + guard + let server: String = threadInfo.server, + let roomToken: String = threadInfo.roomToken, + let publicKey: String = threadInfo.publicKey + else { return nil } + + return VolatileThreadInfo.OpenGroupUrlInfo( + threadId: threadInfo.id, + server: server, + roomToken: roomToken, + publicKey: publicKey + ) + }(), + changes: [ + .markedAsUnread(threadInfo.markedAsUnread ?? false), + .lastReadTimestampMs(threadInfo.timestampMs ?? 0) + ] + ) + } + } + } +} + +fileprivate extension [SessionUtil.VolatileThreadInfo.Change] { + var markedAsUnread: Bool? { + for change in self { + switch change { + case .markedAsUnread(let value): return value + default: continue + } + } + + return nil + } + + var lastReadTimestampMs: Int64? { + for change in self { + switch change { + case .lastReadTimestampMs(let value): return value + default: continue + } + } + + return nil + } +} diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Groups.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Groups.swift new file mode 100644 index 000000000..76cbbcd6f --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Groups.swift @@ -0,0 +1,19 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import GRDB +import SessionUtil +import SessionUtilitiesKit + +internal extension SessionUtil { + // MARK: - Incoming Changes + + static func handleGroupsUpdate( + _ db: Database, + in atomicConf: Atomic?>, + mergeResult: ConfResult + ) throws -> ConfResult { + // TODO: This + return mergeResult + } +} diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift index d89132c58..8b21c6372 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift @@ -11,12 +11,12 @@ internal extension SessionUtil { static func handleUserProfileUpdate( _ db: Database, in atomicConf: Atomic?>, - needsDump: Bool, + mergeResult: ConfResult, latestConfigUpdateSentTimestamp: TimeInterval - ) throws { + ) throws -> ConfResult { typealias ProfileData = (profileName: String, profilePictureUrl: String?, profilePictureKey: Data?) - guard needsDump else { return } + guard mergeResult.needsDump else { return mergeResult } guard atomicConf.wrappedValue != nil else { throw SessionUtilError.nilConfigObject } let userPublicKey: String = getUserHexEncodedPublicKey(db) @@ -52,7 +52,7 @@ internal extension SessionUtil { } // Only save the data in the database if it's valid - guard let profileData: ProfileData = maybeProfileData else { return } + guard let profileData: ProfileData = maybeProfileData else { return mergeResult } // Handle user profile changes try ProfileManager.updateProfileIfNeeded( @@ -90,6 +90,8 @@ internal extension SessionUtil { Contact.Columns.didApproveMe.set(to: true) ) } + + return mergeResult } // MARK: - Outgoing Changes diff --git a/SessionMessagingKit/LibSessionUtil/Database/QueryInterfaceRequest+Utilities.swift b/SessionMessagingKit/LibSessionUtil/Database/QueryInterfaceRequest+Utilities.swift index e186e0027..0c4b7acdb 100644 --- a/SessionMessagingKit/LibSessionUtil/Database/QueryInterfaceRequest+Utilities.swift +++ b/SessionMessagingKit/LibSessionUtil/Database/QueryInterfaceRequest+Utilities.swift @@ -26,6 +26,10 @@ public extension QueryInterfaceRequest { case let profileRequest as QueryInterfaceRequest: return try profileRequest.updateAndFetchAllAndUpdateConfig(db, assignments).count + + case let threadRequest as QueryInterfaceRequest: + return try threadRequest.updateAndFetchAllAndUpdateConfig(db, assignments).count + default: return try self.updateAll(db, assignments) } @@ -73,6 +77,9 @@ public extension QueryInterfaceRequest where RowDecoder: FetchableRecord & Table case is QueryInterfaceRequest: return try SessionUtil.updatingProfiles(db, try updateAndFetchAll(db, assignments)) + case is QueryInterfaceRequest: + return try SessionUtil.updatingThreads(db, try updateAndFetchAll(db, assignments)) + default: return try self.updateAndFetchAll(db, assignments) } } diff --git a/SessionMessagingKit/LibSessionUtil/SessionUtil.swift b/SessionMessagingKit/LibSessionUtil/SessionUtil.swift index 6d95d9a5a..44ba49187 100644 --- a/SessionMessagingKit/LibSessionUtil/SessionUtil.swift +++ b/SessionMessagingKit/LibSessionUtil/SessionUtil.swift @@ -17,6 +17,8 @@ public enum SessionUtil { let needsDump: Bool let messageHashes: [String] let latestSentTimestamp: TimeInterval + + var result: ConfResult { ConfResult(needsPush: needsPush, needsDump: needsDump) } } public struct OutgoingConfResult { @@ -46,7 +48,11 @@ public enum SessionUtil { public static var needsSync: Bool { return configStore .wrappedValue - .contains { _, atomicConf in config_needs_push(atomicConf.wrappedValue) } + .contains { _, atomicConf in + guard atomicConf.wrappedValue != nil else { return false } + + return config_needs_push(atomicConf.wrappedValue) + } } // MARK: - Loading @@ -121,9 +127,12 @@ public enum SessionUtil { switch variant { case .userProfile: return user_profile_init(&conf, &secretKey, cachedDump?.data, (cachedDump?.length ?? 0), error) - + case .contacts: return contacts_init(&conf, &secretKey, cachedDump?.data, (cachedDump?.length ?? 0), error) + + case .convoInfoVolatile: + return convo_info_volatile_init(&conf, &secretKey, cachedDump?.data, (cachedDump?.length ?? 0), error) } }() @@ -229,7 +238,10 @@ public enum SessionUtil { ) // Check if the config needs to be pushed - guard config_needs_push(atomicConf.wrappedValue) else { return nil } + guard + atomicConf.wrappedValue != nil && + config_needs_push(atomicConf.wrappedValue) + else { return nil } var toPush: UnsafeMutablePointer? = nil var toPushLen: Int = 0 @@ -266,6 +278,8 @@ public enum SessionUtil { Atomic(nil) ) + guard atomicConf.wrappedValue != nil else { return false } + // Mark the config as pushed config_confirm_pushed(atomicConf.wrappedValue, message.seqNo) @@ -289,7 +303,7 @@ public enum SessionUtil { .grouped(by: \.kind) // Merge the config messages into the current state - let results: [ConfigDump.Variant: IncomingConfResult] = groupedMessages + let mergeResults: [ConfigDump.Variant: IncomingConfResult] = groupedMessages .reduce(into: [:]) { result, next in let key: ConfigKey = ConfigKey(variant: next.key.configDumpVariant, publicKey: publicKey) let atomicConf: Atomic?> = ( @@ -327,28 +341,43 @@ public enum SessionUtil { } // Process the results from the merging - try results.forEach { variant, result in + let finalResults: [ConfResult] = try mergeResults.map { variant, mergeResult in let key: ConfigKey = ConfigKey(variant: variant, publicKey: publicKey) let atomicConf: Atomic?> = ( SessionUtil.configStore.wrappedValue[key] ?? Atomic(nil) ) + var finalResult: ConfResult = mergeResult.result // Apply the updated states to the database switch variant { case .userProfile: - try SessionUtil.handleUserProfileUpdate( + finalResult = try SessionUtil.handleUserProfileUpdate( db, in: atomicConf, - needsDump: result.needsDump, - latestConfigUpdateSentTimestamp: result.latestSentTimestamp + mergeResult: mergeResult.result, + latestConfigUpdateSentTimestamp: mergeResult.latestSentTimestamp ) case .contacts: - try SessionUtil.handleContactsUpdate( + finalResult = try SessionUtil.handleContactsUpdate( db, in: atomicConf, - needsDump: result.needsDump + mergeResult: mergeResult.result + ) + + case .convoInfoVolatile: + finalResult = try SessionUtil.handleConvoInfoVolatileUpdate( + db, + in: atomicConf, + mergeResult: mergeResult.result + ) + + case .groups: + finalResult = try SessionUtil.handleGroupsUpdate( + db, + in: atomicConf, + mergeResult: mergeResult.result ) } @@ -366,11 +395,11 @@ public enum SessionUtil { .defaulting(to: []) .asSet() let allMessageHashes: [String] = Array(oldMessageHashes - .inserting(contentsOf: result.messageHashes.asSet())) - let messageHashesChanged: Bool = (oldMessageHashes != result.messageHashes.asSet()) + .inserting(contentsOf: mergeResult.messageHashes.asSet())) + let messageHashesChanged: Bool = (oldMessageHashes != mergeResult.messageHashes.asSet()) // Now that the changes are applied, update the cached dumps - switch (result.needsDump, messageHashesChanged) { + switch (finalResult.needsDump, messageHashesChanged) { case (true, _): // The config data had changes so regenerate the dump and save it try atomicConf @@ -400,13 +429,15 @@ public enum SessionUtil { default: break } + + return finalResult + } // Now that the local state has been updated, trigger a config sync (this will push any // pending updates and properly update the state) - if results.contains(where: { $0.value.needsPush }) { + if finalResults.contains(where: { $0.needsPush }) { ConfigurationSyncJob.enqueue(db) } - } } diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/Info.plist b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/Info.plist index c9a2d9b3e..97310ed4d 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/Info.plist +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/Info.plist @@ -4,18 +4,6 @@ AvailableLibraries - - LibraryIdentifier - ios-arm64 - LibraryPath - libsession-util.a - SupportedArchitectures - - arm64 - - SupportedPlatform - ios - LibraryIdentifier ios-arm64_x86_64-simulator @@ -31,6 +19,18 @@ SupportedPlatformVariant simulator + + LibraryIdentifier + ios-arm64 + LibraryPath + libsession-util.a + SupportedArchitectures + + arm64 + + SupportedPlatform + ios + CFBundlePackageType XFWK diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64/libsession-util.a b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64/libsession-util.a index 16f752d7cad87a57c2693076c1a1ffb407bae5a1..ba172f550e29241663eec0cd566935e76c35604d 100644 GIT binary patch delta 132745 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ʟe-YoY|M8D(MHvkKSHe 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 delta 55134 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

    Df?UGMN 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 diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64_x86_64-simulator/libsession-util.a b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64_x86_64-simulator/libsession-util.a index 3c72d9390486b861e85fc20ba479bf041df060cb..65cfc8dcf46d452982feac7e9c24573b67a5d758 100644 GIT binary patch delta 266874 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 z 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

  • -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 E0?SOzE&u=k delta 109548 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 G7XNQNcL=8d diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/module.modulemap b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/module.modulemap index d5540495e..37ffdf02d 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/module.modulemap +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/module.modulemap @@ -3,7 +3,9 @@ module SessionUtil { header "session/export.h" header "session/config.h" header "session/config/error.h" + header "session/config/convo_info_volatile.h" header "session/config/user_profile.h" + header "session/config/util.h" header "session/config/contacts.h" header "session/config/encrypt.h" header "session/config/base.h" diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.hpp index be3a4f9cb..86cdeab24 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.hpp +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.hpp @@ -103,39 +103,47 @@ class ConfigBase { // See if we can find the key without needing to create anything, so that we can attempt to // access values without mutating anything (which allows, among other things, for assigning - // of the existing value to not dirty anything). Returns nullptr if the value or something + // of the existing value to not dirty anything). Returns nullptrs if the value or something // along its path would need to be created, or has the wrong type; otherwise a const pointer - // to the value. The templated type, if provided, can be one of the types a dict_value can - // hold to also check that the returned value has a particular type; if omitted you get back - // the dict_value pointer itself. + // to the key and the value. The templated type, if provided, can be one of the types a + // dict_value can hold to also check that the returned value has a particular type; if + // omitted you get back the dict_value pointer itself. If the field exists but is not the + // requested `T` type, you get back the key string pointer with a nullptr value. template >> - const T* get_clean() const { + std::pair get_clean_pair() const { const config::dict* data = &_conf._config->data(); // All but the last need to be dicts: for (const auto& key : _inter_keys) { auto it = data->find(key); data = it != data->end() ? std::get_if(&it->second) : nullptr; if (!data) - return nullptr; + return {nullptr, nullptr}; } + const std::string* key; const dict_value* val; // The last can be any value type: - if (auto it = data->find(_last_key); it != data->end()) + if (auto it = data->find(_last_key); it != data->end()) { + key = &it->first; val = &it->second; - else - return nullptr; + } else + return {nullptr, nullptr}; if constexpr (std::is_same_v) - return val; + return {key, val}; else if constexpr (is_dict_subtype) { - if (auto* v = std::get_if(val)) - return v; + return {key, std::get_if(val)}; } else { // int64 or std::string, i.e. the config::scalar sub-types. if (auto* scalar = std::get_if(val)) - return std::get_if(scalar); + return {key, std::get_if(scalar)}; + return {key, nullptr}; } - return nullptr; + } + + // Same as above but just gives back the value, not the key + template >> + const T* get_clean() const { + return get_clean_pair().second; } // Returns a lvalue reference to the value, stomping its way through the dict as it goes to @@ -233,6 +241,11 @@ class ConfigBase { return std::move(*this); } + /// Returns a pointer to the (deepest level) key for this dict pair *if* a pair exists at + /// the given location, nullptr otherwise. This allows a caller to get a reference to the + /// actual key, rather than an ephemeral copy of the current key value. + const std::string* key() const { return get_clean_pair().first; } + /// Returns a const pointer to the string if one exists at the given location, nullptr /// otherwise. const std::string* string() const { return get_clean(); } diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.h index ba27f79b9..0046bfd4e 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.h +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.h @@ -6,6 +6,7 @@ extern "C" { #include "base.h" #include "profile_pic.h" +#include "util.h" typedef struct contacts_contact { char session_id[67]; // in hex; 66 hex chars + null terminator. @@ -48,11 +49,6 @@ int contacts_init( size_t dumplen, char* error) __attribute__((warn_unused_result)); -/// Returns true if session_id has the right form (66 hex digits). This is a quick check, not a -/// robust one: it does not check the leading byte prefix, nor the cryptographic properties of the -/// pubkey for actual validity. -bool session_id_is_valid(const char* session_id); - /// Fills `contact` with the contact info given a session ID (specified as a null-terminated hex /// string), if the contact exists, and returns true. If the contact does not exist then `contact` /// is left unchanged and false is returned. diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.hpp index f97199c53..a4d33234a 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.hpp +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.hpp @@ -43,15 +43,26 @@ struct contact_info { bool approved_me = false; bool blocked = false; - contact_info(std::string sid); + explicit contact_info(std::string sid); // Internal ctor/method for C API implementations: contact_info(const struct contacts_contact& c); // From c struct - void into(contacts_contact& c); // Into c struct + void into(contacts_contact& c) const; // Into c struct + + // Sets a name, storing the name internally in the object. This is intended for use where the + // source string is a temporary may not outlive the `contact_info` object: the name is first + // copied into an internal std::string, and then the name string_view references that. + void set_name(std::string name); + + // Same as above, but for nickname. + void set_nickname(std::string nickname); private: friend class Contacts; + std::string name_; + std::string nickname_; + void load(const dict& info_dict); }; @@ -101,8 +112,8 @@ class Contacts : public ConfigBase { void set(const contact_info& contact); /// Alternative to `set()` for setting individual fields. - void set_name(std::string_view session_id, std::string_view name); - void set_nickname(std::string_view session_id, std::string_view nickname); + void set_name(std::string_view session_id, std::string name); + void set_nickname(std::string_view session_id, std::string nickname); void set_profile_pic(std::string_view session_id, profile_pic pic); void set_approved(std::string_view session_id, bool approved); void set_approved_me(std::string_view session_id, bool approved_me); diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.h new file mode 100644 index 000000000..9ec098ece --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.h @@ -0,0 +1,219 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "base.h" +#include "profile_pic.h" + +typedef struct convo_info_volatile_1to1 { + char session_id[67]; // in hex; 66 hex chars + null terminator. + + int64_t last_read; // milliseconds since unix epoch + bool unread; // true if the conversation is explicitly marked unread +} convo_info_volatile_1to1; + +typedef struct convo_info_volatile_open { + char base_url[268]; // null-terminated (max length 267), normalized (i.e. always lower-case, + // only has port if non-default, has trailing / removed) + char room[65]; // null-terminated (max length 64), normalized (always lower-case) + unsigned char pubkey[32]; // 32 bytes (not terminated, can contain nulls) + + int64_t last_read; // ms since unix epoch + bool unread; // true if marked unread +} convo_info_volatile_open; + +typedef struct convo_info_volatile_legacy_closed { + char group_id[67]; // in hex; 66 hex chars + null terminator. Looks just like a Session ID, + // though isn't really one. + + int64_t last_read; // ms since unix epoch + bool unread; // true if marked unread +} convo_info_volatile_legacy_closed; + +/// Constructs a conversations config object and sets a pointer to it in `conf`. +/// +/// \param ed25519_secretkey must be the 32-byte secret key seed value. (You can also pass the +/// pointer to the beginning of the 64-byte value libsodium calls the "secret key" as the first 32 +/// bytes of that are the seed). This field cannot be null. +/// +/// \param dump - if non-NULL this restores the state from the dumped byte string produced by a past +/// instantiation's call to `dump()`. To construct a new, empty object this should be NULL. +/// +/// \param dumplen - the length of `dump` when restoring from a dump, or 0 when `dump` is NULL. +/// +/// \param error - the pointer to a buffer in which we will write an error string if an error +/// occurs; error messages are discarded if this is given as NULL. If non-NULL this must be a +/// buffer of at least 256 bytes. +/// +/// Returns 0 on success; returns a non-zero error code and write the exception message as a +/// C-string into `error` (if not NULL) on failure. +/// +/// When done with the object the `config_object` must be destroyed by passing the pointer to +/// config_free() (in `session/config/base.h`). +int convo_info_volatile_init( + config_object** conf, + const unsigned char* ed25519_secretkey, + const unsigned char* dump, + size_t dumplen, + char* error) __attribute__((warn_unused_result)); + +/// Fills `convo` with the conversation info given a session ID (specified as a null-terminated hex +/// string), if the conversation exists, and returns true. If the conversation does not exist then +/// `convo` is left unchanged and false is returned. +bool convo_info_volatile_get_1to1( + const config_object* conf, convo_info_volatile_1to1* convo, const char* session_id) + __attribute__((warn_unused_result)); + +/// Same as the above except that when the conversation does not exist, this sets all the convo +/// fields to defaults and loads it with the given session_id. +/// +/// Returns true as long as it is given a valid session_id. A false return is considered an error, +/// and means the session_id was not a valid session_id. +/// +/// This is the method that should usually be used to create or update a conversation, followed by +/// setting fields in the convo, and then giving it to convo_info_volatile_set(). +bool convo_info_volatile_get_or_construct_1to1( + const config_object* conf, convo_info_volatile_1to1* convo, const char* session_id) + __attribute__((warn_unused_result)); + +/// open-group versions of the 1-to-1 functions: +/// +/// Gets an open group convo info. `base_url` and `room` are null-terminated c strings; pubkey is +/// 32 bytes. base_url and room will always be lower-cased (if not already). +bool convo_info_volatile_get_open( + const config_object* conf, + convo_info_volatile_open* og, + const char* base_url, + const char* room, + unsigned const char* pubkey) __attribute__((warn_unused_result)); +bool convo_info_volatile_get_or_construct_open( + const config_object* conf, + convo_info_volatile_open* convo, + const char* base_url, + const char* room, + unsigned const char* pubkey) __attribute__((warn_unused_result)); + +/// Fills `convo` with the conversation info given a legacy closed group ID (specified as a +/// null-terminated hex string), if the conversation exists, and returns true. If the conversation +/// does not exist then `convo` is left unchanged and false is returned. +bool convo_info_volatile_get_legacy_closed( + const config_object* conf, convo_info_volatile_legacy_closed* convo, const char* id) + __attribute__((warn_unused_result)); + +/// Same as the above except that when the conversation does not exist, this sets all the convo +/// fields to defaults and loads it with the given id. +/// +/// Returns true as long as it is given a valid legacy closed group id (i.e. same format as a +/// session id). A false return is considered an error, and means the id was not a valid session +/// id. +/// +/// This is the method that should usually be used to create or update a conversation, followed by +/// setting fields in the convo, and then giving it to convo_info_volatile_set(). +bool convo_info_volatile_get_or_construct_legacy_closed( + const config_object* conf, convo_info_volatile_legacy_closed* convo, const char* id) + __attribute__((warn_unused_result)); + +/// Adds or updates a conversation from the given convo info +void convo_info_volatile_set_1to1(config_object* conf, const convo_info_volatile_1to1* convo); +void convo_info_volatile_set_open(config_object* conf, const convo_info_volatile_open* convo); +void convo_info_volatile_set_legacy_closed( + config_object* conf, const convo_info_volatile_legacy_closed* convo); + +/// Erases a conversation from the conversation list. Returns true if the conversation was found +/// and removed, false if the conversation was not present. You must not call this during +/// iteration; see details below. +bool convo_info_volatile_erase_1to1(config_object* conf, const char* session_id); +bool convo_info_volatile_erase_open( + config_object* conf, const char* base_url, const char* room, unsigned const char* pubkey); +bool convo_info_volatile_erase_legacy_closed(config_object* conf, const char* group_id); + +/// Returns the number of conversations. +size_t convo_info_volatile_size(const config_object* conf); +/// Returns the number of conversations of the specific type. +size_t convo_info_volatile_size_1to1(const config_object* conf); +size_t convo_info_volatile_size_open(const config_object* conf); +size_t convo_info_volatile_size_legacy_closed(const config_object* conf); + +/// Functions for iterating through the entire conversation list. Intended use is: +/// +/// convo_info_volatile_1to1 c1; +/// convo_info_volatile_open c2; +/// convo_info_volatile_legacy_closed c3; +/// convo_info_volatile_iterator *it = convo_info_volatile_iterator_new(my_convos); +/// for (; !convo_info_volatile_iterator_done(it); convo_info_volatile_iterator_advance(it)) { +/// if (convo_info_volatile_it_is_1to1(it, &c1)) { +/// // use c1.whatever +/// } else if (convo_info_volatile_it_is_open(it, &c2)) { +/// // use c2.whatever +/// } else if (convo_info_volatile_it_is_legacy_closed(it, &c3)) { +/// // use c3.whatever +/// } +/// } +/// convo_info_volatile_iterator_free(it); +/// +/// It is permitted to modify records (e.g. with a call to one of the `convo_info_volatile_set_*` +/// functions) and add records while iterating. +/// +/// If you need to remove while iterating then usage is slightly different: you must advance the +/// iteration by calling either convo_info_volatile_iterator_advance if not deleting, or +/// convo_info_volatile_iterator_erase to erase and advance. Usage looks like this: +/// +/// convo_info_volatile_1to1 c1; +/// convo_info_volatile_iterator *it = convo_info_volatile_iterator_new(my_convos); +/// while (!convo_info_volatile_iterator_done(it)) { +/// if (convo_it_is_1to1(it, &c1)) { +/// bool should_delete = /* ... */; +/// if (should_delete) +/// convo_info_volatile_iterator_erase(it); +/// else +/// convo_info_volatile_iterator_advance(it); +/// } +/// } +/// convo_info_volatile_iterator_free(it); +/// + +typedef struct convo_info_volatile_iterator convo_info_volatile_iterator; + +// Starts a new iterator that iterates over all conversations. +convo_info_volatile_iterator* convo_info_volatile_iterator_new(const config_object* conf); + +// The same as `convo_info_volatile_iterator_new` except that this iterates *only* over one type of +// conversation. You still need to use `convo_info_volatile_it_is_1to1` (or the alternatives) to +// load the data in each pass of the loop. (You can, however, safely ignore the bool return value +// of the `it_is_whatever` function: it will always be true for the particular type being iterated +// over). +convo_info_volatile_iterator* convo_info_volatile_iterator_new_1to1(const config_object* conf); +convo_info_volatile_iterator* convo_info_volatile_iterator_new_open(const config_object* conf); +convo_info_volatile_iterator* convo_info_volatile_iterator_new_legacy_closed( + const config_object* conf); + +// Frees an iterator once no longer needed. +void convo_info_volatile_iterator_free(convo_info_volatile_iterator* it); + +// Returns true if iteration has reached the end. +bool convo_info_volatile_iterator_done(convo_info_volatile_iterator* it); + +// Advances the iterator. +void convo_info_volatile_iterator_advance(convo_info_volatile_iterator* it); + +// If the current iterator record is a 1-to-1 conversation this sets the details into `c` and +// returns true. Otherwise it returns false. +bool convo_info_volatile_it_is_1to1(convo_info_volatile_iterator* it, convo_info_volatile_1to1* c); + +// If the current iterator record is an open group conversation this sets the details into `c` and +// returns true. Otherwise it returns false. +bool convo_info_volatile_it_is_open(convo_info_volatile_iterator* it, convo_info_volatile_open* c); + +// If the current iterator record is a legacy closed group conversation this sets the details into +// `c` and returns true. Otherwise it returns false. +bool convo_info_volatile_it_is_legacy_closed( + convo_info_volatile_iterator* it, convo_info_volatile_legacy_closed* c); + +// Erases the current convo while advancing the iterator to the next convo in the iteration. +void convo_info_volatile_iterator_erase(config_object* conf, convo_info_volatile_iterator* it); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.hpp new file mode 100644 index 000000000..f6de101e3 --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.hpp @@ -0,0 +1,380 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "base.hpp" + +extern "C" { +struct convo_info_volatile_1to1; +struct convo_info_volatile_open; +struct convo_info_volatile_legacy_closed; +} + +namespace session::config { + +class ConvoInfoVolatile; + +/// keys used in this config, either currently or in the past (so that we don't reuse): +/// +/// Note that this is a high-frequency object, intended only for properties that change frequently ( +/// (currently just the read timestamp for each conversation). +/// +/// 1 - dict of one-to-one conversations. Each key is the Session ID of the contact (in hex). +/// Values are dicts with keys: +/// r - the unix timestamp (in integer milliseconds) of the last-read message. Always +/// included, but will be 0 if no messages are read. +/// u - will be present and set to 1 if this conversation is specifically marked unread. +/// +/// o - open group conversations. Each key is: BASE_URL + '\0' + LC_ROOM_NAME + '\0' + +/// SERVER_PUBKEY (in bytes). Note that room name is *always* lower-cased here (so that clients +/// with the same room but with different cases will always set the same key). Values are dicts +/// with keys: +/// r - the unix timestamp (in integer milliseconds) of the last-read message. Always included, +/// but will be 0 if no messages are read. +/// u - will be present and set to 1 if this conversation is specifically marked unread. +/// +/// C - legacy closed group conversations. The key is the closed group identifier (which looks +/// indistinguishable from a Session ID, but isn't really a proper Session ID). Values are +/// dicts with keys: +/// r - the unix timestamp (integer milliseconds) of the last-read message. Always included, +/// but will be 0 if no messages are read. +/// u - will be present and set to 1 if this conversation is specifically marked unread. +/// +/// c - reserved for future tracking of new closed group conversations. + +namespace convo { + + struct base { + int64_t last_read = 0; + bool unread = false; + + protected: + void load(const dict& info_dict); + }; + + struct one_to_one : base { + std::string session_id; // in hex + + // Constructs an empty one_to_one from a session_id. Session ID can be either bytes (33) or + // hex (66). + explicit one_to_one(std::string&& session_id); + explicit one_to_one(std::string_view session_id); + + // Internal ctor/method for C API implementations: + one_to_one(const struct convo_info_volatile_1to1& c); // From c struct + void into(convo_info_volatile_1to1& c) const; // Into c struct + + friend class session::config::ConvoInfoVolatile; + }; + + struct open_group : base { + // 267 = len('https://') + 253 (max valid DNS name length) + len(':XXXXX') + static constexpr size_t MAX_URL = 267, MAX_ROOM = 64; + + std::string_view base_url() const; // Accesses the base url (i.e. not including room or + // pubkey). Always lower-case. + std::string_view room() + const; // Accesses the room name, always in lower-case. (Note that the + // actual open group info might not be lower-case; it is just in + // the open group convo where we force it lower-case). + ustring_view pubkey() const; // Accesses the server pubkey (32 bytes). + std::string pubkey_hex() const; // Accesses the server pubkey as hex (64 hex digits). + + open_group() = default; + + // Constructs an empty open_group convo struct from url, room, and pubkey. `base_url` and + // `room` will be lower-cased if not already (they do not have to be passed lower-case). + // pubkey is 32 bytes. + open_group(std::string_view base_url, std::string_view room, ustring_view pubkey); + + // Same as above, but takes pubkey as a hex string. + open_group(std::string_view base_url, std::string_view room, std::string_view pubkey_hex); + + // Takes a combined room URL (e.g. https://whatever.com/r/Room?public_key=01234....), either + // new style (with /r/) or old style (without /r/). Note that the URL gets canonicalized so + // the resulting `base_url()` and `room()` values may not be exactly equal to what is given. + // + // See also `parse_full_url` which does the same thing but returns it in pieces rather than + // constructing a new `open_group` object. + explicit open_group(std::string_view full_url); + + // Internal ctor/method for C API implementations: + open_group(const struct convo_info_volatile_open& c); // From c struct + void into(convo_info_volatile_open& c) const; // Into c struct + + // Replaces the baseurl/room/pubkey of this object. Note that changing this and then giving + // it to `set` will end up inserting a *new* record but not removing the *old* one (you need + // to erase first to do that). + void set_server(std::string_view base_url, std::string_view room, ustring_view pubkey); + void set_server( + std::string_view base_url, std::string_view room, std::string_view pubkey_hex); + void set_server(std::string_view full_url); + + // Loads the baseurl/room/pubkey of this object from an encoded key. Throws + // std::invalid_argument if the encoded key does not look right. + void load_encoded_key(std::string key); + + // Takes a base URL as input and returns it in canonical form. This involves doing things + // like lower casing it and removing redundant ports (e.g. :80 when using http://). + static std::string canonical_url(std::string_view url); + + // Takes a full room URL, splits it up into canonical url (see above), lower-case room + // token, and server pubkey. We take both the deprecated form (e.g. + // https://example.com/SomeRoom?public_key=...) and new form + // (https://example.com/r/SomeRoom?public_key=...). The public_key is typically specified + // in hex (64 digits), but we also accept unpadded base64 (43 chars) and base32z (52 chars) + // encodings (for slightly shorter URLs). + static std::tuple parse_full_url( + std::string_view full_url); + + private: + std::string key; + size_t url_size = 0; + + friend class session::config::ConvoInfoVolatile; + + // Returns the key value we use in the stored dict for this open group, i.e. + // lc(URL) + lc(NAME) + PUBKEY_BYTES. + static std::string make_key( + std::string_view base_url, std::string_view room, std::string_view pubkey_hex); + static std::string make_key( + std::string_view base_url, std::string_view room, ustring_view pubkey); + }; + + struct legacy_closed_group : base { + std::string id; // in hex, indistinguishable from a Session ID + + // Constructs an empty legacy_closed_group from a quasi-session_id + explicit legacy_closed_group(std::string&& group_id); + explicit legacy_closed_group(std::string_view group_id); + + // Internal ctor/method for C API implementations: + legacy_closed_group(const struct convo_info_volatile_legacy_closed& c); // From c struct + void into(convo_info_volatile_legacy_closed& c) const; // Into c struct + + private: + friend class session::config::ConvoInfoVolatile; + }; + + using any = std::variant; +} // namespace convo + +class ConvoInfoVolatile : public ConfigBase { + + public: + // No default constructor + ConvoInfoVolatile() = delete; + + /// Constructs a conversation list from existing data (stored from `dump()`) and the user's + /// secret key for generating the data encryption key. To construct a blank list (i.e. with no + /// pre-existing dumped data to load) pass `std::nullopt` as the second argument. + /// + /// \param ed25519_secretkey - contains the libsodium secret key used to encrypt/decrypt the + /// data when pushing/pulling from the swarm. This can either be the full 64-byte value (which + /// is technically the 32-byte seed followed by the 32-byte pubkey), or just the 32-byte seed of + /// the secret key. + /// + /// \param dumped - either `std::nullopt` to construct a new, empty object; or binary state data + /// that was previously dumped from an instance of this class by calling `dump()`. + ConvoInfoVolatile(ustring_view ed25519_secretkey, std::optional dumped); + + Namespace storage_namespace() const override { return Namespace::ConvoInfoVolatile; } + + const char* encryption_domain() const override { return "ConvoInfoVolatile"; } + + /// Looks up and returns a contact by session ID (hex). Returns nullopt if the session ID was + /// not found, otherwise returns a filled out `convo::one_to_one`. + std::optional get_1to1(std::string_view session_id) const; + + /// Looks up and returns an open group conversation. Takes the base URL, room name (case + /// insensitive), and pubkey (in hex). Retuns nullopt if the open group was not found, + /// otherwise a filled out `convo::open_group`. + std::optional get_open( + std::string_view base_url, std::string_view room, std::string_view pubkey_hex) const; + + /// Same as above, but takes the pubkey as bytes instead of hex + std::optional get_open( + std::string_view base_url, std::string_view room, ustring_view pubkey) const; + + /// Looks up and returns a legacy closed group conversation by ID. The ID looks like a hex + /// Session ID, but isn't really a Session ID. Returns nullopt if there is no record of the + /// closed group conversation. + std::optional get_legacy_closed(std::string_view pubkey_hex) const; + + /// These are the same as the above methods (without "_or_construct" in the name), except that + /// when the conversation doesn't exist a new one is created, prefilled with the pubkey/url/etc. + convo::one_to_one get_or_construct_1to1(std::string_view session_id) const; + convo::open_group get_or_construct_open( + std::string_view base_url, std::string_view room, std::string_view pubkey_hex) const; + convo::open_group get_or_construct_open( + std::string_view base_url, std::string_view room, ustring_view pubkey) const; + convo::legacy_closed_group get_or_construct_legacy_closed(std::string_view pubkey_hex) const; + + /// Inserts or replaces existing conversation info. For example, to update a 1-to-1 + /// conversation last read time you would do: + /// + /// auto info = conversations.get_or_construct_1to1(some_session_id); + /// info.last_read = new_unix_timestamp; + /// conversations.set(info); + /// + void set(const convo::one_to_one& c); + void set(const convo::legacy_closed_group& c); + void set(const convo::open_group& c); + + void set(const convo::any& c); // Variant which can be any of the above + + protected: + void set_base(const convo::base& c, DictFieldProxy& info); + + public: + /// Removes a one-to-one conversation. Returns true if found and removed, false if not present. + bool erase_1to1(std::string_view pubkey); + + /// Removes an open group conversation record. Returns true if found and removed, false if not + /// present. Arguments are the same as `get_open`. + bool erase_open(std::string_view base_url, std::string_view room, std::string_view pubkey_hex); + bool erase_open(std::string_view base_url, std::string_view room, ustring_view pubkey); + + /// Removes a legacy closed group conversation. Returns true if found and removed, false if not + /// present. + bool erase_legacy_closed(std::string_view pubkey_hex); + + /// Removes a conversation taking the convo::whatever record (rather than the pubkey/url). + bool erase(const convo::one_to_one& c); + bool erase(const convo::open_group& c); + bool erase(const convo::legacy_closed_group& c); + + bool erase(const convo::any& c); // Variant of any of them + + struct iterator; + + /// This works like erase, but takes an iterator to the conversation to remove. The element is + /// removed and the iterator to the next element after the removed one is returned. This is + /// intended for use where elements are to be removed during iteration: see below for an + /// example. + iterator erase(iterator it); + + /// Returns the number of conversations (of any type). + size_t size() const; + + /// Returns the number of 1-to-1, open group, and legacy closed group conversations, + /// respectively. + size_t size_1to1() const; + size_t size_open() const; + size_t size_legacy_closed() const; + + /// Returns true if the conversation list is empty. + bool empty() const { return size() == 0; } + + /// Iterators for iterating through all conversations. Typically you access this implicit via a + /// for loop over the `ConvoInfoVolatile` object: + /// + /// for (auto& convo : conversations) { + /// if (auto* dm = std::get_if(&convo)) { + /// // use dm->session_id, dm->last_read, etc. + /// } else if (auto* og = std::get_if(&convo)) { + /// // use og->base_url, og->room, om->last_read, etc. + /// } else if (auto* lcg = std::get_if(&convo)) { + /// // use lcg->id, lcg->last_read + /// } + /// } + /// + /// This iterates through all conversations in sorted order (sorted first by convo type, then by + /// id within the type). + /// + /// It is permitted to modify and add records while iterating (e.g. by modifying one of the + /// `dm`/`og`/`lcg` and then calling set()). + /// + /// If you need to erase the current conversation during iteration then care is required: you + /// need to advance the iterator via the iterator version of erase when erasing an element + /// rather than incrementing it regularly. For example: + /// + /// for (auto it = conversations.begin(); it != conversations.end(); ) { + /// if (should_remove(*it)) + /// it = converations.erase(it); + /// else + /// ++it; + /// } + /// + /// Alternatively, you can use the first version with two loops: the first loop through all + /// converations doesn't erase but just builds a vector of IDs to erase, then the second loops + /// through that vector calling `erase_1to1()`/`erase_open()`/`erase_legacy_closed()` for each + /// one. + /// + iterator begin() const { return iterator{data}; } + iterator end() const { return iterator{}; } + + template + struct subtype_iterator; + + /// Returns an iterator that iterates only through one type of conversations + subtype_iterator begin_1to1() const { return {data}; } + subtype_iterator begin_open() const { return {data}; } + subtype_iterator begin_legacy_closed() const { return {data}; } + + using iterator_category = std::input_iterator_tag; + using value_type = + std::variant; + using reference = value_type&; + using pointer = value_type*; + using difference_type = std::ptrdiff_t; + + struct iterator { + protected: + std::shared_ptr _val; + std::optional _it_11, _end_11, _it_open, _end_open, _it_lclosed, + _end_lclosed; + void _load_val(); + iterator() = default; // Constructs an end tombstone + explicit iterator( + const DictFieldRoot& data, + bool oneto1 = true, + bool open = true, + bool closed = true); + friend class ConvoInfoVolatile; + + public: + bool operator==(const iterator& other) const; + bool operator!=(const iterator& other) const { return !(*this == other); } + bool done() const; // Equivalent to comparing against the end iterator + convo::any& operator*() const { return *_val; } + convo::any* operator->() const { return _val.get(); } + iterator& operator++(); + iterator operator++(int) { + auto copy{*this}; + ++*this; + return copy; + } + }; + + template + struct subtype_iterator : iterator { + protected: + subtype_iterator(const DictFieldRoot& data) : + iterator( + data, + std::is_same_v, + std::is_same_v, + std::is_same_v) {} + friend class ConvoInfoVolatile; + + public: + ConvoType& operator*() const { return std::get(*_val); } + ConvoType* operator->() const { return &std::get(*_val); } + subtype_iterator& operator++() { + iterator::operator++(); + return *this; + } + subtype_iterator operator++(int) { + auto copy{*this}; + ++*this; + return copy; + } + }; +}; + +} // namespace session::config diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/namespaces.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/namespaces.hpp index 1ba0226ca..e96a12ab0 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/namespaces.hpp +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/namespaces.hpp @@ -7,6 +7,7 @@ namespace session::config { enum class Namespace : std::int16_t { UserProfile = 2, Contacts = 3, + ConvoInfoVolatile = 4, ClosedGroupInfo = 11, }; diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/profile_pic.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/profile_pic.hpp index 00cd90062..0a076ab97 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/profile_pic.hpp +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/profile_pic.hpp @@ -3,24 +3,37 @@ #include "session/types.hpp" namespace session::config { + // Profile pic info. Note that `url` is null terminated (though the null lies just beyond the end // of the string view: that is, it views into a full std::string). struct profile_pic { + private: + std::string url_; + ustring key_; + + public: std::string_view url; ustring_view key; + // Default constructor, makes an empty profile pic + profile_pic() = default; + + // Constructs from string views: the values must stay alive for the duration of the profile_pic + // instance. (If not, use `set_url`/`set_key` or the rvalue-argument constructor instead). profile_pic(std::string_view url, ustring_view key) : url{url}, key{key} {} + // Constructs from temporary strings; the strings are stored/managed internally + profile_pic(std::string&& url, ustring&& key) : + url_{std::move(url)}, key_{std::move(key)}, url{url_}, key{key_} {} + // Returns true if either url or key are empty bool empty() const { return url.empty() || key.empty(); } - // Guard against accidentally passing in a temporary string or ustring: - template < - typename UrlType, - typename KeyType, - std::enable_if_t< - std::is_same_v || std::is_same_v>> - profile_pic(UrlType&& url, KeyType&& key) = delete; + // Sets the url or key to a temporary value that needs to be copied and owned by this + // profile_pic object. (This is only needed when the source string may not outlive the + // profile_pic object; if it does, the `url` or `key` can be assigned to directly). + void set_url(std::string url); + void set_key(ustring key); }; } // namespace session::config diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/util.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/util.h new file mode 100644 index 000000000..6ae2c890b --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/util.h @@ -0,0 +1,16 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/// Returns true if session_id has the right form (66 hex digits). This is a quick check, not a +/// robust one: it does not check the leading byte prefix, nor the cryptographic properties of the +/// pubkey for actual validity. +bool session_id_is_valid(const char* session_id); + +#ifdef __cplusplus +} +#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/util.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/util.hpp index 8348d908e..6cfa77e2b 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/util.hpp +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/util.hpp @@ -24,4 +24,21 @@ inline std::string_view from_unsigned_sv(ustring_view v) { return {from_unsigned(v.data()), v.size()}; } +/// Returns true if the first string is equal to the second string, compared case-insensitively. +inline bool string_iequal(std::string_view s1, std::string_view s2) { + return std::equal(s1.begin(), s1.end(), s2.begin(), s2.end(), [](char a, char b) { + return std::tolower(static_cast(a)) == + std::tolower(static_cast(b)); + }); +} + +// C++20 starts_/ends_with backport +inline constexpr bool starts_with(std::string_view str, std::string_view prefix) { + return str.size() >= prefix.size() && str.substr(prefix.size()) == prefix; +} + +inline constexpr bool end_with(std::string_view str, std::string_view suffix) { + return str.size() >= suffix.size() && str.substr(str.size() - suffix.size()) == suffix; +} + } // namespace session diff --git a/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift b/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift index 4331ba7a4..7ac0ad123 100644 --- a/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift +++ b/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift @@ -373,7 +373,7 @@ public extension ClosedGroupControlMessage.Kind { let addedMemberNames: [String] = memberIds .map { knownMemberNameMap[$0] ?? - Profile.truncated(id: $0, threadVariant: .closedGroup) + Profile.truncated(id: $0, threadVariant: .legacyClosedGroup) } return String( @@ -396,7 +396,7 @@ public extension ClosedGroupControlMessage.Kind { let removedMemberNames: [String] = memberIds.removing(userPublicKey) .map { knownMemberNameMap[$0] ?? - Profile.truncated(id: $0, threadVariant: .closedGroup) + Profile.truncated(id: $0, threadVariant: .legacyClosedGroup) } let format: String = (removedMemberNames.count > 1 ? "GROUP_MEMBERS_REMOVED".localized() : diff --git a/SessionMessagingKit/Messages/Control Messages/SharedConfigMessage.swift b/SessionMessagingKit/Messages/Control Messages/SharedConfigMessage.swift index 96bb65f4c..c6172c79e 100644 --- a/SessionMessagingKit/Messages/Control Messages/SharedConfigMessage.swift +++ b/SessionMessagingKit/Messages/Control Messages/SharedConfigMessage.swift @@ -22,11 +22,15 @@ public final class SharedConfigMessage: ControlMessage { public enum Kind: CustomStringConvertible, Codable { case userProfile case contacts + case convoInfoVolatile + case groups public var description: String { switch self { case .userProfile: return "userProfile" case .contacts: return "contacts" + case .convoInfoVolatile: return "convoInfoVolatile" + case .groups: return "groups" } } } @@ -77,6 +81,8 @@ public final class SharedConfigMessage: ControlMessage { switch sharedConfigMessage.kind { case .userProfile: return .userProfile case .contacts: return .contacts + case .convoInfoVolatile: return .convoInfoVolatile + case .groups: return .groups } }(), seqNo: sharedConfigMessage.seqno, @@ -91,6 +97,8 @@ public final class SharedConfigMessage: ControlMessage { switch self.kind { case .userProfile: return .userProfile case .contacts: return .contacts + case .convoInfoVolatile: return .convoInfoVolatile + case .groups: return .groups } }(), seqno: self.seqNo, @@ -126,6 +134,8 @@ public extension SharedConfigMessage.Kind { switch self { case .userProfile: return .userProfile case .contacts: return .contacts + case .convoInfoVolatile: return .convoInfoVolatile + case .groups: return .groups } } } diff --git a/SessionMessagingKit/Messages/Message+Destination.swift b/SessionMessagingKit/Messages/Message+Destination.swift index 3714b06c6..476b868f4 100644 --- a/SessionMessagingKit/Messages/Message+Destination.swift +++ b/SessionMessagingKit/Messages/Message+Destination.swift @@ -39,7 +39,7 @@ public extension Message { return .contact(publicKey: thread.id) - case .closedGroup: + case .legacyClosedGroup, .closedGroup: return .closedGroup(groupPublicKey: thread.id) case .openGroup: diff --git a/SessionMessagingKit/Messages/Visible Messages/VisibleMessage.swift b/SessionMessagingKit/Messages/Visible Messages/VisibleMessage.swift index 8bd727094..20b01e9e1 100644 --- a/SessionMessagingKit/Messages/Visible Messages/VisibleMessage.swift +++ b/SessionMessagingKit/Messages/Visible Messages/VisibleMessage.swift @@ -217,7 +217,10 @@ public extension VisibleMessage { sentTimestamp: UInt64(interaction.timestampMs), recipient: (try? interaction.recipientStates.fetchOne(db))?.recipientId, groupPublicKey: try? interaction.thread - .filter(SessionThread.Columns.variant == SessionThread.Variant.closedGroup) + .filter( + SessionThread.Columns.variant == SessionThread.Variant.legacyClosedGroup || + SessionThread.Columns.variant == SessionThread.Variant.closedGroup + ) .select(.id) .asRequest(of: String.self) .fetchOne(db), diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index 0f48ab8d8..d6d2eca27 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -504,7 +504,7 @@ public final class OpenGroupManager { /// Start downloading the room image (if we don't have one or it's been updated) if - let imageId: String = pollInfo.details?.imageId, + let imageId: String = (pollInfo.details?.imageId ?? openGroup.imageId), ( openGroup.imageData == nil || openGroup.imageId != imageId @@ -883,12 +883,11 @@ public final class OpenGroupManager { return dependencies.storage .read { db in - let isDirectModOrAdmin: Bool = (try? GroupMember + let isDirectModOrAdmin: Bool = GroupMember .filter(GroupMember.Columns.groupId == groupId) .filter(GroupMember.Columns.profileId == publicKey) .filter(targetRoles.contains(GroupMember.Columns.role)) - .isNotEmpty(db)) - .defaulting(to: false) + .isNotEmpty(db) // If the publicKey provided matches a mod or admin directly then just return immediately if isDirectModOrAdmin { return true } @@ -942,12 +941,11 @@ public final class OpenGroupManager { SessionId(.blinded, publicKey: blindedKeyPair.publicKey).hexString ]) - return (try? GroupMember + return GroupMember .filter(GroupMember.Columns.groupId == groupId) .filter(possibleKeys.contains(GroupMember.Columns.profileId)) .filter(targetRoles.contains(GroupMember.Columns.role)) - .isNotEmpty(db)) - .defaulting(to: false) + .isNotEmpty(db) } } .defaulting(to: false) diff --git a/SessionMessagingKit/Protos/Generated/SNProto.swift b/SessionMessagingKit/Protos/Generated/SNProto.swift index b13cf6585..b10ef5fac 100644 --- a/SessionMessagingKit/Protos/Generated/SNProto.swift +++ b/SessionMessagingKit/Protos/Generated/SNProto.swift @@ -3715,12 +3715,16 @@ extension SNProtoAttachmentPointer.SNProtoAttachmentPointerBuilder { @objc public enum SNProtoSharedConfigMessageKind: Int32 { case userProfile = 1 case contacts = 2 + case convoInfoVolatile = 3 + case groups = 4 } private class func SNProtoSharedConfigMessageKindWrap(_ value: SessionProtos_SharedConfigMessage.Kind) -> SNProtoSharedConfigMessageKind { switch value { case .userProfile: return .userProfile case .contacts: return .contacts + case .convoInfoVolatile: return .convoInfoVolatile + case .groups: return .groups } } @@ -3728,6 +3732,8 @@ extension SNProtoAttachmentPointer.SNProtoAttachmentPointerBuilder { switch value { case .userProfile: return .userProfile case .contacts: return .contacts + case .convoInfoVolatile: return .convoInfoVolatile + case .groups: return .groups } } diff --git a/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift b/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift index 3dd86bfb7..3b649effc 100644 --- a/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift +++ b/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift @@ -1622,6 +1622,8 @@ struct SessionProtos_SharedConfigMessage { typealias RawValue = Int case userProfile // = 1 case contacts // = 2 + case convoInfoVolatile // = 3 + case groups // = 4 init() { self = .userProfile @@ -1631,6 +1633,8 @@ struct SessionProtos_SharedConfigMessage { switch rawValue { case 1: self = .userProfile case 2: self = .contacts + case 3: self = .convoInfoVolatile + case 4: self = .groups default: return nil } } @@ -1639,6 +1643,8 @@ struct SessionProtos_SharedConfigMessage { switch self { case .userProfile: return 1 case .contacts: return 2 + case .convoInfoVolatile: return 3 + case .groups: return 4 } } @@ -3336,5 +3342,7 @@ extension SessionProtos_SharedConfigMessage.Kind: SwiftProtobuf._ProtoNameProvid static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "USER_PROFILE"), 2: .same(proto: "CONTACTS"), + 3: .same(proto: "CONVO_INFO_VOLATILE"), + 4: .same(proto: "GROUPS"), ] } diff --git a/SessionMessagingKit/Protos/SessionProtos.proto b/SessionMessagingKit/Protos/SessionProtos.proto index 839dd78b6..2014274b0 100644 --- a/SessionMessagingKit/Protos/SessionProtos.proto +++ b/SessionMessagingKit/Protos/SessionProtos.proto @@ -276,6 +276,8 @@ message SharedConfigMessage { enum Kind { USER_PROFILE = 1; CONTACTS = 2; + CONVO_INFO_VOLATILE = 3; + GROUPS = 4; } // @required diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift index 13f42dbf0..3b11e0003 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift @@ -70,7 +70,7 @@ extension MessageReceiver { // Create the group let groupAlreadyExisted: Bool = ((try? SessionThread.exists(db, id: groupPublicKey)) ?? false) let thread: SessionThread = try SessionThread - .fetchOrCreate(db, id: groupPublicKey, variant: .closedGroup) + .fetchOrCreate(db, id: groupPublicKey, variant: .legacyClosedGroup) .with(shouldBeVisible: true) .saved(db) let closedGroup: ClosedGroup = try ClosedGroup( diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift index c3304e9ea..467a41930 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift @@ -168,7 +168,7 @@ extension MessageReceiver { // past two weeks) if isInitialSync { let existingClosedGroupsIds: [String] = (try? SessionThread - .filter(SessionThread.Columns.variant == SessionThread.Variant.closedGroup) + .filter(SessionThread.Columns.variant == SessionThread.Variant.legacyClosedGroup) .fetchAll(db)) .defaulting(to: []) .map { $0.id } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ReadReceipts.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ReadReceipts.swift index 913610e83..79557468b 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ReadReceipts.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ReadReceipts.swift @@ -9,7 +9,7 @@ extension MessageReceiver { guard let timestampMsValues: [Double] = message.timestamps?.map({ Double($0) }) else { return } guard let readTimestampMs: Double = message.receivedTimestamp.map({ Double($0) }) else { return } - try Interaction.markAsRead( + try Interaction.markAsRecipientRead( db, recipientId: sender, timestampMsValues: timestampMsValues, diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+UnsendRequests.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+UnsendRequests.swift index 277f26000..f09b3f487 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+UnsendRequests.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+UnsendRequests.swift @@ -19,7 +19,12 @@ extension MessageReceiver { guard let interactionId: Int64 = maybeInteraction?.id, - let interaction: Interaction = maybeInteraction + let interaction: Interaction = maybeInteraction, + let threadVariant: SessionThread.Variant = try SessionThread + .filter(id: interaction.threadId) + .select(.variant) + .asRequest(of: SessionThread.Variant.self) + .fetchOne(db) else { return } // Mark incoming messages as read and remove any of their notifications @@ -28,6 +33,7 @@ extension MessageReceiver { db, interactionId: interactionId, threadId: interaction.threadId, + threadVariant: threadVariant, includingOlder: false, trySendReadReceipt: false ) diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift index 31045cd8d..c69eed681 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift @@ -54,11 +54,11 @@ extension MessageReceiver { let currentUserPublicKey: String = getUserHexEncodedPublicKey(db, dependencies: dependencies) let thread: SessionThread = try SessionThread .fetchOrCreate(db, id: threadInfo.id, variant: threadInfo.variant) + let maybeOpenGroup: OpenGroup? = openGroupId.map { try? OpenGroup.fetchOne(db, id: $0) } let variant: Interaction.Variant = { guard - let openGroupId: String = openGroupId, let senderSessionId: SessionId = SessionId(from: sender), - let openGroup: OpenGroup = try? OpenGroup.fetchOne(db, id: openGroupId) + let openGroup: OpenGroup = maybeOpenGroup else { return (sender == currentUserPublicKey ? .standardOutgoing : @@ -118,7 +118,17 @@ extension MessageReceiver { variant: variant, body: message.text, timestampMs: Int64(messageSentTimestamp * 1000), - wasRead: (variant == .standardOutgoing), // Auto-mark sent messages as read + wasRead: ( + // Auto-mark sent messages or messages older than the 'lastReadTimestampMs' as read + variant == .standardOutgoing || + SessionUtil.timestampAlreadyRead( + threadId: thread.id, + threadVariant: thread.variant, + timestampMs: Int64(messageSentTimestamp * 1000), + userPublicKey: currentUserPublicKey, + openGroup: maybeOpenGroup + ) + ), hasMention: Interaction.isUserMentioned( db, threadId: thread.id, @@ -383,7 +393,7 @@ extension MessageReceiver { ).save(db) } - case .closedGroup: + case .legacyClosedGroup, .closedGroup: try GroupMember .filter(GroupMember.Columns.groupId == thread.id) .fetchAll(db) @@ -410,6 +420,7 @@ extension MessageReceiver { db, interactionId: interactionId, threadId: thread.id, + threadVariant: thread.variant, includingOlder: true, trySendReadReceipt: true ) diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift index 331118c74..a8b71888b 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift @@ -40,7 +40,7 @@ extension MessageSender { do { // Create the relevant objects in the database thread = try SessionThread - .fetchOrCreate(db, id: groupPublicKey, variant: .closedGroup) + .fetchOrCreate(db, id: groupPublicKey, variant: .legacyClosedGroup) try ClosedGroup( threadId: groupPublicKey, name: name, @@ -81,7 +81,7 @@ extension MessageSender { threadId: thread.id, authorId: userPublicKey, variant: .infoClosedGroupCreated, - timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)) + timestampMs: SnodeAPI.currentOffsetTimestampMs() ).inserted(db) memberSendData = try members @@ -173,7 +173,7 @@ extension MessageSender { threadId: closedGroup.threadId, publicKey: legacyNewKeyPair.publicKey, secretKey: legacyNewKeyPair.privateKey, - receivedTimestamp: Date().timeIntervalSince1970 + receivedTimestamp: (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) ) // Distribute it diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index 64722160e..82557517d 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -289,7 +289,7 @@ public enum MessageReceiver { // Note: We don't want to create a thread for a closed group if it doesn't exist if (try? SessionThread.exists(db, id: groupPublicKey)) != true { return nil } - return (groupPublicKey, .closedGroup) + return (groupPublicKey, .legacyClosedGroup) } // Extract the 'syncTarget' value if there is one diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index 72a73da04..754ecb2e6 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -974,7 +974,7 @@ public final class MessageSender { to: .contact(publicKey: userPublicKey), interactionId: interactionId, userPublicKey: userPublicKey, - messageSendTimestamp: Int64(floor(Date().timeIntervalSince1970 * 1000)), + messageSendTimestamp: SnodeAPI.currentOffsetTimestampMs(), isSyncMessage: true ), using: dependencies diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift index ed88e7edc..d420e709f 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift @@ -8,7 +8,9 @@ import SessionSnodeKit import SessionUtilitiesKit public final class CurrentUserPoller: Poller { - public static var namespaces: [SnodeAPI.Namespace] = [.default, .configUserProfile, .configContacts] + public static var namespaces: [SnodeAPI.Namespace] = [ + .default, .configUserProfile, .configContacts, .configConvoInfoVolatile, .configGroups + ] private var targetSnode: Atomic = Atomic(nil) private var usedSnodes: Atomic> = Atomic([]) diff --git a/SessionMessagingKit/Sending & Receiving/Typing Indicators/TypingIndicators.swift b/SessionMessagingKit/Sending & Receiving/Typing Indicators/TypingIndicators.swift index afebe2c10..97d5957cb 100644 --- a/SessionMessagingKit/Sending & Receiving/Typing Indicators/TypingIndicators.swift +++ b/SessionMessagingKit/Sending & Receiving/Typing Indicators/TypingIndicators.swift @@ -38,7 +38,11 @@ public class TypingIndicators { } // Don't send typing indicators in group threads - guard threadVariant != .closedGroup && threadVariant != .openGroup else { return nil } + guard + threadVariant != .legacyClosedGroup && + threadVariant != .closedGroup && + threadVariant != .openGroup + else { return nil } self.threadId = threadId self.direction = direction diff --git a/SessionMessagingKit/Shared Models/MessageViewModel.swift b/SessionMessagingKit/Shared Models/MessageViewModel.swift index 99664cdeb..129553a42 100644 --- a/SessionMessagingKit/Shared Models/MessageViewModel.swift +++ b/SessionMessagingKit/Shared Models/MessageViewModel.swift @@ -303,6 +303,11 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable, case (false, true): return (.bottom, isOnlyMessageInCluster) } }() + let isGroupThread: Bool = ( + self.threadVariant == .openGroup || + self.threadVariant == .legacyClosedGroup || + self.threadVariant == .closedGroup + ) return ViewModel( threadId: self.threadId, @@ -363,9 +368,7 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable, authorName: authorDisplayName, senderName: { // Only show for group threads - guard self.threadVariant == .openGroup || self.threadVariant == .closedGroup else { - return nil - } + guard isGroupThread else { return nil } // Only show for incoming messages guard self.variant == .standardIncoming || self.variant == .standardIncomingDeleted else { @@ -381,7 +384,7 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable, }(), shouldShowProfile: ( // Only group threads - (self.threadVariant == .openGroup || self.threadVariant == .closedGroup) && + isGroupThread && // Only incoming messages (self.variant == .standardIncoming || self.variant == .standardIncomingDeleted) && diff --git a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift index 616b7c15a..ff45afd9f 100644 --- a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift +++ b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift @@ -100,7 +100,7 @@ public struct SessionThreadViewModel: FetchableRecordWithRowId, Decodable, Equat public var canWrite: Bool { switch threadVariant { case .contact: return true - case .closedGroup: return currentUserIsClosedGroupMember == true + case .legacyClosedGroup, .closedGroup: return currentUserIsClosedGroupMember == true case .openGroup: return openGroupPermissions?.contains(.write) ?? false } } @@ -158,14 +158,15 @@ public struct SessionThreadViewModel: FetchableRecordWithRowId, Decodable, Equat public var profile: Profile? { switch threadVariant { case .contact: return contactProfile - case .closedGroup: return (closedGroupProfileBack ?? closedGroupProfileBackFallback) + case .legacyClosedGroup, .closedGroup: + return (closedGroupProfileBack ?? closedGroupProfileBackFallback) case .openGroup: return nil } } public var additionalProfile: Profile? { switch threadVariant { - case .closedGroup: return closedGroupProfileFront + case .legacyClosedGroup, .closedGroup: return closedGroupProfileFront default: return nil } } @@ -190,7 +191,7 @@ public struct SessionThreadViewModel: FetchableRecordWithRowId, Decodable, Equat public var userCount: Int? { switch threadVariant { case .contact: return nil - case .closedGroup: return closedGroupUserCount + case .legacyClosedGroup, .closedGroup: return closedGroupUserCount case .openGroup: return openGroupUserCount } } @@ -1256,7 +1257,10 @@ public extension SessionThreadViewModel { LEFT JOIN \(Profile.self) AS \(ViewModel.contactProfileKey) ON false LEFT JOIN \(OpenGroup.self) ON false - WHERE \(SQL("\(thread[.variant]) = \(SessionThread.Variant.closedGroup)")) + WHERE ( + \(SQL("\(thread[.variant]) = \(SessionThread.Variant.legacyClosedGroup)")) OR + \(SQL("\(thread[.variant]) = \(SessionThread.Variant.closedGroup)")) + ) GROUP BY \(thread[.id]) """ diff --git a/SessionNotificationServiceExtension/NSENotificationPresenter.swift b/SessionNotificationServiceExtension/NSENotificationPresenter.swift index eb74f0f77..63a6916a1 100644 --- a/SessionNotificationServiceExtension/NSENotificationPresenter.swift +++ b/SessionNotificationServiceExtension/NSENotificationPresenter.swift @@ -26,7 +26,7 @@ public class NSENotificationPresenter: NSObject, NotificationsProtocol { ) var notificationTitle: String = senderName - if thread.variant == .closedGroup || thread.variant == .openGroup { + if thread.variant == .legacyClosedGroup || thread.variant == .closedGroup || thread.variant == .openGroup { if thread.onlyNotifyForMentions && !interaction.hasMention { // Ignore PNs if the group is set to only notify for mentions return @@ -127,7 +127,11 @@ public class NSENotificationPresenter: NSObject, NotificationsProtocol { public func notifyUser(_ db: Database, forIncomingCall interaction: Interaction, in thread: SessionThread) { // No call notifications for muted or group threads guard Date().timeIntervalSince1970 > (thread.mutedUntilTimestamp ?? 0) else { return } - guard thread.variant != .closedGroup && thread.variant != .openGroup else { return } + guard + thread.variant != .legacyClosedGroup && + thread.variant != .closedGroup && + thread.variant != .openGroup + else { return } guard interaction.variant == .infoCall, let infoMessageData: Data = (interaction.body ?? "").data(using: .utf8), @@ -181,7 +185,11 @@ public class NSENotificationPresenter: NSObject, NotificationsProtocol { // No reaction notifications for muted, group threads or message requests guard Date().timeIntervalSince1970 > (thread.mutedUntilTimestamp ?? 0) else { return } - guard thread.variant != .closedGroup && thread.variant != .openGroup else { return } + guard + thread.variant != .legacyClosedGroup && + thread.variant != .closedGroup && + thread.variant != .openGroup + else { return } guard !isMessageRequest else { return } let senderName: String = Profile.displayName(db, id: reaction.authorId, threadVariant: thread.variant) diff --git a/SessionSnodeKit/Types/SnodeAPINamespace.swift b/SessionSnodeKit/Types/SnodeAPINamespace.swift index 17fc93056..d5f559217 100644 --- a/SessionSnodeKit/Types/SnodeAPINamespace.swift +++ b/SessionSnodeKit/Types/SnodeAPINamespace.swift @@ -8,6 +8,8 @@ public extension SnodeAPI { case configUserProfile = 2 case configContacts = 3 + case configConvoInfoVolatile = 4 + case configGroups = 5 case configClosedGroupInfo = 11 case legacyClosedGroup = -10 diff --git a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift index 02b7f9817..e88705002 100644 --- a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift +++ b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift @@ -429,14 +429,14 @@ class ThreadSettingsViewModelSpec: QuickSpec { try SessionThread( id: "TestId", - variant: .closedGroup + variant: .legacyClosedGroup ).insert(db) } viewModel = ThreadSettingsViewModel( dependencies: dependencies, threadId: "TestId", - threadVariant: .closedGroup, + threadVariant: .legacyClosedGroup, didTriggerSearch: { didTriggerSearchCallbackTriggered = true } diff --git a/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift b/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift index 6348bbdf0..e4250cb4d 100644 --- a/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift +++ b/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift @@ -219,7 +219,7 @@ public final class ProfilePictureView: UIView { imageViewHeightConstraint.constant = self.size imageContainerView.layer.cornerRadius = (self.size / 2) - case .closedGroup: + case .legacyClosedGroup, .closedGroup: guard !publicKey.isEmpty else { return } // If the `publicKey` we were given matches the first profile id then we have From 345b693225dc22b139f088c837b2da1dde527b2e Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 1 Feb 2023 18:12:36 +1100 Subject: [PATCH 029/135] Fixed build issues, bugs, added unit tests and added the ConvoInfoVolatile handling Added the unit tests for the ConvoInfoVolatile Added icons to the swipe actions Updated jobs to be able to be prioritised (and added priorities to the launch jobs to avoid some odd behaviours) Fixed some build issues resulting from merging Fixed an issue with the open group pubkey encoding Fixed an issue where an imageView could get it's image set on a background thread Fixed a bug where the swipe actions weren't getting theming applied when the theme changed Fixed a bug where scheduling code after the next db transaction completes couldn't be nested (resulting in code not running) Fixed a bug where the PagedDataObserver might not notify of unobserved changes if they reverted previous unobserved changes Fixed a couple of incorrect SQL ordering use cases (was overriding instead of appending ordering) Fixed an issue where the app would re-upload the avatar every launch (only affected this branch) Fixed an issue where the home screen wouldn't update group avatars when their profile data changed --- Session.xcodeproj/project.pbxproj | 12 + Session/Closed Groups/EditClosedGroupVC.swift | 21 +- .../Conversations/ConversationSearch.swift | 28 +- .../ConversationVC+Interaction.swift | 12 +- Session/Conversations/ConversationVC.swift | 96 ++++-- .../Conversations/ConversationViewModel.swift | 85 ++--- .../GlobalSearchViewController.swift | 22 +- Session/Home/HomeVC.swift | 119 ++++++- Session/Home/HomeViewModel.swift | 87 ++++- .../MessageRequestsViewController.swift | 30 +- .../MessageRequestsViewModel.swift | 15 +- .../MediaGalleryViewModel.swift | 5 +- .../MediaPageViewController.swift | 6 +- Session/Meta/SessionApp.swift | 8 +- .../Translations/de.lproj/Localizable.strings | 2 + .../Translations/en.lproj/Localizable.strings | 2 + .../Translations/es.lproj/Localizable.strings | 2 + .../Translations/fa.lproj/Localizable.strings | 2 + .../Translations/fi.lproj/Localizable.strings | 2 + .../Translations/fr.lproj/Localizable.strings | 2 + .../Translations/hi.lproj/Localizable.strings | 2 + .../Translations/hr.lproj/Localizable.strings | 2 + .../id-ID.lproj/Localizable.strings | 2 + .../Translations/it.lproj/Localizable.strings | 2 + .../Translations/ja.lproj/Localizable.strings | 2 + .../Translations/nl.lproj/Localizable.strings | 2 + .../Translations/pl.lproj/Localizable.strings | 2 + .../pt_BR.lproj/Localizable.strings | 2 + .../Translations/ru.lproj/Localizable.strings | 2 + .../Translations/si.lproj/Localizable.strings | 2 + .../Translations/sk.lproj/Localizable.strings | 2 + .../Translations/sv.lproj/Localizable.strings | 2 + .../Translations/th.lproj/Localizable.strings | 2 + .../vi-VN.lproj/Localizable.strings | 2 + .../zh-Hant.lproj/Localizable.strings | 2 + .../zh_CN.lproj/Localizable.strings | 2 + Session/Open Groups/JoinOpenGroupVC.swift | 2 +- Session/Settings/SettingsViewModel.swift | 4 +- Session/Shared/FullConversationCell.swift | 65 +++- Session/Shared/SessionTableViewModel.swift | 4 - SessionMessagingKit/Configuration.swift | 4 +- .../Database/Models/Attachment.swift | 2 +- .../Database/Models/Interaction.swift | 21 +- .../Jobs/Types/ConfigurationSyncJob.swift | 9 + .../SessionUtil+Contacts.swift | 4 +- .../SessionUtil+ConvoInfoVolatile.swift | 54 +-- .../SessionUtil+UserProfile.swift | 39 ++- .../QueryInterfaceRequest+Utilities.swift | 22 +- .../LibSessionUtil/SessionUtil.swift | 67 ++-- .../Open Groups/OpenGroupManager.swift | 2 +- .../Sending & Receiving/MessageReceiver.swift | 4 +- .../SessionThreadViewModel.swift | 103 +++++- .../Utilities/ProfileManager.swift | 15 +- .../LibSessionUtil/ConfigContactsSpec.swift | 3 +- .../ConfigConvoInfoVolatileSpec.swift | 314 ++++++++++++++++++ .../Open Groups/OpenGroupManagerSpec.swift | 4 +- .../_TestUtilities/TestOnionRequestAPI.swift | 4 +- SessionSnodeKit/Configuration.swift | 3 +- SessionSnodeKit/Database/Models/Snode.swift | 6 +- SessionUIKit/Configuration.swift | 2 +- .../Themes/Theme+ClassicDark.swift | 1 + .../Themes/Theme+ClassicLight.swift | 1 + .../Style Guide/Themes/Theme+OceanDark.swift | 1 + .../Style Guide/Themes/Theme+OceanLight.swift | 1 + SessionUIKit/Style Guide/Themes/Theme.swift | 1 + .../Style Guide/Themes/UIKit+Theme.swift | 11 +- .../UIContextualAction+Theming.swift | 176 ++++++++++ SessionUtilitiesKit/Configuration.swift | 4 +- .../Migrations/_004_AddJobPriority.swift | 42 +++ SessionUtilitiesKit/Database/Models/Job.swift | 26 +- .../Types/PagedDatabaseObserver.swift | 16 +- .../Utilities/Database+Utilities.swift | 89 +++++ .../General/Dictionary+Utilities.swift | 6 + SessionUtilitiesKit/JobRunner/JobRunner.swift | 19 +- 74 files changed, 1451 insertions(+), 290 deletions(-) create mode 100644 SessionMessagingKitTests/LibSessionUtil/ConfigConvoInfoVolatileSpec.swift create mode 100644 SessionUIKit/Utilities/UIContextualAction+Theming.swift create mode 100644 SessionUtilitiesKit/Database/Migrations/_004_AddJobPriority.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index f3aa34610..f42e21826 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -752,6 +752,9 @@ FDB4BBC92839BEF000B7C95D /* ProfileManagerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB4BBC82839BEF000B7C95D /* ProfileManagerError.swift */; }; FDB7400B28EB99A70094D718 /* TimeInterval+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB7400A28EB99A70094D718 /* TimeInterval+Utilities.swift */; }; FDB7400D28EBEC240094D718 /* DateHeaderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB7400C28EBEC240094D718 /* DateHeaderCell.swift */; }; + FDBB25E12983909300F1508E /* ConfigConvoInfoVolatileSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDBB25E02983909300F1508E /* ConfigConvoInfoVolatileSpec.swift */; }; + FDBB25E32988B13800F1508E /* _004_AddJobPriority.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDBB25E22988B13800F1508E /* _004_AddJobPriority.swift */; }; + FDBB25E72988BBBE00F1508E /* UIContextualAction+Theming.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDBB25E62988BBBD00F1508E /* UIContextualAction+Theming.swift */; }; FDC2908727D7047F005DAE71 /* RoomSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2908627D7047F005DAE71 /* RoomSpec.swift */; }; FDC2908927D70656005DAE71 /* RoomPollInfoSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2908827D70656005DAE71 /* RoomPollInfoSpec.swift */; }; FDC2908B27D707F3005DAE71 /* SendMessageRequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2908A27D707F3005DAE71 /* SendMessageRequestSpec.swift */; }; @@ -1866,6 +1869,9 @@ FDB4BBC82839BEF000B7C95D /* ProfileManagerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileManagerError.swift; sourceTree = ""; }; FDB7400A28EB99A70094D718 /* TimeInterval+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimeInterval+Utilities.swift"; sourceTree = ""; }; FDB7400C28EBEC240094D718 /* DateHeaderCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateHeaderCell.swift; sourceTree = ""; }; + FDBB25E02983909300F1508E /* ConfigConvoInfoVolatileSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigConvoInfoVolatileSpec.swift; sourceTree = ""; }; + FDBB25E22988B13800F1508E /* _004_AddJobPriority.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _004_AddJobPriority.swift; sourceTree = ""; }; + FDBB25E62988BBBD00F1508E /* UIContextualAction+Theming.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIContextualAction+Theming.swift"; sourceTree = ""; }; FDC2908627D7047F005DAE71 /* RoomSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSpec.swift; sourceTree = ""; }; FDC2908827D70656005DAE71 /* RoomPollInfoSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPollInfoSpec.swift; sourceTree = ""; }; FDC2908A27D707F3005DAE71 /* SendMessageRequestSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMessageRequestSpec.swift; sourceTree = ""; }; @@ -2881,6 +2887,7 @@ B885D5F52334A32100EE0D8E /* UIView+Constraints.swift */, C33100272559000A00070591 /* UIView+Utilities.swift */, FD71161F28D97ABC00B47552 /* UIImage+Tinting.swift */, + FDBB25E62988BBBD00F1508E /* UIContextualAction+Theming.swift */, ); path = Utilities; sourceTree = ""; @@ -3695,6 +3702,7 @@ FD17D7C927F546D900122BE0 /* _001_InitialSetupMigration.swift */, FD9004132818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift */, FD17D7E627F6A16700122BE0 /* _003_YDBToGRDBMigration.swift */, + FDBB25E22988B13800F1508E /* _004_AddJobPriority.swift */, ); path = Migrations; sourceTree = ""; @@ -4034,6 +4042,7 @@ children = ( FD2B4AFA29429D1000AB4848 /* ConfigContactsSpec.swift */, FD8ECF812934387A00C0D1BB /* ConfigUserProfileSpec.swift */, + FDBB25E02983909300F1508E /* ConfigConvoInfoVolatileSpec.swift */, ); path = LibSessionUtil; sourceTree = ""; @@ -5301,6 +5310,7 @@ FD37E9CF28A1EB1B003AE748 /* Theme.swift in Sources */, C331FFB92558FA8D00070591 /* UIView+Constraints.swift in Sources */, FD37E9F628A5F106003AE748 /* Configuration.swift in Sources */, + FDBB25E72988BBBE00F1508E /* UIContextualAction+Theming.swift in Sources */, C331FFE02558FB0000070591 /* SearchBar.swift in Sources */, FD71162C28E1451400B47552 /* Position.swift in Sources */, FD52090328B4680F006098F6 /* RadioButton.swift in Sources */, @@ -5487,6 +5497,7 @@ FD17D7C127F5200100122BE0 /* TypedTableDefinition.swift in Sources */, FD17D7EA27F6A1C600122BE0 /* SUKLegacy.swift in Sources */, FDA8EB10280F8238002B68E5 /* Codable+Utilities.swift in Sources */, + FDBB25E32988B13800F1508E /* _004_AddJobPriority.swift in Sources */, C352A36D2557858E00338F3E /* NSTimer+Proxying.m in Sources */, 7B7CB192271508AD0079FF93 /* CallRingTonePlayer.swift in Sources */, C3C2ABD22553C6C900C340D1 /* Data+SecureRandom.swift in Sources */, @@ -6017,6 +6028,7 @@ FDC290B327DFF9F5005DAE71 /* TestOnionRequestAPI.swift in Sources */, FDC2909127D709CA005DAE71 /* SOGSMessageSpec.swift in Sources */, FD3C906A27E417CE00CD579F /* SodiumUtilitiesSpec.swift in Sources */, + FDBB25E12983909300F1508E /* ConfigConvoInfoVolatileSpec.swift in Sources */, FD3C907127E445E500CD579F /* MessageReceiverDecryptionSpec.swift in Sources */, FDC2909627D71252005DAE71 /* SOGSErrorSpec.swift in Sources */, FDC2908727D7047F005DAE71 /* RoomSpec.swift in Sources */, diff --git a/Session/Closed Groups/EditClosedGroupVC.swift b/Session/Closed Groups/EditClosedGroupVC.swift index 8a9a74e1f..b866fb510 100644 --- a/Session/Closed Groups/EditClosedGroupVC.swift +++ b/Session/Closed Groups/EditClosedGroupVC.swift @@ -244,12 +244,26 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat return adminIds.contains(userPublicKey) } + func tableView(_ tableView: UITableView, willBeginEditingRowAt indexPath: IndexPath) { + UIContextualAction.willBeginEditing(indexPath: indexPath, tableView: tableView) + } + + func tableView(_ tableView: UITableView, didEndEditingRowAt indexPath: IndexPath?) { + UIContextualAction.didEndEditing(indexPath: indexPath, tableView: tableView) + } + func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { let profileId: String = self.membersAndZombies[indexPath.row].profileId let delete: UIContextualAction = UIContextualAction( - style: .destructive, - title: "GROUP_ACTION_REMOVE".localized() + title: "GROUP_ACTION_REMOVE".localized(), + icon: UIImage(named: "icon_bin"), + themeTintColor: .textPrimary, + themeBackgroundColor: .conversationButton_swipeDestructive, + side: .trailing, + actionIndex: 0, + indexPath: indexPath, + tableView: tableView ) { [weak self] _, _, completionHandler in self?.adminIds.remove(profileId) self?.membersAndZombies.remove(at: indexPath.row) @@ -257,7 +271,6 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat completionHandler(true) } - delete.themeBackgroundColor = .conversationButton_swipeDestructive return UISwipeActionsConfiguration(actions: [ delete ]) } @@ -286,7 +299,7 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat } private func handleMembersChanged() { - tableViewHeightConstraint.constant = CGFloat(membersAndZombies.count) * 67 + tableViewHeightConstraint.constant = CGFloat(membersAndZombies.count) * 72 tableView.reloadData() } diff --git a/Session/Conversations/ConversationSearch.swift b/Session/Conversations/ConversationSearch.swift index 2b6f7e96a..c98e650f9 100644 --- a/Session/Conversations/ConversationSearch.swift +++ b/Session/Conversations/ConversationSearch.swift @@ -84,7 +84,7 @@ extension ConversationSearchController: UISearchResultsUpdating { let threadId: String = self.threadId DispatchQueue.global(qos: .default).async { [weak self] in - let results: [Int64]? = Storage.shared.read { db -> [Int64] in + let results: [Interaction.TimestampInfo]? = Storage.shared.read { db -> [Interaction.TimestampInfo] in self?.resultsBar.willStartSearching(readConnection: db) return try Interaction.idsForTermWithin( @@ -97,7 +97,7 @@ extension ConversationSearchController: UISearchResultsUpdating { // If we didn't get results back then we most likely interrupted the query so // should ignore the results (if there are no results we would succeed and get // an empty array back) - guard let results: [Int64] = results else { return } + guard let results: [Interaction.TimestampInfo] = results else { return } DispatchQueue.main.async { guard let strongSelf = self else { return } @@ -116,11 +116,11 @@ extension ConversationSearchController: SearchResultsBarDelegate { func searchResultsBar( _ searchResultsBar: SearchResultsBar, setCurrentIndex currentIndex: Int, - results: [Int64] + results: [Interaction.TimestampInfo] ) { - guard let interactionId: Int64 = results[safe: currentIndex] else { return } + guard let interactionInfo: Interaction.TimestampInfo = results[safe: currentIndex] else { return } - self.delegate?.conversationSearchController(self, didSelectInteractionId: interactionId) + self.delegate?.conversationSearchController(self, didSelectInteractionInfo: interactionInfo) } } @@ -128,13 +128,13 @@ protocol SearchResultsBarDelegate: AnyObject { func searchResultsBar( _ searchResultsBar: SearchResultsBar, setCurrentIndex currentIndex: Int, - results: [Int64] + results: [Interaction.TimestampInfo] ) } public final class SearchResultsBar: UIView { private var readConnection: Atomic = Atomic(nil) - private var results: Atomic<[Int64]?> = Atomic(nil) + private var results: Atomic<[Interaction.TimestampInfo]?> = Atomic(nil) var currentIndex: Int? weak var resultsBarDelegate: SearchResultsBarDelegate? @@ -249,7 +249,7 @@ public final class SearchResultsBar: UIView { // MARK: - Actions @objc public func handleUpButtonTapped() { - guard let results: [Int64] = results.wrappedValue else { return } + guard let results: [Interaction.TimestampInfo] = results.wrappedValue else { return } guard let currentIndex: Int = currentIndex else { return } guard currentIndex + 1 < results.count else { return } @@ -261,7 +261,7 @@ public final class SearchResultsBar: UIView { @objc public func handleDownButtonTapped() { Logger.debug("") - guard let results: [Int64] = results.wrappedValue else { return } + guard let results: [Interaction.TimestampInfo] = results.wrappedValue else { return } guard let currentIndex: Int = currentIndex, currentIndex > 0 else { return } let newIndex = currentIndex - 1 @@ -288,12 +288,12 @@ public final class SearchResultsBar: UIView { self.readConnection.mutate { $0 = readConnection } } - func updateResults(results: [Int64]?) { + func updateResults(results: [Interaction.TimestampInfo]?) { // We want to ignore search results that don't match the current searchId (this // will happen when searching large threads with short terms as the shorter terms // will take much longer to resolve than the longer terms) currentIndex = { - guard let results: [Int64] = results, !results.isEmpty else { return nil } + guard let results: [Interaction.TimestampInfo] = results, !results.isEmpty else { return nil } if let currentIndex: Int = currentIndex { return max(0, min(currentIndex, results.count - 1)) @@ -313,7 +313,7 @@ public final class SearchResultsBar: UIView { } func updateBarItems() { - guard let results: [Int64] = results.wrappedValue else { + guard let results: [Interaction.TimestampInfo] = results.wrappedValue else { label.text = "" downButton.isEnabled = false upButton.isEnabled = false @@ -363,6 +363,6 @@ public final class SearchResultsBar: UIView { // MARK: - ConversationSearchControllerDelegate public protocol ConversationSearchControllerDelegate: UISearchControllerDelegate { - func conversationSearchController(_ conversationSearchController: ConversationSearchController, didUpdateSearchResults results: [Int64]?, searchText: String?) - func conversationSearchController(_ conversationSearchController: ConversationSearchController, didSelectInteractionId: Int64) + func conversationSearchController(_ conversationSearchController: ConversationSearchController, didUpdateSearchResults results: [Interaction.TimestampInfo]?, searchText: String?) + func conversationSearchController(_ conversationSearchController: ConversationSearchController, didSelectInteractionInfo: Interaction.TimestampInfo) } diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index bfe97c1dc..53e6331c3 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -998,16 +998,18 @@ extension ConversationVC: case .textOnlyMessage: if let quote: Quote = cellViewModel.quote { // Scroll to the original quoted message - let maybeOriginalInteractionId: Int64? = Storage.shared.read { db in + let maybeOriginalInteractionInfo: Interaction.TimestampInfo? = Storage.shared.read { db in try quote.originalInteraction - .select(.id) - .asRequest(of: Int64.self) + .select(.id, .timestampMs) + .asRequest(of: Interaction.TimestampInfo.self) .fetchOne(db) } - guard let interactionId: Int64 = maybeOriginalInteractionId else { return } + guard let interactionInfo: Interaction.TimestampInfo = maybeOriginalInteractionInfo else { + return + } - self.scrollToInteractionIfNeeded(with: interactionId, highlight: true) + self.scrollToInteractionIfNeeded(with: interactionInfo, highlight: true) } else if let linkPreview: LinkPreview = cellViewModel.linkPreview { switch linkPreview.variant { diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index dbb8c9f7e..a469e14b4 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -25,7 +25,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl /// never have disappeared before - this is only needed for value observers since they run asynchronously) private var hasReloadedThreadDataAfterDisappearance: Bool = true - var focusedInteractionId: Int64? + var focusedInteractionInfo: Interaction.TimestampInfo? var shouldHighlightNextScrollToInteraction: Bool = false // Search @@ -331,8 +331,8 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl // MARK: - Initialization - init(threadId: String, threadVariant: SessionThread.Variant, focusedInteractionId: Int64? = nil) { - self.viewModel = ConversationViewModel(threadId: threadId, threadVariant: threadVariant, focusedInteractionId: focusedInteractionId) + init(threadId: String, threadVariant: SessionThread.Variant, focusedInteractionInfo: Interaction.TimestampInfo? = nil) { + self.viewModel = ConversationViewModel(threadId: threadId, threadVariant: threadVariant, focusedInteractionInfo: focusedInteractionInfo) Storage.shared.addObserver(viewModel.pagedDataObserver) @@ -436,6 +436,12 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl name: UIApplication.userDidTakeScreenshotNotification, object: nil ) + + // The first time the view loads we should mark the thread as read (in case it was manually + // marked as unread) - doing this here means if we add a "mark as unread" action within the + // conversation settings then we don't need to worry about the conversation getting marked as + // when when the user returns back through this view controller + self.viewModel.markAsRead(target: .thread, timestampMs: nil) } override func viewWillAppear(_ animated: Bool) { @@ -832,8 +838,8 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl // Animate to the target interaction (or the bottom) after a slightly delay to prevent buggy // animation conflicts - if let focusedInteractionId: Int64 = self.focusedInteractionId { - // If we had a focusedInteractionId then scroll to it (and hide the search + if let focusedInteractionInfo: Interaction.TimestampInfo = self.focusedInteractionInfo { + // If we had a focusedInteractionInfo then scroll to it (and hide the search // result bar loading indicator) let delay: DispatchTime = (didSwapAllContent ? .now() : @@ -843,7 +849,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl DispatchQueue.main.asyncAfter(deadline: delay) { [weak self] in self?.searchController.resultsBar.stopLoading() self?.scrollToInteractionIfNeeded( - with: focusedInteractionId, + with: focusedInteractionInfo, isAnimated: true, highlight: (self?.shouldHighlightNextScrollToInteraction == true) ) @@ -915,13 +921,13 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl self?.tableView.contentOffset.y += oldCellTopOffset } - if let focusedInteractionId: Int64 = self?.focusedInteractionId { + if let focusedInteractionInfo: Interaction.TimestampInfo = self?.focusedInteractionInfo { DispatchQueue.main.async { [weak self] in - // If we had a focusedInteractionId then scroll to it (and hide the search + // If we had a focusedInteractionInfo then scroll to it (and hide the search // result bar loading indicator) self?.searchController.resultsBar.stopLoading() self?.scrollToInteractionIfNeeded( - with: focusedInteractionId, + with: focusedInteractionInfo, isAnimated: true, highlight: (self?.shouldHighlightNextScrollToInteraction == true) ) @@ -935,13 +941,13 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl ) } else if wasLoadingMore { - if let focusedInteractionId: Int64 = self.focusedInteractionId { + if let focusedInteractionInfo: Interaction.TimestampInfo = self.focusedInteractionInfo { DispatchQueue.main.async { [weak self] in - // If we had a focusedInteractionId then scroll to it (and hide the search + // If we had a focusedInteractionInfo then scroll to it (and hide the search // result bar loading indicator) self?.searchController.resultsBar.stopLoading() self?.scrollToInteractionIfNeeded( - with: focusedInteractionId, + with: focusedInteractionInfo, isAnimated: true, highlight: (self?.shouldHighlightNextScrollToInteraction == true) ) @@ -983,8 +989,8 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl // Scroll to the last unread message if possible; otherwise scroll to the bottom. // When the unread message count is more than the number of view items of a page, // the screen will scroll to the bottom instead of the first unread message - if let focusedInteractionId: Int64 = self.viewModel.focusedInteractionId { - self.scrollToInteractionIfNeeded(with: focusedInteractionId, isAnimated: false, highlight: true) + if let focusedInteractionInfo: Interaction.TimestampInfo = self.viewModel.focusedInteractionInfo { + self.scrollToInteractionIfNeeded(with: focusedInteractionInfo, isAnimated: false, highlight: true) } else { self.scrollToBottom(isAnimated: false) @@ -1385,11 +1391,22 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl guard !self.didFinishInitialLayout || !hasNewerItems else { let messages: [MessageViewModel] = self.viewModel.interactionData[messagesSectionIndex].elements - let lastInteractionId: Int64 = self.viewModel.threadData.interactionId - .defaulting(to: messages[messages.count - 1].id) + let lastInteractionInfo: Interaction.TimestampInfo = { + guard + let interactionId: Int64 = self.viewModel.threadData.interactionId, + let timestampMs: Int64 = self.viewModel.threadData.interactionTimestampMs + else { + return Interaction.TimestampInfo( + id: messages[messages.count - 1].id, + timestampMs: messages[messages.count - 1].timestampMs + ) + } + + return Interaction.TimestampInfo(id: interactionId, timestampMs: timestampMs) + }() self.scrollToInteractionIfNeeded( - with: lastInteractionId, + with: lastInteractionInfo, position: .bottom, isJumpingToLastInteraction: true, isAnimated: true @@ -1416,7 +1433,10 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl animated: isAnimated ) - self.viewModel.markAsRead(beforeInclusive: nil) + self.viewModel.markAsRead( + target: .threadAndInteractions(interactionsBeforeInclusive: nil), + timestampMs: nil + ) } func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { @@ -1461,22 +1481,25 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl .last? .cellViewModel { - self.viewModel.markAsRead(beforeInclusive: newestCellViewModel.id) + self.viewModel.markAsRead( + target: .threadAndInteractions(interactionsBeforeInclusive: newestCellViewModel.id), + timestampMs: newestCellViewModel.timestampMs + ) } } func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { guard - let focusedInteractionId: Int64 = self.focusedInteractionId, + let focusedInteractionInfo: Interaction.TimestampInfo = self.focusedInteractionInfo, self.shouldHighlightNextScrollToInteraction else { - self.focusedInteractionId = nil + self.focusedInteractionInfo = nil self.shouldHighlightNextScrollToInteraction = false return } DispatchQueue.main.async { [weak self] in - self?.highlightCellIfNeeded(interactionId: focusedInteractionId) + self?.highlightCellIfNeeded(interactionId: focusedInteractionInfo.id) } } @@ -1590,26 +1613,29 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl hideSearchUI() } - func conversationSearchController(_ conversationSearchController: ConversationSearchController, didUpdateSearchResults results: [Int64]?, searchText: String?) { + func conversationSearchController(_ conversationSearchController: ConversationSearchController, didUpdateSearchResults results: [Interaction.TimestampInfo]?, searchText: String?) { viewModel.lastSearchedText = searchText tableView.reloadRows(at: tableView.indexPathsForVisibleRows ?? [], with: UITableView.RowAnimation.none) } - func conversationSearchController(_ conversationSearchController: ConversationSearchController, didSelectInteractionId interactionId: Int64) { - scrollToInteractionIfNeeded(with: interactionId, highlight: true) + func conversationSearchController(_ conversationSearchController: ConversationSearchController, didSelectInteractionInfo interactionInfo: Interaction.TimestampInfo) { + scrollToInteractionIfNeeded(with: interactionInfo, highlight: true) } func scrollToInteractionIfNeeded( - with interactionId: Int64, + with interactionInfo: Interaction.TimestampInfo, position: UITableView.ScrollPosition = .middle, isJumpingToLastInteraction: Bool = false, isAnimated: Bool = true, highlight: Bool = false ) { // Store the info incase we need to load more data (call will be re-triggered) - self.focusedInteractionId = interactionId + self.focusedInteractionInfo = interactionInfo self.shouldHighlightNextScrollToInteraction = highlight - self.viewModel.markAsRead(beforeInclusive: interactionId) + self.viewModel.markAsRead( + target: .threadAndInteractions(interactionsBeforeInclusive: interactionInfo.id), + timestampMs: interactionInfo.timestampMs + ) // Ensure the target interaction has been loaded guard @@ -1617,7 +1643,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl .firstIndex(where: { $0.model == .messages }), let targetMessageIndex = self.viewModel.interactionData[messageSectionIndex] .elements - .firstIndex(where: { $0.id == interactionId }) + .firstIndex(where: { $0.id == interactionInfo.id }) else { // If not the make sure we have finished the initial layout before trying to // load the up until the specified interaction @@ -1629,13 +1655,13 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl DispatchQueue.global(qos: .userInitiated).async { [weak self] in if isJumpingToLastInteraction { self?.viewModel.pagedDataObserver?.load(.jumpTo( - id: interactionId, + id: interactionInfo.id, paddingForInclusive: 5 )) } else { self?.viewModel.pagedDataObserver?.load(.untilInclusive( - id: interactionId, + id: interactionInfo.id, padding: 5 )) } @@ -1671,12 +1697,12 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl // so it doesn't look buggy with the push transition if highlight { DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(self.didFinishInitialLayout ? 0 : 150)) { [weak self] in - self?.highlightCellIfNeeded(interactionId: interactionId) + self?.highlightCellIfNeeded(interactionId: interactionInfo.id) } } self.shouldHighlightNextScrollToInteraction = false - self.focusedInteractionId = nil + self.focusedInteractionInfo = nil return } @@ -1687,7 +1713,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl let targetRect: CGRect = self.tableView.rectForRow(at: targetIndexPath) guard !self.tableView.bounds.contains(targetRect) else { - self.highlightCellIfNeeded(interactionId: interactionId) + self.highlightCellIfNeeded(interactionId: interactionInfo.id) return } @@ -1696,7 +1722,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl func highlightCellIfNeeded(interactionId: Int64) { self.shouldHighlightNextScrollToInteraction = false - self.focusedInteractionId = nil + self.focusedInteractionInfo = nil // Trigger on the next run loop incase we are still finishing some other animation DispatchQueue.main.async { diff --git a/Session/Conversations/ConversationViewModel.swift b/Session/Conversations/ConversationViewModel.swift index 9cf2ba174..de43ffd5e 100644 --- a/Session/Conversations/ConversationViewModel.swift +++ b/Session/Conversations/ConversationViewModel.swift @@ -34,7 +34,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { public let initialThreadVariant: SessionThread.Variant public var sentMessageBeforeUpdate: Bool = false public var lastSearchedText: String? - public let focusedInteractionId: Int64? // Note: This is used for global search + public let focusedInteractionInfo: Interaction.TimestampInfo? // Note: This is used for global search public lazy var blockedBannerMessage: String = { switch self.threadData.threadVariant { @@ -52,28 +52,30 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { // MARK: - Initialization - init(threadId: String, threadVariant: SessionThread.Variant, focusedInteractionId: Int64?) { + init(threadId: String, threadVariant: SessionThread.Variant, focusedInteractionInfo: Interaction.TimestampInfo?) { // If we have a specified 'focusedInteractionId' then use that, otherwise retrieve the oldest // unread interaction and start focused around that one - let targetInteractionId: Int64? = { - if let focusedInteractionId: Int64 = focusedInteractionId { return focusedInteractionId } + let targetInteractionInfo: Interaction.TimestampInfo? = { + if let focusedInteractionInfo: Interaction.TimestampInfo = focusedInteractionInfo { + return focusedInteractionInfo + } return Storage.shared.read { db in let interaction: TypedTableAlias = TypedTableAlias() return try Interaction - .select(.id) + .select(.id, .timestampMs) .filter(interaction[.wasRead] == false) .filter(interaction[.threadId] == threadId) .order(interaction[.timestampMs].asc) - .asRequest(of: Int64.self) + .asRequest(of: Interaction.TimestampInfo.self) .fetchOne(db) } }() self.threadId = threadId self.initialThreadVariant = threadVariant - self.focusedInteractionId = targetInteractionId + self.focusedInteractionInfo = targetInteractionInfo self.pagedDataObserver = nil // Note: Since this references self we need to finish initializing before setting it, we @@ -89,12 +91,12 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { DispatchQueue.global(qos: .userInitiated).async { [weak self] in // If we don't have a `initialFocusedId` then default to `.pageBefore` (it'll query // from a `0` offset) - guard let initialFocusedId: Int64 = targetInteractionId else { + guard let initialFocusedInfo: Interaction.TimestampInfo = targetInteractionInfo else { self?.pagedDataObserver?.load(.pageBefore) return } - self?.pagedDataObserver?.load(.initialPageAround(id: initialFocusedId)) + self?.pagedDataObserver?.load(.initialPageAround(id: initialFocusedInfo.id)) } } @@ -150,7 +152,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { // MARK: - Interaction Data - private var lastInteractionIdMarkedAsRead: Int64? + private var lastInteractionTimestampMsMarkedAsRead: Int64 = 0 public private(set) var unobservedInteractionDataChanges: ([SectionModel], StagedChangeset<[SectionModel]>)? public private(set) var interactionData: [SectionModel] = [] public private(set) var reactionExpandedInteractionIds: Set = [] @@ -186,7 +188,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { let interaction: TypedTableAlias = TypedTableAlias() let contact: TypedTableAlias = TypedTableAlias() - return SQL("LEFT JOIN \(Contact.self) ON \(contact[.id]) = \(interaction[.threadId])") + return SQL("JOIN \(Contact.self) ON \(contact[.id]) = \(interaction[.threadId])") }() ), PagedData.ObservedChanges( @@ -196,7 +198,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { let interaction: TypedTableAlias = TypedTableAlias() let profile: TypedTableAlias = TypedTableAlias() - return SQL("LEFT JOIN \(Profile.self) ON \(profile[.id]) = \(interaction[.authorId])") + return SQL("JOIN \(Profile.self) ON \(profile[.id]) = \(interaction[.authorId])") }() ) ], @@ -253,7 +255,10 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { currentDataRetriever: { self?.interactionData }, onDataChange: self?.onInteractionChange, onUnobservedDataChange: { updatedData, changeset in - self?.unobservedInteractionDataChanges = (updatedData, changeset) + self?.unobservedInteractionDataChanges = (changeset.isEmpty ? + nil : + (updatedData, changeset) + ) } ) } @@ -389,36 +394,34 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { } } - /// This method will mark all interactions as read before the specified interaction id, if no id is provided then all interactions for - /// the thread will be marked as read - public func markAsRead(beforeInclusive interactionId: Int64?) { + /// This method marks a thread as read and depending on the target may also update the interactions within a thread as read + public func markAsRead( + target: SessionThreadViewModel.ReadTarget, + timestampMs: Int64? + ) { /// Since this method now gets triggered when scrolling we want to try to optimise it and avoid busying the database - /// write queue when it isn't needed, in order to do this we: + /// write queue when it isn't needed, in order to do this we don't bother marking anything as read if this was called with + /// the same `interactionId` that we previously marked as read (ie. when scrolling and the last message hasn't changed) /// - /// - Don't bother marking anything as read if there are no unread interactions (we can rely on the - /// `threadData.threadUnreadCount` to always be accurate) - /// - Don't bother marking anything as read if this was called with the same `interactionId` that we - /// previously marked as read (ie. when scrolling and the last message hasn't changed) - guard - (self.threadData.threadUnreadCount ?? 0) > 0, - let targetInteractionId: Int64 = (interactionId ?? self.threadData.interactionId), - self.lastInteractionIdMarkedAsRead != targetInteractionId - else { return } - - let threadId: String = self.threadData.threadId - let threadVariant: SessionThread.Variant = self.threadData.threadVariant - let trySendReadReceipt: Bool = (self.threadData.threadIsMessageRequest == false) - self.lastInteractionIdMarkedAsRead = targetInteractionId - - Storage.shared.writeAsync { db in - try Interaction.markAsRead( - db, - interactionId: targetInteractionId, - threadId: threadId, - threadVariant: threadVariant, - includingOlder: true, - trySendReadReceipt: trySendReadReceipt - ) + /// The `ThreadViewModel.markAsRead` method also tries to avoid marking as read if a conversation is already fully read + switch target { + case .thread: self.threadData.markAsRead(target: target) + case .threadAndInteractions: + guard + timestampMs == nil || + self.lastInteractionTimestampMsMarkedAsRead < (timestampMs ?? 0) + else { + self.threadData.markAsRead(target: .thread) + return + } + + // If we were given a timestamp then update the 'lastInteractionTimestampMsMarkedAsRead' + // to avoid needless updates + if let timestampMs: Int64 = timestampMs { + self.lastInteractionTimestampMsMarkedAsRead = timestampMs + } + + self.threadData.markAsRead(target: target) } } diff --git a/Session/Home/GlobalSearch/GlobalSearchViewController.swift b/Session/Home/GlobalSearch/GlobalSearchViewController.swift index 674c3eda3..bf90761b5 100644 --- a/Session/Home/GlobalSearch/GlobalSearchViewController.swift +++ b/Session/Home/GlobalSearch/GlobalSearchViewController.swift @@ -207,7 +207,9 @@ class GlobalSearchViewController: BaseVC, UITableViewDelegate, UITableViewDataSo self?.termForCurrentSearchResultSet = searchText self?.searchResultSet = [ - (hasResults ? nil : [ArraySection(model: .noResults, elements: [SessionThreadViewModel(unreadCount: 0)])]), + (hasResults ? nil : [ + ArraySection(model: .noResults, elements: [SessionThreadViewModel()]) + ]), (hasResults ? sections : nil) ] .compactMap { $0 } @@ -271,15 +273,25 @@ extension GlobalSearchViewController { show( threadId: section.elements[indexPath.row].threadId, threadVariant: section.elements[indexPath.row].threadVariant, - focusedInteractionId: section.elements[indexPath.row].interactionId + focusedInteractionInfo: { + guard + let interactionId: Int64 = section.elements[indexPath.row].interactionId, + let timestampMs: Int64 = section.elements[indexPath.row].interactionTimestampMs + else { return nil } + + return Interaction.TimestampInfo( + id: interactionId, + timestampMs: timestampMs + ) + }() ) } } - private func show(threadId: String, threadVariant: SessionThread.Variant, focusedInteractionId: Int64? = nil, animated: Bool = true) { + private func show(threadId: String, threadVariant: SessionThread.Variant, focusedInteractionInfo: Interaction.TimestampInfo? = nil, animated: Bool = true) { guard Thread.isMainThread else { DispatchQueue.main.async { [weak self] in - self?.show(threadId: threadId, threadVariant: threadVariant, focusedInteractionId: focusedInteractionId, animated: animated) + self?.show(threadId: threadId, threadVariant: threadVariant, focusedInteractionInfo: focusedInteractionInfo, animated: animated) } return } @@ -292,7 +304,7 @@ extension GlobalSearchViewController { .viewControllers) .defaulting(to: []) .appending( - ConversationVC(threadId: threadId, threadVariant: threadVariant, focusedInteractionId: focusedInteractionId) + ConversationVC(threadId: threadId, threadVariant: threadVariant, focusedInteractionInfo: focusedInteractionInfo) ) self.navigationController?.setViewControllers(viewControllers, animated: true) diff --git a/Session/Home/HomeVC.swift b/Session/Home/HomeVC.swift index e5d110c4d..37b722bae 100644 --- a/Session/Home/HomeVC.swift +++ b/Session/Home/HomeVC.swift @@ -610,7 +610,7 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, SeedRemi variant: threadViewModel.threadVariant, isMessageRequest: (threadViewModel.threadIsMessageRequest == true), with: .none, - focusedInteractionId: nil, + focusedInteractionInfo: nil, animated: true ) @@ -622,25 +622,102 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, SeedRemi return true } + func tableView(_ tableView: UITableView, willBeginEditingRowAt indexPath: IndexPath) { + UIContextualAction.willBeginEditing(indexPath: indexPath, tableView: tableView) + } + + func tableView(_ tableView: UITableView, didEndEditingRowAt indexPath: IndexPath?) { + UIContextualAction.didEndEditing(indexPath: indexPath, tableView: tableView) + } + + func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + let section: HomeViewModel.SectionModel = self.viewModel.threadData[indexPath.section] + let unswipeAnimationDelay: DispatchTimeInterval = .milliseconds(500) + + switch section.model { + case .threads: + let threadViewModel: SessionThreadViewModel = section.elements[indexPath.row] + let isUnread: Bool = ( + threadViewModel.threadWasMarkedUnread == true || + (threadViewModel.threadUnreadCount ?? 0) > 0 + ) + let changeReadStatus: UIContextualAction = UIContextualAction( + title: (isUnread ? + "MARK_AS_READ".localized() : + "MARK_AS_UNREAD".localized() + ), + icon: (isUnread ? + UIImage(systemName: "envelope.open") : + UIImage(systemName: "envelope.badge") + ), + themeTintColor: .textPrimary, + themeBackgroundColor: .conversationButton_swipeRead, + side: .leading, + actionIndex: 0, + indexPath: indexPath, + tableView: tableView + ) { [weak self] _, _, completionHandler in + // Delay the change to give the cell "unswipe" animation some time to complete + DispatchQueue.global(qos: .default).asyncAfter(deadline: .now() + unswipeAnimationDelay) { + switch isUnread { + case true: + self?.viewModel.markAsRead( + threadViewModel: threadViewModel, + target: .threadAndInteractions( + interactionsBeforeInclusive: threadViewModel.interactionId + ) + ) + + case false: + self?.viewModel.markAsUnread(threadViewModel: threadViewModel) + } + } + completionHandler(true) + } + + return UISwipeActionsConfiguration(actions: [changeReadStatus]) + + default: return nil + } + } + func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { let section: HomeViewModel.SectionModel = self.viewModel.threadData[indexPath.section] let unswipeAnimationDelay: DispatchTimeInterval = .milliseconds(500) switch section.model { case .messageRequests: - let hide: UIContextualAction = UIContextualAction(style: .destructive, title: "TXT_HIDE_TITLE".localized()) { _, _, completionHandler in + let hide: UIContextualAction = UIContextualAction( + title: "TXT_HIDE_TITLE".localized(), + icon: UIImage(systemName: "eye.slash"), + themeTintColor: .textPrimary, + themeBackgroundColor: .conversationButton_swipeDestructive, + side: .trailing, + actionIndex: 0, + indexPath: indexPath, + tableView: tableView + ) { _, _, completionHandler in Storage.shared.write { db in db[.hasHiddenMessageRequests] = true } completionHandler(true) } - hide.themeBackgroundColor = .conversationButton_swipeDestructive return UISwipeActionsConfiguration(actions: [hide]) case .threads: let threadViewModel: SessionThreadViewModel = section.elements[indexPath.row] + let shouldHaveBlockAction: Bool = ( + threadViewModel.threadVariant == .contact && + !threadViewModel.threadIsNoteToSelf + ) let delete: UIContextualAction = UIContextualAction( - style: .destructive, - title: "TXT_DELETE_TITLE".localized() + title: "TXT_DELETE_TITLE".localized(), + icon: UIImage(named: "icon_bin"), + themeTintColor: .textPrimary, + themeBackgroundColor: .conversationButton_swipeDestructive, + side: .trailing, + actionIndex: 2, + indexPath: indexPath, + tableView: tableView ) { [weak self] _, _, completionHandler in let confirmationModal: ConfirmationModal = ConfirmationModal( info: ConfirmationModal.Info( @@ -668,14 +745,22 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, SeedRemi self?.present(confirmationModal, animated: true, completion: nil) } - delete.themeBackgroundColor = .conversationButton_swipeDestructive let pin: UIContextualAction = UIContextualAction( - style: .normal, title: (threadViewModel.threadIsPinned ? "UNPIN_BUTTON_TEXT".localized() : "PIN_BUTTON_TEXT".localized() - ) + ), + icon: (threadViewModel.threadIsPinned ? + UIImage(systemName: "pin.slash") : + UIImage(systemName: "pin") + ), + themeTintColor: .textPrimary, + themeBackgroundColor: .conversationButton_swipeTertiary, + side: .trailing, + actionIndex: (shouldHaveBlockAction ? 0 : 1), + indexPath: indexPath, + tableView: tableView ) { _, _, completionHandler in (tableView.cellForRow(at: indexPath) as? FullConversationCell)?.optimisticUpdate( isPinned: !threadViewModel.threadIsPinned @@ -691,18 +776,23 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, SeedRemi } } } - pin.themeBackgroundColor = .conversationButton_swipeTertiary - guard threadViewModel.threadVariant == .contact && !threadViewModel.threadIsNoteToSelf else { + guard shouldHaveBlockAction else { return UISwipeActionsConfiguration(actions: [ delete, pin ]) } let block: UIContextualAction = UIContextualAction( - style: .normal, title: (threadViewModel.threadIsBlocked == true ? "BLOCK_LIST_UNBLOCK_BUTTON".localized() : "BLOCK_LIST_BLOCK_BUTTON".localized() - ) + ), + icon: UIImage(named: "table_ic_block"), + themeTintColor: .textPrimary, + themeBackgroundColor: .conversationButton_swipeSecondary, + side: .trailing, + actionIndex: 1, + indexPath: indexPath, + tableView: tableView ) { _, _, completionHandler in (tableView.cellForRow(at: indexPath) as? FullConversationCell)?.optimisticUpdate( isBlocked: (threadViewModel.threadIsBlocked == false) @@ -728,7 +818,6 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, SeedRemi .sinkUntilComplete() } } - block.themeBackgroundColor = .conversationButton_swipeSecondary return UISwipeActionsConfiguration(actions: [ delete, block, pin ]) @@ -749,7 +838,7 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, SeedRemi variant: SessionThread.Variant, isMessageRequest: Bool, with action: ConversationViewModel.Action, - focusedInteractionId: Int64?, + focusedInteractionInfo: Interaction.TimestampInfo?, animated: Bool ) { if let presentedVC = self.presentedViewController { @@ -762,7 +851,7 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, SeedRemi ConversationVC( threadId: threadId, threadVariant: variant, - focusedInteractionId: focusedInteractionId + focusedInteractionInfo: focusedInteractionInfo ) ].compactMap { $0 } diff --git a/Session/Home/HomeViewModel.swift b/Session/Home/HomeViewModel.swift index 23c56e8eb..b48afb71b 100644 --- a/Session/Home/HomeViewModel.swift +++ b/Session/Home/HomeViewModel.swift @@ -64,7 +64,8 @@ public class HomeViewModel { .shouldBeVisible, .isPinned, .mutedUntilTimestamp, - .onlyNotifyForMentions + .onlyNotifyForMentions, + .markedAsUnread ] ), PagedData.ObservedChanges( @@ -76,7 +77,7 @@ public class HomeViewModel { joinToPagedType: { let interaction: TypedTableAlias = TypedTableAlias() - return SQL("LEFT JOIN \(Interaction.self) ON \(interaction[.threadId]) = \(thread[.id])") + return SQL("JOIN \(Interaction.self) ON \(interaction[.threadId]) = \(thread[.id])") }() ), PagedData.ObservedChanges( @@ -85,7 +86,7 @@ public class HomeViewModel { joinToPagedType: { let contact: TypedTableAlias = TypedTableAlias() - return SQL("LEFT JOIN \(Contact.self) ON \(contact[.id]) = \(thread[.id])") + return SQL("JOIN \(Contact.self) ON \(contact[.id]) = \(thread[.id])") }() ), PagedData.ObservedChanges( @@ -93,8 +94,53 @@ public class HomeViewModel { columns: [.name, .nickname, .profilePictureFileName], joinToPagedType: { let profile: TypedTableAlias = TypedTableAlias() + let groupMember: TypedTableAlias = TypedTableAlias() + let threadVariants: [SessionThread.Variant] = [.legacyClosedGroup, .closedGroup] + let targetRole: GroupMember.Role = GroupMember.Role.standard - return SQL("LEFT JOIN \(Profile.self) ON \(profile[.id]) = \(thread[.id])") + return SQL(""" + JOIN \(Profile.self) ON ( + ( -- Contact profile change + \(SQL("\(thread[.variant]) = \(SessionThread.Variant.contact)")) AND + \(profile[.id]) = \(thread[.id]) + ) OR ( -- Closed group profile change + \(SQL("\(thread[.variant]) IN \(threadVariants)")) AND ( + profile.id = ( -- Front profile + SELECT MIN(\(groupMember[.profileId])) + FROM \(GroupMember.self) + JOIN \(Profile.self) ON \(profile[.id]) = \(groupMember[.profileId]) + WHERE ( + \(SQL("\(groupMember[.role]) = \(targetRole)")) AND + \(groupMember[.groupId]) = \(thread[.id]) AND + \(groupMember[.profileId]) != \(userPublicKey) + ) + ) OR + profile.id = ( -- Back profile + SELECT MAX(\(groupMember[.profileId])) + FROM \(GroupMember.self) + JOIN \(Profile.self) ON \(profile[.id]) = \(groupMember[.profileId]) + WHERE ( + \(SQL("\(groupMember[.role]) = \(targetRole)")) AND + \(groupMember[.groupId]) = \(thread[.id]) AND + \(groupMember[.profileId]) != \(userPublicKey) + ) + ) OR ( -- Fallback profile + profile.id = \(userPublicKey) AND + ( + SELECT COUNT(\(groupMember[.profileId])) + FROM \(GroupMember.self) + JOIN \(Profile.self) ON \(profile[.id]) = \(groupMember[.profileId]) + WHERE ( + \(SQL("\(groupMember[.role]) = \(targetRole)")) AND + \(groupMember[.groupId]) = \(thread[.id]) AND + \(groupMember[.profileId]) != \(userPublicKey) + ) + ) = 1 + ) + ) + ) + ) + """) }() ), PagedData.ObservedChanges( @@ -103,7 +149,7 @@ public class HomeViewModel { joinToPagedType: { let closedGroup: TypedTableAlias = TypedTableAlias() - return SQL("LEFT JOIN \(ClosedGroup.self) ON \(closedGroup[.threadId]) = \(thread[.id])") + return SQL("JOIN \(ClosedGroup.self) ON \(closedGroup[.threadId]) = \(thread[.id])") }() ), PagedData.ObservedChanges( @@ -112,7 +158,7 @@ public class HomeViewModel { joinToPagedType: { let openGroup: TypedTableAlias = TypedTableAlias() - return SQL("LEFT JOIN \(OpenGroup.self) ON \(openGroup[.threadId]) = \(thread[.id])") + return SQL("JOIN \(OpenGroup.self) ON \(openGroup[.threadId]) = \(thread[.id])") }() ), PagedData.ObservedChanges( @@ -123,8 +169,8 @@ public class HomeViewModel { let recipientState: TypedTableAlias = TypedTableAlias() return """ - LEFT JOIN \(Interaction.self) ON \(interaction[.threadId]) = \(thread[.id]) - LEFT JOIN \(RecipientState.self) ON \(recipientState[.interactionId]) = \(interaction[.id]) + JOIN \(Interaction.self) ON \(interaction[.threadId]) = \(thread[.id]) + JOIN \(RecipientState.self) ON \(recipientState[.interactionId]) = \(interaction[.id]) """ }() ), @@ -134,7 +180,7 @@ public class HomeViewModel { joinToPagedType: { let typingIndicator: TypedTableAlias = TypedTableAlias() - return SQL("LEFT JOIN \(ThreadTypingIndicator.self) ON \(typingIndicator[.threadId]) = \(thread[.id])") + return SQL("JOIN \(ThreadTypingIndicator.self) ON \(typingIndicator[.threadId]) = \(thread[.id])") }() ) ], @@ -155,7 +201,10 @@ public class HomeViewModel { currentDataRetriever: { self?.threadData }, onDataChange: self?.onThreadChange, onUnobservedDataChange: { updatedData, changeset in - self?.unobservedThreadDataChanges = (updatedData, changeset) + self?.unobservedThreadDataChanges = (changeset.isEmpty ? + nil : + (updatedData, changeset) + ) } ) } @@ -223,8 +272,11 @@ public class HomeViewModel { updatedData: updatedThreadData, currentDataRetriever: { [weak self] in (self?.unobservedThreadDataChanges?.0 ?? self?.threadData) }, onDataChange: onThreadChange, - onUnobservedDataChange: { [weak self] updatedThreadData, changeset in - self?.unobservedThreadDataChanges = (updatedThreadData, changeset) + onUnobservedDataChange: { [weak self] updatedData, changeset in + self?.unobservedThreadDataChanges = (changeset.isEmpty ? + nil : + (updatedData, changeset) + ) } ) } @@ -301,6 +353,17 @@ public class HomeViewModel { // MARK: - Functions + public func markAsRead( + threadViewModel: SessionThreadViewModel, + target: SessionThreadViewModel.ReadTarget + ) { + threadViewModel.markAsRead(target: target) + } + + public func markAsUnread(threadViewModel: SessionThreadViewModel) { + threadViewModel.markAsUnread() + } + public func delete(threadId: String, threadVariant: SessionThread.Variant) { Storage.shared.writeAsync { db in switch threadVariant { diff --git a/Session/Home/Message Requests/MessageRequestsViewController.swift b/Session/Home/Message Requests/MessageRequestsViewController.swift index 0e604ca88..c4cc812a7 100644 --- a/Session/Home/Message Requests/MessageRequestsViewController.swift +++ b/Session/Home/Message Requests/MessageRequestsViewController.swift @@ -390,6 +390,14 @@ class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDat return true } + func tableView(_ tableView: UITableView, willBeginEditingRowAt indexPath: IndexPath) { + UIContextualAction.willBeginEditing(indexPath: indexPath, tableView: tableView) + } + + func tableView(_ tableView: UITableView, didEndEditingRowAt indexPath: IndexPath?) { + UIContextualAction.didEndEditing(indexPath: indexPath, tableView: tableView) + } + func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { let section: MessageRequestsViewModel.SectionModel = self.viewModel.threadData[indexPath.section] @@ -398,8 +406,14 @@ class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDat let threadId: String = section.elements[indexPath.row].threadId let threadVariant: SessionThread.Variant = section.elements[indexPath.row].threadVariant let delete: UIContextualAction = UIContextualAction( - style: .destructive, - title: "TXT_DELETE_TITLE".localized() + title: "TXT_DELETE_TITLE".localized(), + icon: UIImage(named: "icon_bin"), + themeTintColor: .textPrimary, + themeBackgroundColor: .conversationButton_swipeDestructive, + side: .trailing, + actionIndex: (threadVariant == .contact ? 1 : 0), + indexPath: indexPath, + tableView: tableView ) { [weak self] _, _, completionHandler in MessageRequestsViewModel.deleteMessageRequest( threadId: threadId, @@ -408,13 +422,18 @@ class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDat ) completionHandler(true) } - delete.themeBackgroundColor = .conversationButton_swipeDestructive switch threadVariant { case .contact: let block: UIContextualAction = UIContextualAction( - style: .normal, - title: "BLOCK_LIST_BLOCK_BUTTON".localized() + title: "BLOCK_LIST_BLOCK_BUTTON".localized(), + icon: UIImage(named: "table_ic_block"), + themeTintColor: .textPrimary, + themeBackgroundColor: .conversationButton_swipeDestructive, + side: .trailing, + actionIndex: 0, + indexPath: indexPath, + tableView: tableView ) { [weak self] _, _, completionHandler in MessageRequestsViewModel.blockMessageRequest( threadId: threadId, @@ -423,7 +442,6 @@ class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDat ) completionHandler(true) } - block.themeBackgroundColor = .conversationButton_swipeSecondary return UISwipeActionsConfiguration(actions: [ delete, block ]) diff --git a/Session/Home/Message Requests/MessageRequestsViewModel.swift b/Session/Home/Message Requests/MessageRequestsViewModel.swift index 8302e1dd8..33ae824de 100644 --- a/Session/Home/Message Requests/MessageRequestsViewModel.swift +++ b/Session/Home/Message Requests/MessageRequestsViewModel.swift @@ -51,7 +51,7 @@ public class MessageRequestsViewModel { joinToPagedType: { let interaction: TypedTableAlias = TypedTableAlias() - return SQL("LEFT JOIN \(Interaction.self) ON \(interaction[.threadId]) = \(thread[.id])") + return SQL("JOIN \(Interaction.self) ON \(interaction[.threadId]) = \(thread[.id])") }() ), PagedData.ObservedChanges( @@ -60,7 +60,7 @@ public class MessageRequestsViewModel { joinToPagedType: { let contact: TypedTableAlias = TypedTableAlias() - return SQL("LEFT JOIN \(Contact.self) ON \(contact[.id]) = \(thread[.id])") + return SQL("JOIN \(Contact.self) ON \(contact[.id]) = \(thread[.id])") }() ), PagedData.ObservedChanges( @@ -69,7 +69,7 @@ public class MessageRequestsViewModel { joinToPagedType: { let profile: TypedTableAlias = TypedTableAlias() - return SQL("LEFT JOIN \(Profile.self) ON \(profile[.id]) = \(thread[.id])") + return SQL("JOIN \(Profile.self) ON \(profile[.id]) = \(thread[.id])") }() ), PagedData.ObservedChanges( @@ -80,8 +80,8 @@ public class MessageRequestsViewModel { let recipientState: TypedTableAlias = TypedTableAlias() return """ - LEFT JOIN \(Interaction.self) ON \(interaction[.threadId]) = \(thread[.id]) - LEFT JOIN \(RecipientState.self) ON \(recipientState[.interactionId]) = \(interaction[.id]) + JOIN \(Interaction.self) ON \(interaction[.threadId]) = \(thread[.id]) + JOIN \(RecipientState.self) ON \(recipientState[.interactionId]) = \(interaction[.id]) """ }() ) @@ -103,7 +103,10 @@ public class MessageRequestsViewModel { currentDataRetriever: { self?.threadData }, onDataChange: self?.onThreadChange, onUnobservedDataChange: { updatedData, changeset in - self?.unobservedThreadDataChanges = (updatedData, changeset) + self?.unobservedThreadDataChanges = (changeset.isEmpty ? + nil : + (updatedData, changeset) + ) } ) } diff --git a/Session/Media Viewing & Editing/MediaGalleryViewModel.swift b/Session/Media Viewing & Editing/MediaGalleryViewModel.swift index 0cd7cfbc7..8aeb4400a 100644 --- a/Session/Media Viewing & Editing/MediaGalleryViewModel.swift +++ b/Session/Media Viewing & Editing/MediaGalleryViewModel.swift @@ -98,7 +98,10 @@ public class MediaGalleryViewModel { currentDataRetriever: { self?.galleryData }, onDataChange: self?.onGalleryChange, onUnobservedDataChange: { updatedData, changeset in - self?.unobservedGalleryDataChanges = (updatedData, changeset) + self?.unobservedGalleryDataChanges = (changeset.isEmpty ? + nil : + (updatedData, changeset) + ) } ) } diff --git a/Session/Media Viewing & Editing/MediaPageViewController.swift b/Session/Media Viewing & Editing/MediaPageViewController.swift index eb6fd7d9b..218655338 100644 --- a/Session/Media Viewing & Editing/MediaPageViewController.swift +++ b/Session/Media Viewing & Editing/MediaPageViewController.swift @@ -390,7 +390,7 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou viewModel.observableAlbumData, onError: { _ in }, onChange: { [weak self] albumData in - // The defaul scheduler emits changes on the main thread + // The default scheduler emits changes on the main thread self?.handleUpdates(albumData) } ) @@ -922,7 +922,9 @@ extension MediaGalleryViewModel.Item: GalleryRailItem { imageView.contentMode = .scaleAspectFill self.thumbnailImage { [weak imageView] image in - imageView?.image = image + DispatchQueue.main.async { + imageView?.image = image + } } return imageView diff --git a/Session/Meta/SessionApp.swift b/Session/Meta/SessionApp.swift index 14e13aa73..2b9381694 100644 --- a/Session/Meta/SessionApp.swift +++ b/Session/Meta/SessionApp.swift @@ -27,7 +27,7 @@ public struct SessionApp { threadVariant: variant, isMessageRequest: isMessageRequest, action: action, - focusInteractionId: nil, + focusInteractionInfo: nil, animated: animated ) } @@ -37,7 +37,7 @@ public struct SessionApp { threadVariant: SessionThread.Variant, isMessageRequest: Bool, action: ConversationViewModel.Action, - focusInteractionId: Int64?, + focusInteractionInfo: Interaction.TimestampInfo?, animated: Bool ) { guard Thread.isMainThread else { @@ -47,7 +47,7 @@ public struct SessionApp { threadVariant: threadVariant, isMessageRequest: isMessageRequest, action: action, - focusInteractionId: focusInteractionId, + focusInteractionInfo: focusInteractionInfo, animated: animated ) } @@ -59,7 +59,7 @@ public struct SessionApp { variant: threadVariant, isMessageRequest: isMessageRequest, with: action, - focusedInteractionId: focusInteractionId, + focusedInteractionInfo: focusInteractionInfo, animated: animated ) } diff --git a/Session/Meta/Translations/de.lproj/Localizable.strings b/Session/Meta/Translations/de.lproj/Localizable.strings index f87572029..9908dc282 100644 --- a/Session/Meta/Translations/de.lproj/Localizable.strings +++ b/Session/Meta/Translations/de.lproj/Localizable.strings @@ -602,3 +602,5 @@ "MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send"; "REMOVE_AVATAR" = "Remove"; "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; +"MARK_AS_READ" = "Mark Read"; +"MARK_AS_UNREAD" = "Mark Unread"; diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index 65479c6f9..5b6918511 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -602,3 +602,5 @@ "MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send"; "REMOVE_AVATAR" = "Remove"; "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; +"MARK_AS_READ" = "Mark Read"; +"MARK_AS_UNREAD" = "Mark Unread"; diff --git a/Session/Meta/Translations/es.lproj/Localizable.strings b/Session/Meta/Translations/es.lproj/Localizable.strings index 5f03273af..37b302693 100644 --- a/Session/Meta/Translations/es.lproj/Localizable.strings +++ b/Session/Meta/Translations/es.lproj/Localizable.strings @@ -602,3 +602,5 @@ "MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send"; "REMOVE_AVATAR" = "Remove"; "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; +"MARK_AS_READ" = "Mark Read"; +"MARK_AS_UNREAD" = "Mark Unread"; diff --git a/Session/Meta/Translations/fa.lproj/Localizable.strings b/Session/Meta/Translations/fa.lproj/Localizable.strings index ae5cb8e29..7ffaaf430 100644 --- a/Session/Meta/Translations/fa.lproj/Localizable.strings +++ b/Session/Meta/Translations/fa.lproj/Localizable.strings @@ -602,3 +602,5 @@ "MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send"; "REMOVE_AVATAR" = "Remove"; "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; +"MARK_AS_READ" = "Mark Read"; +"MARK_AS_UNREAD" = "Mark Unread"; diff --git a/Session/Meta/Translations/fi.lproj/Localizable.strings b/Session/Meta/Translations/fi.lproj/Localizable.strings index 1725ffa06..919afd67f 100644 --- a/Session/Meta/Translations/fi.lproj/Localizable.strings +++ b/Session/Meta/Translations/fi.lproj/Localizable.strings @@ -602,3 +602,5 @@ "MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send"; "REMOVE_AVATAR" = "Remove"; "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; +"MARK_AS_READ" = "Mark Read"; +"MARK_AS_UNREAD" = "Mark Unread"; diff --git a/Session/Meta/Translations/fr.lproj/Localizable.strings b/Session/Meta/Translations/fr.lproj/Localizable.strings index 1dcc2ab69..b84fed7c9 100644 --- a/Session/Meta/Translations/fr.lproj/Localizable.strings +++ b/Session/Meta/Translations/fr.lproj/Localizable.strings @@ -602,3 +602,5 @@ "MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send"; "REMOVE_AVATAR" = "Remove"; "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; +"MARK_AS_READ" = "Mark Read"; +"MARK_AS_UNREAD" = "Mark Unread"; diff --git a/Session/Meta/Translations/hi.lproj/Localizable.strings b/Session/Meta/Translations/hi.lproj/Localizable.strings index 19cfe5fc6..d86aa5f35 100644 --- a/Session/Meta/Translations/hi.lproj/Localizable.strings +++ b/Session/Meta/Translations/hi.lproj/Localizable.strings @@ -602,3 +602,5 @@ "MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send"; "REMOVE_AVATAR" = "Remove"; "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; +"MARK_AS_READ" = "Mark Read"; +"MARK_AS_UNREAD" = "Mark Unread"; diff --git a/Session/Meta/Translations/hr.lproj/Localizable.strings b/Session/Meta/Translations/hr.lproj/Localizable.strings index 8467c1828..f739a6f41 100644 --- a/Session/Meta/Translations/hr.lproj/Localizable.strings +++ b/Session/Meta/Translations/hr.lproj/Localizable.strings @@ -602,3 +602,5 @@ "MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send"; "REMOVE_AVATAR" = "Remove"; "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; +"MARK_AS_READ" = "Mark Read"; +"MARK_AS_UNREAD" = "Mark Unread"; diff --git a/Session/Meta/Translations/id-ID.lproj/Localizable.strings b/Session/Meta/Translations/id-ID.lproj/Localizable.strings index e686de93b..23768f197 100644 --- a/Session/Meta/Translations/id-ID.lproj/Localizable.strings +++ b/Session/Meta/Translations/id-ID.lproj/Localizable.strings @@ -602,3 +602,5 @@ "MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send"; "REMOVE_AVATAR" = "Remove"; "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; +"MARK_AS_READ" = "Mark Read"; +"MARK_AS_UNREAD" = "Mark Unread"; diff --git a/Session/Meta/Translations/it.lproj/Localizable.strings b/Session/Meta/Translations/it.lproj/Localizable.strings index 34f8e5848..3a0a561b1 100644 --- a/Session/Meta/Translations/it.lproj/Localizable.strings +++ b/Session/Meta/Translations/it.lproj/Localizable.strings @@ -602,3 +602,5 @@ "MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send"; "REMOVE_AVATAR" = "Remove"; "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; +"MARK_AS_READ" = "Mark Read"; +"MARK_AS_UNREAD" = "Mark Unread"; diff --git a/Session/Meta/Translations/ja.lproj/Localizable.strings b/Session/Meta/Translations/ja.lproj/Localizable.strings index 7896258da..3f3ef780d 100644 --- a/Session/Meta/Translations/ja.lproj/Localizable.strings +++ b/Session/Meta/Translations/ja.lproj/Localizable.strings @@ -602,3 +602,5 @@ "MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send"; "REMOVE_AVATAR" = "Remove"; "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; +"MARK_AS_READ" = "Mark Read"; +"MARK_AS_UNREAD" = "Mark Unread"; diff --git a/Session/Meta/Translations/nl.lproj/Localizable.strings b/Session/Meta/Translations/nl.lproj/Localizable.strings index c0f5d0841..72542c722 100644 --- a/Session/Meta/Translations/nl.lproj/Localizable.strings +++ b/Session/Meta/Translations/nl.lproj/Localizable.strings @@ -602,3 +602,5 @@ "MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send"; "REMOVE_AVATAR" = "Remove"; "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; +"MARK_AS_READ" = "Mark Read"; +"MARK_AS_UNREAD" = "Mark Unread"; diff --git a/Session/Meta/Translations/pl.lproj/Localizable.strings b/Session/Meta/Translations/pl.lproj/Localizable.strings index 7449928fa..327053a98 100644 --- a/Session/Meta/Translations/pl.lproj/Localizable.strings +++ b/Session/Meta/Translations/pl.lproj/Localizable.strings @@ -602,3 +602,5 @@ "MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send"; "REMOVE_AVATAR" = "Remove"; "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; +"MARK_AS_READ" = "Mark Read"; +"MARK_AS_UNREAD" = "Mark Unread"; diff --git a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings index 9eccc7fa4..a1ba4766d 100644 --- a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings +++ b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings @@ -602,3 +602,5 @@ "MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send"; "REMOVE_AVATAR" = "Remove"; "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; +"MARK_AS_READ" = "Mark Read"; +"MARK_AS_UNREAD" = "Mark Unread"; diff --git a/Session/Meta/Translations/ru.lproj/Localizable.strings b/Session/Meta/Translations/ru.lproj/Localizable.strings index c7fc95ab5..c65055311 100644 --- a/Session/Meta/Translations/ru.lproj/Localizable.strings +++ b/Session/Meta/Translations/ru.lproj/Localizable.strings @@ -602,3 +602,5 @@ "MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send"; "REMOVE_AVATAR" = "Remove"; "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; +"MARK_AS_READ" = "Mark Read"; +"MARK_AS_UNREAD" = "Mark Unread"; diff --git a/Session/Meta/Translations/si.lproj/Localizable.strings b/Session/Meta/Translations/si.lproj/Localizable.strings index 29010a13c..632569e2b 100644 --- a/Session/Meta/Translations/si.lproj/Localizable.strings +++ b/Session/Meta/Translations/si.lproj/Localizable.strings @@ -602,3 +602,5 @@ "MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send"; "REMOVE_AVATAR" = "Remove"; "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; +"MARK_AS_READ" = "Mark Read"; +"MARK_AS_UNREAD" = "Mark Unread"; diff --git a/Session/Meta/Translations/sk.lproj/Localizable.strings b/Session/Meta/Translations/sk.lproj/Localizable.strings index 21741d11c..92709e8ba 100644 --- a/Session/Meta/Translations/sk.lproj/Localizable.strings +++ b/Session/Meta/Translations/sk.lproj/Localizable.strings @@ -602,3 +602,5 @@ "MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send"; "REMOVE_AVATAR" = "Remove"; "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; +"MARK_AS_READ" = "Mark Read"; +"MARK_AS_UNREAD" = "Mark Unread"; diff --git a/Session/Meta/Translations/sv.lproj/Localizable.strings b/Session/Meta/Translations/sv.lproj/Localizable.strings index 58584ccf2..e416a27b4 100644 --- a/Session/Meta/Translations/sv.lproj/Localizable.strings +++ b/Session/Meta/Translations/sv.lproj/Localizable.strings @@ -602,3 +602,5 @@ "MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send"; "REMOVE_AVATAR" = "Remove"; "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; +"MARK_AS_READ" = "Mark Read"; +"MARK_AS_UNREAD" = "Mark Unread"; diff --git a/Session/Meta/Translations/th.lproj/Localizable.strings b/Session/Meta/Translations/th.lproj/Localizable.strings index b4084cce6..1b623ed9d 100644 --- a/Session/Meta/Translations/th.lproj/Localizable.strings +++ b/Session/Meta/Translations/th.lproj/Localizable.strings @@ -602,3 +602,5 @@ "MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send"; "REMOVE_AVATAR" = "Remove"; "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; +"MARK_AS_READ" = "Mark Read"; +"MARK_AS_UNREAD" = "Mark Unread"; diff --git a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings index 9d2ef5956..df89154b9 100644 --- a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings +++ b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings @@ -602,3 +602,5 @@ "MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send"; "REMOVE_AVATAR" = "Remove"; "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; +"MARK_AS_READ" = "Mark Read"; +"MARK_AS_UNREAD" = "Mark Unread"; diff --git a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings index 716c3e2d2..9134bbfbe 100644 --- a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings @@ -602,3 +602,5 @@ "MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send"; "REMOVE_AVATAR" = "Remove"; "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; +"MARK_AS_READ" = "Mark Read"; +"MARK_AS_UNREAD" = "Mark Unread"; diff --git a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings index ce0e604ff..a564cbbec 100644 --- a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings @@ -602,3 +602,5 @@ "MESSAGE_DELIVERY_STATUS_FAILED" = "Failed to send"; "REMOVE_AVATAR" = "Remove"; "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; +"MARK_AS_READ" = "Mark Read"; +"MARK_AS_UNREAD" = "Mark Unread"; diff --git a/Session/Open Groups/JoinOpenGroupVC.swift b/Session/Open Groups/JoinOpenGroupVC.swift index f32320f16..aa8c15340 100644 --- a/Session/Open Groups/JoinOpenGroupVC.swift +++ b/Session/Open Groups/JoinOpenGroupVC.swift @@ -197,7 +197,7 @@ final class JoinOpenGroupVC: BaseVC, UIPageViewControllerDataSource, UIPageViewC threadVariant: .openGroup, isMessageRequest: false, action: .compose, - focusInteractionId: nil, + focusInteractionInfo: nil, animated: false ) } diff --git a/Session/Settings/SettingsViewModel.swift b/Session/Settings/SettingsViewModel.swift index 6ea6c166c..b9024a66d 100644 --- a/Session/Settings/SettingsViewModel.swift +++ b/Session/Settings/SettingsViewModel.swift @@ -525,7 +525,7 @@ class SettingsViewModel: SessionTableViewModel 0 ? + let threadIsUnread: Bool = ( + unreadCount > 0 || + cellViewModel.threadWasMarkedUnread == true + ) + let themeBackgroundColor: ThemeValue = (threadIsUnread ? .conversationButton_unreadBackground : .conversationButton_background ) @@ -349,7 +399,11 @@ public final class FullConversationCell: UITableViewCell { isPinnedIcon.isHidden = !cellViewModel.threadIsPinned unreadCountView.isHidden = (unreadCount <= 0) - unreadCountLabel.text = (unreadCount < 10000 ? "\(unreadCount)" : "9999+") + unreadImageView.isHidden = (!unreadCountView.isHidden || !threadIsUnread) + unreadCountLabel.text = (unreadCount <= 0 ? + "" : + (unreadCount < 10000 ? "\(unreadCount)" : "9999+") + ) unreadCountLabel.font = .boldSystemFont( ofSize: (unreadCount < 10000 ? Values.verySmallFontSize : 8) ) @@ -412,7 +466,10 @@ public final class FullConversationCell: UITableViewCell { } else { accentLineView.themeBackgroundColor = .conversationButton_unreadStripBackground - accentLineView.alpha = (!unreadCountView.isHidden ? 1 : 0.0001) // Setting the alpha to exactly 0 causes an issue on iOS 12 + accentLineView.alpha = (!unreadCountView.isHidden || !unreadImageView.isHidden ? + 1 : + 0.0001 // Setting the alpha to exactly 0 causes an issue on iOS 12 + ) } } diff --git a/Session/Shared/SessionTableViewModel.swift b/Session/Shared/SessionTableViewModel.swift index 908f26928..9c97082da 100644 --- a/Session/Shared/SessionTableViewModel.swift +++ b/Session/Shared/SessionTableViewModel.swift @@ -56,10 +56,6 @@ class SessionTableViewModel { Just(nil).eraseToAnyPublisher() } - open var footerButtonInfo: AnyPublisher { - Just(nil).eraseToAnyPublisher() - } func updateTableData(_ updatedData: [SectionModel]) { self.tableData = updatedData diff --git a/SessionMessagingKit/Configuration.swift b/SessionMessagingKit/Configuration.swift index 49c3c292a..b4dc9015f 100644 --- a/SessionMessagingKit/Configuration.swift +++ b/SessionMessagingKit/Configuration.swift @@ -24,7 +24,9 @@ public enum SNMessagingKit { // Just to make the external API nice [ _008_EmojiReacts.self, _009_OpenGroupPermission.self, - _010_AddThreadIdToFTS.self, + _010_AddThreadIdToFTS.self + ], // Add job priorities + [ _011_SharedUtilChanges.self ] ] diff --git a/SessionMessagingKit/Database/Models/Attachment.swift b/SessionMessagingKit/Database/Models/Attachment.swift index d2f9f217a..fc8ef5723 100644 --- a/SessionMessagingKit/Database/Models/Attachment.swift +++ b/SessionMessagingKit/Database/Models/Attachment.swift @@ -1077,7 +1077,7 @@ extension Attachment { // Check the file size SNLog("File size: \(data.count) bytes.") - if Double(data.count) > Double(FileServerAPI.maxFileSize) / FileServerAPI.fileSizeORMultiplier { + if data.count > FileServerAPI.maxFileSize { return Fail(error: HTTPError.maxFileSizeExceeded) .eraseToAnyPublisher() } diff --git a/SessionMessagingKit/Database/Models/Interaction.swift b/SessionMessagingKit/Database/Models/Interaction.swift index f90b7ae9d..fab33fca5 100644 --- a/SessionMessagingKit/Database/Models/Interaction.swift +++ b/SessionMessagingKit/Database/Models/Interaction.swift @@ -608,13 +608,28 @@ public extension Interaction { // MARK: - Search Queries public extension Interaction { - static func idsForTermWithin(threadId: String, pattern: FTS5Pattern) -> SQLRequest { + struct TimestampInfo: FetchableRecord, Codable { + public let id: Int64 + public let timestampMs: Int64 + + public init( + id: Int64, + timestampMs: Int64 + ) { + self.id = id + self.timestampMs = timestampMs + } + } + + static func idsForTermWithin(threadId: String, pattern: FTS5Pattern) -> SQLRequest { let interaction: TypedTableAlias = TypedTableAlias() let interactionFullTextSearch: SQL = SQL(stringLiteral: Interaction.fullTextSearchTableName) let threadIdLiteral: SQL = SQL(stringLiteral: Interaction.Columns.threadId.name) - let request: SQLRequest = """ - SELECT \(interaction[.id]) + let request: SQLRequest = """ + SELECT + \(interaction[.id]), + \(interaction[.timestampMs]) FROM \(Interaction.self) JOIN \(interactionFullTextSearch) ON ( \(interactionFullTextSearch).rowid = \(interaction.alias[Column.rowID]) AND diff --git a/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift b/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift index 0ff78309d..2fbe245dd 100644 --- a/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift +++ b/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift @@ -25,6 +25,15 @@ public enum ConfigurationSyncJob: JobExecutor { return } + // On startup it's possible for multiple ConfigSyncJob's to run at the same time (which is + // redundant) so check if there is another job already running and, if so, defer this job + let jobDetails: [Int64: Data?] = JobRunner.defailsForCurrentlyRunningJobs(of: .configurationSync) + + guard jobDetails.setting(job.id, nil).count == 0 else { + deferred(job) // We will re-enqueue when needed + return + } + // If we don't have a userKeyPair yet then there is no need to sync the configuration // as the user doesn't exist yet (this will get triggered on the first launch of a // fresh install due to the migrations getting run) diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift index b9cf7951f..ae84db707 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift @@ -256,7 +256,7 @@ internal extension SessionUtil { // If we only updated the current user contact then no need to continue guard !targetContacts.isEmpty else { return updated } - db.afterNextTransaction { db in + db.afterNextTransactionNested { db in do { let atomicConf: Atomic?> = SessionUtil.config( for: .contacts, @@ -312,7 +312,7 @@ internal extension SessionUtil { // Get the user public key (updating their profile is handled separately let userPublicKey: String = getUserHexEncodedPublicKey(db) - db.afterNextTransaction { db in + db.afterNextTransactionNested { db in do { // Update the user profile first (if needed) if let updatedUserProfile: Profile = updatedProfiles.first(where: { $0.id == userPublicKey }) { diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift index 8119f3b24..12a0602e6 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift @@ -52,14 +52,10 @@ internal extension SessionUtil { .map { CChar($0) } .nullTerminated() ) - let publicKey: String = String(cString: withUnsafeBytes(of: openGroup.pubkey) { [UInt8]($0) } - .map { CChar($0) } - .nullTerminated() - ) + let publicKey: String = withUnsafePointer(to: openGroup.pubkey, { pubkeyBytes in + Data(bytes: pubkeyBytes, count: 32).toHexString() + }) - // Note: A normal 'openGroupId' isn't lowercased but the volatile conversation - // info will always be lowercase so we force everything to lowercase to simplify - // the code volatileThreadInfo.append( VolatileThreadInfo( threadId: OpenGroup.idFor(roomToken: roomToken, server: server), @@ -101,7 +97,7 @@ internal extension SessionUtil { convo_info_volatile_iterator_advance(convoIterator) } convo_info_volatile_iterator_free(convoIterator) // Need to free the iterator - + return volatileThreadInfo } @@ -116,7 +112,9 @@ internal extension SessionUtil { // which should override any synced changes (eg. 'lastReadTimestampMs') let newerLocalChanges: [VolatileThreadInfo] = try volatileThreadInfo .compactMap { threadInfo -> VolatileThreadInfo? in - // Fetch the "proper" threadId (we need the correct casing for updating the database) + // Note: A normal 'openGroupId' isn't lowercased but the volatile conversation + // info will always be lowercase so we need to fetch the "proper" threadId (in + // order to be able to update the corrent database entries) guard let threadId: String = try? SessionThread .select(.id) @@ -234,7 +232,7 @@ internal extension SessionUtil { guard var cBaseUrl: [CChar] = threadInfo.cBaseUrl, var cRoomToken: [CChar] = threadInfo.cRoomToken, - var cPubkey: [CChar] = threadInfo.cPubkey + var cPubkey: [UInt8] = threadInfo.cPubkey else { SNLog("Unable to create community conversation when updating last read timestamp due to missing URL info") return @@ -293,7 +291,7 @@ internal extension SessionUtil { ) } - db.afterNextTransaction { db in + db.afterNextTransactionNested { db in do { let atomicConf: Atomic?> = SessionUtil.config( for: .convoInfoVolatile, @@ -484,9 +482,9 @@ public extension SessionUtil { $0.bytes.map { CChar(bitPattern: $0) } } } - var cPubkey: [CChar]? { + var cPubkey: [UInt8]? { (openGroupUrlInfo?.publicKey).map { - $0.bytes.map { CChar(bitPattern: $0) } + Data(hex: $0).bytes } } @@ -533,31 +531,39 @@ public extension SessionUtil { let thread: TypedTableAlias = TypedTableAlias() let interaction: TypedTableAlias = TypedTableAlias() let openGroup: TypedTableAlias = TypedTableAlias() + let timestampMsLiteral: SQL = SQL(stringLiteral: Interaction.Columns.timestampMs.name) let request: SQLRequest = """ SELECT \(thread[.id]), \(thread[.variant]), \(thread[.markedAsUnread]), - MAX(\(interaction[.timestampMs])), + \(interaction[.timestampMs]), \(openGroup[.server]), \(openGroup[.roomToken]), \(openGroup[.publicKey]) FROM \(SessionThread.self) - LEFT JOIN \(Interaction.self) ON ( - \(interaction[.threadId]) = \(thread[.id]) AND - \(interaction[.wasRead]) = true AND - -- Note: Due to the complexity of how call messages are handled and the short - -- duration they exist in the swarm, we have decided to exclude trying to - -- include them when syncing the read status of conversations (they are also - -- implemented differently between platforms so including them could be a - -- significant amount of work) - \(SQL("\(interaction[.variant]) IN \(Interaction.Variant.variantsToIncrementUnreadCount.filter { $0 != .infoCall })")) - ) + LEFT JOIN ( + SELECT + \(interaction[.threadId]), + MAX(\(interaction[.timestampMs])) AS \(timestampMsLiteral) + FROM \(Interaction.self) + WHERE ( + \(interaction[.wasRead]) = true AND + -- Note: Due to the complexity of how call messages are handled and the short + -- duration they exist in the swarm, we have decided to exclude trying to + -- include them when syncing the read status of conversations (they are also + -- implemented differently between platforms so including them could be a + -- significant amount of work) + \(SQL("\(interaction[.variant]) IN \(Interaction.Variant.variantsToIncrementUnreadCount.filter { $0 != .infoCall })")) + ) + GROUP BY \(interaction[.threadId]) + ) AS \(Interaction.self) ON \(interaction[.threadId]) = \(thread[.id]) LEFT JOIN \(OpenGroup.self) ON \(openGroup[.threadId]) = \(thread[.id]) \(ids == nil ? SQL("") : "WHERE \(SQL("\(thread[.id]) IN \(ids ?? [])"))" ) + GROUP BY \(thread[.id]) """ return ((try? request.fetchAll(db)) ?? []) diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift index 8b21c6372..caf197057 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift @@ -63,7 +63,7 @@ internal extension SessionUtil { guard let profilePictureUrl: String = profileData.profilePictureUrl, let profileKey: Data = profileData.profilePictureKey - else { return .none } + else { return .remove } return .updateTo( url: profilePictureUrl, @@ -106,26 +106,35 @@ internal extension SessionUtil { // blocking access in it's `mutate` closure return atomicConf.mutate { conf in // Update the name - user_profile_set_name(conf, profile.name) - - let profilePic: user_profile_pic? = profile.profilePictureUrl? + var updatedName: [CChar] = profile.name .bytes .map { CChar(bitPattern: $0) } - .withUnsafeBufferPointer { profileUrlPtr in - let profileKey: [UInt8]? = profile.profileEncryptionKey?.bytes - - return profileKey?.withUnsafeBufferPointer { profileKeyPtr in + user_profile_set_name(conf, &updatedName) + + // Either assign the updated profile pic, or sent a blank profile pic (to remove the current one) + let profilePic: user_profile_pic? = { + guard + let profilePictureUrl: String = profile.profilePictureUrl, + let profileEncryptionKey: Data = profile.profileEncryptionKey + else { return nil } + + let updatedUrl: [CChar] = profilePictureUrl + .bytes + .map { CChar(bitPattern: $0) } + let updatedKey: [UInt8] = profileEncryptionKey + .bytes + + return updatedUrl.withUnsafeBufferPointer { urlPtr in + updatedKey.withUnsafeBufferPointer { keyPtr in user_profile_pic( - url: profileUrlPtr.baseAddress, - key: profileKeyPtr.baseAddress, - keylen: (profileKey?.count ?? 0) + url: urlPtr.baseAddress, + key: keyPtr.baseAddress, + keylen: updatedKey.count ) } } - - if let profilePic: user_profile_pic = profilePic { - user_profile_set_pic(conf, profilePic) - } + }() + user_profile_set_pic(conf, (profilePic ?? user_profile_pic())) return ConfResult( needsPush: config_needs_push(conf), diff --git a/SessionMessagingKit/LibSessionUtil/Database/QueryInterfaceRequest+Utilities.swift b/SessionMessagingKit/LibSessionUtil/Database/QueryInterfaceRequest+Utilities.swift index 0c4b7acdb..30ce830e0 100644 --- a/SessionMessagingKit/LibSessionUtil/Database/QueryInterfaceRequest+Utilities.swift +++ b/SessionMessagingKit/LibSessionUtil/Database/QueryInterfaceRequest+Utilities.swift @@ -51,16 +51,18 @@ public extension QueryInterfaceRequest where RowDecoder: FetchableRecord & Table _ assignments: [ColumnAssignment] ) throws -> [RowDecoder] { defer { - db.afterNextTransaction { db in - guard - self is QueryInterfaceRequest || - self is QueryInterfaceRequest || - self is QueryInterfaceRequest - else { return } - - // If we change one of these types then we may as well automatically enqueue - // a new config sync job once the transaction completes - ConfigurationSyncJob.enqueue(db) + // If we change one of these types then we may as well automatically enqueue + // a new config sync job once the transaction completes (but only enqueue it + // once per transaction - doing it more than once is pointless) + if + self is QueryInterfaceRequest || + self is QueryInterfaceRequest || + self is QueryInterfaceRequest || + self is QueryInterfaceRequest + { + db.afterNextTransactionNestedOnce(dedupeIdentifier: "EnqueueConfigurationSyncJob") { db in + ConfigurationSyncJob.enqueue(db) + } } } diff --git a/SessionMessagingKit/LibSessionUtil/SessionUtil.swift b/SessionMessagingKit/LibSessionUtil/SessionUtil.swift index 44ba49187..7e9b38608 100644 --- a/SessionMessagingKit/LibSessionUtil/SessionUtil.swift +++ b/SessionMessagingKit/LibSessionUtil/SessionUtil.swift @@ -347,39 +347,40 @@ public enum SessionUtil { SessionUtil.configStore.wrappedValue[key] ?? Atomic(nil) ) - var finalResult: ConfResult = mergeResult.result // Apply the updated states to the database - switch variant { - case .userProfile: - finalResult = try SessionUtil.handleUserProfileUpdate( - db, - in: atomicConf, - mergeResult: mergeResult.result, - latestConfigUpdateSentTimestamp: mergeResult.latestSentTimestamp - ) - - case .contacts: - finalResult = try SessionUtil.handleContactsUpdate( - db, - in: atomicConf, - mergeResult: mergeResult.result - ) - - case .convoInfoVolatile: - finalResult = try SessionUtil.handleConvoInfoVolatileUpdate( - db, - in: atomicConf, - mergeResult: mergeResult.result - ) - - case .groups: - finalResult = try SessionUtil.handleGroupsUpdate( - db, - in: atomicConf, - mergeResult: mergeResult.result - ) - } + let postHandlingResult: ConfResult = try { + switch variant { + case .userProfile: + return try SessionUtil.handleUserProfileUpdate( + db, + in: atomicConf, + mergeResult: mergeResult.result, + latestConfigUpdateSentTimestamp: mergeResult.latestSentTimestamp + ) + + case .contacts: + return try SessionUtil.handleContactsUpdate( + db, + in: atomicConf, + mergeResult: mergeResult.result + ) + + case .convoInfoVolatile: + return try SessionUtil.handleConvoInfoVolatileUpdate( + db, + in: atomicConf, + mergeResult: mergeResult.result + ) + + case .groups: + return try SessionUtil.handleGroupsUpdate( + db, + in: atomicConf, + mergeResult: mergeResult.result + ) + } + }() // We need to get the existing message hashes and combine them with the latest from the // service node to ensure the next push will properly clean up old messages @@ -399,7 +400,7 @@ public enum SessionUtil { let messageHashesChanged: Bool = (oldMessageHashes != mergeResult.messageHashes.asSet()) // Now that the changes are applied, update the cached dumps - switch (finalResult.needsDump, messageHashesChanged) { + switch (postHandlingResult.needsDump, messageHashesChanged) { case (true, _): // The config data had changes so regenerate the dump and save it try atomicConf @@ -430,7 +431,7 @@ public enum SessionUtil { default: break } - return finalResult + return postHandlingResult } // Now that the local state has been updated, trigger a config sync (this will push any diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index d6d2eca27..e12a0a1ec 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -493,7 +493,7 @@ public final class OpenGroupManager { } } - db.afterNextTransaction { db in + db.afterNextTransactionNested { db in // Start the poller if needed if dependencies.cache.pollers[server.lowercased()] == nil { dependencies.mutableCache.mutate { diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index 82557517d..d1da2bc22 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -74,7 +74,9 @@ public enum MessageReceiver { throw MessageReceiverError.invalidGroupPublicKey } guard - let encryptionKeyPairs: [ClosedGroupKeyPair] = try? closedGroup.keyPairs.order(ClosedGroupKeyPair.Columns.receivedTimestamp.desc).fetchAll(db), + let encryptionKeyPairs: [ClosedGroupKeyPair] = try? closedGroup.keyPairs + .order(ClosedGroupKeyPair.Columns.receivedTimestamp.desc) + .fetchAll(db), !encryptionKeyPairs.isEmpty else { throw MessageReceiverError.noGroupKeyPair diff --git a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift index ff45afd9f..4c5610e2a 100644 --- a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift +++ b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift @@ -30,6 +30,7 @@ public struct SessionThreadViewModel: FetchableRecordWithRowId, Decodable, Equat public static let threadOnlyNotifyForMentionsKey: SQL = SQL(stringLiteral: CodingKeys.threadOnlyNotifyForMentions.stringValue) public static let threadMessageDraftKey: SQL = SQL(stringLiteral: CodingKeys.threadMessageDraft.stringValue) public static let threadContactIsTypingKey: SQL = SQL(stringLiteral: CodingKeys.threadContactIsTyping.stringValue) + public static let threadWasMarkedUnreadKey: SQL = SQL(stringLiteral: CodingKeys.threadWasMarkedUnread.stringValue) public static let threadUnreadCountKey: SQL = SQL(stringLiteral: CodingKeys.threadUnreadCount.stringValue) public static let threadUnreadMentionCountKey: SQL = SQL(stringLiteral: CodingKeys.threadUnreadMentionCount.stringValue) public static let contactProfileKey: SQL = SQL(stringLiteral: CodingKeys.contactProfile.stringValue) @@ -60,6 +61,7 @@ public struct SessionThreadViewModel: FetchableRecordWithRowId, Decodable, Equat public static let authorNameInternalKey: SQL = SQL(stringLiteral: CodingKeys.authorNameInternal.stringValue) public static let currentUserPublicKeyKey: SQL = SQL(stringLiteral: CodingKeys.currentUserPublicKey.stringValue) + public static let threadWasMarkedUnreadString: String = CodingKeys.threadWasMarkedUnread.stringValue public static let threadUnreadCountString: String = CodingKeys.threadUnreadCount.stringValue public static let threadUnreadMentionCountString: String = CodingKeys.threadUnreadMentionCount.stringValue public static let closedGroupUserCountString: String = CodingKeys.closedGroupUserCount.stringValue @@ -94,6 +96,7 @@ public struct SessionThreadViewModel: FetchableRecordWithRowId, Decodable, Equat public let threadMessageDraft: String? public let threadContactIsTyping: Bool? + public let threadWasMarkedUnread: Bool? public let threadUnreadCount: UInt? public let threadUnreadMentionCount: UInt? @@ -127,7 +130,7 @@ public struct SessionThreadViewModel: FetchableRecordWithRowId, Decodable, Equat public let interactionId: Int64? public let interactionVariant: Interaction.Variant? - private let interactionTimestampMs: Int64? + public let interactionTimestampMs: Int64? public let interactionBody: String? public let interactionState: RecipientState.State? public let interactionHasAtLeastOneReadReceipt: Bool? @@ -228,6 +231,95 @@ public struct SessionThreadViewModel: FetchableRecordWithRowId, Decodable, Equat ) ) } + + // MARK: - Marking as Read + + public enum ReadTarget { + /// Only the thread should be marked as read + case thread + + /// Both the thread and interactions should be marked as read, if no interaction id is provided then all interactions for the + /// thread will be marked as read + case threadAndInteractions(interactionsBeforeInclusive: Int64?) + } + + /// This method marks a thread as read and depending on the target may also update the interactions within a thread as read + public func markAsRead(target: ReadTarget) { + // Store the logic to mark a thread as read (to paths need to run this) + let threadId: String = self.threadId + let threadWasMarkedUnread: Bool? = self.threadWasMarkedUnread + let markThreadAsReadIfNeeded: () -> () = { + guard threadWasMarkedUnread == true else { return } + + Storage.shared.writeAsync { db in + try SessionThread + .filter(id: threadId) + .updateAllAndConfig( + db, + SessionThread.Columns.markedAsUnread.set(to: false) + ) + } + } + + // Determine what we want to mark as read + switch target { + // Only mark the thread as read + case .thread: markThreadAsReadIfNeeded() + + // We want to mark both the thread and interactions as read + case .threadAndInteractions(let interactionId): + guard + (self.threadUnreadCount ?? 0) > 0, + let targetInteractionId: Int64 = (interactionId ?? self.interactionId) + else { + // No unread interactions so just mark the thread as read if needed + markThreadAsReadIfNeeded() + return + } + + let threadId: String = self.threadId + let threadVariant: SessionThread.Variant = self.threadVariant + let trySendReadReceipt: Bool = (self.threadIsMessageRequest == false) + + Storage.shared.writeAsync { db in + // Only make this change if needed (want to avoid triggering a thread update + // if not needed) + if threadWasMarkedUnread == true { + try SessionThread + .filter(id: threadId) + .updateAllAndConfig( + db, + SessionThread.Columns.markedAsUnread.set(to: false) + ) + } + + try Interaction.markAsRead( + db, + interactionId: targetInteractionId, + threadId: threadId, + threadVariant: threadVariant, + includingOlder: true, + trySendReadReceipt: trySendReadReceipt + ) + } + } + } + + /// This method will mark a thread as read + public func markAsUnread() { + guard self.threadWasMarkedUnread != true else { return } + + let threadId: String = self.threadId + + Storage.shared.writeAsync { db in + try SessionThread + .filter(id: threadId) + .updateAllAndConfig( + db, + SessionThread.Columns.markedAsUnread.set(to: true) + ) + } + } } // MARK: - Convenience Initialization @@ -261,6 +353,7 @@ public extension SessionThreadViewModel { self.threadMessageDraft = nil self.threadContactIsTyping = nil + self.threadWasMarkedUnread = nil self.threadUnreadCount = unreadCount self.threadUnreadMentionCount = nil @@ -325,6 +418,7 @@ public extension SessionThreadViewModel { threadOnlyNotifyForMentions: self.threadOnlyNotifyForMentions, threadMessageDraft: self.threadMessageDraft, threadContactIsTyping: self.threadContactIsTyping, + threadWasMarkedUnread: self.threadWasMarkedUnread, threadUnreadCount: self.threadUnreadCount, threadUnreadMentionCount: self.threadUnreadMentionCount, contactProfile: self.contactProfile, @@ -379,6 +473,7 @@ public extension SessionThreadViewModel { threadOnlyNotifyForMentions: self.threadOnlyNotifyForMentions, threadMessageDraft: self.threadMessageDraft, threadContactIsTyping: self.threadContactIsTyping, + threadWasMarkedUnread: self.threadWasMarkedUnread, threadUnreadCount: self.threadUnreadCount, threadUnreadMentionCount: self.threadUnreadMentionCount, contactProfile: self.contactProfile, @@ -464,7 +559,7 @@ public extension SessionThreadViewModel { /// parse and might throw /// /// Explicitly set default values for the fields ignored for search results - let numColumnsBeforeProfiles: Int = 12 + let numColumnsBeforeProfiles: Int = 13 let numColumnsBetweenProfilesAndAttachmentInfo: Int = 11 // The attachment info columns will be combined let request: SQLRequest = """ @@ -481,6 +576,7 @@ public extension SessionThreadViewModel { \(thread[.onlyNotifyForMentions]) AS \(ViewModel.threadOnlyNotifyForMentionsKey), (\(typingIndicator[.threadId]) IS NOT NULL) AS \(ViewModel.threadContactIsTypingKey), + \(thread[.markedAsUnread]) AS \(ViewModel.threadWasMarkedUnreadKey), \(Interaction.self).\(ViewModel.threadUnreadCountKey), \(Interaction.self).\(ViewModel.threadUnreadMentionCountKey), @@ -724,7 +820,7 @@ public extension SessionThreadViewModel { /// parse and might throw /// /// Explicitly set default values for the fields ignored for search results - let numColumnsBeforeProfiles: Int = 14 + let numColumnsBeforeProfiles: Int = 15 let request: SQLRequest = """ SELECT \(thread.alias[Column.rowID]) AS \(ViewModel.rowIdKey), @@ -750,6 +846,7 @@ public extension SessionThreadViewModel { \(thread[.onlyNotifyForMentions]) AS \(ViewModel.threadOnlyNotifyForMentionsKey), \(thread[.messageDraft]) AS \(ViewModel.threadMessageDraftKey), + \(thread[.markedAsUnread]) AS \(ViewModel.threadWasMarkedUnreadKey), \(Interaction.self).\(ViewModel.threadUnreadCountKey), \(ViewModel.contactProfileKey).*, diff --git a/SessionMessagingKit/Utilities/ProfileManager.swift b/SessionMessagingKit/Utilities/ProfileManager.swift index 458ee0209..9d13ae126 100644 --- a/SessionMessagingKit/Utilities/ProfileManager.swift +++ b/SessionMessagingKit/Utilities/ProfileManager.swift @@ -487,6 +487,7 @@ public struct ProfileManager { // Update the cached avatar image value profileAvatarCache.mutate { $0[fileName] = data } + UserDefaults.standard[.lastProfilePictureUpload] = Date() SNLog("Successfully uploaded avatar image.") success((downloadUrl, fileName, newProfileKey)) @@ -553,11 +554,7 @@ public struct ProfileManager { profileChanges.append(Profile.Columns.profilePictureUrl.set(to: nil)) profileChanges.append(Profile.Columns.profileEncryptionKey.set(to: nil)) - - // Profile filename (this isn't synchronized between devices so can be immediately saved) - _ = try? Profile - .filter(id: publicKey) - .updateAll(db, Profile.Columns.profilePictureFileName.set(to: nil)) + profileChanges.append(Profile.Columns.profilePictureFileName.set(to: nil)) case .updateTo(let url, let key, let fileName): if @@ -573,11 +570,9 @@ public struct ProfileManager { avatarNeedsDownload = true } - // Profile filename (this isn't synchronized between devices so can be immediately saved) + // Profile filename (this isn't synchronized between devices) if let fileName: String = fileName { - _ = try? Profile - .filter(id: publicKey) - .updateAll(db, Profile.Columns.profilePictureFileName.set(to: fileName)) + profileChanges.append(Profile.Columns.profilePictureFileName.set(to: fileName)) } } } @@ -623,7 +618,7 @@ public struct ProfileManager { // Download the profile picture if needed guard avatarNeedsDownload else { return } - db.afterNextTransaction { db in + db.afterNextTransactionNested { db in // Need to refetch to ensure the db changes have occurred ProfileManager.downloadAvatar(for: Profile.fetchOrCreate(db, id: publicKey)) } diff --git a/SessionMessagingKitTests/LibSessionUtil/ConfigContactsSpec.swift b/SessionMessagingKitTests/LibSessionUtil/ConfigContactsSpec.swift index f1426c85b..9012b113d 100644 --- a/SessionMessagingKitTests/LibSessionUtil/ConfigContactsSpec.swift +++ b/SessionMessagingKitTests/LibSessionUtil/ConfigContactsSpec.swift @@ -65,6 +65,7 @@ class ConfigContactsSpec: QuickSpec { expect(toPush).toNot(beNil()) expect(seqno).to(equal(0)) expect(toPushLen).to(equal(256)) + toPush?.deallocate() // Update the contact data let contact2Name: [CChar] = "Joe" @@ -125,7 +126,7 @@ class ConfigContactsSpec: QuickSpec { let error2: UnsafeMutablePointer? = nil var conf2: UnsafeMutablePointer? = nil expect(contacts_init(&conf2, &edSK, dump1, dump1Len, error2)).to(equal(0)) - error?.deallocate() + error2?.deallocate() dump1?.deallocate() expect(config_needs_push(conf2)).to(beFalse()) diff --git a/SessionMessagingKitTests/LibSessionUtil/ConfigConvoInfoVolatileSpec.swift b/SessionMessagingKitTests/LibSessionUtil/ConfigConvoInfoVolatileSpec.swift new file mode 100644 index 000000000..da437701e --- /dev/null +++ b/SessionMessagingKitTests/LibSessionUtil/ConfigConvoInfoVolatileSpec.swift @@ -0,0 +1,314 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import Sodium +import SessionUtil +import SessionUtilitiesKit + +import Quick +import Nimble + +/// This spec is designed to replicate the initial test cases for the libSession-util to ensure the behaviour matches +class ConfigConvoInfoVolatileSpec: QuickSpec { + // MARK: - Spec + + override func spec() { + it("generates ConvoInfoVolatileS configs correctly") { + let seed: Data = Data(hex: "0123456789abcdef0123456789abcdef") + + // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately + let identity = try! Identity.generate(from: seed) + var edSK: [UInt8] = identity.ed25519KeyPair.secretKey + expect(edSK.toHexString().suffix(64)) + .to(equal("4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7")) + expect(identity.x25519KeyPair.publicKey.toHexString()) + .to(equal("d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72")) + expect(String(edSK.toHexString().prefix(32))).to(equal(seed.toHexString())) + + // Initialize a brand new, empty config because we have no dump data to deal with. + let error: UnsafeMutablePointer? = nil + var conf: UnsafeMutablePointer? = nil + expect(convo_info_volatile_init(&conf, &edSK, nil, 0, error)).to(equal(0)) + error?.deallocate() + + // Empty contacts shouldn't have an existing contact + var definitelyRealId: [CChar] = "055000000000000000000000000000000000000000000000000000000000000000" + .bytes + .map { CChar(bitPattern: $0) } + var oneToOne1: convo_info_volatile_1to1 = convo_info_volatile_1to1() + expect(convo_info_volatile_get_1to1(conf, &oneToOne1, &definitelyRealId)).to(beFalse()) + expect(convo_info_volatile_size(conf)).to(equal(0)) + + var oneToOne2: convo_info_volatile_1to1 = convo_info_volatile_1to1() + expect(convo_info_volatile_get_or_construct_1to1(conf, &oneToOne2, definitelyRealId)) + .to(beTrue()) + + let oneToOne2SessionId: [CChar] = withUnsafeBytes(of: oneToOne2.session_id) { [UInt8]($0) } + .map { CChar($0) } + expect(oneToOne2SessionId).to(equal(definitelyRealId.nullTerminated())) + expect(oneToOne2.last_read).to(equal(0)) + expect(oneToOne2.unread).to(beFalse()) + + // No need to sync a conversation with a default state + expect(config_needs_push(conf)).to(beFalse()) + expect(config_needs_dump(conf)).to(beFalse()) + + // Update the last read + let nowTimestampMs: Int64 = Int64(floor(Date().timeIntervalSince1970 * 1000)) + oneToOne2.last_read = nowTimestampMs + + // The new data doesn't get stored until we call this: + convo_info_volatile_set_1to1(conf, &oneToOne2) + + var legacyClosed1: convo_info_volatile_legacy_closed = convo_info_volatile_legacy_closed() + var oneToOne3: convo_info_volatile_1to1 = convo_info_volatile_1to1() + expect(convo_info_volatile_get_legacy_closed(conf, &legacyClosed1, &definitelyRealId)) + .to(beFalse()) + expect(convo_info_volatile_get_1to1(conf, &oneToOne3, &definitelyRealId)).to(beTrue()) + expect(oneToOne3.last_read).to(equal(nowTimestampMs)) + + expect(config_needs_push(conf)).to(beTrue()) + expect(config_needs_dump(conf)).to(beTrue()) + + var openGroupBaseUrl: [CChar] = "http://Example.ORG:5678" + .bytes + .map { CChar(bitPattern: $0) } + let openGroupBaseUrlResult: [CChar] = ("http://Example.ORG:5678" + .lowercased() + .bytes + .map { CChar(bitPattern: $0) } + + [CChar](repeating: 0, count: (268 - openGroupBaseUrl.count)) + ) + var openGroupRoom: [CChar] = "SudokuRoom" + .bytes + .map { CChar(bitPattern: $0) } + let openGroupRoomResult: [CChar] = ("SudokuRoom" + .lowercased() + .bytes + .map { CChar(bitPattern: $0) } + + [CChar](repeating: 0, count: (65 - openGroupRoom.count)) + ) + var openGroupPubkey: [UInt8] = Data(hex: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") + .bytes + var openGroup1: convo_info_volatile_open = convo_info_volatile_open() + expect(convo_info_volatile_get_or_construct_open(conf, &openGroup1, &openGroupBaseUrl, &openGroupRoom, &openGroupPubkey)).to(beTrue()) + expect(withUnsafeBytes(of: openGroup1.base_url) { [UInt8]($0) } + .map { CChar($0) } + ).to(equal(openGroupBaseUrlResult)) + expect(withUnsafeBytes(of: openGroup1.room) { [UInt8]($0) } + .map { CChar($0) } + ).to(equal(openGroupRoomResult)) + expect(withUnsafePointer(to: openGroup1.pubkey) { Data(bytes: $0, count: 32).toHexString() }) + .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) + openGroup1.unread = true + + // The new data doesn't get stored until we call this: + convo_info_volatile_set_open(conf, &openGroup1); + + var toPush: UnsafeMutablePointer? = nil + var toPushLen: Int = 0 + // We don't need to push since we haven't changed anything, so this call is mainly just for + // testing: + let seqno: Int64 = config_push(conf, &toPush, &toPushLen) + expect(toPush).toNot(beNil()) + expect(seqno).to(equal(1)) + expect(toPushLen).to(equal(512)) + toPush?.deallocate() + + // Pretend we uploaded it + config_confirm_pushed(conf, seqno) + expect(config_needs_dump(conf)).to(beTrue()) + expect(config_needs_push(conf)).to(beFalse()) + + var dump1: UnsafeMutablePointer? = nil + var dump1Len: Int = 0 + config_dump(conf, &dump1, &dump1Len) + + let error2: UnsafeMutablePointer? = nil + var conf2: UnsafeMutablePointer? = nil + expect(convo_info_volatile_init(&conf2, &edSK, dump1, dump1Len, error2)).to(equal(0)) + error2?.deallocate() + dump1?.deallocate() + + expect(config_needs_dump(conf2)).to(beFalse()) + expect(config_needs_push(conf2)).to(beFalse()) + + var oneToOne4: convo_info_volatile_1to1 = convo_info_volatile_1to1() + expect(convo_info_volatile_get_1to1(conf2, &oneToOne4, &definitelyRealId)).to(equal(true)) + expect(oneToOne4.last_read).to(equal(nowTimestampMs)) + expect( + withUnsafeBytes(of: oneToOne4.session_id) { [UInt8]($0) } + .map { CChar($0) } + .nullTerminated() + ).to(equal(definitelyRealId.nullTerminated())) + expect(oneToOne4.unread).to(beFalse()) + + var openGroup2: convo_info_volatile_open = convo_info_volatile_open() + expect(convo_info_volatile_get_open(conf2, &openGroup2, &openGroupBaseUrl, &openGroupRoom, &openGroupPubkey)).to(beTrue()) + expect(withUnsafeBytes(of: openGroup2.base_url) { [UInt8]($0) } + .map { CChar($0) } + ).to(equal(openGroupBaseUrlResult)) + expect(withUnsafeBytes(of: openGroup2.room) { [UInt8]($0) } + .map { CChar($0) } + ).to(equal(openGroupRoomResult)) + expect(withUnsafePointer(to: openGroup2.pubkey) { Data(bytes: $0, count: 32).toHexString() }) + .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) + openGroup2.unread = true + + var anotherId: [CChar] = "051111111111111111111111111111111111111111111111111111111111111111" + .bytes + .map { CChar(bitPattern: $0) } + var oneToOne5: convo_info_volatile_1to1 = convo_info_volatile_1to1() + expect(convo_info_volatile_get_or_construct_1to1(conf2, &oneToOne5, &anotherId)).to(beTrue()) + convo_info_volatile_set_1to1(conf2, &oneToOne5) + + var thirdId: [CChar] = "05cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + .bytes + .map { CChar(bitPattern: $0) } + var legacyClosed2: convo_info_volatile_legacy_closed = convo_info_volatile_legacy_closed() + expect(convo_info_volatile_get_or_construct_legacy_closed(conf2, &legacyClosed2, &thirdId)).to(beTrue()) + legacyClosed2.last_read = (nowTimestampMs - 50) + convo_info_volatile_set_legacy_closed(conf2, &legacyClosed2) + expect(config_needs_push(conf2)).to(beTrue()) + + var toPush2: UnsafeMutablePointer? = nil + var toPush2Len: Int = 0 + let seqno2: Int64 = config_push(conf2, &toPush2, &toPush2Len) + expect(seqno2).to(equal(2)) + + // Check the merging + var mergeData: [UnsafePointer?] = [UnsafePointer(toPush2)] + var mergeSize: [Int] = [toPush2Len] + expect(config_merge(conf, &mergeData, &mergeSize, 1)).to(equal(1)) + config_confirm_pushed(conf, seqno) + toPush2?.deallocate() + + expect(config_needs_push(conf)).to(beFalse()) + + for targetConf in [conf, conf2] { + // Iterate through and make sure we got everything we expected + var seen: [String] = [] + expect(convo_info_volatile_size(conf)).to(equal(4)) + expect(convo_info_volatile_size_1to1(conf)).to(equal(2)) + expect(convo_info_volatile_size_open(conf)).to(equal(1)) + expect(convo_info_volatile_size_legacy_closed(conf)).to(equal(1)) + + var c1: convo_info_volatile_1to1 = convo_info_volatile_1to1() + var c2: convo_info_volatile_open = convo_info_volatile_open() + var c3: convo_info_volatile_legacy_closed = convo_info_volatile_legacy_closed() + let it: OpaquePointer = convo_info_volatile_iterator_new(targetConf) + + while !convo_info_volatile_iterator_done(it) { + if convo_info_volatile_it_is_1to1(it, &c1) { + let sessionId: String = String(cString: withUnsafeBytes(of: c1.session_id) { [UInt8]($0) } + .map { CChar($0) } + .nullTerminated() + ) + seen.append("1-to-1: \(sessionId)") + } + else if convo_info_volatile_it_is_open(it, &c2) { + let baseUrl: String = String(cString: withUnsafeBytes(of: c2.base_url) { [UInt8]($0) } + .map { CChar($0) } + .nullTerminated() + ) + let room: String = String(cString: withUnsafeBytes(of: c2.room) { [UInt8]($0) } + .map { CChar($0) } + .nullTerminated() + ) + + seen.append("og: \(baseUrl)/r/\(room)") + } + else if convo_info_volatile_it_is_legacy_closed(it, &c3) { + let groupId: String = String(cString: withUnsafeBytes(of: c3.group_id) { [UInt8]($0) } + .map { CChar($0) } + .nullTerminated() + ) + seen.append("cl: \(groupId)") + } + + convo_info_volatile_iterator_advance(it) + } + + convo_info_volatile_iterator_free(it) + + expect(seen).to(equal([ + "1-to-1: 051111111111111111111111111111111111111111111111111111111111111111", + "1-to-1: 055000000000000000000000000000000000000000000000000000000000000000", + "og: http://example.org:5678/r/sudokuroom", + "cl: 05cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + ])) + } + + var fourthId: [CChar] = "052000000000000000000000000000000000000000000000000000000000000000" + .bytes + .map { CChar(bitPattern: $0) } + expect(config_needs_push(conf)).to(beFalse()) + convo_info_volatile_erase_1to1(conf, &fourthId) + expect(config_needs_push(conf)).to(beFalse()) + convo_info_volatile_erase_1to1(conf, &definitelyRealId) + expect(config_needs_push(conf)).to(beTrue()) + expect(convo_info_volatile_size(conf)).to(equal(3)) + expect(convo_info_volatile_size_1to1(conf)).to(equal(1)) + + // Check the single-type iterators: + var seen1: [String] = [] + var c1: convo_info_volatile_1to1 = convo_info_volatile_1to1() + let it1: OpaquePointer = convo_info_volatile_iterator_new_1to1(conf) + + while !convo_info_volatile_iterator_done(it1) { + expect(convo_info_volatile_it_is_1to1(it1, &c1)).to(beTrue()) + let sessionId: String = String(cString: withUnsafeBytes(of: c1.session_id) { [UInt8]($0) } + .map { CChar($0) } + .nullTerminated() + ) + seen1.append(sessionId) + convo_info_volatile_iterator_advance(it1) + } + + convo_info_volatile_iterator_free(it1) + expect(seen1).to(equal([ + "051111111111111111111111111111111111111111111111111111111111111111" + ])) + + var seen2: [String] = [] + var c2: convo_info_volatile_open = convo_info_volatile_open() + let it2: OpaquePointer = convo_info_volatile_iterator_new_open(conf) + + while !convo_info_volatile_iterator_done(it2) { + expect(convo_info_volatile_it_is_open(it2, &c2)).to(beTrue()) + let baseUrl: String = String(cString: withUnsafeBytes(of: c2.base_url) { [UInt8]($0) } + .map { CChar($0) } + .nullTerminated() + ) + + seen2.append(baseUrl) + convo_info_volatile_iterator_advance(it2) + } + + convo_info_volatile_iterator_free(it2) + expect(seen2).to(equal([ + "http://example.org:5678" + ])) + + var seen3: [String] = [] + var c3: convo_info_volatile_legacy_closed = convo_info_volatile_legacy_closed() + let it3: OpaquePointer = convo_info_volatile_iterator_new_legacy_closed(conf) + + while !convo_info_volatile_iterator_done(it3) { + expect(convo_info_volatile_it_is_legacy_closed(it3, &c3)).to(beTrue()) + let groupId: String = String(cString: withUnsafeBytes(of: c3.group_id) { [UInt8]($0) } + .map { CChar($0) } + .nullTerminated() + ) + + seen3.append(groupId) + convo_info_volatile_iterator_advance(it3) + } + + convo_info_volatile_iterator_free(it3) + expect(seen3).to(equal([ + "05cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + ])) + } + } +} diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift index 65e1a5a71..07e5e81d8 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift +++ b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift @@ -3665,11 +3665,11 @@ class OpenGroupManagerSpec: QuickSpec { it("adds the image retrieval promise to the cache") { class TestNeverReturningApi: OnionRequestAPIType { - static func sendOnionRequest(_ request: URLRequest, to server: String, with x25519PublicKey: String) -> AnyPublisher<(ResponseInfoType, Data?), Error> { + static func sendOnionRequest(_ request: URLRequest, to server: String, with x25519PublicKey: String, timeout: TimeInterval) -> AnyPublisher<(ResponseInfoType, Data?), Error> { return Future<(ResponseInfoType, Data?), Error> { _ in }.eraseToAnyPublisher() } - static func sendOnionRequest(_ payload: Data, to snode: Snode) -> AnyPublisher<(ResponseInfoType, Data?), Error> { + static func sendOnionRequest(_ payload: Data, to snode: Snode, timeout: TimeInterval) -> AnyPublisher<(ResponseInfoType, Data?), Error> { return Just(Data()) .setFailureType(to: Error.self) .map { data in (HTTP.ResponseInfo(code: 0, headers: [:]), data) } diff --git a/SessionMessagingKitTests/_TestUtilities/TestOnionRequestAPI.swift b/SessionMessagingKitTests/_TestUtilities/TestOnionRequestAPI.swift index f92380f95..89aab1217 100644 --- a/SessionMessagingKitTests/_TestUtilities/TestOnionRequestAPI.swift +++ b/SessionMessagingKitTests/_TestUtilities/TestOnionRequestAPI.swift @@ -38,7 +38,7 @@ class TestOnionRequestAPI: OnionRequestAPIType { class var mockResponse: Data? { return nil } - static func sendOnionRequest(_ request: URLRequest, to server: String, with x25519PublicKey: String) -> AnyPublisher<(ResponseInfoType, Data?), Error> { + static func sendOnionRequest(_ request: URLRequest, to server: String, with x25519PublicKey: String, timeout: TimeInterval) -> AnyPublisher<(ResponseInfoType, Data?), Error> { let responseInfo: ResponseInfo = ResponseInfo( requestData: RequestData( urlString: request.url?.absoluteString, @@ -62,7 +62,7 @@ class TestOnionRequestAPI: OnionRequestAPIType { .eraseToAnyPublisher() } - static func sendOnionRequest(_ payload: Data, to snode: Snode) -> AnyPublisher<(ResponseInfoType, Data?), Error> { + static func sendOnionRequest(_ payload: Data, to snode: Snode, timeout: TimeInterval) -> AnyPublisher<(ResponseInfoType, Data?), Error> { let responseInfo: ResponseInfo = ResponseInfo( requestData: RequestData( urlString: "\(snode.address):\(snode.port)/onion_req/v2", diff --git a/SessionSnodeKit/Configuration.swift b/SessionSnodeKit/Configuration.swift index 259ac87ab..dcf685839 100644 --- a/SessionSnodeKit/Configuration.swift +++ b/SessionSnodeKit/Configuration.swift @@ -17,7 +17,8 @@ public enum SNSnodeKit { // Just to make the external API nice ], [ _004_FlagMessageHashAsDeletedOrInvalid.self - ] + ], + [] // Add job priorities ] ) } diff --git a/SessionSnodeKit/Database/Models/Snode.swift b/SessionSnodeKit/Database/Models/Snode.swift index 199ef2139..595b37901 100644 --- a/SessionSnodeKit/Database/Models/Snode.swift +++ b/SessionSnodeKit/Database/Models/Snode.swift @@ -89,8 +89,10 @@ internal extension Snode { return try SnodeSet .filter(SnodeSet.Columns.key.like("\(SnodeSet.onionRequestPathPrefix)%")) - .order(SnodeSet.Columns.nodeIndex) - .order(SnodeSet.Columns.key) + .order( + SnodeSet.Columns.nodeIndex, + SnodeSet.Columns.key + ) .including(required: SnodeSet.node) .asRequest(of: ResultWrapper.self) .fetchAll(db) diff --git a/SessionUIKit/Configuration.swift b/SessionUIKit/Configuration.swift index f5b2a6186..d305968e9 100644 --- a/SessionUIKit/Configuration.swift +++ b/SessionUIKit/Configuration.swift @@ -15,7 +15,7 @@ public enum SNUIKit { [], // YDB Removal [ _001_ThemePreferences.self - ] + ] // Add job priorities ] ) } diff --git a/SessionUIKit/Style Guide/Themes/Theme+ClassicDark.swift b/SessionUIKit/Style Guide/Themes/Theme+ClassicDark.swift index e0310de52..c78c7fc72 100644 --- a/SessionUIKit/Style Guide/Themes/Theme+ClassicDark.swift +++ b/SessionUIKit/Style Guide/Themes/Theme+ClassicDark.swift @@ -86,6 +86,7 @@ internal enum Theme_ClassicDark: ThemeColors { .conversationButton_swipeDestructive: .dangerDark, .conversationButton_swipeSecondary: .classicDark2, .conversationButton_swipeTertiary: Theme.PrimaryColor.orange.color, + .conversationButton_swipeRead: .classicDark3, // InputButton .inputButton_background: .classicDark2, diff --git a/SessionUIKit/Style Guide/Themes/Theme+ClassicLight.swift b/SessionUIKit/Style Guide/Themes/Theme+ClassicLight.swift index c6394ba4e..1324ba925 100644 --- a/SessionUIKit/Style Guide/Themes/Theme+ClassicLight.swift +++ b/SessionUIKit/Style Guide/Themes/Theme+ClassicLight.swift @@ -86,6 +86,7 @@ internal enum Theme_ClassicLight: ThemeColors { .conversationButton_swipeDestructive: .dangerLight, .conversationButton_swipeSecondary: .classicLight1, .conversationButton_swipeTertiary: Theme.PrimaryColor.orange.color, + .conversationButton_swipeRead: .classicLight3, // InputButton .inputButton_background: .classicLight4, diff --git a/SessionUIKit/Style Guide/Themes/Theme+OceanDark.swift b/SessionUIKit/Style Guide/Themes/Theme+OceanDark.swift index 11962ff05..42612a138 100644 --- a/SessionUIKit/Style Guide/Themes/Theme+OceanDark.swift +++ b/SessionUIKit/Style Guide/Themes/Theme+OceanDark.swift @@ -86,6 +86,7 @@ internal enum Theme_OceanDark: ThemeColors { .conversationButton_swipeDestructive: .dangerDark, .conversationButton_swipeSecondary: .oceanDark2, .conversationButton_swipeTertiary: Theme.PrimaryColor.orange.color, + .conversationButton_swipeRead: .primary, // InputButton .inputButton_background: .oceanDark4, diff --git a/SessionUIKit/Style Guide/Themes/Theme+OceanLight.swift b/SessionUIKit/Style Guide/Themes/Theme+OceanLight.swift index 8611f0864..5f7215101 100644 --- a/SessionUIKit/Style Guide/Themes/Theme+OceanLight.swift +++ b/SessionUIKit/Style Guide/Themes/Theme+OceanLight.swift @@ -86,6 +86,7 @@ internal enum Theme_OceanLight: ThemeColors { .conversationButton_swipeDestructive: .dangerLight, .conversationButton_swipeSecondary: .oceanLight2, .conversationButton_swipeTertiary: Theme.PrimaryColor.orange.color, + .conversationButton_swipeRead: .primary, // InputButton .inputButton_background: .oceanLight5, diff --git a/SessionUIKit/Style Guide/Themes/Theme.swift b/SessionUIKit/Style Guide/Themes/Theme.swift index bb39cffdb..579bd6499 100644 --- a/SessionUIKit/Style Guide/Themes/Theme.swift +++ b/SessionUIKit/Style Guide/Themes/Theme.swift @@ -174,6 +174,7 @@ public indirect enum ThemeValue: Hashable { case conversationButton_swipeDestructive case conversationButton_swipeSecondary case conversationButton_swipeTertiary + case conversationButton_swipeRead // InputButton case inputButton_background diff --git a/SessionUIKit/Style Guide/Themes/UIKit+Theme.swift b/SessionUIKit/Style Guide/Themes/UIKit+Theme.swift index 97916601d..2948a4b5f 100644 --- a/SessionUIKit/Style Guide/Themes/UIKit+Theme.swift +++ b/SessionUIKit/Style Guide/Themes/UIKit+Theme.swift @@ -447,7 +447,16 @@ public extension UIToolbar { public extension UIContextualAction { var themeBackgroundColor: ThemeValue? { - set { ThemeManager.set(self, keyPath: \.backgroundColor, to: newValue) } + set { + guard let newValue: ThemeValue = newValue else { + self.backgroundColor = nil + return + } + + self.backgroundColor = UIColor(dynamicProvider: { _ in + (ThemeManager.currentTheme.color(for: newValue) ?? .clear) + }) + } get { return nil } } } diff --git a/SessionUIKit/Utilities/UIContextualAction+Theming.swift b/SessionUIKit/Utilities/UIContextualAction+Theming.swift new file mode 100644 index 000000000..408ce8d36 --- /dev/null +++ b/SessionUIKit/Utilities/UIContextualAction+Theming.swift @@ -0,0 +1,176 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import UIKit +import SessionUtilitiesKit + +public extension UIContextualAction { + private static var lookupMap: Atomic<[Int: [String: [Int: ThemeValue]]]> = Atomic([:]) + + enum Side: Int { + case leading + case trailing + + func key(for indexPath: IndexPath) -> String { + return "\(indexPath.section)-\(indexPath.row)-\(rawValue)" + } + + init?(for view: UIView) { + guard view.frame.minX == 0 else { + self = .trailing + return + } + + self = .leading + } + } + + convenience init( + title: String? = nil, + icon: UIImage? = nil, + iconHeight: CGFloat = Values.mediumFontSize, + themeTintColor: ThemeValue = .textPrimary, + themeBackgroundColor: ThemeValue, + side: Side, + actionIndex: Int, + indexPath: IndexPath, + tableView: UITableView, + handler: @escaping UIContextualAction.Handler + ) { + self.init(style: .normal, title: title, handler: handler) + self.image = UIContextualAction + .imageWith( + title: title, + icon: icon, + iconHeight: iconHeight, + themeTintColor: themeTintColor + )? + .withRenderingMode(.alwaysTemplate) + self.themeBackgroundColor = themeBackgroundColor + + UIContextualAction.lookupMap.mutate { + $0[tableView.hashValue] = ($0[tableView.hashValue] ?? [:]) + .setting( + side.key(for: indexPath), + (($0[tableView.hashValue] ?? [:])[side.key(for: indexPath)] ?? [:]) + .setting(actionIndex, themeTintColor) + ) + } + } + + private static func imageWith( + title: String?, + icon: UIImage?, + iconHeight: CGFloat, + themeTintColor: ThemeValue + ) -> UIImage? { + let stackView: UIStackView = UIStackView() + stackView.axis = .vertical + stackView.alignment = .center + stackView.spacing = 3 + + if let icon: UIImage = icon { + let aspectRatio: CGFloat = (icon.size.width / icon.size.height) + let imageView: UIImageView = UIImageView(image: icon) + imageView.frame = CGRect(x: 0, y: 0, width: (iconHeight * aspectRatio), height: iconHeight) + imageView.contentMode = .scaleAspectFit + imageView.themeTintColor = themeTintColor + stackView.addArrangedSubview(imageView) + } + + if let title: String = title { + let label: UILabel = UILabel() + label.font = .systemFont(ofSize: Values.smallFontSize) + label.text = title + label.textAlignment = .center + label.themeTextColor = themeTintColor + label.minimumScaleFactor = 0.75 + label.numberOfLines = (title.components(separatedBy: " ").count > 1 ? 2 : 1) + label.frame = CGRect( + origin: .zero, + size: label.sizeThatFits(CGSize(width: 68, height: 999)) + ) + label.set(.width, to: label.frame.width) + + stackView.addArrangedSubview(label) + } + + stackView.frame = CGRect( + origin: .zero, + size: stackView.systemLayoutSizeFitting(CGSize(width: 999, height: 999)) + ) + + // Based on https://stackoverflow.com/a/41288197/1118398 + let renderFormat: UIGraphicsImageRendererFormat = UIGraphicsImageRendererFormat() + renderFormat.scale = UIScreen.main.scale + + let renderer: UIGraphicsImageRenderer = UIGraphicsImageRenderer( + size: stackView.bounds.size, + format: renderFormat + ) + return renderer.image { rendererContext in + stackView.layer.render(in: rendererContext.cgContext) + } + } + + private static func firstSubviewOfType(in superview: UIView) -> T? { + guard !(superview is T) else { return superview as? T } + guard !superview.subviews.isEmpty else { return nil } + + for subview in superview.subviews { + if let result: T = firstSubviewOfType(in: subview) { + return result + } + } + + return nil + } + + static func willBeginEditing(indexPath: IndexPath, tableView: UITableView) { + guard + let targetCell: UITableViewCell = tableView.cellForRow(at: indexPath), + targetCell.superview != tableView, + let targetSuperview: UIView = targetCell.superview? + .subviews + .filter({ $0 != targetCell }) + .first, + let side: Side = Side(for: targetSuperview), + let themeMap: [Int: ThemeValue] = UIContextualAction.lookupMap.wrappedValue + .getting(tableView.hashValue)? + .getting(side.key(for: indexPath)), + targetSuperview.subviews.count == themeMap.count + else { return } + + let targetViews: [UIImageView] = targetSuperview.subviews + .compactMap { subview in firstSubviewOfType(in: subview) } + + guard targetViews.count == themeMap.count else { return } + + // Set the imageView and background colours (so they change correctly when the theme changes) + targetViews.enumerated().forEach { index, targetView in + guard let themeTintColor: ThemeValue = themeMap[index] else { return } + + targetView.themeTintColor = themeTintColor + } + } + + static func didEndEditing(indexPath: IndexPath?, tableView: UITableView) { + guard let indexPath: IndexPath = indexPath else { return } + + let leadingKey: String = Side.leading.key(for: indexPath) + let trailingKey: String = Side.trailing.key(for: indexPath) + + guard + UIContextualAction.lookupMap.wrappedValue[tableView.hashValue]?[leadingKey] != nil || + UIContextualAction.lookupMap.wrappedValue[tableView.hashValue]?[trailingKey] != nil + else { return } + + UIContextualAction.lookupMap.mutate { + $0[tableView.hashValue]?[leadingKey] = nil + $0[tableView.hashValue]?[trailingKey] = nil + + if $0[tableView.hashValue]?.isEmpty == true { + $0[tableView.hashValue] = nil + } + } + } +} diff --git a/SessionUtilitiesKit/Configuration.swift b/SessionUtilitiesKit/Configuration.swift index c6f72e019..616e27ed3 100644 --- a/SessionUtilitiesKit/Configuration.swift +++ b/SessionUtilitiesKit/Configuration.swift @@ -17,7 +17,9 @@ public enum SNUtilitiesKit { // Just to make the external API nice ], [], // Other DB migrations [], // Legacy DB removal - [] + [ + _004_AddJobPriority.self + ] ] ) } diff --git a/SessionUtilitiesKit/Database/Migrations/_004_AddJobPriority.swift b/SessionUtilitiesKit/Database/Migrations/_004_AddJobPriority.swift new file mode 100644 index 000000000..384c25195 --- /dev/null +++ b/SessionUtilitiesKit/Database/Migrations/_004_AddJobPriority.swift @@ -0,0 +1,42 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import GRDB +import YapDatabase + +enum _004_AddJobPriority: Migration { + static let target: TargetMigrations.Identifier = .utilitiesKit + static let identifier: String = "AddJobPriority" + static let needsConfigSync: Bool = false + static let minExpectedRunDuration: TimeInterval = 0.1 + + static func migrate(_ db: Database) throws { + // Add `priority` to the job table + try db.alter(table: Job.self) { t in + t.add(.priority, .integer).defaults(to: 0) + } + + // Update the priorities for the below job types (want to ensure they run in the order + // specified to avoid weird bugs) + let variantPriorities: [Int: [Job.Variant]] = [ + 7: [Job.Variant.disappearingMessages], + 6: [Job.Variant.failedMessageSends, Job.Variant.failedAttachmentDownloads], + 5: [Job.Variant.getSnodePool], + 4: [Job.Variant.syncPushTokens], + 3: [Job.Variant.retrieveDefaultOpenGroupRooms], + 2: [Job.Variant.updateProfilePicture], + 1: [Job.Variant.garbageCollection] + ] + + try variantPriorities.forEach { priority, variants in + try Job + .filter(variants.contains(Job.Columns.variant)) + .updateAll( + db, + Job.Columns.priority.set(to: priority) + ) + } + + Storage.update(progress: 1, for: self, in: target) // In case this is the last migration + } +} diff --git a/SessionUtilitiesKit/Database/Models/Job.swift b/SessionUtilitiesKit/Database/Models/Job.swift index 84bd5b970..5f3ed92b3 100644 --- a/SessionUtilitiesKit/Database/Models/Job.swift +++ b/SessionUtilitiesKit/Database/Models/Job.swift @@ -28,6 +28,7 @@ public struct Job: Codable, Equatable, Identifiable, FetchableRecord, MutablePer public typealias Columns = CodingKeys public enum CodingKeys: String, CodingKey, ColumnExpression { case id + case priority case failureCount case variant case behaviour @@ -132,6 +133,15 @@ public struct Job: Codable, Equatable, Identifiable, FetchableRecord, MutablePer /// the database yet this value will be `nil` public var id: Int64? = nil + /// The `priority` value is used to allow for forcing some jobs to run before others (Default value `0`) + /// + /// Jobs will be run in the following order: + /// - Jobs scheduled in the past (or with no `nextRunTimestamp`) first + /// - Jobs with a higher `priority` value + /// - Jobs with a sooner `nextRunTimestamp` value + /// - The order the job was inserted into the database + public var priority: Int64 + /// A counter for the number of times this job has failed public let failureCount: UInt @@ -190,6 +200,7 @@ public struct Job: Codable, Equatable, Identifiable, FetchableRecord, MutablePer fileprivate init( id: Int64?, + priority: Int64 = 0, failureCount: UInt, variant: Variant, behaviour: Behaviour, @@ -207,6 +218,7 @@ public struct Job: Codable, Equatable, Identifiable, FetchableRecord, MutablePer ) self.id = id + self.priority = priority self.failureCount = failureCount self.variant = variant self.behaviour = behaviour @@ -219,6 +231,7 @@ public struct Job: Codable, Equatable, Identifiable, FetchableRecord, MutablePer } public init( + priority: Int64 = 0, failureCount: UInt = 0, variant: Variant, behaviour: Behaviour = .runOnce, @@ -234,6 +247,7 @@ public struct Job: Codable, Equatable, Identifiable, FetchableRecord, MutablePer shouldSkipLaunchBecomeActive: shouldSkipLaunchBecomeActive ) + self.priority = priority self.failureCount = failureCount self.variant = variant self.behaviour = behaviour @@ -246,6 +260,7 @@ public struct Job: Codable, Equatable, Identifiable, FetchableRecord, MutablePer } public init?( + priority: Int64 = 0, failureCount: UInt = 0, variant: Variant, behaviour: Behaviour = .runOnce, @@ -268,6 +283,7 @@ public struct Job: Codable, Equatable, Identifiable, FetchableRecord, MutablePer let detailsData: Data = try? JSONEncoder().encode(details) else { return nil } + self.priority = priority self.failureCount = failureCount self.variant = variant self.behaviour = behaviour @@ -328,8 +344,12 @@ extension Job { ) ) .filter(variants.contains(Job.Columns.variant)) - .order(Job.Columns.nextRunTimestamp) - .order(Job.Columns.id) + .order( + Job.Columns.nextRunTimestamp > Date().timeIntervalSince1970, // Past jobs first + Job.Columns.priority.desc, + Job.Columns.nextRunTimestamp, + Job.Columns.id + ) if excludeFutureJobs { query = query.filter(Job.Columns.nextRunTimestamp <= Date().timeIntervalSince1970) @@ -352,6 +372,7 @@ public extension Job { ) -> Job { return Job( id: self.id, + priority: self.priority, failureCount: failureCount, variant: self.variant, behaviour: self.behaviour, @@ -369,6 +390,7 @@ public extension Job { return Job( id: self.id, + priority: self.priority, failureCount: self.failureCount, variant: self.variant, behaviour: self.behaviour, diff --git a/SessionUtilitiesKit/Database/Types/PagedDatabaseObserver.swift b/SessionUtilitiesKit/Database/Types/PagedDatabaseObserver.swift index d052e4e11..30d8fb877 100644 --- a/SessionUtilitiesKit/Database/Types/PagedDatabaseObserver.swift +++ b/SessionUtilitiesKit/Database/Types/PagedDatabaseObserver.swift @@ -1013,18 +1013,20 @@ public enum PagedData { target: updatedData ) - // No need to do anything if there were no changes - guard !changeset.isEmpty else { return } - - // If we have the callback then trigger it, otherwise just store the changes to be sent - // to the callback if we ever start observing again (when we have the callback it needs - // to do the data updating as it's tied to UI updates and can cause crashes if not updated - // in the correct order) + /// If we have the callback then trigger it, otherwise just store the changes to be sent to the callback if we ever + /// start observing again (when we have the callback it needs to do the data updating as it's tied to UI updates + /// and can cause crashes if not updated in the correct order) + /// + /// **Note:** We do this even if the 'changeset' is empty because if this change reverts a previous change we + /// need to ensure the `onUnobservedDataChange` gets cleared so it doesn't end up in an invalid state guard let onDataChange: (([SectionModel], StagedChangeset<[SectionModel]>) -> ()) = onDataChange else { onUnobservedDataChange(updatedData, changeset) return } + // No need to do anything if there were no changes + guard !changeset.isEmpty else { return } + onDataChange(updatedData, changeset) } diff --git a/SessionUtilitiesKit/Database/Utilities/Database+Utilities.swift b/SessionUtilitiesKit/Database/Utilities/Database+Utilities.swift index b8e849c74..2e09ce275 100644 --- a/SessionUtilitiesKit/Database/Utilities/Database+Utilities.swift +++ b/SessionUtilitiesKit/Database/Utilities/Database+Utilities.swift @@ -36,4 +36,93 @@ public extension Database { sqlite3_interrupt(sqliteConnection) } + + /// This is a custom implementation of the `afterNextTransaction` method which executes the closures within their own + /// transactions to allow for nesting of 'afterNextTransaction' actions + /// + /// **Note:** GRDB doesn't notify read-only transactions to transaction observers + func afterNextTransactionNested( + onCommit: @escaping (Database) -> Void, + onRollback: @escaping (Database) -> Void = { _ in } + ) { + afterNextTransactionNestedOnce( + dedupeIdentifier: UUID().uuidString, + onCommit: onCommit, + onRollback: onRollback + ) + } + + func afterNextTransactionNestedOnce( + dedupeIdentifier: String, + onCommit: @escaping (Database) -> Void, + onRollback: @escaping (Database) -> Void = { _ in } + ) { + // Only allow a single observer per `dedupeIdentifier` per transaction, this allows us to + // schedule an action to run at most once per transaction (eg. auto-scheduling a ConfigSyncJob + // when receiving messages) + guard !TransactionHandler.registeredHandlers.wrappedValue.contains(dedupeIdentifier) else { + return + } + + add( + transactionObserver: TransactionHandler( + identifier: dedupeIdentifier, + onCommit: onCommit, + onRollback: onRollback + ), + extent: .nextTransaction + ) + } +} + +fileprivate class TransactionHandler: TransactionObserver { + static var registeredHandlers: Atomic> = Atomic([]) + + let identifier: String + let onCommit: (Database) -> Void + let onRollback: (Database) -> Void + + init( + identifier: String, + onCommit: @escaping (Database) -> Void, + onRollback: @escaping (Database) -> Void + ) { + self.identifier = identifier + self.onCommit = onCommit + self.onRollback = onRollback + + TransactionHandler.registeredHandlers.mutate { $0.insert(identifier) } + } + + // Ignore changes + func observes(eventsOfKind eventKind: DatabaseEventKind) -> Bool { false } + func databaseDidChange(with event: DatabaseEvent) { } + + func databaseDidCommit(_ db: Database) { + TransactionHandler.registeredHandlers.mutate { $0.remove(identifier) } + + do { + try db.inTransaction { + onCommit(db) + return .commit + } + } + catch { + SNLog("[Database] afterNextTransactionNested onCommit failed") + } + } + + func databaseDidRollback(_ db: Database) { + TransactionHandler.registeredHandlers.mutate { $0.remove(identifier) } + + do { + try db.inTransaction { + onRollback(db) + return .commit + } + } + catch { + SNLog("[Database] afterNextTransactionNested onRollback failed") + } + } } diff --git a/SessionUtilitiesKit/General/Dictionary+Utilities.swift b/SessionUtilitiesKit/General/Dictionary+Utilities.swift index d05bfe761..7adfe64aa 100644 --- a/SessionUtilitiesKit/General/Dictionary+Utilities.swift +++ b/SessionUtilitiesKit/General/Dictionary+Utilities.swift @@ -34,6 +34,12 @@ public extension Dictionary { return self[key] } + func getting(_ key: Key?) -> Value? { + guard let key: Key = key else { return nil } + + return self[key] + } + func setting(_ key: Key?, _ value: Value?) -> [Key: Value] { guard let key: Key = key else { return self } diff --git a/SessionUtilitiesKit/JobRunner/JobRunner.swift b/SessionUtilitiesKit/JobRunner/JobRunner.swift index 460c0632d..7296a0779 100644 --- a/SessionUtilitiesKit/JobRunner/JobRunner.swift +++ b/SessionUtilitiesKit/JobRunner/JobRunner.swift @@ -143,7 +143,7 @@ public final class JobRunner { guard canStartJob else { return } // Start the job runner if needed - db.afterNextTransaction { _ in + db.afterNextTransactionNested { _ in queues.wrappedValue[updatedJob.variant]?.start() } } @@ -166,7 +166,7 @@ public final class JobRunner { guard canStartJob else { return } // Start the job runner if needed - db.afterNextTransaction { _ in + db.afterNextTransactionNested { _ in queues.wrappedValue[job.variant]?.start() } } @@ -211,7 +211,10 @@ public final class JobRunner { ].contains(Job.Columns.behaviour) ) .filter(Job.Columns.shouldBlock == true) - .order(Job.Columns.id) + .order( + Job.Columns.priority.desc, + Job.Columns.id + ) .fetchAll(db) let nonblockingJobs: [Job] = try Job .filter( @@ -221,7 +224,10 @@ public final class JobRunner { ].contains(Job.Columns.behaviour) ) .filter(Job.Columns.shouldBlock == false) - .order(Job.Columns.id) + .order( + Job.Columns.priority.desc, + Job.Columns.id + ) .fetchAll(db) return (blockingJobs, nonblockingJobs) @@ -260,7 +266,10 @@ public final class JobRunner { .read { db in return try Job .filter(Job.Columns.behaviour == Job.Behaviour.recurringOnActive) - .order(Job.Columns.id) + .order( + Job.Columns.priority.desc, + Job.Columns.id + ) .fetchAll(db) } .defaulting(to: []) From f30b383bb81f592908623d3e16170047c4bc14a4 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Mon, 20 Feb 2023 12:56:48 +1100 Subject: [PATCH 030/135] Updated to the latest version of libSession-util Updated the SharedConfigMessage type to have a TTL of 30 days Updated the SnodeAPI to have a 'poll' method to be more consistent with the OpenGroupAPI (it also does multiple things now so is cleaner) Added logic to limit the number of config messages to be retrieved per poll Added the 'ValidatableResponse' protocol to standardise SnodeAPI response validation Added the libSession version to the logs Fixed an issue where the user profile pic wouldn't get synced correctly due to memory going out of scope Fixed some threading issues Refactored the thread variants to follow the updated terminology (will think about refactoring other code areas later) Cleaned up the Combine error handling Started fixing broken unit tests --- Session.xcodeproj/project.pbxproj | 28 + .../Calls/Call Management/SessionCall.swift | 6 +- Session/Closed Groups/EditClosedGroupVC.swift | 7 +- Session/Closed Groups/NewClosedGroupVC.swift | 3 +- .../Context Menu/ContextMenuVC+Action.swift | 8 +- .../ConversationVC+Interaction.swift | 60 +-- .../Conversations/ConversationViewModel.swift | 4 +- .../Message Cells/VisibleMessageCell.swift | 24 +- .../Settings/ThreadSettingsViewModel.swift | 28 +- .../ConversationTitleView.swift | 4 +- Session/Home/HomeVC.swift | 2 +- Session/Home/HomeViewModel.swift | 6 +- .../MessageRequestsViewController.swift | 4 +- .../MessageRequestsViewModel.swift | 4 +- .../GIFs/GiphyAPI.swift | 9 +- .../PhotoCapture.swift | 42 +- .../PhotoLibrary.swift | 102 ++-- Session/Notifications/AppNotifications.swift | 23 +- .../PushRegistrationManager.swift | 18 +- Session/Notifications/SyncPushTokensJob.swift | 18 +- .../UserNotificationsAdaptee.swift | 38 +- Session/Onboarding/Onboarding.swift | 9 +- Session/Open Groups/JoinOpenGroupVC.swift | 4 +- .../Open Groups/OpenGroupSuggestionGrid.swift | 2 +- Session/Settings/HelpViewModel.swift | 2 +- Session/Shared/FullConversationCell.swift | 14 +- Session/Utilities/BackgroundPoller.swift | 12 +- Session/Utilities/MockDataGenerator.swift | 4 +- SessionMessagingKit/Calls/WebRTCSession.swift | 88 +-- .../Migrations/_003_YDBToGRDBMigration.swift | 2 +- .../Database/Models/Attachment.swift | 13 +- .../Database/Models/Interaction.swift | 4 +- .../Database/Models/LinkPreview.swift | 119 ++-- .../Database/Models/Profile.swift | 4 +- .../Database/Models/SessionThread.swift | 12 +- .../Database/Models/SharedConfigDump.swift | 8 +- .../File Server/FileServerAPI.swift | 11 +- .../Jobs/Types/AttachmentDownloadJob.swift | 72 ++- .../Jobs/Types/ConfigurationSyncJob.swift | 54 +- .../Jobs/Types/GarbageCollectionJob.swift | 2 +- .../Jobs/Types/MessageSendJob.swift | 4 +- .../Jobs/Types/SendReadReceiptsJob.swift | 9 +- .../SessionUtil+Contacts.swift | 80 +-- .../SessionUtil+ConvoInfoVolatile.swift | 126 ++--- .../SessionUtil+UserProfile.swift | 52 +- .../LibSessionUtil/SessionUtil.swift | 30 +- .../LibSessionUtil/SessionUtilError.swift | 1 + .../Utilities/TypeConversion+Utilities.swift | 104 ++++ .../libsession-util.xcframework/Info.plist | 24 +- .../ios-arm64/libsession-util.a | Bin 1065320 -> 2034440 bytes .../libsession-util.a | Bin 2357680 -> 4484472 bytes .../module.modulemap | 3 + .../session/config.hpp | 11 +- .../session/config/base.hpp | 18 +- .../session/config/community.hpp | 239 ++++++++ .../session/config/contacts.h | 13 +- .../session/config/contacts.hpp | 59 +- .../session/config/convo_info_volatile.h | 87 +-- .../session/config/convo_info_volatile.hpp | 222 ++++---- .../session/config/expiring.h | 7 + .../session/config/expiring.hpp | 8 + .../session/config/namespaces.hpp | 2 +- .../session/config/profile_pic.h | 13 +- .../session/config/profile_pic.hpp | 60 ++- .../session/config/user_groups.h | 181 +++++++ .../session/config/user_groups.hpp | 335 ++++++++++++ .../session/config/user_profile.hpp | 6 +- .../session/version.h | 19 + .../ClosedGroupControlMessage.swift | 4 +- .../SharedConfigMessage.swift | 12 +- .../Messages/Message+Destination.swift | 4 +- SessionMessagingKit/Messages/Message.swift | 2 +- .../Visible Messages/VisibleMessage.swift | 4 +- .../Open Groups/OpenGroupAPI.swift | 37 +- .../Open Groups/OpenGroupManager.swift | 107 ++-- .../Protos/Generated/SNProto.swift | 6 +- .../Protos/Generated/SessionProtos.pb.swift | 8 +- .../Protos/SessionProtos.proto | 2 +- .../Attachments/SignalAttachment.swift | 32 +- .../MessageReceiver+ClosedGroups.swift | 2 +- ...essageReceiver+ConfigurationMessages.swift | 2 +- .../MessageReceiver+VisibleMessages.swift | 4 +- .../MessageSender+ClosedGroups.swift | 2 +- .../Sending & Receiving/MessageReceiver.swift | 4 +- .../MessageSender+Convenience.swift | 3 +- .../Sending & Receiving/MessageSender.swift | 49 +- .../Notifications/PushNotificationAPI.swift | 2 +- .../Pollers/ClosedGroupPoller.swift | 9 +- .../Pollers/CurrentUserPoller.swift | 9 +- .../Pollers/OpenGroupPoller.swift | 4 +- .../Sending & Receiving/Pollers/Poller.swift | 29 +- .../Typing Indicators/TypingIndicators.swift | 6 +- .../Shared Models/MentionInfo.swift | 6 +- .../Shared Models/MessageViewModel.swift | 10 +- .../SessionThreadViewModel.swift | 20 +- .../Utilities/ProfileManager.swift | 2 +- .../LibSessionUtil/ConfigContactsSpec.swift | 116 ++-- .../ConfigConvoInfoVolatileSpec.swift | 76 ++- .../ConfigUserProfileSpec.swift | 72 +-- .../TypeConversionUtilitiesSpec.swift | 196 +++++++ .../Open Groups/OpenGroupAPISpec.swift | 144 ++--- .../Open Groups/OpenGroupManagerSpec.swift | 36 +- .../NSENotificationPresenter.swift | 18 +- .../NotificationServiceExtension.swift | 2 +- .../ShareNavController.swift | 267 +++++---- SessionShareExtension/ThreadPickerVC.swift | 9 +- .../Models/DeleteAllBeforeResponse.swift | 27 +- .../Models/DeleteAllMessagesResponse.swift | 89 +-- .../Models/DeleteMessagesResponse.swift | 87 +-- .../Models/GetMessagesRequest.swift | 12 +- .../Models/LegacyGetMessagesRequest.swift | 6 + .../Models/RevokeSubkeyResponse.swift | 41 +- .../Models/SendMessageRequest.swift | 19 +- .../Models/SendMessageResponse.swift | 52 +- SessionSnodeKit/Models/SnodeSwarmItem.swift | 7 +- .../Models/UpdateExpiryAllResponse.swift | 99 ++-- .../Models/UpdateExpiryRequest.swift | 25 + .../Models/UpdateExpiryResponse.swift | 83 +-- .../OnionRequestAPI+Encryption.swift | 24 +- .../Networking/OnionRequestAPI.swift | 15 +- SessionSnodeKit/Networking/SnodeAPI.swift | 509 ++++++++++-------- SessionSnodeKit/Types/SnodeAPIError.swift | 2 + SessionSnodeKit/Types/SnodeAPINamespace.swift | 49 +- .../Types/ValidatableResponse.swift | 86 +++ .../ThreadSettingsViewModelSpec.swift | 8 +- .../UIContextualAction+Theming.swift | 5 + .../Combine/Publisher+Utilities.swift | 48 +- SessionUtilitiesKit/Database/Storage.swift | 28 +- .../General/Array+Utilities.swift | 8 - .../General/String+Utilities.swift | 14 - SessionUtilitiesKit/JobRunner/JobRunner.swift | 20 +- .../Networking/BatchResponse.swift | 44 +- .../Networking/ProxiedContentDownloader.swift | 34 +- .../Profile Pictures/ProfilePictureView.swift | 4 +- _SharedTestUtilities/SynchronousStorage.swift | 22 +- 135 files changed, 3315 insertions(+), 2056 deletions(-) create mode 100644 SessionMessagingKit/LibSessionUtil/Utilities/TypeConversion+Utilities.swift create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/community.hpp create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/expiring.h create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/expiring.hpp create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.h create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.hpp create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/version.h create mode 100644 SessionMessagingKitTests/LibSessionUtil/Utilities/TypeConversionUtilitiesSpec.swift create mode 100644 SessionSnodeKit/Types/ValidatableResponse.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index f42e21826..ce6b2ef85 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -631,6 +631,8 @@ FD3C907127E445E500CD579F /* MessageReceiverDecryptionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C907027E445E500CD579F /* MessageReceiverDecryptionSpec.swift */; }; FD3E0C84283B5835002A425C /* SessionThreadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3E0C83283B5835002A425C /* SessionThreadViewModel.swift */; }; FD42F9A8285064B800A0C77D /* PushNotificationAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBDE255A581900E217F9 /* PushNotificationAPI.swift */; }; + FD4324302999F0BC008A0213 /* ValidatableResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD43242F2999F0BC008A0213 /* ValidatableResponse.swift */; }; + FD432437299DEA38008A0213 /* TypeConversion+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD432436299DEA38008A0213 /* TypeConversion+Utilities.swift */; }; FD43EE9D297A5190009C87C5 /* SessionUtil+Groups.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD43EE9C297A5190009C87C5 /* SessionUtil+Groups.swift */; }; FD43EE9F297E2EE0009C87C5 /* SessionUtil+ConvoInfoVolatile.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD43EE9E297E2EE0009C87C5 /* SessionUtil+ConvoInfoVolatile.swift */; }; FD4B200E283492210034334B /* InsetLockableTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD4B200D283492210034334B /* InsetLockableTableView.swift */; }; @@ -806,6 +808,7 @@ FDD2506E283711D600198BDA /* DifferenceKit+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD2506D283711D600198BDA /* DifferenceKit+Utilities.swift */; }; FDD250702837199200198BDA /* GarbageCollectionJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD2506F2837199200198BDA /* GarbageCollectionJob.swift */; }; FDD250722837234B00198BDA /* MediaGalleryNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD250712837234B00198BDA /* MediaGalleryNavigationController.swift */; }; + FDDC08F229A300E800BF9681 /* TypeConversionUtilitiesSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDDC08F129A300E800BF9681 /* TypeConversionUtilitiesSpec.swift */; }; FDE658A129418C7900A33BC1 /* CryptoKit+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE658A029418C7900A33BC1 /* CryptoKit+Utilities.swift */; }; FDE658A329418E2F00A33BC1 /* KeyPair.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE658A229418E2F00A33BC1 /* KeyPair.swift */; }; FDE77F6B280FEB28002CFC5D /* ControlMessageProcessRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE77F6A280FEB28002CFC5D /* ControlMessageProcessRecord.swift */; }; @@ -1756,6 +1759,8 @@ FD3C907027E445E500CD579F /* MessageReceiverDecryptionSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageReceiverDecryptionSpec.swift; sourceTree = ""; }; FD3C907427E83AC200CD579F /* OpenGroupServerIdLookup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupServerIdLookup.swift; sourceTree = ""; }; FD3E0C83283B5835002A425C /* SessionThreadViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionThreadViewModel.swift; sourceTree = ""; }; + FD43242F2999F0BC008A0213 /* ValidatableResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValidatableResponse.swift; sourceTree = ""; }; + FD432436299DEA38008A0213 /* TypeConversion+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TypeConversion+Utilities.swift"; sourceTree = ""; }; FD43EE9C297A5190009C87C5 /* SessionUtil+Groups.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionUtil+Groups.swift"; sourceTree = ""; }; FD43EE9E297E2EE0009C87C5 /* SessionUtil+ConvoInfoVolatile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionUtil+ConvoInfoVolatile.swift"; sourceTree = ""; }; FD4B200D283492210034334B /* InsetLockableTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsetLockableTableView.swift; sourceTree = ""; }; @@ -1922,6 +1927,7 @@ FDD2506D283711D600198BDA /* DifferenceKit+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DifferenceKit+Utilities.swift"; sourceTree = ""; }; FDD2506F2837199200198BDA /* GarbageCollectionJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GarbageCollectionJob.swift; sourceTree = ""; }; FDD250712837234B00198BDA /* MediaGalleryNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaGalleryNavigationController.swift; sourceTree = ""; }; + FDDC08F129A300E800BF9681 /* TypeConversionUtilitiesSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypeConversionUtilitiesSpec.swift; sourceTree = ""; }; FDE658A029418C7900A33BC1 /* CryptoKit+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CryptoKit+Utilities.swift"; sourceTree = ""; }; FDE658A229418E2F00A33BC1 /* KeyPair.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyPair.swift; sourceTree = ""; }; FDE7214F287E50D50093DF33 /* ProtoWrappers.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = ProtoWrappers.py; sourceTree = ""; }; @@ -3853,6 +3859,14 @@ path = "Shared Models"; sourceTree = ""; }; + FD432435299DEA1C008A0213 /* Utilities */ = { + isa = PBXGroup; + children = ( + FD432436299DEA38008A0213 /* TypeConversion+Utilities.swift */, + ); + path = Utilities; + sourceTree = ""; + }; FD7115F528C8150600B47552 /* Combine */ = { isa = PBXGroup; children = ( @@ -4030,6 +4044,7 @@ children = ( FD2B4B022949886900AB4848 /* Database */, FD8ECF8E29381FB200C0D1BB /* Config Handling */, + FD432435299DEA1C008A0213 /* Utilities */, FD8ECF7829340F7100C0D1BB /* libsession-util.xcframework */, FD8ECF882935AB7200C0D1BB /* SessionUtilError.swift */, FD8ECF7A29340FFD00C0D1BB /* SessionUtil.swift */, @@ -4040,6 +4055,7 @@ FD8ECF802934385900C0D1BB /* LibSessionUtil */ = { isa = PBXGroup; children = ( + FDDC08F029A300D500BF9681 /* Utilities */, FD2B4AFA29429D1000AB4848 /* ConfigContactsSpec.swift */, FD8ECF812934387A00C0D1BB /* ConfigUserProfileSpec.swift */, FDBB25E02983909300F1508E /* ConfigConvoInfoVolatileSpec.swift */, @@ -4203,6 +4219,14 @@ path = _TestUtilities; sourceTree = ""; }; + FDDC08F029A300D500BF9681 /* Utilities */ = { + isa = PBXGroup; + children = ( + FDDC08F129A300E800BF9681 /* TypeConversionUtilitiesSpec.swift */, + ); + path = Utilities; + sourceTree = ""; + }; FDE7214E287E50D50093DF33 /* Scripts */ = { isa = PBXGroup; children = ( @@ -4259,6 +4283,7 @@ FDF848DE29405D6E007DCAE5 /* OnionRequestAPIVersion.swift */, FDF848E229405D6E007DCAE5 /* OnionRequestAPIError.swift */, FDF848E129405D6E007DCAE5 /* OnionRequestAPIDestination.swift */, + FD43242F2999F0BC008A0213 /* ValidatableResponse.swift */, ); path = Types; sourceTree = ""; @@ -5430,6 +5455,7 @@ FDF848CE29405C5B007DCAE5 /* UpdateExpiryAllRequest.swift in Sources */, FDF848C229405C5A007DCAE5 /* OxenDaemonRPCRequest.swift in Sources */, FDF848DC29405C5B007DCAE5 /* RevokeSubkeyRequest.swift in Sources */, + FD4324302999F0BC008A0213 /* ValidatableResponse.swift in Sources */, FD17D7A027F40CC800122BE0 /* _001_InitialSetupMigration.swift in Sources */, FDF848EC29405E4F007DCAE5 /* OnionRequestAPI+Encryption.swift in Sources */, FD17D7AA27F41BF500122BE0 /* SnodeSet.swift in Sources */, @@ -5762,6 +5788,7 @@ C32C5DBF256DD743003C73A2 /* ClosedGroupPoller.swift in Sources */, C352A35B2557824E00338F3E /* AttachmentUploadJob.swift in Sources */, FD5C7305284F0FF30029977D /* MessageReceiver+VisibleMessages.swift in Sources */, + FD432437299DEA38008A0213 /* TypeConversion+Utilities.swift in Sources */, FD2B4B042949887A00AB4848 /* QueryInterfaceRequest+Utilities.swift in Sources */, FD09797027FA6FF300936362 /* Profile.swift in Sources */, FD245C56285065EA00B966DD /* SNProto.swift in Sources */, @@ -6023,6 +6050,7 @@ buildActionMask = 2147483647; files = ( FD3C905C27E3FBEF00CD579F /* BatchRequestInfoSpec.swift in Sources */, + FDDC08F229A300E800BF9681 /* TypeConversionUtilitiesSpec.swift in Sources */, FD859EFA27C2F5C500510D0C /* MockGenericHash.swift in Sources */, FDC2909427D710B4005DAE71 /* SOGSEndpointSpec.swift in Sources */, FDC290B327DFF9F5005DAE71 /* TestOnionRequestAPI.swift in Sources */, diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index 3c4901fc7..d708302f9 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -326,7 +326,7 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate { guard shouldMarkAsRead, - let threadVariant: SessionThread.Variant = try SessionThread + let threadVariant: SessionThread.Variant = try? SessionThread .filter(id: interaction.threadId) .select(.variant) .asRequest(of: SessionThread.Variant.self) @@ -421,7 +421,9 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate { let webRTCSession: WebRTCSession = self.webRTCSession Storage.shared - .readPublisherFlatMap { db in webRTCSession.sendOffer(db, to: sessionId, isRestartingICEConnection: true) } + .readPublisherFlatMap(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db in + webRTCSession.sendOffer(db, to: sessionId, isRestartingICEConnection: true) + } .sinkUntilComplete() } diff --git a/Session/Closed Groups/EditClosedGroupVC.swift b/Session/Closed Groups/EditClosedGroupVC.swift index b866fb510..c215c86f0 100644 --- a/Session/Closed Groups/EditClosedGroupVC.swift +++ b/Session/Closed Groups/EditClosedGroupVC.swift @@ -462,18 +462,19 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat ModalActivityIndicatorViewController.present(fromViewController: navigationController) { _ in Storage.shared - .writePublisherFlatMap { db -> AnyPublisher in + .writePublisherFlatMap(receiveOn: DispatchQueue.main) { db -> AnyPublisher in if !updatedMemberIds.contains(userPublicKey) { - return try MessageSender.leave(db, groupPublicKey: threadId) + return MessageSender.leave(db, groupPublicKey: threadId) } - return try MessageSender.update( + return MessageSender.update( db, groupPublicKey: threadId, with: updatedMemberIds, name: updatedName ) } + .receive(on: DispatchQueue.main) .sinkUntilComplete( receiveCompletion: { [weak self] result in self?.dismiss(animated: true, completion: nil) // Dismiss the loader diff --git a/Session/Closed Groups/NewClosedGroupVC.swift b/Session/Closed Groups/NewClosedGroupVC.swift index c12118742..887fa9b08 100644 --- a/Session/Closed Groups/NewClosedGroupVC.swift +++ b/Session/Closed Groups/NewClosedGroupVC.swift @@ -333,10 +333,9 @@ final class NewClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegate let message: String? = (selectedContacts.count > 20 ? "GROUP_CREATION_PLEASE_WAIT".localized() : nil) ModalActivityIndicatorViewController.present(fromViewController: navigationController!, message: message) { [weak self] _ in Storage.shared - .writePublisherFlatMap { db in + .writePublisherFlatMap(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db in MessageSender.createClosedGroup(db, name: name, members: selectedContacts) } - .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .receive(on: DispatchQueue.main) .sinkUntilComplete( receiveCompletion: { result in diff --git a/Session/Conversations/Context Menu/ContextMenuVC+Action.swift b/Session/Conversations/Context Menu/ContextMenuVC+Action.swift index b8f19816b..bb3ab7fd7 100644 --- a/Session/Conversations/Context Menu/ContextMenuVC+Action.swift +++ b/Session/Conversations/Context Menu/ContextMenuVC+Action.swift @@ -158,20 +158,20 @@ extension ContextMenuVC { ) let canCopySessionId: Bool = ( cellViewModel.variant == .standardIncoming && - cellViewModel.threadVariant != .openGroup + cellViewModel.threadVariant != .community ) let canDelete: Bool = ( - cellViewModel.threadVariant != .openGroup || + cellViewModel.threadVariant != .community || currentUserIsOpenGroupModerator || cellViewModel.state == .failed ) let canBan: Bool = ( - cellViewModel.threadVariant == .openGroup && + cellViewModel.threadVariant == .community && currentUserIsOpenGroupModerator ) let shouldShowEmojiActions: Bool = { - if cellViewModel.threadVariant == .openGroup { + if cellViewModel.threadVariant == .community { return OpenGroupManager.isOpenGroupSupport(.reactions, on: cellViewModel.threadOpenGroupServer) } return !currentThreadIsMessageRequest diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 53e6331c3..9ab7b8efd 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -421,7 +421,7 @@ extension ConversationVC: // Send the message Storage.shared - .writePublisher { [weak self] db in + .writePublisher(receiveOn: DispatchQueue.global(qos: .userInitiated)) { [weak self] db in guard let thread: SessionThread = try SessionThread.fetchOne(db, id: threadId) else { return } @@ -545,7 +545,7 @@ extension ConversationVC: // Send the message Storage.shared - .writePublisher { [weak self] db in + .writePublisher(receiveOn: DispatchQueue.global(qos: .userInitiated)) { [weak self] db in guard let thread: SessionThread = try SessionThread.fetchOne(db, id: threadId) else { return } @@ -1110,9 +1110,9 @@ extension ConversationVC: guard cellViewModel.reactionInfo?.isEmpty == false && ( - self.viewModel.threadData.threadVariant == .legacyClosedGroup || - self.viewModel.threadData.threadVariant == .closedGroup || - self.viewModel.threadData.threadVariant == .openGroup + self.viewModel.threadData.threadVariant == .legacyGroup || + self.viewModel.threadData.threadVariant == .group || + self.viewModel.threadData.threadVariant == .community ), let allMessages: [MessageViewModel] = self.viewModel.interactionData .first(where: { $0.model == .messages })? @@ -1173,10 +1173,10 @@ extension ConversationVC: } func removeAllReactions(_ cellViewModel: MessageViewModel, for emoji: String) { - guard cellViewModel.threadVariant == .openGroup else { return } + guard cellViewModel.threadVariant == .community else { return } Storage.shared - .readPublisherFlatMap { db -> AnyPublisher<(OpenGroupAPI.ReactionRemoveAllResponse, OpenGroupAPI.PendingChange), Error> in + .readPublisherFlatMap(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db -> AnyPublisher<(OpenGroupAPI.ReactionRemoveAllResponse, OpenGroupAPI.PendingChange), Error> in guard let openGroup: OpenGroup = try? OpenGroup .fetchOne(db, id: cellViewModel.threadId), @@ -1185,10 +1185,7 @@ extension ConversationVC: .filter(id: cellViewModel.id) .asRequest(of: Int64.self) .fetchOne(db) - else { - return Fail(error: StorageError.objectNotFound) - .eraseToAnyPublisher() - } + else { throw StorageError.objectNotFound } let pendingChange: OpenGroupAPI.PendingChange = OpenGroupManager .addPendingReaction( @@ -1267,7 +1264,7 @@ extension ConversationVC: // TODO: Need to test emoji reacts for both open groups and one-to-one to make sure this isn't broken // Perform the sending logic Storage.shared - .writePublisherFlatMap { db -> AnyPublisher in + .writePublisherFlatMap(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db -> AnyPublisher in guard let thread: SessionThread = try SessionThread.fetchOne(db, id: cellViewModel.threadId) else { return Just(nil) .setFailureType(to: Error.self) @@ -1360,10 +1357,7 @@ extension ConversationVC: .filter(id: cellViewModel.id) .asRequest(of: Int64.self) .fetchOne(db) - else { - return Fail(error: MessageSenderError.invalidMessage) - .eraseToAnyPublisher() - } + else { throw MessageSenderError.invalidMessage } let pendingChange: OpenGroupAPI.PendingChange = OpenGroupManager .addPendingReaction( @@ -1552,7 +1546,7 @@ extension ConversationVC: } Storage.shared - .writePublisherFlatMap { db in + .writePublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupManager.shared.add( db, roomToken: room, @@ -1674,9 +1668,11 @@ extension ConversationVC: // Remote deletion logic func deleteRemotely(from viewController: UIViewController?, request: AnyPublisher, onComplete: (() -> ())?) { // Show a loading indicator - Future { resolver in - ModalActivityIndicatorViewController.present(fromViewController: viewController, canCancel: false) { _ in - resolver(Result.success(())) + Deferred { + Future { resolver in + ModalActivityIndicatorViewController.present(fromViewController: viewController, canCancel: false) { _ in + resolver(Result.success(())) + } } } .flatMap { _ in request } @@ -1707,7 +1703,7 @@ extension ConversationVC: // How we delete the message differs depending on the type of thread switch cellViewModel.threadVariant { // Handle open group messages the old way - case .openGroup: + case .community: // If it's an incoming message the user must have moderator status let result: (openGroupServerMessageId: Int64?, openGroup: OpenGroup?)? = Storage.shared.read { db -> (Int64?, OpenGroup?) in ( @@ -1786,7 +1782,7 @@ extension ConversationVC: // Delete the message from the open group deleteRemotely( from: self, - request: Storage.shared.readPublisherFlatMap { db in + request: Storage.shared.readPublisherFlatMap(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db in OpenGroupAPI.messageDelete( db, id: openGroupServerMessageId, @@ -1800,7 +1796,7 @@ extension ConversationVC: self?.showInputAccessoryView() } - case .contact, .legacyClosedGroup, .closedGroup: + case .contact, .legacyGroup, .group: let serverHash: String? = Storage.shared.read { db -> String? in try Interaction .select(.serverHash) @@ -1859,7 +1855,7 @@ extension ConversationVC: }) actionSheet.addAction(UIAlertAction( - title: (cellViewModel.threadVariant == .legacyClosedGroup || cellViewModel.threadVariant == .closedGroup ? + title: (cellViewModel.threadVariant == .legacyGroup || cellViewModel.threadVariant == .group ? "delete_message_for_everyone".localized() : String(format: "delete_message_for_me_and_recipient".localized(), threadName) ), @@ -1963,7 +1959,7 @@ extension ConversationVC: } func ban(_ cellViewModel: MessageViewModel) { - guard cellViewModel.threadVariant == .openGroup else { return } + guard cellViewModel.threadVariant == .community else { return } let threadId: String = self.viewModel.threadData.threadId let modal: ConfirmationModal = ConfirmationModal( @@ -1975,10 +1971,9 @@ extension ConversationVC: cancelStyle: .alert_text, onConfirm: { [weak self] _ in Storage.shared - .readPublisherFlatMap { db -> AnyPublisher in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db -> AnyPublisher in guard let openGroup: OpenGroup = try OpenGroup.fetchOne(db, id: threadId) else { - return Fail(error: StorageError.objectNotFound) - .eraseToAnyPublisher() + throw StorageError.objectNotFound } return OpenGroupAPI @@ -2020,7 +2015,7 @@ extension ConversationVC: } func banAndDeleteAllMessages(_ cellViewModel: MessageViewModel) { - guard cellViewModel.threadVariant == .openGroup else { return } + guard cellViewModel.threadVariant == .community else { return } let threadId: String = self.viewModel.threadData.threadId let modal: ConfirmationModal = ConfirmationModal( @@ -2032,10 +2027,9 @@ extension ConversationVC: cancelStyle: .alert_text, onConfirm: { [weak self] _ in Storage.shared - .readPublisherFlatMap { db -> AnyPublisher in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db -> AnyPublisher in guard let openGroup: OpenGroup = try OpenGroup.fetchOne(db, id: threadId) else { - return Fail(error: StorageError.objectNotFound) - .eraseToAnyPublisher() + throw StorageError.objectNotFound } return OpenGroupAPI @@ -2300,7 +2294,7 @@ extension ConversationVC { } Storage.shared - .writePublisher { db in + .writePublisher(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db in // If we aren't creating a new thread (ie. sending a message request) then send a // messageRequestResponse back to the sender (this allows the sender to know that // they have been approved and can now use this contact in closed groups) diff --git a/Session/Conversations/ConversationViewModel.swift b/Session/Conversations/ConversationViewModel.swift index de43ffd5e..d5aee357d 100644 --- a/Session/Conversations/ConversationViewModel.swift +++ b/Session/Conversations/ConversationViewModel.swift @@ -107,7 +107,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { threadId: self.threadId, threadVariant: self.initialThreadVariant, threadIsNoteToSelf: (self.threadId == getUserHexEncodedPublicKey()), - currentUserIsClosedGroupMember: ((self.initialThreadVariant != .legacyClosedGroup && self.initialThreadVariant != .closedGroup) ? + currentUserIsClosedGroupMember: ((self.initialThreadVariant != .legacyGroup && self.initialThreadVariant != .group) ? nil : Storage.shared.read { db in GroupMember @@ -342,7 +342,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { .read { db -> [MentionInfo] in let userPublicKey: String = getUserHexEncodedPublicKey(db) let pattern: FTS5Pattern? = try? SessionThreadViewModel.pattern(db, searchTerm: query, forTable: Profile.self) - let capabilities: Set = (threadData.threadVariant != .openGroup ? + let capabilities: Set = (threadData.threadVariant != .community ? nil : try? Capability .select(.variant) diff --git a/Session/Conversations/Message Cells/VisibleMessageCell.swift b/Session/Conversations/Message Cells/VisibleMessageCell.swift index 49f688752..61dd3f5fb 100644 --- a/Session/Conversations/Message Cells/VisibleMessageCell.swift +++ b/Session/Conversations/Message Cells/VisibleMessageCell.swift @@ -306,18 +306,18 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { ) ) let isGroupThread: Bool = ( - cellViewModel.threadVariant == .openGroup || - cellViewModel.threadVariant == .legacyClosedGroup || - cellViewModel.threadVariant == .closedGroup + cellViewModel.threadVariant == .community || + cellViewModel.threadVariant == .legacyGroup || + cellViewModel.threadVariant == .group ) - // Profile picture view + // Profile picture view (should always be handled as a standard 'contact' profile picture) profilePictureViewLeadingConstraint.constant = (isGroupThread ? VisibleMessageCell.groupThreadHSpacing : 0) profilePictureViewWidthConstraint.constant = (isGroupThread ? VisibleMessageCell.profilePictureSize : 0) profilePictureView.isHidden = (!cellViewModel.shouldShowProfile || cellViewModel.profile == nil) profilePictureView.update( publicKey: cellViewModel.authorId, - threadVariant: cellViewModel.threadVariant, + threadVariant: .contact, // Should always be '.contact' customImageData: nil, profile: cellViewModel.profile, additionalProfile: nil @@ -710,9 +710,9 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { maxWidth: maxWidth, showingAllReactions: showExpandedReactions, showNumbers: ( - cellViewModel.threadVariant == .legacyClosedGroup || - cellViewModel.threadVariant == .closedGroup || - cellViewModel.threadVariant == .openGroup + cellViewModel.threadVariant == .legacyGroup || + cellViewModel.threadVariant == .group || + cellViewModel.threadVariant == .community ) ) } @@ -860,7 +860,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { if profilePictureView.bounds.contains(profilePictureView.convert(location, from: self)), cellViewModel.shouldShowProfile { // For open groups only attempt to start a conversation if the author has a blinded id - guard cellViewModel.threadVariant != .openGroup else { + guard cellViewModel.threadVariant != .community else { guard SessionId.Prefix(from: cellViewModel.authorId) == .blinded else { return } delegate?.startThread( @@ -1070,9 +1070,9 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { case .standardIncoming, .standardIncomingDeleted: let isGroupThread = ( - cellViewModel.threadVariant == .openGroup || - cellViewModel.threadVariant == .legacyClosedGroup || - cellViewModel.threadVariant == .closedGroup + cellViewModel.threadVariant == .community || + cellViewModel.threadVariant == .legacyGroup || + cellViewModel.threadVariant == .group ) let leftGutterSize = (isGroupThread ? leftGutterSize : contactThreadHSpacing) diff --git a/Session/Conversations/Settings/ThreadSettingsViewModel.swift b/Session/Conversations/Settings/ThreadSettingsViewModel.swift index 612bfb602..ace9bf25d 100644 --- a/Session/Conversations/Settings/ThreadSettingsViewModel.swift +++ b/Session/Conversations/Settings/ThreadSettingsViewModel.swift @@ -180,7 +180,7 @@ class ThreadSettingsViewModel: SessionTableViewModel = TypedTableAlias() let groupMember: TypedTableAlias = TypedTableAlias() - let threadVariants: [SessionThread.Variant] = [.legacyClosedGroup, .closedGroup] + let threadVariants: [SessionThread.Variant] = [.legacyGroup, .group] let targetRole: GroupMember.Role = GroupMember.Role.standard return SQL(""" @@ -367,12 +367,12 @@ public class HomeViewModel { public func delete(threadId: String, threadVariant: SessionThread.Variant) { Storage.shared.writeAsync { db in switch threadVariant { - case .legacyClosedGroup, .closedGroup: + case .legacyGroup, .group: MessageSender .leave(db, groupPublicKey: threadId) .sinkUntilComplete() - case .openGroup: + case .community: OpenGroupManager.shared.delete(db, openGroupId: threadId) default: break diff --git a/Session/Home/Message Requests/MessageRequestsViewController.swift b/Session/Home/Message Requests/MessageRequestsViewController.swift index c4cc812a7..98b424f4e 100644 --- a/Session/Home/Message Requests/MessageRequestsViewController.swift +++ b/Session/Home/Message Requests/MessageRequestsViewController.swift @@ -445,7 +445,7 @@ class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDat return UISwipeActionsConfiguration(actions: [ delete, block ]) - case .legacyClosedGroup, .closedGroup, .openGroup: + case .legacyGroup, .group, .community: return UISwipeActionsConfiguration(actions: [ delete ]) } @@ -469,7 +469,7 @@ class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDat let closedGroupThreadIds: [String] = (viewModel.threadData .first { $0.model == .threads }? .elements - .filter { $0.threadVariant == .legacyClosedGroup || $0.threadVariant == .closedGroup } + .filter { $0.threadVariant == .legacyGroup || $0.threadVariant == .group } .map { $0.threadId }) .defaulting(to: []) let alertVC: UIAlertController = UIAlertController( diff --git a/Session/Home/Message Requests/MessageRequestsViewModel.swift b/Session/Home/Message Requests/MessageRequestsViewModel.swift index 33ae824de..058c24486 100644 --- a/Session/Home/Message Requests/MessageRequestsViewModel.swift +++ b/Session/Home/Message Requests/MessageRequestsViewModel.swift @@ -186,12 +186,12 @@ public class MessageRequestsViewModel { ) { _ in Storage.shared.write { db in switch threadVariant { - case .contact, .openGroup: + case .contact, .community: _ = try SessionThread .filter(id: threadId) .deleteAll(db) - case .legacyClosedGroup, .closedGroup: + case .legacyGroup, .group: try ClosedGroup.removeKeysAndUnsubscribe( db, threadId: threadId, diff --git a/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift b/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift index 3b563a7dd..4f0495bf8 100644 --- a/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift +++ b/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift @@ -346,17 +346,14 @@ enum GiphyAPI { // URLError codes are negative values return HTTPError.generic } - .flatMap { data, _ -> AnyPublisher<[GiphyImageInfo], Error> in + .tryMap { data, _ -> [GiphyImageInfo] in Logger.error("search request succeeded") guard let imageInfos = self.parseGiphyImages(responseData: data) else { - return Fail(error: HTTPError.invalidResponse) - .eraseToAnyPublisher() + throw HTTPError.invalidResponse } - return Just(imageInfos) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() + return imageInfos } .eraseToAnyPublisher() } diff --git a/Session/Media Viewing & Editing/PhotoCapture.swift b/Session/Media Viewing & Editing/PhotoCapture.swift index 08fdf783b..51205b62f 100644 --- a/Session/Media Viewing & Editing/PhotoCapture.swift +++ b/Session/Media Viewing & Editing/PhotoCapture.swift @@ -86,26 +86,17 @@ class PhotoCapture: NSObject { return Just(()) .subscribe(on: sessionQueue) .setFailureType(to: Error.self) - .flatMap { [weak self] _ -> AnyPublisher in + .tryMap { [weak self] _ -> Void in self?.session.beginConfiguration() defer { self?.session.commitConfiguration() } - do { - try self?.updateCurrentInput(position: .back) - } - catch { - return Fail(error: error) - .eraseToAnyPublisher() - } + try self?.updateCurrentInput(position: .back) - guard let photoOutput = self?.captureOutput.photoOutput else { - return Fail(error: PhotoCaptureError.initializationFailed) - .eraseToAnyPublisher() - } - - guard self?.session.canAddOutput(photoOutput) == true else { - return Fail(error: PhotoCaptureError.initializationFailed) - .eraseToAnyPublisher() + guard + let photoOutput = self?.captureOutput.photoOutput, + self?.session.canAddOutput(photoOutput) == true + else { + throw PhotoCaptureError.initializationFailed } if let connection = photoOutput.connection(with: .video) { @@ -130,9 +121,7 @@ class PhotoCapture: NSObject { } } - return Just(()) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() + return () } .handleEvents( receiveCompletion: { [weak self] result in @@ -172,21 +161,12 @@ class PhotoCapture: NSObject { return Just(()) .setFailureType(to: Error.self) .subscribe(on: sessionQueue) - .flatMap { [weak self, newPosition = self.desiredPosition] _ -> AnyPublisher in + .tryMap { [weak self, newPosition = self.desiredPosition] _ -> Void in self?.session.beginConfiguration() defer { self?.session.commitConfiguration() } - do { - try self?.updateCurrentInput(position: newPosition) - } - catch { - return Fail(error: error) - .eraseToAnyPublisher() - } - - return Just(()) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() + try self?.updateCurrentInput(position: newPosition) + return () } .eraseToAnyPublisher() } diff --git a/Session/Media Viewing & Editing/PhotoLibrary.swift b/Session/Media Viewing & Editing/PhotoLibrary.swift index 056514ebd..84fd28be2 100644 --- a/Session/Media Viewing & Editing/PhotoLibrary.swift +++ b/Session/Media Viewing & Editing/PhotoLibrary.swift @@ -137,64 +137,68 @@ class PhotoCollectionContents { } private func requestImageDataSource(for asset: PHAsset) -> AnyPublisher<(dataSource: DataSource, dataUTI: String), Error> { - return Future { [weak self] resolver in - - let options: PHImageRequestOptions = PHImageRequestOptions() - options.isNetworkAccessAllowed = true - - _ = self?.imageManager.requestImageData(for: asset, options: options) { imageData, dataUTI, orientation, info in - - guard let imageData = imageData else { - resolver(Result.failure(PhotoLibraryError.assertionError(description: "imageData was unexpectedly nil"))) - return + return Deferred { + Future { [weak self] resolver in + + let options: PHImageRequestOptions = PHImageRequestOptions() + options.isNetworkAccessAllowed = true + + _ = self?.imageManager.requestImageData(for: asset, options: options) { imageData, dataUTI, orientation, info in + + guard let imageData = imageData else { + resolver(Result.failure(PhotoLibraryError.assertionError(description: "imageData was unexpectedly nil"))) + return + } + + guard let dataUTI = dataUTI else { + resolver(Result.failure(PhotoLibraryError.assertionError(description: "dataUTI was unexpectedly nil"))) + return + } + + guard let dataSource = DataSourceValue.dataSource(with: imageData, utiType: dataUTI) else { + resolver(Result.failure(PhotoLibraryError.assertionError(description: "dataSource was unexpectedly nil"))) + return + } + + resolver(Result.success((dataSource: dataSource, dataUTI: dataUTI))) } - - guard let dataUTI = dataUTI else { - resolver(Result.failure(PhotoLibraryError.assertionError(description: "dataUTI was unexpectedly nil"))) - return - } - - guard let dataSource = DataSourceValue.dataSource(with: imageData, utiType: dataUTI) else { - resolver(Result.failure(PhotoLibraryError.assertionError(description: "dataSource was unexpectedly nil"))) - return - } - - resolver(Result.success((dataSource: dataSource, dataUTI: dataUTI))) } } .eraseToAnyPublisher() } private func requestVideoDataSource(for asset: PHAsset) -> AnyPublisher<(dataSource: DataSource, dataUTI: String), Error> { - return Future { [weak self] resolver in - - let options: PHVideoRequestOptions = PHVideoRequestOptions() - options.isNetworkAccessAllowed = true - - _ = self?.imageManager.requestExportSession(forVideo: asset, options: options, exportPreset: AVAssetExportPresetMediumQuality) { exportSession, foo in - - guard let exportSession = exportSession else { - resolver(Result.failure(PhotoLibraryError.assertionError(description: "exportSession was unexpectedly nil"))) - return - } - - exportSession.outputFileType = AVFileType.mp4 - exportSession.metadataItemFilter = AVMetadataItemFilter.forSharing() - - let exportPath = OWSFileSystem.temporaryFilePath(withFileExtension: "mp4") - let exportURL = URL(fileURLWithPath: exportPath) - exportSession.outputURL = exportURL - - Logger.debug("starting video export") - exportSession.exportAsynchronously { - Logger.debug("Completed video export") - - guard let dataSource = DataSourcePath.dataSource(with: exportURL, shouldDeleteOnDeallocation: true) else { - resolver(Result.failure(PhotoLibraryError.assertionError(description: "Failed to build data source for exported video URL"))) + return Deferred { + Future { [weak self] resolver in + + let options: PHVideoRequestOptions = PHVideoRequestOptions() + options.isNetworkAccessAllowed = true + + _ = self?.imageManager.requestExportSession(forVideo: asset, options: options, exportPreset: AVAssetExportPresetMediumQuality) { exportSession, foo in + + guard let exportSession = exportSession else { + resolver(Result.failure(PhotoLibraryError.assertionError(description: "exportSession was unexpectedly nil"))) return } - - resolver(Result.success((dataSource: dataSource, dataUTI: kUTTypeMPEG4 as String))) + + exportSession.outputFileType = AVFileType.mp4 + exportSession.metadataItemFilter = AVMetadataItemFilter.forSharing() + + let exportPath = OWSFileSystem.temporaryFilePath(withFileExtension: "mp4") + let exportURL = URL(fileURLWithPath: exportPath) + exportSession.outputURL = exportURL + + Logger.debug("starting video export") + exportSession.exportAsynchronously { + Logger.debug("Completed video export") + + guard let dataSource = DataSourcePath.dataSource(with: exportURL, shouldDeleteOnDeallocation: true) else { + resolver(Result.failure(PhotoLibraryError.assertionError(description: "Failed to build data source for exported video URL"))) + return + } + + resolver(Result.success((dataSource: dataSource, dataUTI: kUTTypeMPEG4 as String))) + } } } } diff --git a/Session/Notifications/AppNotifications.swift b/Session/Notifications/AppNotifications.swift index 88abcbd3f..79575da2f 100644 --- a/Session/Notifications/AppNotifications.swift +++ b/Session/Notifications/AppNotifications.swift @@ -88,7 +88,7 @@ let kAudioNotificationsThrottleInterval: TimeInterval = 5 protocol NotificationPresenterAdaptee: AnyObject { - func registerNotificationSettings() -> Future + func registerNotificationSettings() -> AnyPublisher func notify( category: AppNotificationCategory, @@ -150,7 +150,6 @@ public class NotificationPresenter: NSObject, NotificationsProtocol { func registerNotificationSettings() -> AnyPublisher { return adaptee.registerNotificationSettings() - .eraseToAnyPublisher() } public func notifyUser(_ db: Database, for interaction: Interaction, in thread: SessionThread) { @@ -163,7 +162,7 @@ public class NotificationPresenter: NSObject, NotificationsProtocol { // Try to group notifications for interactions from open groups let identifier: String = interaction.notificationIdentifier( - shouldGroupMessagesForThread: (thread.variant == .openGroup) + shouldGroupMessagesForThread: (thread.variant == .community) ) // While batch processing, some of the necessary changes have not been commited. @@ -203,7 +202,7 @@ public class NotificationPresenter: NSObject, NotificationsProtocol { case .contact: notificationTitle = (isMessageRequest ? "Session" : senderName) - case .legacyClosedGroup, .closedGroup, .openGroup: + case .legacyGroup, .group, .community: notificationTitle = String( format: NotificationStrings.incomingGroupMessageTitleFormat, senderName, @@ -275,9 +274,9 @@ public class NotificationPresenter: NSObject, NotificationsProtocol { // No call notifications for muted or group threads guard Date().timeIntervalSince1970 > (thread.mutedUntilTimestamp ?? 0) else { return } guard - thread.variant != .legacyClosedGroup && - thread.variant != .closedGroup && - thread.variant != .openGroup + thread.variant != .legacyGroup && + thread.variant != .group && + thread.variant != .community else { return } guard interaction.variant == .infoCall, @@ -347,9 +346,9 @@ public class NotificationPresenter: NSObject, NotificationsProtocol { // No reaction notifications for muted, group threads or message requests guard Date().timeIntervalSince1970 > (thread.mutedUntilTimestamp ?? 0) else { return } guard - thread.variant != .legacyClosedGroup && - thread.variant != .closedGroup && - thread.variant != .openGroup + thread.variant != .legacyGroup && + thread.variant != .group && + thread.variant != .community else { return } guard !isMessageRequest else { return } @@ -539,7 +538,7 @@ class NotificationActionHandler { } return Storage.shared - .writePublisher { db in + .writePublisher(receiveOn: DispatchQueue.main) { db in let interaction: Interaction = try Interaction( threadId: thread.id, authorId: getUserHexEncodedPublicKey(db), @@ -607,7 +606,7 @@ class NotificationActionHandler { private func markAsRead(thread: SessionThread) -> AnyPublisher { return Storage.shared - .writePublisher { db in + .writePublisher(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db in try Interaction.markAsRead( db, interactionId: try thread.interactions diff --git a/Session/Notifications/PushRegistrationManager.swift b/Session/Notifications/PushRegistrationManager.swift index f1ff01c69..aa3c3c2d1 100644 --- a/Session/Notifications/PushRegistrationManager.swift +++ b/Session/Notifications/PushRegistrationManager.swift @@ -54,10 +54,9 @@ public enum PushRegistrationError: Error { return registerUserNotificationSettings() .setFailureType(to: Error.self) .receive(on: DispatchQueue.main) // MUST be on main thread - .flatMap { _ -> AnyPublisher<(pushToken: String, voipToken: String), Error> in + .tryFlatMap { _ -> AnyPublisher<(pushToken: String, voipToken: String), Error> in #if targetEnvironment(simulator) - return Fail(error: PushRegistrationError.pushNotSupported(description: "Push not supported on simulators")) - .eraseToAnyPublisher() + throw PushRegistrationError.pushNotSupported(description: "Push not supported on simulators") #endif return self.registerForVanillaPushToken() @@ -101,7 +100,6 @@ public enum PushRegistrationError: Error { public func registerUserNotificationSettings() -> AnyPublisher { AssertIsOnMainThread() return notificationPresenter.registerNotificationSettings() - .eraseToAnyPublisher() } /** @@ -142,8 +140,10 @@ public enum PushRegistrationError: Error { UIApplication.shared.registerForRemoteNotifications() // No pending vanilla token yet; create a new publisher - let publisher: AnyPublisher = Future { self.vanillaTokenResolver = $0 } - .eraseToAnyPublisher() + let publisher: AnyPublisher = Deferred { + Future { self.vanillaTokenResolver = $0 } + } + .eraseToAnyPublisher() self.vanillaTokenPublisher = publisher return publisher @@ -238,8 +238,10 @@ public enum PushRegistrationError: Error { } // No pending voip token yet. Create a new publisher - let publisher: AnyPublisher = Future { self.voipTokenResolver = $0 } - .eraseToAnyPublisher() + let publisher: AnyPublisher = Deferred { + Future { self.voipTokenResolver = $0 } + } + .eraseToAnyPublisher() self.voipTokenPublisher = publisher return publisher diff --git a/Session/Notifications/SyncPushTokensJob.swift b/Session/Notifications/SyncPushTokensJob.swift index 4aa25e4b3..ba51ed243 100644 --- a/Session/Notifications/SyncPushTokensJob.swift +++ b/Session/Notifications/SyncPushTokensJob.swift @@ -73,14 +73,16 @@ public enum SyncPushTokensJob: JobExecutor { .eraseToAnyPublisher() } - return Future { resolver in - SyncPushTokensJob.registerForPushNotifications( - pushToken: pushToken, - voipToken: voipToken, - isForcedUpdate: shouldUploadTokens, - success: { resolver(Result.success(())) }, - failure: { resolver(Result.failure($0)) } - ) + return Deferred { + Future { resolver in + SyncPushTokensJob.registerForPushNotifications( + pushToken: pushToken, + voipToken: voipToken, + isForcedUpdate: shouldUploadTokens, + success: { resolver(Result.success(())) }, + failure: { resolver(Result.failure($0)) } + ) + } } .handleEvents( receiveCompletion: { result in diff --git a/Session/Notifications/UserNotificationsAdaptee.swift b/Session/Notifications/UserNotificationsAdaptee.swift index 525edc592..3af0cccd3 100644 --- a/Session/Notifications/UserNotificationsAdaptee.swift +++ b/Session/Notifications/UserNotificationsAdaptee.swift @@ -73,25 +73,27 @@ class UserNotificationPresenterAdaptee: NSObject, UNUserNotificationCenterDelega } extension UserNotificationPresenterAdaptee: NotificationPresenterAdaptee { - func registerNotificationSettings() -> Future { - return Future { [weak self] resolver in - self?.notificationCenter.requestAuthorization(options: [.badge, .sound, .alert]) { (granted, error) in - self?.notificationCenter.setNotificationCategories(UserNotificationConfig.allNotificationCategories) - - if granted {} - else if let error: Error = error { - Logger.error("failed with error: \(error)") + func registerNotificationSettings() -> AnyPublisher { + return Deferred { + Future { [weak self] resolver in + self?.notificationCenter.requestAuthorization(options: [.badge, .sound, .alert]) { (granted, error) in + self?.notificationCenter.setNotificationCategories(UserNotificationConfig.allNotificationCategories) + + if granted {} + else if let error: Error = error { + Logger.error("failed with error: \(error)") + } + else { + Logger.error("failed without error.") + } + + // Note that the promise is fulfilled regardless of if notification permssions were + // granted. This promise only indicates that the user has responded, so we can + // proceed with requesting push tokens and complete registration. + resolver(Result.success(())) } - else { - Logger.error("failed without error.") - } - - // Note that the promise is fulfilled regardless of if notification permssions were - // granted. This promise only indicates that the user has responded, so we can - // proceed with requesting push tokens and complete registration. - resolver(Result.success(())) } - } + }.eraseToAnyPublisher() } func notify( @@ -114,7 +116,7 @@ extension UserNotificationPresenterAdaptee: NotificationPresenterAdaptee { content.threadIdentifier = (threadIdentifier ?? content.threadIdentifier) let shouldGroupNotification: Bool = ( - threadVariant == .openGroup && + threadVariant == .community && replacingIdentifier == threadIdentifier ) let isAppActive = UIApplication.shared.applicationState == .active diff --git a/Session/Onboarding/Onboarding.swift b/Session/Onboarding/Onboarding.swift index 948893aeb..acdf75b68 100644 --- a/Session/Onboarding/Onboarding.swift +++ b/Session/Onboarding/Onboarding.swift @@ -23,11 +23,8 @@ enum Onboarding { return Atomic( SnodeAPI.getSwarm(for: userPublicKey) .subscribe(on: DispatchQueue.global(qos: .userInitiated)) - .flatMap { swarm -> AnyPublisher in - guard let snode = swarm.randomElement() else { - return Fail(error: SnodeAPIError.generic) - .eraseToAnyPublisher() - } + .tryFlatMap { swarm -> AnyPublisher in + guard let snode = swarm.randomElement() else { throw SnodeAPIError.generic } return CurrentUserPoller.poll( namespaces: [.configUserProfile], @@ -41,7 +38,7 @@ enum Onboarding { ) } .flatMap { _ -> AnyPublisher in - Storage.shared.readPublisher { db in + Storage.shared.readPublisher(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db in try Profile .filter(id: userPublicKey) .select(.name) diff --git a/Session/Open Groups/JoinOpenGroupVC.swift b/Session/Open Groups/JoinOpenGroupVC.swift index aa8c15340..e204a2005 100644 --- a/Session/Open Groups/JoinOpenGroupVC.swift +++ b/Session/Open Groups/JoinOpenGroupVC.swift @@ -169,7 +169,7 @@ final class JoinOpenGroupVC: BaseVC, UIPageViewControllerDataSource, UIPageViewC ModalActivityIndicatorViewController.present(fromViewController: navigationController, canCancel: false) { [weak self] _ in Storage.shared - .writePublisherFlatMap { db in + .writePublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupManager.shared.add( db, roomToken: roomToken, @@ -194,7 +194,7 @@ final class JoinOpenGroupVC: BaseVC, UIPageViewControllerDataSource, UIPageViewC if shouldOpenCommunity { SessionApp.presentConversation( for: OpenGroup.idFor(roomToken: roomToken, server: server), - threadVariant: .openGroup, + threadVariant: .community, isMessageRequest: false, action: .compose, focusInteractionInfo: nil, diff --git a/Session/Open Groups/OpenGroupSuggestionGrid.swift b/Session/Open Groups/OpenGroupSuggestionGrid.swift index eb12b2e66..706df79f4 100644 --- a/Session/Open Groups/OpenGroupSuggestionGrid.swift +++ b/Session/Open Groups/OpenGroupSuggestionGrid.swift @@ -322,7 +322,7 @@ extension OpenGroupSuggestionGrid { Publishers .MergeMany( Storage.shared - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupManager .roomImage(db, fileId: imageId, for: room.token, on: OpenGroupAPI.defaultServer) } diff --git a/Session/Settings/HelpViewModel.swift b/Session/Settings/HelpViewModel.swift index 64aab5884..b93e0fcc4 100644 --- a/Session/Settings/HelpViewModel.swift +++ b/Session/Settings/HelpViewModel.swift @@ -150,7 +150,7 @@ class HelpViewModel: SessionTableViewModel 0) && ( - cellViewModel.threadVariant == .legacyClosedGroup || - cellViewModel.threadVariant == .closedGroup || - cellViewModel.threadVariant == .openGroup + cellViewModel.threadVariant == .legacyGroup || + cellViewModel.threadVariant == .group || + cellViewModel.threadVariant == .community ) ) profilePictureView.update( @@ -514,7 +514,7 @@ public final class FullConversationCell: UITableViewCell { )) } - if cellViewModel.threadVariant == .legacyClosedGroup || cellViewModel.threadVariant == .closedGroup || cellViewModel.threadVariant == .openGroup { + if cellViewModel.threadVariant == .legacyGroup || cellViewModel.threadVariant == .group || cellViewModel.threadVariant == .community { let authorName: String = cellViewModel.authorName(for: cellViewModel.threadVariant) result.append(NSAttributedString( diff --git a/Session/Utilities/BackgroundPoller.swift b/Session/Utilities/BackgroundPoller.swift index 5d4a6adf6..b5391a23a 100644 --- a/Session/Utilities/BackgroundPoller.swift +++ b/Session/Utilities/BackgroundPoller.swift @@ -67,11 +67,8 @@ public final class BackgroundPoller { return SnodeAPI.getSwarm(for: userPublicKey) .subscribeOnMain(immediately: true) .receiveOnMain(immediately: true) - .flatMap { swarm -> AnyPublisher in - guard let snode = swarm.randomElement() else { - return Fail(error: SnodeAPIError.generic) - .eraseToAnyPublisher() - } + .tryFlatMap { swarm -> AnyPublisher in + guard let snode = swarm.randomElement() else { throw SnodeAPIError.generic } return CurrentUserPoller.poll( namespaces: CurrentUserPoller.namespaces, @@ -104,10 +101,9 @@ public final class BackgroundPoller { SnodeAPI.getSwarm(for: groupPublicKey) .subscribeOnMain(immediately: true) .receiveOnMain(immediately: true) - .flatMap { swarm -> AnyPublisher in + .tryFlatMap { swarm -> AnyPublisher in guard let snode: Snode = swarm.randomElement() else { - return Fail(error: OnionRequestAPIError.insufficientSnodes) - .eraseToAnyPublisher() + throw OnionRequestAPIError.insufficientSnodes } return ClosedGroupPoller.poll( diff --git a/Session/Utilities/MockDataGenerator.swift b/Session/Utilities/MockDataGenerator.swift index 3bea51b61..4e8b92997 100644 --- a/Session/Utilities/MockDataGenerator.swift +++ b/Session/Utilities/MockDataGenerator.swift @@ -241,7 +241,7 @@ enum MockDataGenerator { } let thread: SessionThread = try! SessionThread - .fetchOrCreate(db, id: randomGroupPublicKey, variant: .legacyClosedGroup) + .fetchOrCreate(db, id: randomGroupPublicKey, variant: .legacyGroup) .with(shouldBeVisible: true) .saved(db) _ = try! ClosedGroup( @@ -367,7 +367,7 @@ enum MockDataGenerator { // Create the open group model and the thread let thread: SessionThread = try! SessionThread - .fetchOrCreate(db, id: randomGroupPublicKey, variant: .openGroup) + .fetchOrCreate(db, id: randomGroupPublicKey, variant: .community) .with(shouldBeVisible: true) .saved(db) _ = try! OpenGroup( diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index 2d0fcafa3..4b32ea07e 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -163,48 +163,50 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { .eraseToAnyPublisher() } - return Future { [weak self] resolver in - self?.peerConnection?.offer(for: mediaConstraints) { sdp, error in - if let error = error { - return - } - - guard let sdp: RTCSessionDescription = self?.correctSessionDescription(sdp: sdp) else { - preconditionFailure() - } - - self?.peerConnection?.setLocalDescription(sdp) { error in + return Deferred { + Future { [weak self] resolver in + self?.peerConnection?.offer(for: mediaConstraints) { sdp, error in if let error = error { - print("Couldn't initiate call due to error: \(error).") - resolver(Result.failure(error)) return } - } - - Storage.shared - .writePublisher { db in - try MessageSender - .preparedSendData( - db, - message: CallMessage( - uuid: uuid, - kind: .offer, - sdps: [ sdp.sdp ], - sentTimestampMs: UInt64(SnodeAPI.currentOffsetTimestampMs()) - ), - to: try Message.Destination.from(db, thread: thread), - interactionId: nil - ) + + guard let sdp: RTCSessionDescription = self?.correctSessionDescription(sdp: sdp) else { + preconditionFailure() } - .flatMap { MessageSender.sendImmediate(preparedSendData: $0) } - .sinkUntilComplete( - receiveCompletion: { result in - switch result { - case .finished: resolver(Result.success(())) - case .failure(let error): resolver(Result.failure(error)) - } + + self?.peerConnection?.setLocalDescription(sdp) { error in + if let error = error { + print("Couldn't initiate call due to error: \(error).") + resolver(Result.failure(error)) + return } - ) + } + + Storage.shared + .writePublisher(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db in + try MessageSender + .preparedSendData( + db, + message: CallMessage( + uuid: uuid, + kind: .offer, + sdps: [ sdp.sdp ], + sentTimestampMs: UInt64(SnodeAPI.currentOffsetTimestampMs()) + ), + to: try Message.Destination.from(db, thread: thread), + interactionId: nil + ) + } + .flatMap { MessageSender.sendImmediate(preparedSendData: $0) } + .sinkUntilComplete( + receiveCompletion: { result in + switch result { + case .finished: resolver(Result.success(())) + case .failure(let error): resolver(Result.failure(error)) + } + } + ) + } } } .eraseToAnyPublisher() @@ -216,10 +218,9 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { let mediaConstraints: RTCMediaConstraints = mediaConstraints(false) return Storage.shared - .readPublisherFlatMap { db -> AnyPublisher in + .readPublisherFlatMap(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db -> AnyPublisher in guard let thread: SessionThread = try? SessionThread.fetchOne(db, id: sessionId) else { - return Fail(error: WebRTCSessionError.noThread) - .eraseToAnyPublisher() + throw WebRTCSessionError.noThread } return Just(thread) @@ -246,7 +247,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { } Storage.shared - .writePublisher { db in + .writePublisher(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db in try MessageSender .preparedSendData( db, @@ -293,10 +294,9 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { self.queuedICECandidates.removeAll() Storage.shared - .writePublisherFlatMap { db in + .writePublisherFlatMap(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db in guard let thread: SessionThread = try SessionThread.fetchOne(db, id: contactSessionId) else { - return Fail(error: WebRTCSessionError.noThread) - .eraseToAnyPublisher() + throw WebRTCSessionError.noThread } SNLog("[Calls] Batch sending \(candidates.count) ICE candidates.") diff --git a/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift b/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift index fc84b96b0..73141b519 100644 --- a/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift +++ b/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift @@ -565,7 +565,7 @@ enum _003_YDBToGRDBMigration: Migration { switch legacyThread { case let groupThread as SMKLegacy._GroupThread: - threadVariant = (groupThread.isOpenGroup ? .openGroup : .legacyClosedGroup) + threadVariant = (groupThread.isOpenGroup ? .community : .legacyGroup) onlyNotifyForMentions = groupThread.isOnlyNotifyingForMentions default: diff --git a/SessionMessagingKit/Database/Models/Attachment.swift b/SessionMessagingKit/Database/Models/Attachment.swift index fc8ef5723..e9c3de4e0 100644 --- a/SessionMessagingKit/Database/Models/Attachment.swift +++ b/SessionMessagingKit/Database/Models/Attachment.swift @@ -1036,7 +1036,7 @@ extension Attachment { let attachmentId: String = self.id return Storage.shared - .writePublisherFlatMap { db -> AnyPublisher<(String?, Data?, Data?), Error> in + .writePublisherFlatMap(receiveOn: queue) { db -> AnyPublisher<(String?, Data?, Data?), Error> in // If the attachment is a downloaded attachment, check if it came from // the server and if so just succeed immediately (no use re-uploading // an attachment that is already present on the server) - or if we want @@ -1068,8 +1068,7 @@ extension Attachment { if destination.shouldEncrypt { guard let ciphertext = Cryptography.encryptAttachmentData(data, shouldPad: true, outKey: &encryptionKey, outDigest: &digest) else { SNLog("Couldn't encrypt attachment.") - return Fail(error: AttachmentError.encryptionFailed) - .eraseToAnyPublisher() + throw AttachmentError.encryptionFailed } data = ciphertext @@ -1077,10 +1076,7 @@ extension Attachment { // Check the file size SNLog("File size: \(data.count) bytes.") - if data.count > FileServerAPI.maxFileSize { - return Fail(error: HTTPError.maxFileSizeExceeded) - .eraseToAnyPublisher() - } + if data.count > FileServerAPI.maxFileSize { throw HTTPError.maxFileSizeExceeded } // Update the attachment to the 'uploading' state _ = try? Attachment @@ -1131,13 +1127,14 @@ extension Attachment { .eraseToAnyPublisher() } } + .receive(on: queue) .flatMap { fileId, encryptionKey, digest -> AnyPublisher in /// Save the final upload info /// /// **Note:** We **MUST** use the `.with` function here to ensure the `isValid` flag is /// updated correctly Storage.shared - .writePublisher { db in + .writePublisher(receiveOn: queue) { db in try self .with( serverId: fileId, diff --git a/SessionMessagingKit/Database/Models/Interaction.swift b/SessionMessagingKit/Database/Models/Interaction.swift index fab33fca5..0d8585fd0 100644 --- a/SessionMessagingKit/Database/Models/Interaction.swift +++ b/SessionMessagingKit/Database/Models/Interaction.swift @@ -353,7 +353,7 @@ public struct Interaction: Codable, Identifiable, Equatable, FetchableRecord, Mu state: .sending ).insert(db) - case .legacyClosedGroup, .closedGroup: + case .legacyGroup, .group: let closedGroupMemberIds: Set = (try? GroupMember .select(.profileId) .filter(GroupMember.Columns.groupId == threadId) @@ -379,7 +379,7 @@ public struct Interaction: Codable, Identifiable, Equatable, FetchableRecord, Mu ).insert(db) } - case .openGroup: + case .community: // Since we use the 'RecipientState' type to manage the message state // we need to ensure we have a state for all threads; so for open groups // we just use the open group id as the 'recipientId' value diff --git a/SessionMessagingKit/Database/Models/LinkPreview.swift b/SessionMessagingKit/Database/Models/LinkPreview.swift index fba228a70..35018ce59 100644 --- a/SessionMessagingKit/Database/Models/LinkPreview.swift +++ b/SessionMessagingKit/Database/Models/LinkPreview.swift @@ -318,17 +318,12 @@ public extension LinkPreview { .flatMap { data, response in parseLinkDataAndBuildDraft(linkData: data, response: response, linkUrlString: previewUrl) } - .flatMap { linkPreviewDraft -> AnyPublisher in - guard linkPreviewDraft.isValid() else { - return Fail(error: LinkPreviewError.noPreview) - .eraseToAnyPublisher() - } + .tryMap { linkPreviewDraft -> LinkPreviewDraft in + guard linkPreviewDraft.isValid() else { throw LinkPreviewError.noPreview } setCachedLinkPreview(linkPreviewDraft, forPreviewUrl: previewUrl) - return Just(linkPreviewDraft) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() + return linkPreviewDraft } .eraseToAnyPublisher() } @@ -362,25 +357,18 @@ public extension LinkPreview { .dataTaskPublisher(for: request) .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .mapError { _ -> Error in HTTPError.generic } // URLError codes are negative values - .flatMap { data, response -> AnyPublisher<(Data, URLResponse), Error> in + .tryMap { data, response -> (Data, URLResponse) in guard let urlResponse: HTTPURLResponse = response as? HTTPURLResponse else { - return Fail(error: LinkPreviewError.assertionFailure) - .eraseToAnyPublisher() + throw LinkPreviewError.assertionFailure } if let contentType: String = urlResponse.allHeaderFields["Content-Type"] as? String { guard contentType.lowercased().hasPrefix("text/") else { - return Fail(error: LinkPreviewError.invalidContent) - .eraseToAnyPublisher() + throw LinkPreviewError.invalidContent } } - guard data.count > 0 else { - return Fail(error: LinkPreviewError.invalidContent) - .eraseToAnyPublisher() - } + guard data.count > 0 else { throw LinkPreviewError.invalidContent } - return Just((data, response)) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() + return (data, response) } .catch { error -> AnyPublisher<(Data, URLResponse), Error> in guard isRetryable(error: error), remainingRetries > 0 else { @@ -496,63 +484,44 @@ public extension LinkPreview { priority: .high, shouldIgnoreSignalProxy: true ) - .flatMap { asset, _ -> AnyPublisher in - do { - let imageSize = NSData.imageSize(forFilePath: asset.filePath, mimeType: imageMimeType) - - guard imageSize.width > 0, imageSize.height > 0 else { - return Fail(error: LinkPreviewError.invalidContent) - .eraseToAnyPublisher() - } - - let data = try Data(contentsOf: URL(fileURLWithPath: asset.filePath)) - - guard let srcImage = UIImage(data: data) else { - return Fail(error: LinkPreviewError.invalidContent) - .eraseToAnyPublisher() - } - - // Loki: If it's a GIF then ensure its validity and don't download it as a JPG - if - imageMimeType == OWSMimeTypeImageGif && - NSData(data: data).ows_isValidImage(withMimeType: OWSMimeTypeImageGif) - { - return Just(data) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - } - - let maxImageSize: CGFloat = 1024 - let shouldResize = imageSize.width > maxImageSize || imageSize.height > maxImageSize - - guard shouldResize else { - guard let dstData = srcImage.jpegData(compressionQuality: 0.8) else { - return Fail(error: LinkPreviewError.invalidContent) - .eraseToAnyPublisher() - } - - return Just(dstData) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - } - - guard let dstImage = srcImage.resized(withMaxDimensionPoints: maxImageSize) else { - return Fail(error: LinkPreviewError.invalidContent) - .eraseToAnyPublisher() - } - guard let dstData = dstImage.jpegData(compressionQuality: 0.8) else { - return Fail(error: LinkPreviewError.invalidContent) - .eraseToAnyPublisher() - } - - return Just(dstData) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() + .tryMap { asset, _ -> Data in + let imageSize = NSData.imageSize(forFilePath: asset.filePath, mimeType: imageMimeType) + + guard imageSize.width > 0, imageSize.height > 0 else { + throw LinkPreviewError.invalidContent } - catch { - return Fail(error: LinkPreviewError.assertionFailure) - .eraseToAnyPublisher() + + guard let data: Data = try? Data(contentsOf: URL(fileURLWithPath: asset.filePath)) else { + throw LinkPreviewError.assertionFailure } + + guard let srcImage = UIImage(data: data) else { throw LinkPreviewError.invalidContent } + + // Loki: If it's a GIF then ensure its validity and don't download it as a JPG + if + imageMimeType == OWSMimeTypeImageGif && + NSData(data: data).ows_isValidImage(withMimeType: OWSMimeTypeImageGif) + { + return data + } + + let maxImageSize: CGFloat = 1024 + let shouldResize = imageSize.width > maxImageSize || imageSize.height > maxImageSize + + guard shouldResize else { + guard let dstData = srcImage.jpegData(compressionQuality: 0.8) else { + throw LinkPreviewError.invalidContent + } + + return dstData + } + + guard + let dstImage = srcImage.resized(withMaxDimensionPoints: maxImageSize), + let dstData = dstImage.jpegData(compressionQuality: 0.8) + else { throw LinkPreviewError.invalidContent } + + return dstData } .mapError { _ -> Error in LinkPreviewError.couldNotDownload } .eraseToAnyPublisher() diff --git a/SessionMessagingKit/Database/Models/Profile.swift b/SessionMessagingKit/Database/Models/Profile.swift index 737d78cd6..4b73b0d2d 100644 --- a/SessionMessagingKit/Database/Models/Profile.swift +++ b/SessionMessagingKit/Database/Models/Profile.swift @@ -324,9 +324,9 @@ public extension Profile { } switch threadVariant { - case .contact, .legacyClosedGroup, .closedGroup: return name + case .contact, .legacyGroup, .group: return name - case .openGroup: + case .community: // In open groups, where it's more likely that multiple users have the same name, // we display a bit of the Session ID after a user's display name for added context return "\(name) (\(Profile.truncated(id: id, truncating: .middle)))" diff --git a/SessionMessagingKit/Database/Models/SessionThread.swift b/SessionMessagingKit/Database/Models/SessionThread.swift index b5ddd28be..bd3820816 100644 --- a/SessionMessagingKit/Database/Models/SessionThread.swift +++ b/SessionMessagingKit/Database/Models/SessionThread.swift @@ -37,9 +37,9 @@ public struct SessionThread: Codable, Identifiable, Equatable, FetchableRecord, public enum Variant: Int, Codable, Hashable, DatabaseValueConvertible { case contact - case legacyClosedGroup - case openGroup - case closedGroup + case legacyGroup + case community + case group } /// Unique identifier for a thread (formerly known as uniqueId) @@ -312,8 +312,8 @@ public extension SessionThread { profile: Profile? = nil ) -> String { switch variant { - case .legacyClosedGroup, .closedGroup: return (closedGroupName ?? "Unknown Group") - case .openGroup: return (openGroupName ?? "Unknown Group") + case .legacyGroup, .group: return (closedGroupName ?? "Unknown Group") + case .community: return (openGroupName ?? "Unknown Community") case .contact: guard !isNoteToSelf else { return "NOTE_TO_SELF".localized() } guard let profile: Profile = profile else { @@ -329,7 +329,7 @@ public extension SessionThread { threadVariant: Variant ) -> String? { guard - threadVariant == .openGroup, + threadVariant == .community, let blindingInfo: (edkeyPair: Box.KeyPair?, publicKey: String?) = Storage.shared.read({ db in return ( Identity.fetchUserEd25519KeyPair(db), diff --git a/SessionMessagingKit/Database/Models/SharedConfigDump.swift b/SessionMessagingKit/Database/Models/SharedConfigDump.swift index 5b424eb8d..db2fae388 100644 --- a/SessionMessagingKit/Database/Models/SharedConfigDump.swift +++ b/SessionMessagingKit/Database/Models/SharedConfigDump.swift @@ -20,7 +20,7 @@ public struct ConfigDump: Codable, Equatable, Hashable, FetchableRecord, Persist case userProfile case contacts case convoInfoVolatile - case groups + case userGroups } /// The type of config this dump is for @@ -66,14 +66,14 @@ public extension ConfigDump { } public extension ConfigDump.Variant { - static let userVariants: [ConfigDump.Variant] = [ .userProfile, .contacts, .convoInfoVolatile, .groups ] + static let userVariants: [ConfigDump.Variant] = [ .userProfile, .contacts, .convoInfoVolatile, .userGroups ] var configMessageKind: SharedConfigMessage.Kind { switch self { case .userProfile: return .userProfile case .contacts: return .contacts case .convoInfoVolatile: return .convoInfoVolatile - case .groups: return .groups + case .userGroups: return .userGroups } } @@ -82,7 +82,7 @@ public extension ConfigDump.Variant { case .userProfile: return SnodeAPI.Namespace.configUserProfile case .contacts: return SnodeAPI.Namespace.configContacts case .convoInfoVolatile: return SnodeAPI.Namespace.configConvoInfoVolatile - case .groups: return SnodeAPI.Namespace.configGroups + case .userGroups: return SnodeAPI.Namespace.configUserGroups } } } diff --git a/SessionMessagingKit/File Server/FileServerAPI.swift b/SessionMessagingKit/File Server/FileServerAPI.swift index 0676547e1..15d9ec9c6 100644 --- a/SessionMessagingKit/File Server/FileServerAPI.swift +++ b/SessionMessagingKit/File Server/FileServerAPI.swift @@ -79,15 +79,10 @@ public enum FileServerAPI { return OnionRequestAPI.sendOnionRequest(urlRequest, to: request.server, with: serverPublicKey, timeout: FileServerAPI.fileTimeout) .subscribe(on: DispatchQueue.global(qos: .userInitiated)) - .flatMap { _, response -> AnyPublisher in - guard let response: Data = response else { - return Fail(error: HTTPError.parsingFailed) - .eraseToAnyPublisher() - } + .tryMap { _, response -> Data in + guard let response: Data = response else { throw HTTPError.parsingFailed } - return Just(response) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() + return response } .eraseToAnyPublisher() } diff --git a/SessionMessagingKit/Jobs/Types/AttachmentDownloadJob.swift b/SessionMessagingKit/Jobs/Types/AttachmentDownloadJob.swift index 47fe9e0c6..471ce896f 100644 --- a/SessionMessagingKit/Jobs/Types/AttachmentDownloadJob.swift +++ b/SessionMessagingKit/Jobs/Types/AttachmentDownloadJob.swift @@ -87,17 +87,14 @@ public enum AttachmentDownloadJob: JobExecutor { Just(attachment.downloadUrl) .setFailureType(to: Error.self) - .flatMap { maybeDownloadUrl -> AnyPublisher in + .tryFlatMap { maybeDownloadUrl -> AnyPublisher in guard let downloadUrl: String = maybeDownloadUrl, let fileId: String = Attachment.fileId(for: downloadUrl) - else { - return Fail(error: AttachmentDownloadError.invalidUrl) - .eraseToAnyPublisher() - } + else { throw AttachmentDownloadError.invalidUrl } return Storage.shared - .readPublisher { db in try OpenGroup.fetchOne(db, id: threadId) } + .readPublisher(receiveOn: queue) { db in try OpenGroup.fetchOne(db, id: threadId) } .flatMap { maybeOpenGroup -> AnyPublisher in guard let openGroup: OpenGroup = maybeOpenGroup else { return FileServerAPI @@ -109,7 +106,7 @@ public enum AttachmentDownloadJob: JobExecutor { } return Storage.shared - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: queue) { db in OpenGroupAPI .downloadFile( db, @@ -123,41 +120,34 @@ public enum AttachmentDownloadJob: JobExecutor { } .eraseToAnyPublisher() } - .flatMap { data -> AnyPublisher in - do { - // Store the encrypted data temporarily - try data.write(to: temporaryFileUrl, options: .atomic) - - // Decrypt the data - let plaintext: Data = try { - guard - let key: Data = attachment.encryptionKey, - let digest: Data = attachment.digest, - key.count > 0, - digest.count > 0 - else { return data } // Open group attachments are unencrypted - - return try Cryptography.decryptAttachment( - data, - withKey: key, - digest: digest, - unpaddedSize: UInt32(attachment.byteCount) - ) - }() - - // Write the data to disk - guard try attachment.write(data: plaintext) else { - throw AttachmentDownloadError.failedToSaveFile - } - - return Just(()) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - } - catch { - return Fail(error: error) - .eraseToAnyPublisher() + .receive(on: queue) + .tryMap { data -> Void in + // Store the encrypted data temporarily + try data.write(to: temporaryFileUrl, options: .atomic) + + // Decrypt the data + let plaintext: Data = try { + guard + let key: Data = attachment.encryptionKey, + let digest: Data = attachment.digest, + key.count > 0, + digest.count > 0 + else { return data } // Open group attachments are unencrypted + + return try Cryptography.decryptAttachment( + data, + withKey: key, + digest: digest, + unpaddedSize: UInt32(attachment.byteCount) + ) + }() + + // Write the data to disk + guard try attachment.write(data: plaintext) else { + throw AttachmentDownloadError.failedToSaveFile } + + return () } .sinkUntilComplete( receiveCompletion: { result in diff --git a/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift b/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift index 2fbe245dd..d5ba2baad 100644 --- a/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift +++ b/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift @@ -39,18 +39,7 @@ public enum ConfigurationSyncJob: JobExecutor { // fresh install due to the migrations getting run) guard let pendingSwarmConfigChanges: [SingleDestinationChanges] = Storage.shared - .read({ db -> [SessionUtil.OutgoingConfResult]? in - guard - Identity.userExists(db), - let ed25519SecretKey: [UInt8] = Identity.fetchUserEd25519KeyPair(db)?.secretKey - else { return nil } - - return try SessionUtil.pendingChanges( - db, - userPublicKey: getUserHexEncodedPublicKey(db), - ed25519SecretKey: ed25519SecretKey - ) - })? + .read({ db in try SessionUtil.pendingChanges(db) })? .grouped(by: { $0.destination }) .map({ (destination: Message.Destination, value: [SessionUtil.OutgoingConfResult]) -> SingleDestinationChanges in SingleDestinationChanges( @@ -75,7 +64,7 @@ public enum ConfigurationSyncJob: JobExecutor { } Storage.shared - .readPublisher { db in + .readPublisher(receiveOn: queue) { db in try pendingSwarmConfigChanges .map { (change: SingleDestinationChanges) -> (messages: [TargetedMessage], allOldHashes: Set) in ( @@ -96,8 +85,6 @@ public enum ConfigurationSyncJob: JobExecutor { ) } } - .subscribe(on: queue) - .receive(on: queue) .flatMap { (pendingSwarmChange: [(messages: [TargetedMessage], allOldHashes: Set)]) -> AnyPublisher<[HTTP.BatchResponse], Error> in Publishers .MergeMany( @@ -119,17 +106,17 @@ public enum ConfigurationSyncJob: JobExecutor { .collect() .eraseToAnyPublisher() } - .flatMap { (responses: [HTTP.BatchResponse]) -> AnyPublisher<[SuccessfulChange], Error> in + .receive(on: queue) + .tryMap { (responses: [HTTP.BatchResponse]) -> [SuccessfulChange] in // We make a sequence call for this so it's possible to get fewer responses than // expected so if that happens fail and re-run later guard responses.count == pendingSwarmConfigChanges.count else { - return Fail(error: HTTPError.invalidResponse) - .eraseToAnyPublisher() + throw HTTPError.invalidResponse } // Process the response data into an easy to understand for (this isn't strictly // needed but the code gets convoluted without this) - let successfulChanges: [SuccessfulChange] = zip(responses, pendingSwarmConfigChanges) + return zip(responses, pendingSwarmConfigChanges) .compactMap { (batchResponse: HTTP.BatchResponse, pendingSwarmChange: SingleDestinationChanges) -> [SuccessfulChange]? in let maybePublicKey: String? = { switch pendingSwarmChange.destination { @@ -179,10 +166,6 @@ public enum ConfigurationSyncJob: JobExecutor { } } .flatMap { $0 } - - return Just(successfulChanges) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() } .map { (successfulChanges: [SuccessfulChange]) -> [ConfigDump] in // Now that we have the successful changes, we need to mark them as pushed and @@ -213,8 +196,7 @@ public enum ConfigurationSyncJob: JobExecutor { receiveCompletion: { result in switch result { case .finished: break - case .failure(let error): - failure(job, error, false) + case .failure(let error): failure(job, error, false) } }, receiveValue: { (configDumps: [ConfigDump]) in @@ -354,7 +336,7 @@ public extension ConfigurationSyncJob { // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent guard Features.useSharedUtilForUserConfig else { return Storage.shared - .writePublisher { db -> MessageSender.PreparedSendData in + .writePublisher(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db -> MessageSender.PreparedSendData in // If we don't have a userKeyPair yet then there is no need to sync the configuration // as the user doesn't exist yet (this will get triggered on the first launch of a // fresh install due to the migrations getting run) @@ -369,21 +351,21 @@ public extension ConfigurationSyncJob { interactionId: nil ) } - .subscribe(on: DispatchQueue.global(qos: .userInitiated)) - .receive(on: DispatchQueue.global(qos: .userInitiated)) .flatMap { MessageSender.sendImmediate(preparedSendData: $0) } .eraseToAnyPublisher() } // Trigger the job emitting the result when completed - return Future { resolver in - ConfigurationSyncJob.run( - Job(variant: .configurationSync), - queue: DispatchQueue.global(qos: .userInitiated), - success: { _, _ in resolver(Result.success(())) }, - failure: { _, error, _ in resolver(Result.failure(error ?? HTTPError.generic)) }, - deferred: { _ in } - ) + return Deferred { + Future { resolver in + ConfigurationSyncJob.run( + Job(variant: .configurationSync), + queue: DispatchQueue.global(qos: .userInitiated), + success: { _, _ in resolver(Result.success(())) }, + failure: { _, error, _ in resolver(Result.failure(error ?? HTTPError.generic)) }, + deferred: { _ in } + ) + } } .eraseToAnyPublisher() } diff --git a/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift b/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift index 99d47161b..9724921b4 100644 --- a/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift +++ b/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift @@ -84,7 +84,7 @@ public enum GarbageCollectionJob: JobExecutor { SELECT \(interaction.alias[Column.rowID]) FROM \(Interaction.self) JOIN \(SessionThread.self) ON ( - \(SQL("\(thread[.variant]) = \(SessionThread.Variant.openGroup)")) AND + \(SQL("\(thread[.variant]) = \(SessionThread.Variant.community)")) AND \(thread[.id]) = \(interaction[.threadId]) ) JOIN ( diff --git a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift index 27c234908..f71a8005a 100644 --- a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift +++ b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift @@ -165,7 +165,7 @@ public enum MessageSendJob: JobExecutor { /// **Note:** No need to upload attachments as part of this process as the above logic splits that out into it's own job /// so we shouldn't get here until attachments have already been uploaded Storage.shared - .writePublisher { db in + .writePublisher(receiveOn: queue) { db in try MessageSender.preparedSendData( db, message: details.message, @@ -173,9 +173,9 @@ public enum MessageSendJob: JobExecutor { interactionId: job.interactionId ) } - .subscribe(on: queue) .map { sendData in sendData.with(fileIds: messageFileIds) } .flatMap { MessageSender.sendImmediate(preparedSendData: $0) } + .receive(on: queue) .sinkUntilComplete( receiveCompletion: { result in switch result { diff --git a/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift b/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift index 0bbbc6bc8..b8b99052a 100644 --- a/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift +++ b/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift @@ -36,7 +36,7 @@ public enum SendReadReceiptsJob: JobExecutor { } Storage.shared - .writePublisher { db in + .writePublisher(receiveOn: queue) { db in try MessageSender.preparedSendData( db, message: ReadReceipt( @@ -46,7 +46,6 @@ public enum SendReadReceiptsJob: JobExecutor { interactionId: nil ) } - .subscribe(on: queue) .flatMap { MessageSender.sendImmediate(preparedSendData: $0) } .receive(on: queue) .sinkUntilComplete( @@ -120,9 +119,9 @@ public extension SendReadReceiptsJob { .joining( // Don't send read receipts in group threads required: Interaction.thread - .filter(SessionThread.Columns.variant != SessionThread.Variant.legacyClosedGroup) - .filter(SessionThread.Columns.variant != SessionThread.Variant.closedGroup) - .filter(SessionThread.Columns.variant != SessionThread.Variant.openGroup) + .filter(SessionThread.Columns.variant != SessionThread.Variant.legacyGroup) + .filter(SessionThread.Columns.variant != SessionThread.Variant.group) + .filter(SessionThread.Columns.variant != SessionThread.Variant.community) ) .distinct() ) diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift index ae84db707..09f85da72 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift @@ -42,14 +42,17 @@ internal extension SessionUtil { isBlocked: contact.blocked, didApproveMe: contact.approved_me ) + let profilePictureUrl: String? = String(libSessionVal: contact.profile_pic.url, nullIfEmpty: true) let profileResult: Profile = Profile( id: contactId, - name: (contact.name.map { String(cString: $0) } ?? ""), - nickname: contact.nickname.map { String(cString: $0) }, - profilePictureUrl: contact.profile_pic.url.map { String(cString: $0) }, - profileEncryptionKey: (contact.profile_pic.key != nil && contact.profile_pic.keylen > 0 ? - Data(bytes: contact.profile_pic.key, count: contact.profile_pic.keylen) : - nil + name: (String(libSessionVal: contact.name) ?? ""), + nickname: String(libSessionVal: contact.nickname, nullIfEmpty: true), + profilePictureUrl: profilePictureUrl, + profileEncryptionKey: (profilePictureUrl == nil ? nil : + Data( + libSessionVal: contact.profile_pic.key, + count: ProfileManager.avatarAES256KeyByteLength + ) ) ) @@ -165,9 +168,7 @@ internal extension SessionUtil { // Update the name targetContacts .forEach { (id, maybeContact, maybeProfile) in - var sessionId: [CChar] = id - .bytes - .map { CChar(bitPattern: $0) } + var sessionId: [CChar] = id.cArray var contact: contacts_contact = contacts_contact() guard contacts_get_or_construct(conf, &contact, &sessionId) else { SNLog("Unable to upsert contact from Config Message") @@ -179,60 +180,33 @@ internal extension SessionUtil { contact.approved = updatedContact.isApproved contact.approved_me = updatedContact.didApproveMe contact.blocked = updatedContact.isBlocked + + // Store the updated contact (needs to happen before variables go out of scope) + contacts_set(conf, &contact) } - // Update the profile data (if there is one) + // Update the profile data (if there is one - users we have sent a message request to may + // not have profile info in certain situations) if let updatedProfile: Profile = maybeProfile { - /// Users we have sent a message request to may not have profile info in certain situations - /// - /// Note: We **MUST** store these in local variables rather than access them directly or they won't - /// exist in memory long enough to actually be assigned in the C type - let updatedName: [CChar]? = (updatedProfile.name.isEmpty ? - nil : - updatedProfile.name - .bytes - .map { CChar(bitPattern: $0) } + let oldAvatarUrl: String? = String(libSessionVal: contact.profile_pic.url) + let oldAvatarKey: Data? = Data( + libSessionVal: contact.profile_pic.key, + count: ProfileManager.avatarAES256KeyByteLength ) - let updatedNickname: [CChar]? = updatedProfile.nickname? - .bytes - .map { CChar(bitPattern: $0) } - let updatedAvatarUrl: [CChar]? = updatedProfile.profilePictureUrl? - .bytes - .map { CChar(bitPattern: $0) } - let updatedAvatarKey: [UInt8]? = updatedProfile.profileEncryptionKey? - .bytes - let oldAvatarUrl: String? = contact.profile_pic.url.map { String(cString: $0) } - let oldAvatarKey: Data? = (contact.profile_pic.key != nil && contact.profile_pic.keylen > 0 ? - Data(bytes: contact.profile_pic.key, count: contact.profile_pic.keylen) : - nil - ) - updatedName?.withUnsafeBufferPointer { contact.name = $0.baseAddress } - (updatedNickname == nil ? - contact.nickname = nil : - updatedNickname?.withUnsafeBufferPointer { contact.nickname = $0.baseAddress } - ) - (updatedAvatarUrl == nil ? - contact.profile_pic.url = nil : - updatedAvatarUrl?.withUnsafeBufferPointer { - contact.profile_pic.url = $0.baseAddress - } - ) - (updatedAvatarKey == nil ? - contact.profile_pic.key = nil : - updatedAvatarKey?.withUnsafeBufferPointer { - contact.profile_pic.key = $0.baseAddress - } - ) - contact.profile_pic.keylen = (updatedAvatarKey?.count ?? 0) + + contact.name = updatedProfile.name.toLibSession() + contact.nickname = updatedProfile.nickname.toLibSession() + contact.profile_pic.url = updatedProfile.profilePictureUrl.toLibSession() + contact.profile_pic.key = updatedProfile.profileEncryptionKey.toLibSession() // Download the profile picture if needed if oldAvatarUrl != updatedProfile.profilePictureUrl || oldAvatarKey != updatedProfile.profileEncryptionKey { ProfileManager.downloadAvatar(for: updatedProfile) } + + // Store the updated contact (needs to happen before variables go out of scope) + contacts_set(conf, &contact) } - - // Store the updated contact - contacts_set(conf, &contact) } return ConfResult( diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift index 12a0602e6..7c6d9facd 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift @@ -21,8 +21,8 @@ internal extension SessionUtil { let volatileThreadInfo: [VolatileThreadInfo] = atomicConf.mutate { conf -> [VolatileThreadInfo] in var volatileThreadInfo: [VolatileThreadInfo] = [] var oneToOne: convo_info_volatile_1to1 = convo_info_volatile_1to1() - var openGroup: convo_info_volatile_open = convo_info_volatile_open() - var legacyClosedGroup: convo_info_volatile_legacy_closed = convo_info_volatile_legacy_closed() + var community: convo_info_volatile_community = convo_info_volatile_community() + var legacyGroup: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() let convoIterator: OpaquePointer = convo_info_volatile_iterator_new(conf) while !convo_info_volatile_iterator_done(convoIterator) { @@ -43,23 +43,23 @@ internal extension SessionUtil { ) ) } - else if convo_info_volatile_it_is_open(convoIterator, &openGroup) { - let server: String = String(cString: withUnsafeBytes(of: openGroup.base_url) { [UInt8]($0) } + else if convo_info_volatile_it_is_community(convoIterator, &community) { + let server: String = String(cString: withUnsafeBytes(of: community.base_url) { [UInt8]($0) } .map { CChar($0) } .nullTerminated() ) - let roomToken: String = String(cString: withUnsafeBytes(of: openGroup.room) { [UInt8]($0) } + let roomToken: String = String(cString: withUnsafeBytes(of: community.room) { [UInt8]($0) } .map { CChar($0) } .nullTerminated() ) - let publicKey: String = withUnsafePointer(to: openGroup.pubkey, { pubkeyBytes in + let publicKey: String = withUnsafePointer(to: community.pubkey, { pubkeyBytes in Data(bytes: pubkeyBytes, count: 32).toHexString() }) volatileThreadInfo.append( VolatileThreadInfo( threadId: OpenGroup.idFor(roomToken: roomToken, server: server), - variant: .openGroup, + variant: .community, openGroupUrlInfo: VolatileThreadInfo.OpenGroupUrlInfo( threadId: OpenGroup.idFor(roomToken: roomToken, server: server), server: server, @@ -67,14 +67,14 @@ internal extension SessionUtil { publicKey: publicKey ), changes: [ - .markedAsUnread(openGroup.unread), - .lastReadTimestampMs(openGroup.last_read) + .markedAsUnread(community.unread), + .lastReadTimestampMs(community.last_read) ] ) ) } - else if convo_info_volatile_it_is_legacy_closed(convoIterator, &legacyClosedGroup) { - let groupId: String = String(cString: withUnsafeBytes(of: legacyClosedGroup.group_id) { [UInt8]($0) } + else if convo_info_volatile_it_is_legacy_group(convoIterator, &legacyGroup) { + let groupId: String = String(cString: withUnsafeBytes(of: legacyGroup.group_id) { [UInt8]($0) } .map { CChar($0) } .nullTerminated() ) @@ -82,10 +82,10 @@ internal extension SessionUtil { volatileThreadInfo.append( VolatileThreadInfo( threadId: groupId, - variant: .legacyClosedGroup, + variant: .legacyGroup, changes: [ - .markedAsUnread(legacyClosedGroup.unread), - .lastReadTimestampMs(legacyClosedGroup.last_read) + .markedAsUnread(legacyGroup.unread), + .lastReadTimestampMs(legacyGroup.last_read) ] ) ) @@ -183,11 +183,13 @@ internal extension SessionUtil { convoInfoVolatileChanges: [VolatileThreadInfo], in atomicConf: Atomic?> ) throws -> ConfResult { + guard atomicConf.wrappedValue != nil else { throw SessionUtilError.nilConfigObject } + // Since we are doing direct memory manipulation we are using an `Atomic` type which has // blocking access in it's `mutate` closure return atomicConf.mutate { conf in convoInfoVolatileChanges.forEach { threadInfo in - var cThreadId: [CChar] = threadInfo.cThreadId + var cThreadId: [CChar] = threadInfo.threadId.cArray switch threadInfo.variant { case .contact: @@ -209,10 +211,10 @@ internal extension SessionUtil { } convo_info_volatile_set_1to1(conf, &oneToOne) - case .legacyClosedGroup: - var legacyClosedGroup: convo_info_volatile_legacy_closed = convo_info_volatile_legacy_closed() + case .legacyGroup: + var legacyGroup: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() - guard convo_info_volatile_get_or_construct_legacy_closed(conf, &legacyClosedGroup, &cThreadId) else { + guard convo_info_volatile_get_or_construct_legacy_group(conf, &legacyGroup, &cThreadId) else { SNLog("Unable to create legacy group conversation when updating last read timestamp") return } @@ -220,27 +222,27 @@ internal extension SessionUtil { threadInfo.changes.forEach { change in switch change { case .lastReadTimestampMs(let lastReadMs): - legacyClosedGroup.last_read = lastReadMs + legacyGroup.last_read = lastReadMs case .markedAsUnread(let unread): - legacyClosedGroup.unread = unread + legacyGroup.unread = unread } } - convo_info_volatile_set_legacy_closed(conf, &legacyClosedGroup) + convo_info_volatile_set_legacy_group(conf, &legacyGroup) - case .openGroup: + case .community: guard - var cBaseUrl: [CChar] = threadInfo.cBaseUrl, - var cRoomToken: [CChar] = threadInfo.cRoomToken, - var cPubkey: [UInt8] = threadInfo.cPubkey + var cBaseUrl: [CChar] = threadInfo.openGroupUrlInfo?.server.cArray, + var cRoomToken: [CChar] = threadInfo.openGroupUrlInfo?.roomToken.cArray, + var cPubkey: [UInt8] = threadInfo.openGroupUrlInfo?.publicKey.bytes else { SNLog("Unable to create community conversation when updating last read timestamp due to missing URL info") return } - var openGroup: convo_info_volatile_open = convo_info_volatile_open() + var community: convo_info_volatile_community = convo_info_volatile_community() - guard convo_info_volatile_get_or_construct_open(conf, &openGroup, &cBaseUrl, &cRoomToken, &cPubkey) else { + guard convo_info_volatile_get_or_construct_community(conf, &community, &cBaseUrl, &cRoomToken, &cPubkey) else { SNLog("Unable to create legacy group conversation when updating last read timestamp") return } @@ -248,15 +250,15 @@ internal extension SessionUtil { threadInfo.changes.forEach { change in switch change { case .lastReadTimestampMs(let lastReadMs): - openGroup.last_read = lastReadMs + community.last_read = lastReadMs case .markedAsUnread(let unread): - openGroup.unread = unread + community.unread = unread } } - convo_info_volatile_set_open(conf, &openGroup) + convo_info_volatile_set_community(conf, &community) - case .closedGroup: return // TODO: Need to add when the type is added to the lib + case .group: return // TODO: Need to add when the type is added to the lib } } @@ -284,7 +286,7 @@ internal extension SessionUtil { VolatileThreadInfo( threadId: thread.id, variant: thread.variant, - openGroupUrlInfo: (thread.variant != .openGroup ? nil : + openGroupUrlInfo: (thread.variant != .community ? nil : try VolatileThreadInfo.OpenGroupUrlInfo.fetchOne(db, id: thread.id) ), changes: [.markedAsUnread(thread.markedAsUnread ?? false)] @@ -340,7 +342,7 @@ internal extension SessionUtil { let change: VolatileThreadInfo = VolatileThreadInfo( threadId: threadId, variant: threadVariant, - openGroupUrlInfo: (threadVariant != .openGroup ? nil : + openGroupUrlInfo: (threadVariant != .community ? nil : try VolatileThreadInfo.OpenGroupUrlInfo.fetchOne(db, id: threadId) ), changes: [.lastReadTimestampMs(lastReadTimestampMs)] @@ -394,47 +396,36 @@ internal extension SessionUtil { return atomicConf.mutate { conf in switch threadVariant { case .contact: - var cThreadId: [CChar] = threadId - .bytes - .map { CChar(bitPattern: $0) } + var cThreadId: [CChar] = threadId.cArray var oneToOne: convo_info_volatile_1to1 = convo_info_volatile_1to1() guard convo_info_volatile_get_1to1(conf, &oneToOne, &cThreadId) else { return false } return (oneToOne.last_read > timestampMs) - case .legacyClosedGroup: - var cThreadId: [CChar] = threadId - .bytes - .map { CChar(bitPattern: $0) } - var legacyClosedGroup: convo_info_volatile_legacy_closed = convo_info_volatile_legacy_closed() + case .legacyGroup: + var cThreadId: [CChar] = threadId.cArray + var legacyGroup: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() - guard convo_info_volatile_get_legacy_closed(conf, &legacyClosedGroup, &cThreadId) else { + guard convo_info_volatile_get_legacy_group(conf, &legacyGroup, &cThreadId) else { return false } - return (legacyClosedGroup.last_read > timestampMs) + return (legacyGroup.last_read > timestampMs) - case .openGroup: + case .community: guard let openGroup: OpenGroup = openGroup else { return false } - var cBaseUrl: [CChar] = openGroup.server - .bytes - .map { CChar(bitPattern: $0) } - var cRoomToken: [CChar] = openGroup.roomToken - .bytes - .map { CChar(bitPattern: $0) } - var cPubKey: [CChar] = openGroup.publicKey - .bytes - .map { CChar(bitPattern: $0) } - var convoOpenGroup: convo_info_volatile_open = convo_info_volatile_open() + var cBaseUrl: [CChar] = openGroup.server.cArray + var cRoomToken: [CChar] = openGroup.roomToken.cArray + var convoCommunity: convo_info_volatile_community = convo_info_volatile_community() - guard convo_info_volatile_get_open(conf, &convoOpenGroup, &cBaseUrl, &cRoomToken, &cPubKey) else { + guard convo_info_volatile_get_community(conf, &convoCommunity, &cBaseUrl, &cRoomToken) else { return false } - return (convoOpenGroup.last_read > timestampMs) + return (convoCommunity.last_read > timestampMs) - case .closedGroup: return false // TODO: Need to add when the type is added to the lib + case .group: return false // TODO: Need to add when the type is added to the lib } } } @@ -466,28 +457,9 @@ public extension SessionUtil { let threadId: String let variant: SessionThread.Variant - private let openGroupUrlInfo: OpenGroupUrlInfo? + fileprivate let openGroupUrlInfo: OpenGroupUrlInfo? let changes: [Change] - var cThreadId: [CChar] { - threadId.bytes.map { CChar(bitPattern: $0) } - } - var cBaseUrl: [CChar]? { - (openGroupUrlInfo?.server).map { - $0.bytes.map { CChar(bitPattern: $0) } - } - } - var cRoomToken: [CChar]? { - (openGroupUrlInfo?.roomToken).map { - $0.bytes.map { CChar(bitPattern: $0) } - } - } - var cPubkey: [UInt8]? { - (openGroupUrlInfo?.publicKey).map { - Data(hex: $0).bytes - } - } - fileprivate init( threadId: String, variant: SessionThread.Variant, diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift index caf197057..f6c1cb8d6 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift @@ -31,23 +31,18 @@ internal extension SessionUtil { let profileName: String = String(cString: profileNamePtr) let profilePic: user_profile_pic = user_profile_get_pic(conf) - var profilePictureUrl: String? = nil - var profilePictureKey: Data? = nil - - // Make sure the url and key exist before reading the memory - if - profilePic.keylen > 0, - let profilePictureUrlPtr: UnsafePointer = profilePic.url, - let profilePictureKeyPtr: UnsafePointer = profilePic.key - { - profilePictureUrl = String(cString: profilePictureUrlPtr) - profilePictureKey = Data(bytes: profilePictureKeyPtr, count: profilePic.keylen) - } + let profilePictureUrl: String? = String(libSessionVal: profilePic.url, nullIfEmpty: true) + // Make sure the url and key exists before reading the memory return ( profileName: profileName, profilePictureUrl: profilePictureUrl, - profilePictureKey: profilePictureKey + profilePictureKey: (profilePictureUrl == nil ? nil : + Data( + libSessionVal: profilePic.url, + count: ProfileManager.avatarAES256KeyByteLength + ) + ) ) } @@ -106,35 +101,14 @@ internal extension SessionUtil { // blocking access in it's `mutate` closure return atomicConf.mutate { conf in // Update the name - var updatedName: [CChar] = profile.name - .bytes - .map { CChar(bitPattern: $0) } + var updatedName: [CChar] = profile.name.cArray user_profile_set_name(conf, &updatedName) // Either assign the updated profile pic, or sent a blank profile pic (to remove the current one) - let profilePic: user_profile_pic? = { - guard - let profilePictureUrl: String = profile.profilePictureUrl, - let profileEncryptionKey: Data = profile.profileEncryptionKey - else { return nil } - - let updatedUrl: [CChar] = profilePictureUrl - .bytes - .map { CChar(bitPattern: $0) } - let updatedKey: [UInt8] = profileEncryptionKey - .bytes - - return updatedUrl.withUnsafeBufferPointer { urlPtr in - updatedKey.withUnsafeBufferPointer { keyPtr in - user_profile_pic( - url: urlPtr.baseAddress, - key: keyPtr.baseAddress, - keylen: updatedKey.count - ) - } - } - }() - user_profile_set_pic(conf, (profilePic ?? user_profile_pic())) + var profilePic: user_profile_pic = user_profile_pic() + profilePic.url = profile.profilePictureUrl.toLibSession() + profilePic.key = profile.profileEncryptionKey.toLibSession() + user_profile_set_pic(conf, profilePic) return ConfResult( needsPush: config_needs_push(conf), diff --git a/SessionMessagingKit/LibSessionUtil/SessionUtil.swift b/SessionMessagingKit/LibSessionUtil/SessionUtil.swift index 7e9b38608..b16038aa2 100644 --- a/SessionMessagingKit/LibSessionUtil/SessionUtil.swift +++ b/SessionMessagingKit/LibSessionUtil/SessionUtil.swift @@ -55,6 +55,8 @@ public enum SessionUtil { } } + public static var libSessionVersion: String { String(cString: LIBSESSION_UTIL_VERSION_STR) } + // MARK: - Loading public static func loadState( @@ -133,6 +135,9 @@ public enum SessionUtil { case .convoInfoVolatile: return convo_info_volatile_init(&conf, &secretKey, cachedDump?.data, (cachedDump?.length ?? 0), error) + + case .userGroups: + return user_groups_init(&conf, &secretKey, cachedDump?.data, (cachedDump?.length ?? 0), error) } }() @@ -208,11 +213,10 @@ public enum SessionUtil { // MARK: - Pushes - public static func pendingChanges( - _ db: Database, - userPublicKey: String, - ed25519SecretKey: [UInt8] - ) throws -> [OutgoingConfResult] { + public static func pendingChanges(_ db: Database) throws -> [OutgoingConfResult] { + guard Identity.userExists(db) else { throw SessionUtilError.userDoesNotExist } + + let userPublicKey: String = getUserHexEncodedPublicKey(db) let existingDumpInfo: Set = try ConfigDump .select(.variant, .publicKey, .combinedMessageHashes) .asRequest(of: DumpInfo.self) @@ -287,6 +291,20 @@ public enum SessionUtil { return config_needs_dump(atomicConf.wrappedValue) } + public static func configHashes(for publicKey: String) -> [String] { + return Storage.shared + .read { db in + try ConfigDump + .filter(ConfigDump.Columns.publicKey == publicKey) + .select(.combinedMessageHashes) + .asRequest(of: String.self) + .fetchAll(db) + } + .defaulting(to: []) + .compactMap { ConfigDump.messageHashes(from: $0) } + .flatMap { $0 } + } + // MARK: - Receiving public static func handleConfigMessages( @@ -373,7 +391,7 @@ public enum SessionUtil { mergeResult: mergeResult.result ) - case .groups: + case .userGroups: return try SessionUtil.handleGroupsUpdate( db, in: atomicConf, diff --git a/SessionMessagingKit/LibSessionUtil/SessionUtilError.swift b/SessionMessagingKit/LibSessionUtil/SessionUtilError.swift index 2425f7398..ff887336a 100644 --- a/SessionMessagingKit/LibSessionUtil/SessionUtilError.swift +++ b/SessionMessagingKit/LibSessionUtil/SessionUtilError.swift @@ -5,4 +5,5 @@ import Foundation public enum SessionUtilError: Error { case unableToCreateConfigObject case nilConfigObject + case userDoesNotExist } diff --git a/SessionMessagingKit/LibSessionUtil/Utilities/TypeConversion+Utilities.swift b/SessionMessagingKit/LibSessionUtil/Utilities/TypeConversion+Utilities.swift new file mode 100644 index 000000000..f418ecae2 --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/Utilities/TypeConversion+Utilities.swift @@ -0,0 +1,104 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +// MARK: - String + +public extension String { + var cArray: [CChar] { [UInt8](self.utf8).map { CChar(bitPattern: $0) } } + + /// Initialize with an optional pointer and a specific length + init?(pointer: UnsafeRawPointer?, length: Int, encoding: String.Encoding = .utf8) { + guard + let pointer: UnsafeRawPointer = pointer, + let result: String = String(data: Data(bytes: pointer, count: length), encoding: encoding) + else { return nil } + + self = result + } + + init?( + libSessionVal: T, + nullTerminated: Bool = true, + nullIfEmpty: Bool = false + ) { + let result: String = { + guard !nullTerminated else { + return String(cString: withUnsafeBytes(of: libSessionVal) { [UInt8]($0) }) + } + + return String( + data: Data(libSessionVal: libSessionVal, count: MemoryLayout.size), + encoding: .utf8 + ) + .defaulting(to: "") + }() + + guard !nullIfEmpty || !result.isEmpty else { return nil } + + self = result + } + + func toLibSession() -> T { + let targetSize: Int = MemoryLayout.stride + let result: UnsafeMutableRawPointer = UnsafeMutableRawPointer.allocate( + byteCount: targetSize, + alignment: MemoryLayout.alignment + ) + self.utf8CString.withUnsafeBytes { result.copyMemory(from: $0.baseAddress!, byteCount: $0.count) } + + return result.withMemoryRebound(to: T.self, capacity: targetSize) { $0.pointee } + } +} + +public extension Optional { + func toLibSession() -> T { + switch self { + case .some(let value): return value.toLibSession() + case .none: return "".toLibSession() + } + } +} + +// MARK: - Data + +public extension Data { + var cArray: [UInt8] { [UInt8](self) } + + init(libSessionVal: T, count: Int) { + self = Data( + bytes: Swift.withUnsafeBytes(of: libSessionVal) { [UInt8]($0) }, + count: count + ) + } + + func toLibSession() -> T { + let targetSize: Int = MemoryLayout.stride + let result: UnsafeMutableRawPointer = UnsafeMutableRawPointer.allocate( + byteCount: targetSize, + alignment: MemoryLayout.alignment + ) + self.withUnsafeBytes { result.copyMemory(from: $0.baseAddress!, byteCount: $0.count) } + + return result.withMemoryRebound(to: T.self, capacity: targetSize) { $0.pointee } + } +} + +public extension Optional { + func toLibSession() -> T { + switch self { + case .some(let value): return value.toLibSession() + case .none: return Data().toLibSession() + } + } +} + +// MARK: - Array + +public extension Array where Element == CChar { + func nullTerminated() -> [Element] { + guard self.last != CChar(0) else { return self } + + return self.appending(CChar(0)) + } +} diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/Info.plist b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/Info.plist index 97310ed4d..c9a2d9b3e 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/Info.plist +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/Info.plist @@ -4,6 +4,18 @@ AvailableLibraries + + LibraryIdentifier + ios-arm64 + LibraryPath + libsession-util.a + SupportedArchitectures + + arm64 + + SupportedPlatform + ios + LibraryIdentifier ios-arm64_x86_64-simulator @@ -19,18 +31,6 @@ SupportedPlatformVariant simulator - - LibraryIdentifier - ios-arm64 - LibraryPath - libsession-util.a - SupportedArchitectures - - arm64 - - SupportedPlatform - ios - CFBundlePackageType XFWK diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64/libsession-util.a b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64/libsession-util.a index ba172f550e29241663eec0cd566935e76c35604d..3f7bee4866f4335c016eb5b69d3b158242fccb27 100644 GIT binary patch literal 2034440 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 z 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%0Dt7DM(KDYoGCp8XgJOw|? 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

    vXBeL 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

    w2>iuqn8VDx^S&z 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

    =W&oZ9wTb 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%0A|Az0$J7Pjs)K?(x^?T<URRFxUawM%jX$o&{hCj+ zSip5Wy1uaY(otWa`(2!csgV0Qs=rR_N?eV4tyv${2S}0f)9xq9GJie2t{jD>Ubl*0 z_1y64=zcE&_qi>3RcE$Tww7XgV7MC5R?oV{80!gAJuJlkoXCSUt zT{?B`>~sI>(!EFbZb3mk0-x~l>DIXiJS6WfojZ3A2=MU_~3^Nd+`|UC9mrmj?I8{mGCe!jpYVLKR*48 znV5J83x$-f$jI=1!^36jJ_cYjA6*n5rofrl!n~i34qqhDefi=ubux+gtH&3}blh`ml6E7bW-J z)fFy0lHbH)i~HGII!G2 zW#mb(FJnm#^SZ&>nF_x5!V`2uM$hgQ*UJN*`&9BP>E%Dm~kQ!h>Fj;|va3 zG1tc--+Uh{l|PbyLppKC5)gI;Rm$sLi!SPRvx%Nz5I2 zw=&=c?xBudXSQU?8|>M&^gET|(F)O&f%v=yS3{r5%5X2iQ(vU?$~vp`$WovzC=LhK zL|xigG!0OyLe=x5R^wZmS(7wkZb5U=6c-a6_`SPm(jkxA9CiAYkBO@@=XNw=o!bR@ zZ-l&i`3e5&m>A!{-zzIU?z}bt zVm!#hCg8qg2&og+&&C>^rS2nxfu6_w$wQBKjeEO{m5BzjIqBlf|% zBw{(!TM(u%gg^F{yJByPMgJD??+*Vdi1TgmPnpxn_y_n`g8vNo&;C^zj&|%S$9<%V z-yZl;y3qDbMM#e%s|i1PBVE0bPPE@uu_%iU(b0i%bK_9XD!mrzqa(c~s?o@EH5z%WN>?iFkCnD^ zn_Mx~9d+x0I`%|eH$k0yq3txyyz{MHvPr4P3UL*4{m@2RLbekt!jGqgiPvpy6H_B? z*z}MkX&zEe0{8RGhXT{<_;7Tnn?69=OCL`p6uhN6}$?3oy6RX z#qLJG4@AE+f>UfCH48`k|M8O2DeDEKp@Y4co7M7g>HwuCw3MkdvFL{`t3}f`pkXJ= zY_DQ2QI0mcvpVq(3)tAPCEP8RZ0g7!8{M%;$L<(fin+0pNyu;66G>iiOIgl9 zTXtUJscZ*!NlU#nmEpdV|Hpz_9}D{9#jeI8 zPHj*jUzznO;z8^-s5C$J#eWU#}3RYtcVzyC!)h37Xh(=$lbb zt3O76Y*4n(DpMw<#p$9W1LcR^?yM3FpV;PG* zjNNqX*k?+2qpuxkWn-f+>c-EO9V;EmOeJHP3GX?5hSJrTu{hj_ws@>0l$mZRowLfH zVy4>@(U+gI$uvx4xi>p86TJ^A&h^bas|?A+Yu}vSmQS7H~u9kXh10G|fccU#@JDW$G!KN@Ec}>gx7H(R6bn-s^9Q z#@FCqq-=`!fqRwrft&g%rMFR45p~?RT~EK|YP`Q8n+|x-hB!Ltecu#}Oh_=%JLW9j zF%FQ`()J;le#l$t^UP#Yx*|WK3GbIiIViJlX2$w$>GM*~%}%1}T&!qeAg5h_6 z7OsLm(-X2zZN&zCZ5p$URl%L6Y#zH8eRvvc9-Fy%sIgs_l+;r6XpbN_8S;t+) zz}>8I>~D>irBdGZE42kAD{>wMq;Wm+`yuj0d0y_w!Y@FdnW+>_6BKrb4tTNc2Uy-O z2T(=_P)6oB7?myZcfB$k?{nAl$lGq_X3iJNB=;lydJ6WjnaI<3=6t=Gy2W{UYNm3i zaW_*LC|?LS7vbib!y!-Kne!!w8)gnC=PMKSN_>GXF#Jk0LS?U(1Eui$A zyIdLG6f#J%d9Vz!0vTBjqwZ=X=mu|%)*<*&NPmUo@pdq@I7Ud zLqV05+uJB5c^a?lr#D54Cb?}^m8;Q6=Ld-M{8-WSA>!@FRQiul{=1pIVUZ4X7ae2l zkDnjf&v|Bh$QJQ#K^>5swsGudF8jq7D#Q09jtkH+@1k!xqm;_OUb#Z~ll(nMu0BV3 zQ#iT&Wtz(W0LtzLkuZ zpbzvEnj8FpR5!&a11k3msGIK){~G)%A+s$YCsa3QP&WtZ*$DF{eyCSNvC`gLH-19X z0*Y%P+V&ZxyMgpp==(nwBQ48N-^5Gut~y;A{vrB^Gmw+aU(vTA4Cz7_LPgV?cpl2l z{LDSS;Qfm}kmMl-Zc5j8_z~X){><<1R(epJhk+Lm#{tO10rXu|-*OvQBJVn?AJp*y za~Kus&CQ^^SQ-8d?l;EspG7$pf_EqC-WzFeiSpQ~3^x`|nrZx*%T=J#^N{gw<#s#B z6}7<$x@YgnRn@krJXsmO1@V3eeoD`9#NAxk*c_H*qtBJf@C5kZemg9a-Wi>LsNBB% zK(9>di$(Nh$!PQGf|JHsA?8jKoI~FhoIyj!l~9rFU-UCufe{WZK>H1oO`4S2T<@HyxeBlxKg;@y%eIO5$Unj)1d z!@{#3Fppr!|YM{t$?w|Ik8R!3tnEOB;!jG?@v7bA1 zF1M`Tp(~w3KUoYNE}iu}8QqA=$g{mqYm_5oCI)&q<)!_?#MBNbt0a`w0+e%JOBOyE zaOa{b`fU?*n&$+^*u|}+vWl@|rX+J&IhxCAatqc?E8u--5B*HxsgFjyF^JzA zaXCEI>rXZMrhMpmB?^bogB_tu3C=E&$hQJzPh$W-!TANGW4c;Q7~)5BJh+$Nk>#56 zrNX>RGt>d*TY3SdyqV`0hLBGBFOUb*TFHGsF zNrT>=iFCvXp0Sthp;sY4WlwBKoecWRg{fW}7N(^dFwYeGy11IstzEc0^)1Au_H$f6 z5bvWjlv5GXJ7`a2&Sc2*02dY>xyL(a5XLzlgKkCiewIUeWHicWA!JC2w8?ZcV`aeq zR~g{h<}v`*ECbBLxz@b*r_#&ck{q13M}G%dNJLpB_GZQs)Yk^|Z)$-RcneJpD#V|O zx{Fn+4V%!G4A9|FSB5?)J3l;!{6rxy*hfeR&Z0UN*|me1M;g$JB@Yys9;Uns3<`}t z7Ic*Do~M0m7e0;o1q0su;h48|Z7*o_;=TJC^ZiSBkJB^gJ)ly0=sO7Nf&q}dv=;2z zw`hMPlQiEo8TTRuM?-J8FPq1w>a0UHVvAHXpm<#3}a|b%HW0Ac?^RqujIU&sjvEU5@ z@79hgKYvFymvj`ImoRw9(F{P}HxuF07P4z&kbfFyU@N@q+oi9qeh%e21;4A9 zBS>GmU|ZhOp4+x9WXVVHb6zBkvC{-6LmtL4X|}Il&qMxKU3-1?WWiZK2)}`XlRg%| zD}r-@xKyl&TFNS}v|yi6+0hty;mykMPx0(Q2s0VK47h8`R%^7inzBW5b?3Eb(f9R3 zAJ`XtVITB~z3~p|m5DwwIh}c*EJdB6og1i6U7%+5hNnKA`rAneM?48=PcZ+BWA?B? zGkbPqhub+f8||AH+ct8|lEd1WhcdVI-}&2T&%1qF_8~gjD`Dfo9 zWb@&ByZW3`KKZ=OzLu?RzCZGhHm#TLfAzOl4_(^!$}Qie8~kReez)r}Y}xX{Ssz69 zSNL5pYzcbT|MO3SE-ViG#kJj$e_UwkvFOw03g4YR%f`U=;LSsc5&hmuwGkJ-bHU}* zu%+H3qblM*xozX~jY5tw(n|>O+!sfS`OaA%nu)Wk|*DXkTY<9u;f3{!=^TnR0)+$G=-}$xkwC~Fo ztv^*^8r5&~o&j$T+_7OJt7e3BbKU%9+t@D?`d^;c zs8vx=-?m4NI{Y*zDt>bJCEs{lp7Hd%M=}IWpVUpQzHRYG!l&mvPL6r+mD@=h9xK&; zyz9BtV;5HHhR%rSc~hHotE1hE8KQgilX(+WT{ff*nld2Wb^VSRHpA!4UK;q+-qwNb zhVB@7;>p#!CoKDNb?XscpY(ruUCVzAvpLsos^-mS{XNI`Z?slb+GgjMaeG=WeYs=m z)_+}ZJz>HWhZ)m*%z0|?A?4sRs^DRBe%+Io_`Yw&*72S{4ZqfU$bnC$zv^*nVrj2U zg>(N(-Td<*;q2M=v7UZYIz_jcFnCJdynaL935x&f$-UbvBb2{og?qpJ&-Z()oL^UF zD17(dXru8CY*#SpsYTEK@j{zX<+t}Hyz}!Wo&N0E?YlO-aQP{<@Kx}lA>~dJe++(p z^yS{~zqbFSF?o&Gywclsl%D0dZVV0H5@&^X0VAY3dP;cm3i&=5}=92D^{*a*LLK*<;tOA>FzP z1NZd*D&>!@p>x^uKgG-&`}%M8(?_+rzVoTMpC+d4c<;^FXAj(X`Kbk2?gtY0-ua-S z_YY$aEexHwsr!);g^3rJx0-uA*4FXyj`S?k%{`!rF>w%?dwDqroAz^pgI@jWmA5B<<8iv%>Av4zRjT5eT)T5=#?Fb?7KL42Q~BzL?+4rNw{d=K zh+_5O4M$VI?D4{1Tj%*bcYaB~0M|*gLpAD^0h-^ZpYU-w{cLc~(}D4x&CXug_}w3u z-_7cMx~b-T=g+$qO*`^plb;{^Dah~bobO$?9M5(9xo4~7cI)Rk?GIB=d~M5_1GfIJ z=gt1}!=ukm(9b?MVB(JczpUKwx$l)XHz@PUO4ZE-sB)95d6ncd@5n~E>GpZv`2Ap7OV?4M_Sxwpl} zaVw&Jh=`5UzGRAQ^TdtQf1Eht*?RR;V+Ve2lU?+$?t4ab3XRBKe0cbey;pAfSL^Mg zQoav*w|oCHV?2%&FKxZOX#2%u8Kyl~&${o{4YQw^{O!um-#>i)@hM-9aeuOKc4fJ# zVBfCxuOB`2^PKWt9g5C|y?Et0)8RK7Eu8h1?TV)hx<9caGx_>tXlz+d* zVO;crb2B%GvDuV(X`7zWL*~pF~f7 zBc^Yv?^gpleY3{-wQVbcJB}^;YVhJkm(I4b|2ghP`|K|9|9EYA|7k0pD7f>F7Q^i3 zZ@TKDeCp!5U-Oc8d;I#(ElUT*-Z_!eiuD@_wTVB|>?eiO-Yfm}=T}vdu(5U*w@0-6T0-sv` zIXt+Jgthz&x&Isi=hrMOZ4c%g(5>jI#qW%TQo7Qy;D#|Ar=)xIKO4#%T^l5*#UDnC z@VMqkP|JTG_n!vE-%|Rv(>Y3*d>T&YHGzGBc;w?^BaWCPM--1=#X=Y6s2|@7L{=V; zhf<7(g-w$%I2r`e<0K5m_{SWczU4*X=^y^=ijYpiJdWuc6F5e36gm2E1ep0?@5QDB z)>^LGm77ljh9fdMhb~|ASy`gxa)*v?wi6-mrx;X&4j~$OkoQN>ei1sSC|~3xS}w<< zoR;f7meX=Qe!yv2ZgM$YRn90rx!#6yf4QEYh95mszAE6TN|)52->gCNeLC1B6FG*? zJoe!SAgkOTJ#Nyhnel^1&lo*Y*+$;d_8FYk{r+DJYA;+p?2pdM+U4NU*BHg zz3vAH1V3F-=KtuEYy^g8%dR>JWS^|o6&V>91K}PW*9AKyaMj#jmCobi-6NZW_^Nik z=nJb|587Xq&EqpC3Y>{NeS=}3)KiLymxM$_Mvjk;oIp)brmOG&$_77OWWS8AM0+W} z&MpGk^P~M$(Pj_+5Bogo-LJK(5%>@#C4$nE*H|Dse{2;x%vE>>;=8wBi?+nk&jG)7 z_{lN^XM?ZGKD4h%iE+IR#`m@u=i6btZ;!c7hfD_>(bR!CgsMA;xsjOL*wmEPBYNeA zVh!?ntp7cSwFKMFwAY6v|FK)hZ0p8e5CTQ)wMb|bgn5-RtdC#tV3}&nbLc?BmXE1a zp$^47%L|y>i9NWG_Jygjhe8{>*OP^x!`y(opw#P(*e{^PJSpZmQ17c?TP=AxM9FE= zgg`%)lio-0K6wyxI8ISt{D{||E8MxAy1zZ-%7M>k_`UAb^be&jv~!yyUf?A^)6^SZ z{D?9#yPcwve*Coji|Z&#$uEiMo!8%9f9m?wbEfOyU(fkdb*)GFVIKK~Qq24Rsr2)A zy7k8K9nn*B1`A%X6;G`48i2I`zq#H+14I1us%B!Yv#*%jvDFc;WI+|WM9_uyROn*a z3US^)*mAFz+AtSg1@?YKcgoqVXdb&5b2+Y<1ERH)rdTuW0=`nL4YWi4n_!NT(l-lh zj}*2K*3r@wjSb7Xu1P(obn~}Gn0*R6eKxbxFA!`Dekdmu@`?RJrh@|3Z!xDYuitjW z9M{u`>m25mOR;Bb0oESRVNQ7gb2RM593S}R#+lbHwXs+SPS&b1=hP@Ebu8967hq2s zg>4Uh)ZdQ>nNnYdG_Dd<`YU*Lsi4F9x5m($o0U|ErlEQL6gg|?}GMDbg^U8TimF`+5_r{ zhe7uqegporZ_n(H^<=6;Blh2+ExY~&T!64>1G#=mJIswMr)xcFZam4Wr_%0P$AxKL zWmp%w;2~zp?WY^&#-LwQ7tot_bS}9(VA1pgntL-ci~mpVzb8KOqa}u#e;~zSj_KW0eEu*6Ej_Y-034 zyZ7|Oeh;*D2kbA3LHw7VSf1)| zVYv?IFEYM$&v8f;O(j_OxPdhiW-D$hdee5BgA49qUkS}`)BQ@gSuuOQXtC|K60Bc3 zVD7b~4>SEVV_;_GIhI>F6>A^gDv|}H;dksqL_XK%|Bn9QN0xhVr_P(Hnam@jQiNuWjOz^g*MSY#gL;>|Mp(r;5986?eZX?q{pG`&V&?RB;ce z;vQJV9a_a5R>l2X75AVj?&qtx2Ul^wP{lo@ihF1kcX$=|i&fmis<>aO;vQbb{c;ue zh$`+^s<=m1aYs~fkE-I1yyx~od9$3)2B}%w=8-CKdMmUs75a{WuWN@6bjcer(1i{8 zf9$<`e3aFl`2So;$YgRQfrJS)35aGA!lhsZwVMG{BqLZbtyQ-LVO5fci>bXBQNu+o zVL+w2-*!WPjlkM6frxhPHtBx1Ahs4VfYsf#yY2qUB?3tX2w4ylwC4N%oacFxNdPb1 z?&bSk{+QR?&T}rG^EsdU=X0hKk0yJb-}wB}k%P3`Og<+<6K)(DXn6d_O-oipi)el|t z`t@{g+LUR0PUCZmH+jNzJ}2`z$?Nss$Y-y296ZSL#hd&cy2sn05A|lwx|z?Je9rLZ zRL$UX4xg{^`tH4j&ptj6_hx@(CZDtUoaG%nyp+#_`5fzR&W%lOz8P^0rKL+svlT;FvbI`QHgTZRWXczTkf+H7Tae9RE9((`LEd zrY9~?xe%XK$D`icJAicuu%3418@_3SwDf%5^LaPoG}tfj;X8sgsa~0L9^1e zn{@vK4S4ZAjqeknod-t0;gfjOv?+#vtdZhv;Jd_$9sq{x_`HtK$rDWfKHzEbU1AgQ z$9hZojE^_n>mTG_3w-^2hnJ?80^@D;FYP?VasHXKUCVcAi$BL}IsYXV$E&@mxVrDZ z2%qJ-#Nxr~ztWihihBP`Q2!;y{1@%*)1&@tXYKBI5kA{}CaVAHWB!X06X32XvDj-r zyJM=~U7MP(v*yI&B>Vpy{PWJ*<^Sl!*gle-K4x5^j}-g=;DA2n#rBbEw;6ZqCHhFS z|F`TuqRZ`P-SygQP4|xj?r;1p{@7BXmD8Y=frddtr#HlZW@yYA7L%T#E=^U#4UvtQu-1>Oe@ z)tTS4hS&N2I^U1;ew_FC<>kdPCaH&o51Ta?9)qyieerJC)Qr=JI_m-yh`tLEeYV z8q|>a#hV(Q zbq}wVyf*Pt>vw3M7r2#=-m7rl^CmnWT-f_F;5|Fgoi-)l-wQoTo**>JrA0!c99mR0 zopUa<=n8L6`Hg%Q`s4Eszei})a6V^yv%V-aDvQrTuY^X~^lI>Mp-~PkN*5XxN1H)j z(<3y>}b z{D&0%p^c(H>0Y5fE!+FkAD5Q&(GksmfVE$oj_4mjM~n;Th)WMV#ipVov9u!COAiJq zxE99H18?ttarRk>p+7Eda_P^-X_HNVE>1^m`V&h>LcKJ`rau)iG$z>lpG|)XWB%(; zJ6!s6Dca%CpO2&+4*j_#?QrPNN79Z|MLSLo(zqHet#c86Uq7y@T(*KZ03UL|L3s6p=r@yi=CC8rVUrB58M()YE?b!}w9dB|`K_Sp zy2iaf4m1%*?cKvOcM&@${m(N8bxy@^EHT81EAFWZqMyn5MfnjQaURdklboZWb+ref zZOzC7-UM$bY51zS7I6kAdg|uRV(g_)1R4*(uXaqG9UPbchVS6iK;@~?b-vl<1&wbY zQy$P$y$5(cfxL^{sYa*7eW>vYzI5sFINQz8QW?*x9J9e4>nO3*A35I7obh;GjW@^a z8!zt*mmcruj0Y`IgP%_S#yf2fDAwC$^lpk-f;)vVVuz zSAoCqY(=-fk`4XNp1XH9`#df9K>>Lc+T&YOv|FAZtCba`wq4El5&p4l1KRJJmh{2i z1LOxF+e-}2sG*`)UJ>2AQY&i>5kF)m6nfAM~th~%CbEWoX z_o$g+Vk+MBbXR|;H3ywT5B#|GYCT-jLJrV_+J@Gv6T{ynzT2m5o%@gufF?uKO7QH#yLhpR$aSeU-YKfWC%CI9nlWl4nT4n^gZv!qSXQ_Mn zjIi`A&&#?OcJ-9~$kZ}RRtGBG=jA`?V=8^@eVzO+&Xctsc>?%Zz|R7HR(Zx;;af#{ ztL73@bAxRsOF zPYtWKdP;O!mMm5$kf{8Xf@CFykh&mNlJ^{=>G4MUBLdW(rL9(dRmy zycnKc2TaxhlXVIvNnRNzcDts;fXq* z?ES0kJ7d086c&o?lS7a8*a^Ekjf4uB_1 z(Rs#sv^h#2;I|Hb6B;nS@~0a;_v!xB&uL=gN|!$NLg!ksIjAwoLmD{deD>iZ$3(V! zy6N#D9ivmc2WM%O53om_E1&VbYTW7n^`FfQS0Yp4%NPc^V-4eUuP$*D<#X#$sMFj?lG>jkO>ADTh{jh$USlIj@2X zj9tLlll>2R0^Qrptgh1G*mqZFHRfV7Gg(*t)l2>LKN{;#;QWDinepI_JeL68B=QW5 zuRDQf3WjS<1^id1rA8CT@gM~}vjjQDGjvUD34QGX?${TvAz!Ca_!`mFlOy18J-p-= z;D~=EbL&Ipjq-h)_SlcN=V2!SSISqetMx+@WDZ;5O~^2FY11WmQNh^djC++c?k&VP zR-2jD{Lg4*at6vD{prRUi#~M&+K^~1A=dvE)?dK0!&&nl@H>|=PL50R=dsp3;PYN+ zp^P_>z7qTE6`tFQE`h%*b2wwhJ})ujJ^a@>io7}ckDID(h~>FryM7nG+r|4k@KTre z4&=Fi4bOeID4^_x!gnQJTEku_Ja_9*^3Q=)cy^)Ul~Khjy;c{wG{F0r-@_kUr-0w+ zTGiMy*YFwM^;UFS&`<8R7kH&x&|x|EtPeXBG&n;mdojfm5_%3@cx4}P#DZJ<(eHNf z?%H6UfhMM1mFn$*CRRdc^i>;t2cfsGk#qKIfg|?hSHagb;L-sNSO$HrG*i5X%=G$X z#vAp>PdBc%7B#{(nt%QF*6IF&@v)B6YQ8&k!xhUA9huL=NIH%9EUgGe?`{D z2Wj)*`RebS4_wiB{&BPM!9<%!OHP{L=0a^ytF zywkty=9%HC=$vAE%GXTq0@l}p{a40&lJz}l-PcHeRw1#fkF&m~Sl<)Q`slN7eP?^h zegR#tgRXC3?8jN-z0ma+v_V1H8)*15FThh?xaBVFHJjvxLoS~%!KLlQ9 zv@0USxvCi6~(E$K`I=ivJ^mx}8306C`5=54JpB$97E44|tK*N$~yB)e^ zx6Nv-hSms7w@zH@-}*OW{p7Ptd28e@t#a|ukWX@rSMdJ{u0GrD(4q!8Tj1ah<|*)( z_trZXH_Gp)^v8bOkx!gGbYkDir)xz{YM@`CMV!g5t(w-oo!Sr3u=FJ6bFw7Rw7wX6 zey{Ein_up#fkuT_f4Q*+I@JbkZ#K0_MYLOd%~bz(==Cf<&nIsEo>zC>u<6wmH#|@L zI=p2Lwq2`jVvSGo-Dc7^XVnsU_h#UDHTZZtubJ=<@gsnpR>6F0t%H9|AHr%W#lwV>|_{gW{hIBR2=W5_?%PYnH}b?}S*jJZ?v zEBr<9tV#Rf!>4$rgZ2FyKUNw(#+S<hQD!1^%^tTL8nGArNwz8`AAlyGG?NP($OCz!e2Mgz{ziFm<9_s9 z;nDlibA@KDr!Vro=N>?(yr3;QlWL3JxP-H}Q@78a@YkiBz5RN!I(sgk-4CBVfG&K| zanW~GU+BBV%y}nuCeZ!LmZ1YYg^z9(yrdI&oFW#!#7I^-3SVS>OZY#}-1T|cTI9`1)%9MBU(LRoJ7qyI)AK$-b(kACL>&*d9tXo6vc0_y6t;Sp?cd^RI_PX<0X zM9jaIXUF{K8fJ7Q;|pHo)Bb>NgvOC$`UW`_CHQ?MZ$seO;H-(f2zBn4UKxhv)hJ=(=N+yw19LSHS8U5d55CAKK>Rq4D6^@Pwg?BrKsuf8#J z@-p#VE#Un|?A79(ph&td~KCa`Ffw*0WEvOu_s@w-;ww` zuf(1|XsAhlh8jogJ_2Wvk-FxUhFgKzK)cU950D4_zRJmXuGg=;TKvix_?0t(=TP7} z4ESaNXMD=bhg1J1#;=U;_8hqV9=_!S<{ax+&c?4iJR3TDF~9QNALCa>K8*D%??K*D zKIOsjwomy%P3gxyyeogQ8Q&o5Q~qRZLJ1GBUh07KoWZC3EquzBL9hYCbh4yanE1U=X+jB-JhKU{LVW+ zf!{fdtS^4&FuJz*owquE=jT6#-+3prt{z$c)B2rvIDY3BdH&P*op*B9>o4Yarhdyu z`GR*ie&-i?{?quKccMeoU&QY$KF*ICZ<*tJUN*q@d^7tg@|Eag(#P{LI$0~cLqpzH zdKt33tCtB+5#6l(PoSI4M>kvaN9tya*sJ-M(9M=PH29P1W&)S@(8-UoZ{003!>!o< z62vEu-kV?!50zs#Dgj1$*h$^9?%6Csa)9O`k-K+*5uB4TD4a8{zR>Nxt0^D#-7>d*N>|| zT!(IW_l)p6`1iYV*#ly{&tr@2^)CQ}>DVfMVfccgGb-N#`=mL(g*|cFr1SXmJtqG9 z4D7Jff)Ibc2YF8U^Ox!V!{BV;uWg_HY5Ldk0hZ%uQ9irgJ&gE@^1WWy2Q9b|zSZ&V z69Xu|{jO1g?iuL*k70LfC9ckshd-J4`8xdOtFeWgpzo93K=)taPyee}fBN4A!m1s% zv3opP*%yHEbm9PHjQ1xJ%i!#73$+%xDmAm3H~{%CcDz@?J^wEPO+`2M#Q`h^_Uo9x ziUk!=4GjLJeD!(at55m}Up?y^C%$<-Va|AL z?rB4I-IX>(o7*}1*S<7jZpCjteq@?=LggzP{Y!QKc*kdt9*CVe$tuUrEk14Kx8IN7 zp7F((?T#{CasE@j`YV6odkwvDnl{M0Kr@1Q`0DW$1~vTl`LdS9{eAXpVtn?~9iRO! zQwttpZP(&M*fp8j##^+e>)6|q_(8~b%5>=l@nPxn%q(l`wOZL}>g3#_eD;jt8R)am z?Dg6IgxV>Ik7g>L{g^}GHRqFaRJoG9EOmVLEBGuvaAH!+iX0qrefBScn+JfK^4pVd zgzs4V+riTOlwfjh%W>!VNOBs*D}dz^47mW+}k`o)#A?EvLt?s^_`3V;a0{f+Qm5u zU>p9ozSw|)^K<4F6#s|7CO|He#0QA)Nak=WYYj23oB=mJK+ZP#GS95jwGHcMT9f50 zK6R(nC@_`y@;7*{16ZoK6=c5#JyrEzs;2-?Vj=MBNW4Hkwt6>S;8OESa^eOAM=rMh zV&ZW2>b5`s7Uj=>Wupgpe3U{QKauieFej?I0Id#Gmpt&VcLh-;SMHU@Pxys%*c12w&T}{Ezng z3m#N>`uzSEo!`-(GC4;YaR7_(_b2d74mSJ(&W8B=PZeq19UHZ#0PrYa@BHi|_V|m+ zBU1A#AwMyQ9XONPh;s+_p+mP4^5A3Wb*HaJ&S5Oq_kVn0FnkDJM!s9U%bR2Q@fPZi z{em2?UVcnlmmen#vwi)Z+ZXexLI77w}np{p}kAO;L0Z!RsJ& zG|D=I`22+j3cQiIl;3|oe*Y6i!EkA^Csd9d`c}v9KMVifPU!J#tV?Q$HG|iZ^Ku(_ zeFz$y= z-U(lH{r+#zhpbI6xYMuQI5T_%n3igZUh(};W4+?@x7TXcuXWaXle1RW&o67;f}fw@ zfI0K^DER6gE!^Jf6cl-Bo5$L=CDxng3OI?Yr`UM^Zicbuse~%-gaUpdWcytaG7m(8%7qEx3Hio&!#sw5J_D;rj;{uA|!KaAt__(-$ zV&*Av0mU!%j|(_STmUk5=E4DS0lxyjh`CYsCp1VMZRq#K{Pe}I*naxrSNg{XjJ@^3 z_<&;cF!AXZ|L?x|06DWF_v|+>j13rj%O%Eaboyze9~B!AuVMo_QUcuvsON0Q2IO22 z8*q~U=R2_hr}#WDHelKGkBbe+xkzk4d~a;P1I(qGI?3}_KY7X6fRofFlsxM{J~kld zPZ%4J^Y4lc5O_*#fUHSk1B3>NYZOe?X9V2ijzUwdJ!@dJw_LuN!e~}o0GGYX7U(&PMju8-9 zqGz@KX4gD}b|&oy;p?Ub>~wv@8R8IQYrnontQ#^;udllmpK$``Ct;9w&EAoLu-H#F ziCv5MeqynLZG}=JHqpw{Z+ZAIa#VGETIUwtw*t3T;)u4=t`$4Nkx}3+wO%FfpoLf! ziO+L<^3O@FMww$Pdyt@KL|UOUtv<#iXSH>t>2DpYHM!3oL53Rmd@DS_*1`K?DGsy8 ze^`DKYrT;*PX{*BxQ9hoF%vS@P0&l;Gu`*m&Ug1+YV;<=Q%`u1U4wQrHjYFs&>bS@ zrr7V@D6#6^1mu2ngK9k~x^F_AuZ}p^1G>B$)+%(Z9q4PbG~FvXI8XE3IO;LnR=%w9 z8RUmI$s0?%G0vYlUTY$5#|qPbGWqd~b)!h?K1)1^)V*ELbA?Wg*J@y|A#crUs`jH- zdDrX7)+oMo%iP{&x?Ap5nI{dyPtb@yx}c7k)*z9cta?pvuJi_Jk8tV3$7O0D52aF@Ew`&oy?DoKo=$$Pu$EksCE z@jX+)$(?+5^KXuD_5>GYUg5$((=GZi?qwNf1vY%?VXbER!8E%Cpl;9)N3Px2i~B-WSr`p+V#660G#Ja0i9{xhCQfDWy#C}>ol;cw_e z)MDu0caLdOKP4U-GMMv{z?dG!P6Wo@u%B}JCj-`x!T}p-1u+TcAD{pnYX* z>ZTMKsgY%RTqt712YYmHG=g3h`A*2!qi09o<~{q_r)#$vsj6 z4nQ;HSuf8n+Pt?=S2a$$c_&u52^?(ln(^SCUE{=}#z`(UPAqDi z9DN$Tk`w55ao@$^GWv0G+hE=u=Ctr=XnPU$MI;{o1ocI}Nk39w<>6}sjlt@aoH5z+ z{3pz$-~#Nk=n8V? zyJ*v+Wk;u>S0vq95PH#w3qD00wfIJ=p^XJY1Xr^a&P^qreZ7J&^{=gEU5ico98tr% z-#O~Qr(m&0x9sl* zgL^h3$-$8HDKLudcQbhW5Ao@u}1EH;nK0bA*^}p0C;m zEf6{ofd-u5j$Kv;QHR7+Zlm5s^g`Q+s(MN{dKA*imOdn(QRA?f61^kOZ52>4^XkyXWzP(%q zP3%e5I#(4*JhGN4bJ%7~NwlCjKb%<nltz!aqJ_(hbTPh z8@%@z!=l}v{a@6v$}G$yrdfV}SRYP(sNt-|7sU=*X7T*3yoA1o68qL7^nD?1tHJd> z(7O{nJCQYsUsz~THF#jy&#M|zvVRuqTb!Y7Se_v@t!#QP?c|IxP8Bdy`)sk#i@B4? zZCA}cyYyZ@D|&C*^`6I~_Sowr6qGtxF>(sq& zRzWC~Y@bP)cZjuD4+?bt#fBTtOAhf0>|G%vP3=n)`{G@+aPqqHg^ZJ=?o{|k_M*;6 zx5}^3%C2HP@9|7GI4<<&tn=SqY_`XVa~Jsy9$tBSM_rF0HaJ+^t7GQkmGx<;>hWLS&adI!XRZx=SlJV}JgSpq0Czv$3>tB(yS* zIY3_{mg!Y@+Y#5!9TF+l33$L#`0);HqoOau#|5uAV^zo<3SXkognhgYKo^7-_2@Q^ z&<5I*+Dm(cZbbE*pwK*_K`uQV2@Hi+KN&-(Ti@yFqtp0QMghM#2WD0#X9&GA7H%)Z z!0pKI{`24_`H2Fz`+%F!&1%y=Pf_r3@#w%qz_Ci;c%S}cZ4^ConZ8lMXA65EHmMHm zTD|a6GSnsDg$&@rYk>o=Ct~2Wh&8)>Z6)yf4f=lvxGy*uvu920S(BWjDD@on@pAV} z<}U4o7JS^^y0oBo-WSY|M%e9AEV1)SIKl`xhl;QyikuB zZnca;f6L+?8FJK)g2Tr~X-#r>;>ZE|3~T^GW+eKM=ra?{KTe;y z8NPoheP(m7K4bLiGq*at`{MeH@n5LV7?DaxpAkJNLP;;tXG#oLpV@#ulYW6dvsvjg zm6z0K?tm{#e(MqJTbI^n(&2GNKYd2-P3f=Cq{iqo%QzRyx;nt`f%;4o`Ln-1gS@Hq znbd!mKGOm(=dR4ASe?ejW4UY3)oD!Vo}9U((8DPM^qQ}u*Srhe`rDfWjgiODYi_Xh zn)}deGVZ>wpI-A@rPo|nSY%$P*No*(*@MtI(QC$_7l~f85xquyHVYiR=3AoIjOx>C zI&!oy_DZGKT#c>u5%|=H=o9nwq0#bDwq9fFG-{uaJF>Z-Z-lzr?_jE}(`*CR`*fPY zN~cL?z1tpo%+YBEE1CBMa2lx7)FDUh8aex6$#d)F&*0W7N0&LpTn|vswNIbPwALi` z=`;602yOYR`MFaF1E8Wp$=zFuH%aDiu)>6?skUby59+hh*s(a7& zA?K`n6F6-nx0f=;8@zLmkF{?q zeex20A{)6>_)so9RCJhJ(~%<-U(D{6Bcz>+KVEZaq~G|+T0dcYw;$1ABFME#a@QvK z;6<+8hkPq~PtVB!(`RV-h}k)`soEvjJEZHQ-jehs>U`}8^)hRHxNDi4e}``ctFCS!cA$yKU@}e%``@Q4WPBoB!TP^WSI7u1?x!ncd?H;zLzcHLp({*7KKKXp zfogPxHfk!z>I$M0=-6zY0awmAZ9KGz)fsg9Uc>*cp0JU7zy4S~A%SzYnZ86%2+_~Q z^aPXfen=b96LuO2(Ie;yoJ*xAh};v+aAjVb{!Agx%cVgJh^6^c(x84i0(4>lI5-lV z6j^s0^Iwb{`J&OMBP@f*MUm<6)Bm#94q*WQ=*QR(M3@)if#TV zHu@B5kv~NZ@uk!d$DV%?IXF(q!AFsS2ioz?7`ZnQxVd)xOWE)T$h_Uy@HfWT@M~Qg z{<2Rg^M0HSe>%MOqipz>WnQu2|GzKu9(Cm1Se$opc%aNH^0A!Nfwue@nYRm>ccUxw zR*l|Qo3Xs=qB8F%wdG$_=8duCci>CAg*;ZV<^KpKW z6@6#Vgm;L2U*d0F`@ZdW>u=wey@G%ClZ&Te+n0MU2llla`ItWX+4x;wpKIe6`7_G1 zf(Kpbs2?RCFRq&Mr;&}3voFiW*-_h${vRtFmkmOYofoB7(W*XO{gcqEfwJ;6&WmeLcV*>M*g9N$ zy2#3}X||l~`e{T?j`hE|em0>2uFu7mlkGb`@Qo>-i};2uM^<*_zG)ZOhpFF)jNFe_ zBKwWtOuK#v`S0H0)I}WtJMl-ju$vYmEB~oz{l#D>zIxF!w#3NGvJX-0LSj>Q<>iI? z>e?N^@lvv~O&j~$(}g|{l#@4K;D5-pw*Iz1yhK+1wIeGxJF@bgKY^^gl>2xt%gTYv z_Tm3QvT`iWUzU|6_QkdnUzU}zGhLRIKYfhJWm#Fp!CaP=?YN(TcI3;l@?}~1zlE&4 zdJys9NyN$}6El}W>|81_bZNwgr-vsIA6`Iwc#V<3J&Jwt;eVk1!jbP9;p>SHf1mj9 zZ)lml@!>VZhqvuWAg5%gb?Dn>_#J9(oV|^h@8866pMze%YCQg&9PZt`T5EclSn)8i z;%#?R!*Hawf&9SkpOJqt6Q5@1j|PRY-maofjx+}ZIxBP-;=S6B3CPR{l#ZGW|ezq(=kSN+tJos*Kc&-bA@ zw$LJmd`yMan5ti4otyZyFGU}lc!1c$4r&y%%p4oqJbAi*2mb2fy#2nHjFez5c^wq2 z3?9OF{UiM;-*9}81rMgS zo)OA4hlY}kp`ipVIYQ13{|}0$OnAI@&u6qZbE#Rom$qSIE}zEV+{S&EGn2Jf3W%NN zUXkCX5U046Sk_!!i|XX1tT9rEZA^%Iup73O*!eceJR6pKGC$MzTmsLXy^TKEhi8~m z0&_@Z4(eHA#oKtseXeb2|K}3)p`mZ{Tq`*h9pnn_rCw_fxfljIq=$8P@+#5s2@?O5 z&MP5(1959LwR@?}BJCE~?W$;3rP>kOmd?v<*F#)tUkp@=iiaehYf2mON@IzK%tbes z{!Zzc5qIqYbWB;#pEIvh#E-TGCH~c(6KiSOujZ6&?PWeEd3B_(uRTB?Z8Pk4GAG(q z(T+LMj`^JA<+eM>p15<8&*E33{gjhtO8pMzRD!Sl0DbP!6Cwhi66PWCBVl0MiCxie zctV}i1Ks(+cruhjfFY9z+PrH7wf0=cJV-9Euc5A$|5@%s)qT+^yx;Zg znp%%3^k-1?dEzS8>sKT;a6V6QKKIWo4y9Bqn(SvR_D$s^ES6YPV#?dFMQvd%QPz7B z8wEC#N}bqE97LJxOuLPTHt+NQ2jHaoAA8KL{GaUX!70w_0bv&%+k( zF__LXf&-_Xy*a#>@nu~1nX&TO!P6G(W?!ML;LKjeI>|gavmwFhV#e9YTm?r@-a)RD z;EWS{RBR+SCkxIv^KE6mwe-_QJ9$p}61)@q8z=K+Ji&u4)O=V@yOtJ1H5>yYJ>-Dg$HmitD^lv z>m|;*rASSx7Ealst<`6GFZgat$Au6lkoe&dJ#}9{Mh}M&gV7 z)D`KZR)g@8$#<1EZgcKPPJ)-zK!4k)+jok)l3%`Zy8l))J#+&4dlGnUCy(a@H2D;K zX*>5KpP0DLcVdgyByr*)VnMbWo}lEtY=;+MKMbyb{#$yDubTY8a_$6cTTaX%_W&GB z(VxEgR-wpbR(w$$?zP5>W#(wz5PI$cEYt!)u zi1+gto@P&G|GS#yPG!L-=v`FyXsS;u`#Srz_o3yD1Lw5>e{7rSDO_TBA`Q+w8psbR zF}>FFH}&6pwBag_z=Ka|DZG8`HFhjI_1CR}8>r31T7(AVWApp}@oS$n zJ&|QvGQ7u(&a~-p_fqD}d1^OLP3OeAP9pO7|%;`Ui#_uKeR_5_b z<{@Nj{n5}H=iZ0jU7*H@umtpzIeQr)-3BxqkoTK_y1xN z{eO3QICgDveuNeajg)%(GR`M~z3{+K4tqO3MC4Q(_Jg8FdU;`5{U!x_-BR-f{vIPW zB5NMYnc5F5q)xr8wHUvn@NgGDMs;xa4gXQM1lb@ZbjT}oI<1$!Cog&cUi3g$8ZvaT zmaO0*^gZmr+mkXgbd2YdwX~2tKb~jZ^^Za}keUlZuY^Vk{V5?|C;>PpkuP+De4#M; zLfgm}I+7LWo&^m`Azui-7;J$KK{Kq=(9euIUUrST5{(&xo3%* zG|4JwsHQq8$ItK6;TOxe=Ztv7;CsshO+9*+RsQIZ+w-6a$Key3pea4bf|6f#0GzVv zRZ65Bek3$t4D@QfqE{Kwy|md2Za#pVEA$GO_2TCk@KfNsS5NAtS^2}XvdQd^%w5Kk z@tgSG#v1H=rHp6|YnMJ3(`OCzq-{W-&(mIHxDI$wO^h5XXZSQP?toTu>`UqlKcnQ> z8b^+G&oJ#c!?ff4zNF5u-A?A)qbK#o8M)uxGc36q>lGd)BU7!bJ)vhr`mGNceyNef z`AvzIBj32^7dofp`L@sZ!AH(7@>1pr)*<*%2ah+{N7ml7Uace9s$mWLd5N65&XIkk z-2%H^7429D?O4NpUT(WO_Q{nA<+IG6evt{2dTG!)>67`3%q6(prVmqmaXxsggR62E z#1ZZ|6Wlhyal!AYn%D z(5Y#YgibB)dWjdg$cj!$KPfS^Y8|xdO4)DdMhAN?a1vQa;Pg@ZJy3SO)PDEGz)3#4 z`z$=-CAH6KN_LicLC+#%n0p<$6RR125%H-BQaKzefpatoL_e}~^lto$GyS0-qL(hXl6rjCqU%27Z_K5x))D$W$obsKYbLpMLWjb= z`E_%!4SWF`!EM+IW@9s$MSk6_)LdLIqyIr-}r_;)37dN8hCoOSkz;fcHmzpDx<@(lk7#gtJ6dsOj zB=Q`%>t`+aUTwH!^O)w{tan{;a>r%S-0v0Z$}PQT*OonyvpzJDET2s%WR? z?;(a;@)X^6xiLA4@>%9j`zcZCIk@xZnAdtV2d{x?0`t9>`G^fgaDY38WzL~|_NGY7 zA&(1~2Ef+~IjkQ=tokXM|(nBDMLXewFBVs&DYV zukU2~CVvV2x(K~))FNBATS(vJxxG^E(3>#yK>BrYU=4DG-2Ex^L2|!^K70}!5c=@R zaG;NlRy(ox;0!SAj|1hV84)>xypoVw8+dIwXXyC&P;DKsZDYS?YS}@rndDV8eB~-% zo1PtvLR&2IKg%cBGIzPqlej?VmE?p8?O1WJr_5t!_R@|e(2ll6izizZi?LyNRqjM1 z^rCGGbt_DdwHG+Q0KR|7vnQz|A#or79P_`2|Bs<_pXYz>g{xfguRUdr@Eb*c80!#v zVeH-)>p2SMesJJ=KEvbR6xg+(uNSkQsx7**?Byu*zwCHS;W@-t#^sT^Di^K65`*v_*u4|lI$JKa;Zj{lFI zF*dx++>i0TgLlQxkhO}Tapl~}G>&)SRj$mi`sdU=`2+3Pt2XBDr|+?@#yHehOO(rgxsCy==o{L#^YIsD;K+R_cEEA(+_%J zUtfHdIm)=g<4kI~&8BUSDK_Fn=)D)Zp9K9+h7Y7*BTfx}&an~i9bh9)#75lmT|NAD zZCG!tagDMK4-R9W#5PRbBJ}jL3$X>0Yj28GSZk>N-&0*cSvI*dbh-u+J-F9T}&Uoi`H-e`EMUZHCx{ z({ske*o2E6n{W~~;SbC)$|k&dk=2-@kFY+R_>?b6AERnuRF#eiT{C{VU-FlhU=M!U zNFwK*drPqgOD@xo^mV>$?7>?X6>%5hU`yhW@uk0&$twXHp47^ImG|r62f~+Kd+>Q` zR>tYRkcQnwY{4IJmyp;%7H}q{pO&kz0T6y;jeoB8glQBm0#C4YD?3yxbR>=M9mM5u z?# z?&bMpUSZ*}sRQl2-q7Fh+!@YyD>96nRrW*4OXa{mUCEW{mIwM!iJtr5kPWp7j-6N9 zDV~CUOFMW9?cj%%=(%n??6|#hrF>RA#RyCh`|X>GC#G8o=}*<3;hd%Dsmj*tu1P1R zwiOzw)`2b24?lF*5zg+vjz?I-39*$z*Xx}5v+hgF#n2pSC-bM>zgsR|ql-=08=B8N zK0qfs4!wV$`y4-ncOIe+Q9HhrO7{KmD5)`!9y$k|eSuu}m&K-IB&hti%8K%?O^eFA zghouz(wiqh3kuMY#Ro7Jn_nyXkHG1$>QAdbO3kiwtoe`{N5+$JN7R2XkGmRyn+r#| z3-m?ko$%3e<{);`z0fb}mW1Ab=Jvoxj{x6s@Xw-jja1fG!1_+I7G;+P);$ia z!P9;|fqr&rU@h$g*0lQ*+NHhNrIVv8PT*^XVhP0_qT z(L>|}1+@_>H_a+7BF5gfNrMl{CXKzAb9{w06JJXec;5nyquBSv=6sm=J+b?v>#8<# zzRvQ$-0`gbhaZZZDrdfvy)IzP3}7vLwTClF4cpK;_R-zLi|w6z22J?oRqUJW*$MWn zhG)f|9A@9f@b2RJYt+cQm3I5tp9XAfJ7_0+Dts~(e0sUu#;I`w_Ot+;vhl(L9(buw zkQ(aXS?O2yYa#U>HqdYPLfd})KJVmyHbJ{DX+J)kH9dS98h3VKpvk5CVm}r>aK0$e zy&C=`JflEt$oLwCU%2f|;3l!v$EkBa2_42CV?&;T4f$j$ensfi-7^A><<;pqMU&_G z#fCf%8}d)TysWV(#&-MzbeT@`&k9FkH_pJXw~yBe==M7D*p#gpn(f+(*EzQ0G4KiU z%9O2mq1cKwY{i+_in$-A3tO?&@savHN4dxC6uMCd{49ZW?YR<}o&9~nFoI7SX7GM& zL+8x2`lQd2w>e7der1BTVJ7EX&UM>ht@{JhTX>jtoCar3X4rP*cgchOK6d0TYU|aR zgQKfPYGrlUk;MiqXG`qJQDC}Er&c|mtEpYHTG^4u71Fo2T8~$Dc5KH*nkQm0b`N%@`J;l$X6yk*@0y;%Tyto2H})$pHsf_> zT5$1bTYfFkhg-K$Ys9bNQ-cS-ip}^KxuYJeDz9aJ}*L@M0%WKR<>?fy~PdR+`pfjH^b9qC}Csp|>cQBuZ^rw}t zvK+gUv~y`C?U)bkn9B}cZo7l%#IC(YKKJRGDZRSd0qTgz{zaINyNCC%-?vg{W1gO5 z+lx7qe&8xNAo4-@B5<9>UaZ6B8$ym1oof}a3PDqzf?o+vM;!Uvtb;&tV_F~p^3Yg0{p9J2r|DO!reR|zNXaCv93w*;euf2Lwq=+*EuBA_7jcu$? z$?ec=nX`)vuVE*Cod4Iu7Y+@U8mAs*=aRiofo|_`==LeyUjIJMe+%?W5njs9o0PKXR~A3CAbYLcAnwp?m=K(ohG6*abT zS5lARQFf&sbijkWq-Ill4sjmEZr!Ha&?jc2SIk1cxD`Dkh+U~PjNZ{}R}#HV>{PB@ z343FDTL_&K-luF!*prGR9~U|*wMUhWX_VG2baD!NevW-Sj~pZSPJEZM;?8R!bmpL$ z6OByVh-{rzD7;a8(nrYWG0`D!fHywGtK7)GsLiPjnc8Cpx>xc-r)>7b*owsFbQm7v z!4@O*?yzffV$P!&XDt1GotN00YT)}d*qiq|=VqYI=_I%=I8cpTvIo1<-zj>QO$|)j z?o=1!Mvi#Zrh@Zu?_vw8ZFcY)+dvo8NQjpKjXyj-g-od`iEu^WBQw={ki2 z+zG_K2rRL^^xCCUO&bS1=$bALWMg|2`XudK9H5=T0r++=4oEwx!Qk=(`P_#C*}btV z*%|n-%uHo>f@X*0?3@I*xAQJ%XyJJH7kDc;+s597wXEO?!;5U;QMJd~LdYR{R&YDE zry3`B+|PNac5F}T-n8H2LpIVCojC!WSqGgdUt~=#<|TY}8vXtTn$ko4Pm!;mg!fS& zq+aUliEU{U^7Rp9#P^&wT>4h?)`M^P*NM=*n`G~7g*DA-na1n$JlR@Kc)R< zqSCWhVNo_bIi5rZu;dEoe*%)KPsl9B&>5J=g_4La&oXa+x%Ql?L zHk|)XHk?@6A8W^11)myd$BAwGDQr1i*m5?;*m7!r`p{)tj>H7^v*l3N?7!5OlYjSR zTh3)$&ShH;I>%*O&ShK9N$hHuZ8?{1IhSoYmu)#>pZmYXmQzWsl*_i9%kek~*rG4T z<6Mr%!H0P{9tR)D|Acs)Sla(_ww&0umu)$J;<%j4wjAa6tii8OybZqZ%W*lE<8u1^ z-l}H9<+z;7aXC&5%H_D6%W*lE<8m&?<@}e&<-8_&RmsFVrV#g-O8jFQaggccRSgLr zB^LY;b+g8hUsX&Et~JCA<&w`?PE4D`h~vAxh;A9I^6ih&75Qj{bUyqjzd+CGvTmCU-Tb z1ugQZ=ILImbY;jli+jzbPlLF_)70wNKu%jB@p{2WGjhfj%=4e({v(UL{&ADP>gSHo zIS29%_@u7Z49jXfOO37P$-#P;yP02fYHRH-9UB_!+|9gf)EmAyp5LjZTB&BLx|_M1 zx?0Z@M>@+YYNRfR%A=CpH_0!)ofy-HM>YFKFbC7AoAo2|RHa=DadbWU718%6{D3&+ z!G%u(v(e=2{WE!^C3>8?%Pi64eo^v%D)hwW_5|&fQg}isv3sS&B}#p0$yaP&D0K}I zl}t(drftZS0*iWL%e=(X$sI)v>1%6u>RBp|jy6@asZwo-sh0bU`r7ox(d~5Nd}&Yq zW^(;f@|B4Fv>J%3uGixtv&bn-)CLD->~<~LoA}IHAN3WY8t)6ASzlX7Jo7yIkb9Y< zTBgc3`6c-#kwr@;ml8WnEVYVVM~6W{WuaY+}tqW=?3So~YueB~~rPs_6Pwc|}(#_;bps>k$Ela=&#c zaoX?Aqh=B@X!5+5=O5x(>0=4+?`;c)Wo-)?<7L_$8D*+ELT7ldWIjvRqfxEJca~B|sO#y{u#4|fPsqjHFVc^T)04^ZC?v;Yo>M1i8qar=lY0&KG1otq zkyG@5o!@ac`5m|4y}Yrg;H&;p@RdBKAwSP=_H~foF}h&7|0(KkoEoQfru^yUgM1f$lF92g2mcMdo}a z(6qux3ASfx-KBqTk5|uFC(XjbW<5PP4_&2MPY#x@+~7M|l;Up>2AXCtr(^e1f0{HB*320bSV{i;ay>B^ybxZQ2-DKVQv9vt;gF#EL)9oKA5jTs$~Lu7kkmcJ?+1d;{Q=8(&Lf`IODwfZqg*(LSf4z5FSIEZ)~|8r*LG*^+3=j`V8M@^&`oAG zHTc*IVu!Ge}^2Lu*gHPyQ9OKk8j_?yLwKOF2j-B&L@NJF@Q}Fhf!)p%4 zz?3tfVA>o5)7OF3L1%vjp0rW$Y>t7a-NxQuiGM#xy#% zHAUpi3f*j;(*NAfqaMpN@}(tbTjnEu$b8zb6?`pBET!hk{?fwGZfdQx=W_m`C-=ew z+Tml-QGxE8$&-1VIw{r0U~2(;wo})-r6y4~XHRm+2lJ8`euBd{tOnoejKR_Ge)ik7 z=go}5ztnx9@0nSlzoXu1iO$_5z@P&9;^igttr$RK?$EPBJ%$$D3g7=MW1MFUk$2{q zHjUxgrXVjlucgR37B8tWa#zOM+IIF)+R3@5T@~#V9+o2SSiIbJ^I~!b+Cyi2 z^~)6==2&;pXQ`PTap%)UpQTzx&@!^TJyOen+7!)vj{)+t%Ha)#SBprfmm# zK7xDldygZViVPlgWYc!=qllcD2}45En_62-O)a%gw5g(v+NXA8(<1U^`r7pIgC0jV zr9JzUT3@O5N$PfnYTNY@5y5e(p|O>`t3?Hxe~s|)sUua*p~HFP`#3d+&I6|&;0g{m zo#XS#iMEW_!Mpo=7rf-?XS7LcfpHN!OcWia^igk4lsZ6?e_M)t){({8n83YHcUCl> z{Wkl+l~prGsCt2KrEnfPrT`0Xpu6nRz;`%j-QNaw?IqAHk#X+Ox_`hqNi>r}xAJ|b zo)Z1(gr{n`!?sY`e2F>>e+3@>mNS!R_BSaR+JZyqw?+s?f5$w{BWXOTN!Z}DID?$`2)KESS^yDX^%(G#Jh|PRl^=lb4?6h%m~lljb?aV< zIQaf$bO43JB3G*Z((Bzhtz;g*1DD?)M~)P*a&cVFX$5c!I^YFOngW!qgRr*|XW>H9RzFpybqE$eRSE)bd z?uo^o2<<_Jua7{7gm$=ac5!d|iJr1Wz}d_CBIM>iNSkW%mZkycXW6%hoxQuu%xJ!j zy$i&$73W~zEiR*d{0bWiBdNa_R? zIsG87tG?Dkhi;?KSlT%KH$7#$RNhk@_LUUol*-)Fm}5F~9fEx&BYY{l%5L(Wj**{# z6?Ewsbg5;qH~cLvDd^f){+F_^WLd4hO9~%ah+PHUOzz9P9=pmlj$I`KeR2}(es2f& z$#;0diR|A;+g7rykr&uj;xB4jSvu0Tt=x}orAygXvaRkI+sft`+se};-|)4ddsbsx z=`d{D%6Z4OvP9Wdd>7bOHjeDGtsK7JuEqN+cubGxvtD!TD<`q9998y}xX>hQ5(lUe zQNsE0vzN{2Vj<)ukv))4`_Y(O;Bb^&#hse2^cmVVVT)1pu^xFh0uPfMb6YnUruY|a zs%WG5S3UA>1RmDcrjLK^bn1)I9@-e!Yg-Zhp`QBmv#VvfF|wMk z@1?c$bSOYUfaq-$F{=z$Jtg6BiFh3{@#uNI6E`! z;&2(~-No%|FJfOg3*TvBPanQ9&{$d>pHnnxp8q8EEQF7L6+XW7?#KGsS;Y3yj@{w| z>>rb{wLA?S?!dOuVT`akCO+-!ppM2k>Sm1R{CG!3e5Z}XU?*~ZuHm%~VQZO)t%dbe zRyeknaoAc;V{6f|wRB=@nQ!R9_M`pnEQOkFXDQTNJ4?D>=&Q1`ke{yXEaZ@0_YjmVl(S5+Hi*LBV%mv|bsmE0t)^Hq4F==EY-IZMAm$F3r|8tvw!es-1D z7AL-^lso^a3 zIH={JWcI;YeDMAl8_A+s=u+G>FZL0+yA2y;=2`3`_i35X4{X~)KbWJ%OXvrBML+s+ zjJ+zQ27=f}#IDy4eJXKeEuL#awo$aC5IHLXJrO;33bIy->Ch7B2yGNCDMZGKKu`MG z^wE+MM`xi=Xvy&U`;?p&Z%skYk~tJoYft7O_7REMmbo-z8*%5d7P&ljE+Li6ZqKC| z{vqv@+=px)J0A!Xx$N$o<9p@45;NYG`;=_ne?C_+7kBSAA)9@GT)12lds9wyuh{00 zV@jdPmLtbjB=v{sZ1y^0W`_O+K3al4vx+^af(E_r*iT;9hBs3)sGmOX?!#)mYkA za=q+zJ9{K>kn=^me)}C{zuk7S$6|XFTbaAxv{(CmkJ@kDIt*{K_d6@%&Z&)l>$Q|% zM9=V6GtRv$pYSy!W4Sc1VwP>EC}geGj-4V8eD9{la_)_BocHGb^<(dATE3EI0M$>%l22M&_4w%09YlOyKV2e*%Z3Ke11U4j_FBp1Ez^ z{n)~J-$Gkgx9?ycCm(cle%{^R1&`v<`3r?ke~ivQAN?m+8)4Pt4AA#e1Ko2(-$#dE z#av!PkGL8<9H{4?F}1>fGBZNgp&yA|ERpkCD)tKaj_CNA==gPpuh&)Aqgyx8zFM3v;!*_(%hrmbK$F<<0OY6Vy==jo3aFKRZw6kdsc<9o4X;)_byH79iKCLi2Z+G(PUr0b6PRroLay~Y!w5~ zDed~5Qw#XG?JjmsX@7}x>Qj1sCVKB$)))k5=TSrYC+PKc(36>>*MmD1==Iys}(C3!`W6{Z@v~|z6;MGdb z_1B%V{hXmUPa424q_4l@Sy!i*=iPJcp5fQf>%UJMRhJ1}psy~|?dZ#M(VOR>KYsx| z`Zjd?+0lR`bY#lqtIy5*jeC~d9 z?5DM+&FI)iGqmn&+**H@reYYlA7aS0=nC(0e~cI07CjsNfLhF%^_Hd$h-1)*W5}^~ zqiY``j$su(k5XjK;G@|aW8xTU-8hC9oj8UcR#=VQ=-Mw)SML18XL{opPQz<59DRE{ zI!g!ob|rqn5+l_TTCvPT=Uy9l_&NZ6O!-Hv=$=nPTOVbQq{U^70b+j0_Zs%WEVRWtf_lviJyJ~{cIV>6~b z=P{*znc|yrr&XvH-*#j+bHtw?M5c%~Ybo9e;usFX{}=N9-glm=Ev06;(Bxg5qlh+K z>D(Q_v;!O1w(_NuOJiah?la(z*dRQdsWZ&^IOECv3)DF5jcJHC?KYx!pXUEA?AYpm zSMO%LRP^SE85jB{=j@=Pcc0_@e8V|Ie~T>oIgxi`WiI5*{hW_&JTH3pGM<$_ME^d= ze*)X-jPVj}&NzB^7w@^uM|AIYqkl|8k736&9R5vdSnxFVEV%eAe!^H>S26R*!bdZ> zM`?f_eg|<5XF0c`hd+2@pliWn*&7QVn&(Gf3;y|)&9w~=Khmp%V>=(xEOOrrt?6{E z4vrpsI+uGnOzs<)h#r1_e?9yG`l>{yclGe=(8K4MT4iY#F%NZi%)@Qy;fHl^JbuNf|Nba5H;6yp^VTO=_rg~(8)E1&V5DjMP!da88sjo`e*JYWwv&6)3?fRHvqpn9r5q-RpzHNQnuom)ryE(zp$5ZS$2l)+tQp(*? zLC#dQo?#t^2j2{Sw(Ie|x_B8f$J#3-&cUw7kN>v&8@#rmi+f{q@iGs5#?Ir&06n~fx>xh_Z&$Sxg!Z|5xPc5Obul-XnZe3l9UQym>^>b_M^}H) ziBYIjI-?m4Lr)5!C9V#hI6wy%z1JP%2skf(-Ls;LL$`W$F_DR4bunn1_?-IbVj>U4 z>SB&x%5Kv~V}$>U&x1KBp6~jlY`NTwoCRlM=QNEPv%8s7C;KdV`P!5|y_`P!>E*7! zOl$;eJ{`QZ^rlJEL0rr*9p?`FA?+u1sv$-gS77bkQZhUf2I{j2akKBD|K`wuR--Tx$ zg%>G3e6Z-@>AiaR0%XT;pohQh=;3c0Lly07cJ%PCqlX`Y<_L{@ADgPs9(PSE!7tlx z6R+;yvh7mXm%wYW-L%K|mpk*pA4 z_e}-s%#gsk#Ni*E__HRUx4S&#W%ex{9um86N@u5?t+U(DyKrbhXa6Pr_Ui02M83Cm z_9*;t>CD2=y;q~3Uv0+@D7_uIPHX^o6Fcw?FaOoyZ*p|^gE}!@$m1VCSN{^Yi>x8~ z`>&x(-^VsNQgnBb*K@J2+;7*!+|M4keXK@*@6pWYx$&!OJCT!~1!u0+hJ_wBhJ7Oa zeU45$M}LGkz`rqbPI zKfcL+R8&+V_gD7n@B7isMYf#DSw8?CA7>utkU?DieH`;lMi!7Z<;XCnsZrWO8&`i9 z{W4-8IN`4g0&7>sH_%x=R)1%mg0r%h?mAnY7z61uM4zI=OTU7*hl#my+Y1ay3H!S<)S)! zYV;oVp;s@4&bjo-)r-Zhh@ZKiZY;I-J1?T+3*U&<@m;<#P{$X(5v$|7d?QxJmpp0F z_xt$9h+e+YtKS1>Tfa_?9{NA*y$f7b*OmCc&%Hb@_wrO!L?a-Y0*a4R6PZH2fEomY zFVduG8$dMSrT8GJk0_wFMXn}ZYtsy7Dw>!IhE^M$pUh+?=F!whYBWjHw7*QnM_Lr4 zlG+4}hTnIcbFN+xlC;0+eER?Wb3Yu`+3&U2UTeSBUi;9`3a)b3XB+!5S6RPtM#kii zurIc5hd(_1fhXVmYn)G{&-p~}lUcWW$tU#fCZC>ldr$c!UN`yltlRsNPvZ9~pHjaB z&S8j7K8!VP0`)A>$4;f|1%Of-_tY5$W{lobU;5X3x4KjZb zJ$~H^=*4lpOdsFRC^W2J|L_3=2Sp5y95VElVNuc1F)_o3$HtD}HSz9X!SnCw)sr7RWxXPeZ?J^NONXYvV(uqDVUKRV*Nt>))-a`_G@YHgg%2HW zIQvRJ!VK@|D}AbwJ`}|HF14_0x8LYA!`WB<6Fw(A`E$ajd`|cbGrUd++Y5cpryKa? z5Duws!rwX72!B0LhbA=*KlvXx$9TVL*Wpd!w(l6>qaD46PbmEC@VsS4_=thMr(eIp z2tO3od-#-XM)>P}goieMcKCb$X@uwXkv{pD86Kj;o9h4M2_t-eAK|k&;3&Vo!b6Q< zrLCN!wB^IJ(7-6gnL8E1bEeDqd1OF+J9xA96YXCtKeTT3`H}DAen0kq!uHPrwf1d} zCynM)YAES)cy^mjp77yFpEHC+TOP?6^%QN~D zNq6Y;(oG@VK{K7%&wQbDkC3kNuS~apiav-WW~3=akC5mDQr*N!rhA;}jsqs(uYjYJ zD!rPj!|z-MO1kHP@xcA2dxjBSnqk22L;xlIdrl+$y9WGD>DQHFD4J1dz?6;@cnSAq z1Ew7Mnm$g?c;0|1Pnzy0O!q+)qmq8V0aGs1*)p!rxMaYT*MZDc6YKbhw&eSy0aF&= zt`uhnW|SH*Wj;{C^9-0$KG6tYY`~OPU|7lbkO5Oh0Hxe$1Ey4S&_VKBZ@`pCfRf*G z1EwU8H}Kdv&d8_QfGJaee3NvB(|{@S$0`+sJI{bAvw-wDi5Wn~gc(x|m=XwNh@N3H zV9H($1UN&G*g@ntpe@#D*Sz5dy}xb1??eNoy&{0p9*1L$@?J4u%6_1f_bgD#+ik#< z#Xu>q)PN}wKq+s3w2}W_1AeCzDD^HhV2X@KLZ=x9OnG*Q5x(1iDO-U;*Ubh@c{|dG zf6Ra>w!ucYj{#F!7)Yf(TL&8SK4ZX?DW=%1iN=rW@{(A;YIR+FwUpHXN zE2jI9=`J+g^G$b->7He}BY=W;xB*jInQusbXAGF~E>ObXHegCAQ1Gq}HQIZ<0aGez z3~8^u$S!}0HvWlx`55p!hY?1R$ETIP_M1^>z?6G|kSKT-5Yhx^03l7V6F3M6+bc*B z3|pxIK*~^%A~+h@A4psU>cOC)KrI;D74Kl9R0y!eqL=4)$b@w!tTbVs37sa4HlZ?M zi)N-bp^T&a`Bj=Q&xB4BMw?KXumwDL^*dz3Iull!FwcZe6God5pr@;Zez4pyfkLMY z(=AZw6>7Q#3f{Megff};KQa{ASeg#Hr)cb&n>vxbPJ5ZU1+)mj=-H^x&=b3 z;0V(#Fc$Z_&`qyD5GDvdXu1Us$GzNi3#7`y_nK~joG}lUd6}dacq?vyOP%(#=3`xN ztF~>l`CHbqLu%LM!quA-}3s%WBh+_ zWDEbV##P4SKl+u?#7S&TB%j-=ZzI*jw&eHG{VlD|^G+Y@ z-qii62UGnmucRMNCp`UG{+~~~l;&f7Z%WIQGgJI6E%%?fpYZz+^S}E3jq*Q#V?HJ2 zy^`l|>6m|YJ|!)vUO+7hpDx6|=#?UW%cbJW#iT7gQ%ZiNm-zo=SzQ_aOD-?*x9orD z;6whFC%*gScl|BTfAmG)kh)=~vY4zJZ-U#lHk%j?y3TIG6e zJrmY7(x|kB=eJ(m@Pbd5;gj3Ly}*p`SufXUckFSs{n1-698JR9M!#(TAd6T@lxNRnWSHulYb!K_n%`5$JuXT3SwbbHp9@0;;G>q%^l_4JP~Ne}nzH(WErjo3;Z$IajM zpR8%T-9PH#4(#Ec1U}yJf8E2aa_^g)GbOj6Y~kXi3zjWQD=jN1$(>hTwz$0SklY2! z%cm3-^cCZw((-MN@1+e(8c~OBXCzHfCP_ zvIR3{JRtAt^Wx2%mfmZ`WeZ9d6qKix-?^YXwY2oE{PKdr1R(V;lKVh@QR!!pU_SNNvz}X?U%sG^hz2EkjS7Kh%wL$h zcxhPyWY=>_&tEjZX#S1dAk2dDxeJ!eEiYT@DS>3U)Fhi0(9@(Ynt%6VNpmA!#`L*4 z(D$K5<+;m>7A`I&X~x~t^i=cGXozV`%NC~R&Be}Qt5x%HQ*sRas?u3}pQx8L;d9FBE8HxruizeKP0-8w>gSZzSGZYLU%@@f@0%$YteS7yMBZ{x?_HYxhXZ~X0d+&(cW z>5fSgqN68{yMut+#!2Y-apP{EfZzD9PMUa|9zD;#a#{KOh2_`#vM~i?%C7gDZ(RA{ zQ`Sj(S1AAJ@|DctR5TAi=9;S7yQ9>7Z~V)PH{PW`m7mAW+CkP2GS}08xw+-Yrs)9W z=H^VjH|K`i^X4NHeSVUkWcdV(iEoRCkE#^l`O#G_D_>f)=t0w?XMUc1Ote%oi$(9B z$KT^;cD)|wP59YNyqGKNKPUP1%;S1uUL?PTi}R7vhZcReXwm%K+!<56!=ugooF;AW zB|nB-wS0`e?vtq9vcC4JXUo}hI{5);L zdS6dFT=)HJ|5d(u$eR1kp@C|DSGwi+*;t>29l5^P+WKL0>(3okw)$&UiyK?^23uWe zQlV0J-wzvT2wdh;0Ke*bpztGOTlksWsVq3ZvJGJ;2&guKAFvkGyDGsx{ z)JSXne{rU{Vohj$MUCo=qaKB)>`i$uy_6!j7uHPehI={raq-0dAI($l>VD3q!1zSm>PkAtRq5CzCTu;vha@twVZ*JvvqAr@TjLKhyw~COxZr(6cxH z7Hn^YKG#-dz^`}0x6`l@pIU!5cFIEdnX(Q3&I?uhua^BAw&NP%7UEfn?*o7P)?Z84 zbpB=)pyP2FUN55TxG>t9seaS|wd5t#AJ{d%Dec0#JX|1a%y7G}b4!%3^FtRrRIG8I zBKJ3s%Gcb&@8_-d1`W8dN*&+1$vI;_k8^!XcGdcm*(I9h-WaCZV`$50{M5nr`v<58 ze&Qf~JUkkIQn?d+Q#r;U)xLvx1F;1yg!d!humaGi<&S9e1`16 zh8sK5ydOAqo*9G<3$~^0Riurm__u9w{%ZE%_SJ6{!5>n$-SDy0Z72LDb&KR-?Hn$7 z^&0=vu89)ozq{U%z0~_YE%JK3hfwc4lSc}-I6I511G!gWfGZO_jZAGY&mh9P@J3V1%ZHNl)~-u#u^vjvdueU*wN>eqlg51n znto2=YT3Wv#AgA^r*_(_#YQ*4Wgn^P9gj@ybR=r^f$wHE1+Mmq4{CKbJ*x#e!rsno z+RXjsMckVibS$$enC~t`*=ZwP{Z={WjMag?d04}A>1)9W{N{*F9mDFIod15)kH@ky zs^3_P#o-T+{Ml;Z{uW0AV@Y`A*ZjBLx82#91dQPM+E<;OLwV$m7ZN<)RJv_u+0<+SG{3$SCX3wwMuBk>k0mXBoI1q8}D{xShWFV{2GK zlQyX4pf=EcJ7o;|+E2IrZ~E^mJQXLjeGh+idu=Z89$>|3Z6D)F&P3uJ_z8C_;y%lr z1j1v#rGMs*OIJPp{(5Y1O0aQ>X84P;SV~fbU!kE7^kdVh>E<^6rQXJ&)SnSHJZ)0w zwsh`m$keQ^G-Nzpvyi7oy~13%*cVEjxSKg_yB1P{-YF-H@=sbr5_W1@O)6<)X@iRQ zw0-&Gz_~@+S52JR_>TwYv)~y(o7rf`$(pSs-)JX&Ol5q0(@q;s*8*zn;S zwL$Q01mm)wyAwOskFklpZ1v%u0W0^ySg`lg_>O{V`1cvkrpP+wwv}TOZne?nsGQaW zXD4?T9JhD9&~LJ4Z%gBD8=F=)=f9v+9^?8+?9PPG>*lD5m+8ODvFVgP$0qc#*h-49 zd&{FOAEN(np|7r!zB;5bMIZZO>F<#xNni1%y{Wq2q5EuMYR+_cbVAJGNU3Axn%&#_ zN>evR?a%Y$J`F$V=j=^au^E1ZemFT!?SE)h)ZF@D6;|D!F_N)=Cug$GhNz|v?D0S1 zj=Ze}%3Znc#pF8c6UiF}G0(6L?%05xF?Z2)%6KjJr#}vEhZwIbO`P@d#S7;M?C|yc zt;TrWTf7$T+LHS1hWBkBJn|NE6nCBV-DI5x)^B#SdgX`CV}EN1{mCPic!{GbtCDvU z#}=+STah!HkLt{`tIm(OPpAW1#z*MSug+2XtNo>pLrOBKqtveh8_lih)X)D=ieA6| zM*X_Y6`txbSMc|mEA&!dPdy456Ktzj|4_Zi9rb(^KNJx=>c+ezQ@ zgyiktciZXnBl4B`k<7I|tL^kUe@@%A{FB;0q?dLZC~ekHbzc5fYQ5C0g?pH^+bZXr zGUetfZPQc6tnV`ig;oJ-_fmMbtw7{m?Vi(`pgIdrAzJ}zV$tt2n~LAb#Qr>>L~LzK zkhwzSED)KwOLNpT#sqHrndat!N-`F2PDMD{QG~)yu8KF*?7u+ zC?(aU8lB7^TfU##ciyX;3e>eTa8F*`#)pEQAIXj7bGv3cR4etss=RHbZTYNjC6OiTmoJ}W( zIrTn=I!=h^o}5XVEh{R3`$uRq8|lu@$lC!Tila=S9q|MHGoZv$nxZqN1y#Q$%lTT#lLdPGEIdqr<|VgQjw+0Yn`%+-)iF? zlBm1Y61m5r&{8=^`t6s&uNB;+&)C6xi`}S;cYF&tOMICF-W0w4XI4F3aR|?nh;ZF9b*=k7|e>Y_Z_rU2C@=}ez?Q`lTKh;08b1B7@B#dm z$~RMNZSyF*ko;P#wgy>C3e78_OJR-K9uhvr*(7C4o)&Pl(dR9H=C1n5nrfdeH1XX~ zTl_HhQ_y~yzuX>audr{6V*IvM)zl`%L~N6Jag;W=Z7prt5IA z0Ig+CUAAq==;fczi60#EcJ1SqfUHM;lG^mp&eWzjXc`&wPHhWgW1aQ!g#{|4)TtjXHIk~G@Xlct`$>dm}_ zpZd-(QV(Mt(~#Fs)A76f+f?oXR{Ib0tlH`CKIv(5$~(z8CV5pvTAF^tohhx1Z9-SM zzu-r#wThUp$lcnN;8J)+=Fqn0VoO?6v7e=>$YSB1oq*;N=rT&PXI0#ymh7hfMTDKR z1~k}d#|rpK`hj$LXZ)0RC+~%ya!-q28hNBOJ!Dm#_S6K|3)}@kf0%RD8jxUTe33ix z>yeS&7F)B7pK&i%M;4NI3uCU_Eqcm2fIBR>?_T(!z}Zv?k03v+i^%`O)F#qp6;iJ} ze`jOfGiy`w8`h>2GENuX=Zh>>zFD}_&wX;;6!w~sl~aE17S~kceYn5dB7XDy-7R+< zO3}l2`npf8OEcs8x?3JgH{<%b`3@X${oIR#sZ_b{jb26Ownnd9fMKAp?n#T@o7{PeY?%=`5DAZ>AdU+Ag7ZLU`lY|#A_YZhB6 zbA)Oh=&7^~wZ?iw_WvxbH>}{|15P*Z1KQq`y*ITaFT%O9PGpWXhpaLG!kR<&O>C@V zTa%d2G&t$I>}?y8=y%t$x5fTT3F`~FPb7go4i|mPh8npCD4>~rx;E|(t95;!?{19d zZcgIlRyo_(jpnWd3+-Q}+GBaId0=X%Yu?n((v2J6eD?Ccez=i)`%1aDFY(&!w>E{U z19z#qIgR9ZVeF9GCTqcMwvmeaN`qW)1l>RBn6ESGVrt;F(AEF)=H5qleR!$=>mTkt zb?U>5e82BlfDv{vUb&A2dA)~uy$|quAMEvhi`RRM*ZWAX_js@OM6dVpUhfmV-jlrE zQ@q|&z22vKy-)Xgzsu`=w%7aJUhm)ZdjF2sd%oBE0 z%Y0Y%2;?rQ>ig%^+q6N={|rAxG5(zML#At4NJnP_m&7I3U$3{Q&@9d#Q((XOm^(ET%iASBCKVIYPyiZg6C-Ds8 z`Gj#Y!#ad-lJd^p{X1o2&!w`C=Df&~clgG^$J_ypzQ%RR9A^ueFCllXLgr@BS%0r) zPS|Bp?H$l@rWc;OE$a5Fl}_fr5scNwIQ_&*)%j!QmK$i(IP&>7+LC(hU%~S&o=n;_ zSF^R9rQUY-BXem}Xd~2by9vL&`hn2;?a)i+ zc`{Ze@tqinGl_Xi(N1Um3HDVj+Vm^Gy%n6#@!ef$T&$^y8}xoIK)-7qS}*Y#?7Xsr z@|Ytp$>)yN4S&dNiVso~UG%LR=~Ko3EcM<*f7{6Za)lOD5{Eqev*xfL0smV1+)Bn} zk;&mDwrv`7+!$r-g zlu}u>K3V(|@q6~9=6+$ds;^x8QgYSWmy_47eIDG>U&a4d z$?Mi0PJXIt6X6>PcM)Dg_-lk8A-tLJ*OS+7@@d>?@p12{dW`T#lQ)4EYwU@xEwnXd z27}9(ieSc#VAtk{r*?K-v(`%-cQ$JcXeI6a0&|xZ=y8&Fq0K4Y+g|KfzxnH_^+Jm* zYp`o^kfo!nDyrjA);Ev6d(s@SADAAOu}p=dVO zqwwwJ-Qbj?dvcP@3`@CSQSP=K1ydb#UBG_;d-NA)??w1M`hp?$uo1=tS~ zUJahIf1Jj?e>HgMd&RNr6|)~)BKyV6k@B%c7XLW>9rTel_JV}(>dgLR1M5xlXbY&? zP@4%(=o*@9ZVjoowRau(;8NFwRit^CJ+;C?=sDQGU_6~&I7l@W&W&>^-}L&Z)z(S-z z_qe03`63o-D=O{q?b)g+mpQ4d2|VG-7ukR&GUivqcirgqQP)Io8ZC!DQRoz20@sz) zaTRpQMOU$d?~Mddetc}ew)oW4rg-?dlC`6hE9pI93FwVSk%zNr)WHbu1~2A*!UW2n zoT@cV1wWCc$)Za}zGXiyQM2E)9~VoQ$Vi12pwCZS?8mW3FlPYqWsgnvR4CWbk3&m+ zAI?iZDscp-*VYX9tM}pby3h~FKHO{U!wFw`_TfHa4HX}ApxZv2?0bI^{rGnDHyi(9V14Q}UR{+o^vtdlfcq zsLKYOHGK>*c%%V54BUshB(F^FYhbL%Nvbp4EJ z`@QQTdpz+mH|zR2C($R}_3|k%z9PV&JAH=u>1Pz+fekCCveu1sZCL5sxnYgGN4Peu z;T`$a`$TDHaF5`dC1)vL`fld5ZLMEnY(e)L@PqBOoa+d$utv7U!6V-D-lvFPP2bLX zb)xj|$mD)c|L@5|_JpG7v!cVEsO_oazVhG{){E(^7t?fIx2)l+>HnpzB#q2>q>t<9 zUtQaVe08mpd(uN$+l8^V>&LpYKkLtMbV&oyf%_Ud@XB8&+gdcYR{46erLsBsl)q1- zR(XW?qnzvTZM0NPX*{)#vl^9eB)5FEGDTJXim=0kabBbHwd59mYojd?*$Qwr)%!C4 zp^ssYq{;T{cvm=QLcTkYdt9OKK-ps$=xoX``yuQon$FINb4`p%ub)gmBz@01=o7$Q z#O#6ScjmXOHTogaUGIllSo@1S_a0{O*57(PA~mLRs5-aR0`Yt^>065WR#azx}>y|HH$ao$oN7 ze|I=(pECAO4#Bet;qZ-y5OVIoj-F zN#|8(qh7s-8T-~BnRWiy`b6zV;Pmhxto0t8UWBd(lsX`1SX$|G3;m4mycQON8+#{R z+_{|r-?cu)x7i<4O$!*~ehF_rSS#NO*3gTGi(Xv+4vXl;(LL+B@hEhpJNZuSBaw_T zOYApD{8r>e!m8F(&XK)(X@4pECGLyOubEQs!7MXp0=P@Rr{ywdtMQ3bt1O~#-3NrTh68pqax#_ z-*d$VUf=IJ3{5xumOIwj?~;9|>Id217?a0gubs;sNLAtswNLP zGb4H>(LtOs_voDLT~&T-ZAw+{+LR3TuBu=5>8xgtj&dh7(Y}?ZW7*G>`L(f+TMeD{ zv2$)f(<$_eYl(A``gqO=2^|ZWPZo?Z{7y1%%iLVf5$Nmsy^YbNVf4e=BJ@h8_s1l|#M=H9F`#GmOw@jYy z{4gFIMg%yo1d#U#z8TnEWkA&-dd0S z?E&m@dL3sxebj!*FPQvxIpgD3JAWv79)WL|YlPXQj|>W+k5sT8x)SqTm_et3E~$GF zw7RzHZu;yT`tG^(;dAKAXEO%Os;6()_Y0-2oc)x!##X%#5SgS5$Ps55G22Q4R8~F@Rk1icA##Da8v#r7)i-b2*&TG^nqqT3kON) zr*2ArKE_BGflWnk^}F;H-A*gHju`fLh`~pp-KL$ob5~mf?o~D?stT z%0Ge2`8N)@o=S-OG85K;z(;@_K~2m7#sDV)B|HM8AzflP(2BWFr~y;TF>)J^{{*1q z*GeRb|2z;B62;Cj4j2d=3lyU?f$xPG`5yvGe9p${WS~cN02!2DjP9^=M3490kSBT_D{TIH~e2Q;VhugJsK$CR|AajV?d$Dy+HAwVEUt5 z)$^@Ju*4swvhGG9DE4}Zqmc(#An{or{_!Vvf}??=aI8_ z{KtB@ITX>rHA|e9`1kc;cn@X-}ifj|5J}}WWF0dJ9@ZJ z^l*RL!~IGR_kZ-rpZ(`<`Ll1?&CT9)H+M>p{Py*5NB7A8u^#DhcFV7(M||Bjs9^C5 z!xrVPoLo%D7Nn<_FE@=-x;qJ9R0=(k)%^z=EX<78NX* zx?Bw5@=LKMdq~`#2vX*Q3l=R{YFL_?VOY>%n_^}zgfdFY#cb_5YG$aB0XA#Pv*(ey z%AL1z!O}7#5CVbtG^xw1rDYF_8Ddcx)_7u^Mp)6JqVn{)XFwNCD=nKNgYOk_H!7|pLU;=;0rJ$7{-!>RPL zMdd|{9`bk_tYH*lq98PUps2JoBY#<;X}_lzBCPAd60Rs)IPHdqv`Q}Km<88kcnm!; z5bSBPH`U_kAI{$ikI+l0kmtt({$yE1B_wo`P zOkB;z*BP+LOyLy_vt*XdGfm>e;Ll@rChz0u7v$HcUD@ru+m(Iwjyon8c4&F_!iOHn zoxi|?O*gYL)tg<}LHZsQEu|WEWpU!}Z*Fj^YiS~oMwLPtLwiAGrzs$ zXHKEa7Ba$fa~BjchR|nu+}-W`oMndpZnFU+mKyk7T5H&+F&#Er%7}Lxfc1;sX8KJp zJ?!2Ve~&dDW4a(A@$xn(btmxHChQi$nf$KXy)mDbIkn7Fb$SaQ{k1>#gyxVYylb6> zJy9Rl(Y~yw{a9D~v%bdWZB+n!q87L86>fJ9YM;l{uH3hB%bk2T^3R-k`6FjuTG6}8 z-r~c~{_Y3sH23=s)fBLhGobAEoIb7GXZ|evI0GBBXgMF;zu|rJyq4@GMu{E411hJ9 zZ@R=qseX3$0CE%6k|H@%I5uivfHpvIuCwT z+M$%;Pef;1hR(}QT_2NO zZrS(G*PPc#A58jS(g){%PhH&@qxM}-PaWDU_U25|WRhkE-&5Sqp5F)74UuB=GGl<6 zRDz9@#GkB1==&_^sB;_pIHmU$A1qxt>LB6AIqy7M^ectLL)SWivzG}rtIZ*2(emur z2eL1tp}V17w{}ccO})|TUajK}=rYIVufopKCZqUXUcyAxRIJ$@@uX$XtX*{OMQ?J> zFD=B~)lV(i!(OG(vMpH6F16TQn^NZ{HB#1?qJ;8?~hKMn{<@0 zu|0yZnLnK|03fp+s+@y_qo~34i^M0E9Vt>wq4kK&} zVd$%N4&;3f@0{@;0SnL~T zClzlkCXexy6^8v#lqIZfXMdHmIAEYF#B!r-3x1xmk4t%!y@Pb2f=4)Ize(Arq6{3S zsY&Dg4F3Oxi9>6B+R8S*YZyV_aAl3u{{|h}p+j!EY7gN&^URkYtHpNdc-McZ_6*L~ z$hTZm!SN#cg7pN2_6@(O?m5moI!F9T` zH!pPe(7YX*w?p%>Yy7sI46fHt}A_Iw1 z_qFIq*pC<7rz2mw8(NB+p*&m*v1Om2pI;kd(Dw~5KTDJmFIST*Fe~5WBA~a%Ux7!; z(&{h5!?S3+U9GF$+SRgR%L2Y9yGw<+#>1~+MN1}q0{&kcw*0vz6@J@df^FHKfcvL> z&-O+CuQ$HvKbc4IXpMisMtqftzkH+dQ*j~%)r`*f9> z5wn*4#Hz+D)Qv+u}D`tdG4dGr_ts9 z(%0&`qG`>;zwf-dqjG!TeX(la4l@tu@B6@qcJ<&BCiqx19iJ&fcV>?RuiJROI&5Dy z`_waHEFSzUI(}8ik&fSQ4gA>P?S|hhf9*PcF9(FU7WhP7$1m2IH*5;e4rSn(Z{n$b z)0J}$y8WK9d({0f}iPs$@KTf|4;bOH2r6p{ucaC;UDsKBcD)LbQg z>%g|kBhof&KFk{|Z4=q>`-j(T$#+D$gctY1ug8NzTqFI4xIWP$n=R9v&kJ2=CfkEg zYhM{QGdVc;^eute1y4ugX9+%oU%}95>hBkP2LHAAr{PY^$(UIn5404z3VmjS<9KLU z!22V-4+mE_vK9*zS}&m=6q>~ji^$#?SoJJ2woj9}064e2(v|abksWy8dGP!LJa7fR zU+b^)z*u--EIdGa+sfbpcpEo1M&X||DL=Nfqw`7GtKz3%=rEA{}BIl z#&9^o3_t*X6UZ$uwi?%FKtpY<_`Ns_8aJx zKjeLI)WPgG)@kkz-hVsnU-7rNKjQsI!}jBEabM>B4Xf4tzQA=>_bK2XtUm7h;3@60 zA@42!%it*%x`w>NQ!7KX8$1;j;`$mq^*(*k4W6pVA-Fp(FbK^gU4@Ywbl1&^s2 zpU}VW3RU~^(UVKRRkyDzXCizi{pioGbq$jKsu?Kq5rV(eDG?|-#%rslvwq589d#$` zscEdMrlOymf_~DX>nE#yoN|ZA1cm*SoL7-G*gACW_^((Lt=bD%YZNJqE1^AiQbVws zAokXA{Z!8M!bKgK*kIlD9W~p*{G=vSxo7Yl^vR*B-Ojl9f~GBDEtGR2Mzt4n4l+BK zGn=ceb06ajT$g5Pdkwp_#-M<#2Pm(_W|Tc$Ig^TQd=GnoYA-~O%^Gua3HCi3nTswg zDxS14AjnmzMZJ?5;Y^x-%dF=z!<|X-JM6B^P+%1>5SS0N0aJn0Ek4HXDhB^d@IQwR zI+*cR@XidfX5GcW+7tII=9+^0Vdg4jLF)Kxy>1D?#=y!t+I|k41Q5+fc~xEQ=Dyh%Az>?Ahx98#O;qv823YbbPqPexv%UN zo);eM>93#P>P0?2YmcHLPPNm1IRVH-wsNk>LXPgTIaeGRtlSxz!)4QgT!q*mjt*4r zA6!d&tH1JhEw=c(0(sWPOuteD?m{E6CHp+t{aKirFf&5Ae?%GZ=8|;MzQLO2BD@|# z*zW_>@p}qac3gnpeU$)^r>t&AZ@T&`67LRtV>T9En;12)z_uj*#5jqUywZjSchCfRTJF(&T_xbShdrEgti%5uJogo@>L?*i zIe4NE4&R5&?g7sV6HoR6HXjAg$H4RDqLN7$1FWtV`m81E6LHYv9xC@8BOT;cKke6QLykUr@&^yHoz9oH=O zVcusiAnK&Hua&h~cG1Hfg{1QZU-sw1TjBE}MK@YnlX`h?B&K|20V zdEuW0ofmodc zzlnag_Q0*pwSVy);CkNAnREbqiviXFjzGROE82<#y${g8Td(PMHx*NVohu01S zsiY&6;gz@((b6yYbnan#*vj^gh z>k<4eFmC-GIsJgN5?|tc1pohu{iiF8$DcBu1(5dz{G|M0$U`gg5UFg8HU2JtpzyAY zDN(9_TgI=M16xDeY`|P=VB6u>yBKd&a};-9H8U@sY}J;00$pwFWfTUhAwTJt=2?f*PjNQZoF@tG#a^KVS{wB~tjmR`-bqq# zulbTjK6cBn=3LrE_L!rCQ$saG052t@1KLF?_uvI@{y(F?EjFqny=PG zrtuyTV$b>~(l}^Oi7Rth!S5<(z9qhsI8kbdK8IaPnurjKUN@P;S_l_edoq1^v!t2E zeN88od$b3c)Br`d*f>T<}ck`6^5VSanKwuSkv4{a#(TgrC4;{O?8 zj^4vK_vAQ)Eyyb&D(O?`D|MGPMAsB9bXvq;vNC+#|GV)w@N$Iu*V?nPy&s&TeKw~FwPW0cNb$=1#2Xsr{w<>^q2l8 zROo5x-*$;UVad3f$gRO)Iz8EwoZ}{3`lbTs_-08H2EMt_^CkMOy~u>oXBzbM;3VI+ zp9MX~Arqmu9c&H@nz;5Qa^{zU;W#PsT<>M^VVRyUOx%b-PBL&C9)uU{d+7yF6nDd(^m@Z zH$ubX_w3GFwc^N}@!&^|%)*J(cr+P|O-erJBB(_Hf11kI&CFQFf*&<3=fhvs|6 z*F^3Lbm%w*Li0U@OJCoZI;vUHEMgtj#XhmzS+xLvBu6==}gQ*Wu8)Jf=l-s;~V`W>NjJhbbrZo8yz^flOmT&J^? zou}P>#b)D9FiXZtrRH8y){K=-^{p$>z(e+F`r0i{RoDLR>ObB(?YjOrU2JMX*yjjk zZ5PH`vL9>7{^*aftFijJw@1h5x?|bz5M8X?(I)y|4tY0BywiD5VfU4%1~xu7O!doC z0~+&Gcw^p^l_?dsafb$NdgiCrX~&;lrg3-CAXCpg@VcH^ z)}|~rbDmDW@8EXTzhpXXl!p%c+NvCIoC~gVz8XPogee{IRQVk zyU%`2bp||TcW)o3mdKo=Vhnn@A8(J$#D3JJY>wvpN)DnkoxP{4YeLhy!5x3$zOu`# zT^yump0cDEefBrPei&Ip+^Oh2U$7baJN8B91ilsEZuC*dUFcv-v|z`hNver+zwQg% zSJ-IkgB^P;O`}$fJUDor!@b9P-$B;@6OgetKPWDp)KdpLx@3&VvRmxXM4qEaE9F_a zgF{^ake6sdX_IdOh8}3pEH!VqPYK%D&S+4~+B#uWP{YTOqw%8oh zG3j%a=*tB67j9AQ(q}dBK5EULZRgn=_y8M_3+OAkzo6;j$k!u(jxM&5 zdL1=&%YtLcapm5w*-D;5H!u&rIP%DzZKCUa_VSvyn#b+g7HAE0{j7cUmKU_(HnDFG zqRu~~j5qMxO!?#2?A_KvS)yxI=v1JWd;FWq&3vciCB}*!t3$WV(=?aGYH_6kHLHfc zC9rMy__Ql0!S59FM%gz|fj-%W-AG77jPp?PaOaE3)`NA6Vx~N`D2*o;T~NNWGI^e} zDmkW7xzp&IUj*mV%n@Uiv#DUjp~%?6@dsn*XVTD-iGJce!USLTWq$D(xQ<)%^KJXU zef)>3*=O);*s(gKPuya!;}iyt5e=!}l-2{MbaY1^oYKH4#>7eLBz@9w+FEm1TsxJe z`38>Ty5Z=dS*S^~0`@vvO`9#(SFrc-QDLpQ*kot}vs!0jr$wIC%cH*9x?MHd#gBa; z2l_`FeA$YwT<)|D2-DncKeg7|W-E8=DC2%S&LMREagblV^tZd$eX~Oa3~|L+2e^h? zgI#BsYfC?JD-U}@ThGMKjbHD>eSFB1l=H{fY4vCCG;Df1CObRN{n*)5H(d6@>Z zTE_?Z_r~YYhg~_#Dx5zotXT6y!Ru_g*oGPRSuqL7X#hX&q)o^KzftJsEAo{-2GA$I z*^fG&kHvOgv$;yJJ6uRx{p^D^Tbj9-*NJ}kB0OBFeZ8?#o6Mtlw8o8^#eI?aV5+{a z8Qc~P{Yp7Y^T8-*=d%HUuA2DC^)>NpcbCLS-xts(cR!qkCJoRem-8~`8T0F*-Pb6y zPukF(|24GvH)tbms!?BW`ky~*wAFKF8|2-}cvAG%;XTU#fOa{o4ahpnoIhS0;3|ecMh$RI zdKP|gX^w^x_~BV-;VMe#koq9!OHRUP7U46Kw+%k`;%|%U?6b}ON`CH1!+h#+8okX~ z>g?!G37w4x1~3{m{%RAp4=2`9StVGgE=oF*C-- zp1DznR_+WCxfR@P=x_qrOYDh%Ec+&n1A_a#prtR_DeGhEau{3H#F6Uw1?CS&*~^f6 z$-2!EX4Gr5UawHSUKg2H6_4Mn*G<-v+zT_OhVYH(a!OJYnuT^zDyVHZwzcRuCeDm` zz4qeBqqUFOLeM2TSD-6daf$uVjjXXQvOgN4LR_+Re65SD3}QqFqxf!t4?! z%n=&QolNMo|EJ=#P?pf-Ed6+f7teTfKoXY0{!Zd8H`9v^C7loc7JBVuPw6rHKNh__ zvqLNRe;K(L!(RDCc<3TLBy?(JE-1Vt>oxWk_e=Ul&EEV1 z@*(U0V%BUTBf>|r{`byb@@`<=7NvsQ3YllkvZ#p$54>@mPr~3OZ$3ec^tLOauMnMF zH~hfiUHUFN{#o!_$;dZqT{eenvB{H*O`a@)Cpp3El5$_8zl&nMxRdgylScNd;#lu) zu-Uj54jE+)oIre;n^e)q#OZn1P#LgCaE&_+@T(@S4S(h!jwomnz#1|T{jSibt{0u( zKcbGWuYR=aWjT-R%bLuOwV6L_G#hI*&LXd}*L&;w==WC4Fm-)d==ut^!0WocG3fe^ zqU(!P!LA(UnQlw4YxwYEolgE4lyfKLO=Ar>wVv|zHDC?;yvOOIMcZfLE zhOX}-{ra8o<+lqT>^Miicn^JgAbWEaw9!@Ca2EQhs9tq_i?8eYW})jFimq=Ky1pl$ zqAx<%_vAX@2w(v)8ki15*Y{*>sLNA!h^gy42an%FU;Hb09Np%GZ1`)tsq3rJf_43S z1G>H+qC@DD#~N_VHFanny}#(t7NhsS7!a!K(2k&kx|cEI2s)^U zr|hmH=%6Cj0pZVx0w6pXkq%^^CL-4E@|1fHU0;933`6f9nDs623_{m;j5d0~#Ib=s zQO*d|pzC`E9DR!)&~d!m#L<_%Ha6OC!cop9l<8*^LiDo<_DE;aRB*HpbSCYpH5A)|E|@``Q^69EcK_k^M0~raNAB`tJj{noS*v$o&9OPuh5Bp zqbByZ`d)L5&F;#gPooYiMDN&UaX2!;&#vsQz}Ww-@7Ha#1-dp7=hs%XViR!|T7w)L ziSs(^_)g~gV`G2An0@&An0?G*jM;lF#+dyEXNNB^|2h=&s=kL^X;XCX&J_zN)0er) zFO{!DF;~e`%C#=Gxwe3P?sYb+>jC0SV{CtbI3=2&qd?{}eCNO#`|ECTzHi29G~;X} zPHgP4ZgKu-#tF3;`CKH<%jnL$%VNJlFKd@F;`CQmS0(t!zPsRT1s~1@tgwR5cQvcS z0X|O6;j)8|?Av>2=^~EMa*t-va-GJ#py1<)gN?w7^Te4!oB^CA7kYn09MAru)A)H}I8mHj+mT;l+g45Wb19(X^B7ze;;PM%WwV z|3kv0E#IJRyxVXu>AEHvIz$<3KVd)mPijc>$LvQ--`M#T?xND{4&mW8(oaSgQo@)n z^SkBf5R$AB&CJRBU0{CQ`4!dqZqbU43+$&Ju=ICyvc{J9dl>KOOXmEPypyc{%?HT) zPpmKgRJfv}bGx^)y<+K3~$R2$a(rd|2Vb2mGhXbR%cV_cS7f8SkV{K zCq2d7I*NT!3+Kwa&>vKD#y;aKH*|*Bk@S=HpufuLLO)o^xv@6rc1}5(KZu|&MW1%= zr|fs^b47j}Z0y(D!As&uoBhc-s#)4ibcQ>*ONKLM6AfN^{W{5-xq{QFLt==tB>3PV@x2zf;KX5#-m- z_?$WN*VpB@*=meCB@9h6rW~=_UBSrjQQ|ym<2)hqI}`bRLzmwW$5CB=ga7VvR%s!Q zX3DxB`4!sKBft03p7MSe`8^M9whL{L-O-!{ZBT*TXj7xp#jeY*q!B&+#$mnFc8usS z|1W53f&b4TzyAod?Mb)SkY8Dk7Mpz02Hj*2rA_CJk`B%uhKr7%3->v~>X09uKTWyb zWH)FgGA(q>K&Cx>nE+oNm3=Pqwlk)DQJ$=W))DBZPgsN6JaTQ&?#8&{;mcJfU;Y8U zl=9kXOS!I&yCy~=8LoO(IEKEX0jm)QdN zDEF(_9ufY6^l`}SXfK(~W&cj(bsq9sY_YiZ&~F;@+FzH~tZ}Z$172gTgv%Tzjj?ud z@w|=`mcX{P?3ERV!B4|~(_L1Xd+BpTv#&eC+%9_4WGA>Y`?{>^eCZ{tCSM*f`SKn3Qp%M*GQm&M>azL; z(n(yARhjqda!Q(;oy$voLQ&)4PNYx?wg>yXFb9h1bsg9b8|j(eLeDj5$6H=KUr&dXzG#GBiH#Q zLFX5dh1u|#&YvDxWl!ijU7(Z5s?bMd^zoH@TIhmM^+`R$m$nKCvinqE0nQ+y@9rny2Ot>!u&Eo_K1>bkEASUQ(?}Nr$hHw zRe(NEK5^C9zurLHc>G_(f3i7GzLj~h;1z92yCVD9b(ZNX4{39+rA=Si zV!LrB>ai>TcxR$SKZt&3f}t1W4%8m*$x5~Dls$dyk8J^mYHh2XTVyWQ${8V#-p+nC zdt`)$2!xksU{4?7&uP4#Ak?n3V`@n2*gT++z>DW4Tz&eU~r z23Gj1Rd3_By75=h7iu%(zFN5l*JJ0^Yn!pgh*E>v=Fw&^(PpPDV(%sO>d%=Hv4OnV z-m6=iVRNjfr+!~F{b$*WdCOdHZTLm(+1_X0QRw^kuxI-`{QmzUx85|4`)`!nt7ZR* zeo*7SBJNsAk2`LoZ?Z9mm1p<5VI8MqmD|TzHOIq)yVu>{Vf459xus6|X2cHUhO~S$ zTfUL~lK%Z<;fLP%Ny_Mrr@EIrR4>=)1AQ92%NHIig!iT2vH*MQ_oQqY8-(w7ulx2* z_@2CuZ+BUfu|d*aExXySGa&7IbI@UL4rR`uER)~qayXA0b+NK1ZK01dzhTymenUIB z!`7`ho2jYw`Z>Kq#sFL1j-}XzHMJ0S+TnB;L@Vy!mOHcud}dum-k+ksI72$)er%&( z`>eWZ&i;)n_5ZDQ`hQoRF3-F%uEsDAmhquNX>DRNzf8B8m$_>y=V*-a@;SyT zV?KMsPx|hYj4j>guQ$RgI5RJCQaQ)iXB=$#WL{gSubck*d9BQ0<$E-qHP(o47Iw&7 z)q{H_w)cJUWY01E6yK5PwjKa4p~W=DYN@~I4?Xk!7YXY*-%lo7=JpcyVvo7~M8f_% z=J7J$F~(N&n-+M+o0=tWN`y$tL57uMN`#5xf7*|bMFbh z*w>P`+I4f@S}%DQ4JGeDBX0}u=X&HlePmUfC-44d-qAa6-oaGdQPkmV6VCal2-35W4`(p~z3Ur&Zmy zoAZz9Eq`f;a5Mj#+ri9V)fbJ_>Px7TloLZazm#%%v}w`E8*OUg{XFzJN1INkP3xoC zFVg}X8)(;D6W9H{aIXxs>m zPmrHRex>C1g2p|c6F6h8H1}wDEl>+=*hsmR*p-~ertSmmhI+Ft!8T2|EfL=1d(6lg zc~aMV%sh8^<@s!Mpo4rJe`~%cxpya(d{vN7_T(zg{!gUNoZsX76NX=^YEMg+I#j-y zh^?DFFvvBUGfQJQuT-W5HOu-<7OSaF@r?bDuvslyI^EYsp zmC~}U+;=5+UKMOmGuqIHUgTa{k*&#EP}|9tuAE^WTqt*Eih+xi>j?FfYv=6g&E?ud z?Pj@Jb^&EeIePir%eDUL=&}doelFl*?%gu(;>vq&9QSR7uE~2Yl)K!H;eTAeqbu*Z zTTOpI{5Rw8HvQ%9E{Si&e=q*~O#j>PmpjWOtRG==Z`mclD$N}V%oIB!>YvKqgVaCuL|4uqxX)N`3!!_uSN+33r~cu6*I()`^%FXH>L_)0 z<5x)?)FXM%g{ud-_b%_b3E;mE|2os(2Y;cP)J^VMdz?q&jUe8Sd6)M6KF<$$+Ntx1 z;O#TGWNU`dRA|zVr$5gu>KcmwV)9$Dj|oN1Z;u>&kf? z`b>s4mdqPGnE*cqYQ71T@UVw3IUloG?ok%FRv>4=9Q5%9ZyG#OkP|m^J&(`&r?ai= z;me?^XQe$(n*12^ZdcAqv%KM1e`R^d$qiY!xjdnpA7u)EhI(#bq>Spc1pOoU4=OruRzm>cCp2goID-p;F_W+iOU71H#B;FIm z6Iqcv3?q4l@W|cWPXukBu{8*pAlzG4vXGU<_?M7(sfoinaFBcpi0dsYS;$HOaYa_f z59cmQT~>%6go#63(vL zGRmpkfz(aztJ*=^3fyVJr%d=XP{!B=|5DNT3~j!bHs7bL*}Ifa_J_FtjQcX~u%Y|1 zLx=9ocBg*5@lS;La{rfj`7d=Y9^p?OX&3zmw_02@)QuX>e+VZAxH{=UZcZ_B(U}xMP zgiT@sIErlMgNs0sJAnlzoDVeax$hfCp}(|87`R9~{0fpNTFT^nciU z_xP%+bMJS~wL-F1ZUhLo$Ts1kCJ4w~KvuK3sNo`Fh?S}|3D<;#d(a5FSqR+?S%_e% z(ugK1+61Li8`@+`OB8EEK?`avdvEq3D7Fcq0tGco!JO|i$C$~=4e5U0bKdjES)a`O z=D3gXjAuOKGRJrZ*bUr^{ma<@f_Cyc?c^N%{Tb~|fXRUugS3V?P7?yS4o{+Wx;J ze)4%v=kp5oLTAbAf8+MM_}|WuI{0sNpo%PbH2%#Q|Ah|IUthm(hSbBKc^?;FIYa8= zKX`Ar{x{e=w*7^7smCqr9os(SeGBs7sNj0%w%>!BkqbH0*;}Fm6&|e`!@ZdMq|Uk;3=5r|%Ws|ZTrCb)hDO0dald>H}-n~nG{RzKE31f}D7>BgypL^e? zzXIRdX-K$W`UTf#a5_R03Hv^2{7KWgW53YKO2^rh&b6fxVuh1DU0XVxEdL^RgtuRT zZ?7#}s0!DL$2%@hnB||d?5fT=7cX_5ki`6IkFImZiTTudIQ^uTe)4KEMP#ZiPn0Ze zU#7gw7%JxbiFf}Ee3vJ5<0~^_HtwC#Y2(W?wstr7blUiHKn>5mrvKb)s7TGle-8h=0Z(*C4)#C}-U^NYk7ED(R=o26 z%ft5N;RG!Yz3fZh^m6>IHzN-tk%wP>)7WzmeOMIjqlEU+dVC>$?cd><&*@`Dr@i!N zpIQBE$PAnJrN26l{JDVqk-1XvI67EXo#$Ln4D%(CBggQ65dZU-i!Il4v7y(ul%X>ELaeZouWL)IljWZ~E@p@v z5t$+K@!GHe${J|C^_Z=cG1EBEQAFg3b1e!9|$& zgHLiOeF=C6{@w)9`(UgD_i8ix=nOWy8Dk0e@8BMk?G=DWF;{}KSOk~6qT|$Z?}7Ps z%-p|WxgR9_A2Dy#=3;He%7~1KiHYKg=IPAi;&JnY^F;83@O0t{eQHDtt%rr%h7snv8AoH~%}_^R)YP6-}G)RoY!Pl($#E zvA3&${G&6jp8m+YuO5EDOyzzSpo-g+ zKjt3gzO{q;FOe^U{5V6_F8v$6!+j>2HRU(6y9I6dGsdzeZ);KwGyi%sCNkc-x33rW zryJGJmVx3`HU-O5t=&W}TEC3Qe~EeCe9rL`=k}1Fc6vJWnXnzMpSg>|pA}Wl*4^kv zF1S^P)ACG()$W_Y<=S1#%O>|hyuHRVQ@d}qUDDb~7dMd?CwLM%NKeXena9xXT7DYF zgV81CRmvgx7)f1Kf2#ssOFrh~e~0!TOiM{#@*#J0=WBP7v)1n#=!t%D!>hwdd4qpe z`EgIs?pppfd69Bl#@*2F*HVr_aL>^zs@$#9lX6VQeXn-c@>uA(_q*sRP7PP5$=gsaleGSOS?~R6<+8P>okno%c`6r7p-3hd?x-UolyUH_w^ijZV)PAGN|DaVE{>{|w z>{+M9N|Mj<_^Z7T4$D?Sl_^YORiJN!wzK&P^? zT>I1gm5P)96$`EVWT%&cJrOm#K!$wNOB8x`f^>1ycPR9npRfF%R_Lh#CEN*xp5vf| z|45w-HD)bBhC47QHPajai_fqJI1tolpLQly&W!|pPBNwHJ|IG?L z1)#*YQK82NN_vkf^z6*k^ z90w)*M+!YhK?#3Ep=UBEu71*)vF)Ju^ATEAG=3dQw2~ zH(8 zd?zUP+&&8DTxK3A`EF1+=L9J6m4lMbUWIc?K#4COl=xN512C6pv*38l%e7e$7LN96vtT^tUfL`;7IQ82X5|+gjk#Kz1;=3Cq0NGQ zG3RTu;6ThN+ANrextBHzGG2(53yQ4#gUOhWYqQ|3m@BkdFbQ*kHVY2JjE-6>ydX^@ zdZ;!F4#zBfBk>E~fVqas7qcL8Ai7GM1#iS$pv{7`^XM#X7W7~qqs@Yr=+R~aq)MV| z;1er;a0uqT+ANrWd8;-HLW1b!+AP=)Gc>ir3)23hiOVtzj>8=0_$c&vXqaPrpB;U| z9GCm~`iD762J9LT=BU2m$PHnRf}1wq6y`X7_ldj19JTkKy+6#c`N6FZhB@}mDW4PO zIRC>-KMZqh{js_I$7YzLs=$1w0KDWk(LWyMXn4}R{G=J;^gU&6e9A0%iq{j{%$jZH z>217z^ptu0Df5Kb?)`~b{u8s}C%o?5ZkB8}cWpOaj)tF@mo2NGnk7Fqcl{LCjZd2e zPn(;c=C$Uh=INiBwPJhxY4gO>W{udEKVw!rWA1;3*ODFPt{rCS4r03ejOp7URzEX$ z{md-=8Lz$~b7PTNP{iwrBD1E*JT0%)&zeV`HIF_^m|f4BrO%4hPV>l4^XN`qOP@1e zd(PbZ9IqRnHw&IOH$Tto`RC0`&zlX;6XwYC=26S4*t}G1HWUjjUN9S80FS(29(_UL zc+q_AMRV_qSnX#Y*GpKP-DRHNWnS9F>(Q6Zk6t#9zs&32m(B8*%?f!feZ_q36?5+^ zycWD-Zhpnw`U=5oUop?VVmcgEubc0@ZkmpY*G*m>g8MDpYwq8R{ocK1#a`^ea>0r> z%>8eO{Tn8)*uPgYzqw!V>~GEUzs3Ia zZ%tkug0+HGzcb(Yo%sKq$t(VUXYLpC-rt$LlKGB z7usDeE0g%jwfO~Y{&#Kutq3jLtF-x-WJA)kzX#l_;}@I=ub8 zl|IIDx4-`k)Bf%6iC;i#32%SjT&>fyzbE$5<*~nit=I9{-^+fi_V?WW+Q0q%%dO4!_pYuweAOfz_IvA=hQ>+;#(FVR74a;7G`k^c0ba}Y@{z)uw>ryM~w88d+$h}zhK_7CCgVX zShZ^8?DVYF3-4Z<+n&qhNqPCL0}Pv;-k$Zy*~8nlAJ&2W$l1d>@E>fyFgrJQa$4rx z+y%3T-?41veG9S|W#w5#S+xLcS(Mi1A|q zYiC{TcHQiYNAXt8IDPDIO6(n_)KX zc(G`wka&lwSc2l!Ho+xnV+Gl)c!yfiQ762p%276>#e#%LKzp$*Y#hdm{Q3{OW%!tc z=HIZTVMkXYT8hR@Z!-tCNu%SaokHl`oX#QEhjBo^XkmOF!Trz+3$RV?a zSR;@>ef{@uSr4B$A`Nw*bj4g`OYEJcz(?E^-s)VFX1IiDg2YZm$|($lSh7HFOU3eRHkeH zFX4j6;kufwda^1)Q@^R6Mzvp0!^aHO6_pXbW>wz&^!f9%=j9EVr)tMA+N&Q~doU7I zUZs9&)oZ)|Wt~ZW1-hWN*{&Z&KLf#XZS}KP7(~Y>VRsObMg5SL^+RXDNFj4xUi#{# zbQbf|)2C0i0_uNuJQ+G)W5_2%6|1tOuK3wmvkYzO+Y!^`N$PtxmCRDAk|mM`*+=mMwvV0@zf-A7leoY$g=Z?J&_v0xr2ld zmbkZ%yW&nX%iP=-m~9%B?oMDQ?wNFZD>;{5Y2}GKVa1=kH#!~18q!11ZPW=pBcbT| z3M;#E(A-uqz+1*R@nUIY3^F0ps~OZJfa zj4Dq}gwInzxof$n_0$G*TRreZDg@jv`YJNqMtKeAs%yi!b9(WpK*k$Ip7=0h1q4s~ z5*;WX@rb*`A%5&QMBj$Qk>N1PN%-T(h4@%_2>M{qvG{x7!=2qx>17 z3ry^sJ`Z&esO65BQ%-0?S!yDbz1;0-+l$@~ar-%XUX`wwJE@mDsGr-pM`>~y^<~|k zlz}eOM(*al#2qzP)^f5@7Grmf>0eoUJL!_% z29vz{KCpP@pmFTVT1VLx>a?-ZC~M?i($oEoZ4T(+P1} z9Wn7PFGm}#J4!xK^k~*kX6rK%9(2KD*|e2n^kTToO-1phS?~UiaF@7aRcI#s(vz?u z+=n3X2=COOYfb6Z>2B4>Cz#iT8)a-GxKQi-X;BuuwF%yGqoXSu-fD!m*blyuZ+e5xs?Vfi*pSW%Gmen||v|}wdDo=ihMftUmrDCD& zZEWMNq-#^(-^QK%SicHnEnR@_6|^NlyS9GP<>RG|yX>8l-%tf@+(WF+pkw!uwtENZ z&(|@=s`%UFKVQ2K=)m3H$JkN$Qo?s;$$tjGl7`Q{C6w)tcc=FYg=^_s}Lm~B1@*Jit3Pig;lJw2k$ zHs9Q(&2~L((q_Bfi?!LV#{g!MX!>!dR15RSR_@|A*uT_su-Qnze^%P$^xS34Z*E_j zw{n>*f!IqSmeb_P4AjzPv^HrO6WgV%D_Hi67UYS`l*#D}S1wy3ukAYoxy)l)k+;B# zQ2SlDazP7o_R{P;nZRey3-+}td(DDn3)6$G=8;j$JTr4;=8^?WxL4^YEn6$Uvk0aw z7p%(5USg*mWIu29s=Q@O?jRT1V^9{;=RT6UVBvz53zp7n&oy)YLz%eDw*y)+-IbY} zyKJ64$6}_X+kMCLnvTZHqehNa<7<1o+_WUsJZPpaTaTM%%-0M4HWGOF$++1b6I&Lo z{w1a;i!l<0pKZ3s&02h1^SHSe9}DFt@kx{!Q`MO%B%@F4?O%F&ZZ?&cnLCWHtNdEe zlM-}18I~*KUOtlAc@Zn?!@hPCka76ZRe4P|t>g3Q_!ysBzY5~hqO~bZ@XL3N>$AMd zca;!|U&%xjU-eAu`>9yzl;qX=Cfjlzr^oetD;2qDR4@uT3Q8au=NGW=Y`Gx&+!S3U}x8ec-fv6zBb^Zf6k5r>4V+>v4wGa$x7*u zZQXcQ--~?$_VUhH>H`_`Oh9kF3;RUulMPq>L3GKvjw^G#%oT{&{l*shfAm&diAaj? zbz7jYYD=oz8JIjFl6%dKK>X!#=-xE~2IGC#n_E(itkUsb)8|R=qVAh_8857K#oQK1 zUSU+4wZy5@lej-&T;vM5El|VQUh>sA$vpPueGbNiPR5&zIi2RVZZo}Q>lky(9HAJr z=Logdr+(iDSJqC(|0Kq)j0qW^N<7^elU)4JDEor3<0;0DhAz9%%lB<#xn0I?A2pXT z_V~Dfl(YMZaRDj&KU3a#%GFVs+sj{jW7anLWbAF{^M9o-g7X$ndE=#glvU;z!FAJO zOxq2h}sDrUPYF*l!~IF!Hx(tC%n!S(xlt{3wvHpLW#l=bQ*lvz z7u8M~RP2dIKiU+1hW8Y-UB~zyOnaRBn5B%O#|}Bh5JcrGiL%?}mU+emWQE%>{pcz@ zCVJ?ma&N>m@;#k=ZzkXAtm{YZ9h!&LIUyZhAeY~)lDcgjlM zr3}gFf;(jCmULS(VK+1@gl6eorsS+=Zj;WPI1c2N!MsT5RR`T9OaVF)+`URONqt=jiS5l=1xY}!Ho4i0}d zI1SN%L!1?KDBBnZ#PXicK$dx~5sTiq)In~?tOKMC-=`fQFVtDu0rF;ogR?Rk!tn>@ z??G1y-I~11_N>@!Bk|TK>P+InZjA7EGPIszK2a2f&b{RI-V0NWlGLPOg+=bi97z$s zbS4QM_1rqP(P=WDM*cFt{sbOi9btD3GSr@Xm%u9t#4kF$@oUN3+IVkeJb8VAyh?h( zZZ%=vK(P7C`+keOM$fyRa`zbE2hmo4!z5%8)?HLmoD8s0r`H=(kq%J zbnh#=CPV7aQ1?^TGY`MP7*>jZ7w@rsjbqL2?B1MH#2Js@Y?$BBKMdbKXC60okD{Jq-@9l({%o+}Lb5PaI zCF)Q7{+f35H0|gs@+^F^9Ubng(BZxgomGe6#R7-7@?_VPob|-Lt{=Kfse^)0>J{1+ z@a}@<@n*OBn#T)@;!l{rbV47_B*Y4DoT8pY@2|}x42<^KJdvq+g7ILi@Iri~VXZ-Q zH@d(2Hf~aW(c^0%^JRTv3iqrk{%XHG`1?*CtudO@_emO(uFLr4B%POFeiT}_rnB7h zA^lx4Jh=hB$$~b`ypdw@R2H-;gXhAbOS;*;z8<{0Vedv{P$G5=Sj{HyYXD;_h_( z)b}Uv+wou9*;`raFh;cw^KmG3N?mTCE+yUe(v-Y(V6iv@XNmJiEA(Jwz5FoT_F5Af_^`kH>58UzL2}S8G~5mS_K_O z8QuNK)UD{lY%ME9Z*yzAwrQ$$rA_gq5XBCt#+lYhHU$SyGXuvhVBlq{&$HrY`M$l8)O}OV|EvmY)S3G z`1dARhbE8Gt|jdNZeo^q;gPQ~Cp#JQh84%9gJ;0%Ewo7!y~I+_(x;oT^<~iGZ^ZG@ z7^D0f>gx~aA$`-?t3mE)j(BlPYNRv5AF*RgYB#~KAEovIqc#}ZWKBO>>_RuB#t3$L zJT(FwXoiOmbH@4I&oK^iGN*WDOR58W2^q2h8FHF5WE}S+`ruB!7goCI0%o?P%4#QZugNNtJoZBNsrvr;hWi?ZA=@Oa4ALon z%qZ(5@&1*06LD6v-dfFgquRwc1HQ3DO)|uNcnrU9X3G_zp#98H+cJCSr{~$L?6D6F>KZQH0UC)fW5J6 zM0mwzK3gR9D!e-vo)unY>KrTX!pm;6r!{sP$T;$58AmaWOlBN;mT_bfV;I&fx9%~~ zPyNW^2K)Wh?3{*8-KT`7Fz$76Kc1_5LD6;OQTVBV`<=5oqr;grUqTO6%?P1^+-G&2 zq5)-32}O_1H;wzMzlClx*5e*McjKDC6zWELw;kk&S%ADa7BlGyO@^ z2+v`xu;vALUHrQ6dy%;i`6@TgtqIiX_~Xxwv+^KyDe<^3%6!$=_0Z=s&o*KeKkITO zaz`8Ui@J1gCG{1abu#Q$t9|t{(tiHZ!)4s>4y~>`T#AfnEl+NtTtcr?BPP?nQp!H( z4)=Qa^~|l_%9=axDEs7QZ=fSNa0>pnWk5gl5})|Cv8T}O40qHP&`%utD{bF&<-9;Y zRgD}dHI0$gPYJ&oD`Z}O=xNHr_mLXXFqN#`E@6e&zG25VdKY@0#7~OQ$-K^=LVKt| zUaUu6yhxqY{MbYk8rx-ELHL@smsUo3WlqFBHvy@uTJEWLMH$Cj%o(^7C-7-KcSlnv z;Zer6(hajK;u&k7i69D5#8E~Y z78##Fe#P%8!a7Wod$oFT7gzZ1)A+do`iAmY`D_ZK@;ikzpQMa-I&V6EP!VrlSKl8V zO`xxsz#PlwT{}G)%h8s~hl96;8l$d*Zq-jibJB*EBZZb{DEBGSx5J8?9iDlZzZQNo zOozYp;OvSTc(;@~kT62iQ_xHDDrFP83ZIGWIQT_l+I;dV<(&(<9$pi8z;qv${=SB` zz0T>XF9FwbufqMfze!u44NARBzN~scXSeXT1AcfDf9SX9c^l*K8Q@o>bBVO4@%|B9BH?a;V>t*qin%4m-kJV51w$=eF$IIGN0&9WEtOd&2lac$J zClTJa*PY^5iOMT$Otn3Jo}xv~;>xSiL65<#@BBh9;RUJNi>yhhFMwa5+`hb7TJ7P3rzvvp5J zgx_n%e=O-D^F|6kdZXY+@-rc!WxSs<4v~BRL_Xc9<&(@E&y$y)jJvM~UC+`6BaD$! zhmyBLJs7Xit{;APTtMc61%Fm+$MS7E>s>34hpTv(F^;{zAy4cXS7u=EB);H17Q%NT zx3i$ZSCQ}`aZ4TB>vLb;H;Hb^Tu=h==Uh5l69Vk3Q7E&blZ zE5`-u`g@byH`9MIF3bMwxIoYDM!C>Fi?FUv1#y(~1J^A^<)^fJ*GrbX8R=r|D|r<@ zE2S?Jd1E4v#qT=ejK}|9#Q(=ZWp~6L-h|m+hYv2lgsUc=EW*}Gx`h7&;fozT>t+1* z8_IkTbKYOq1jMcmJ6UJg0(~X@;CW~!V-=g`lE>ERxU4Z(>|sf(TBr3IasLLnhydvM zWt7Z6WDi1f{S<#<{&EvM0uRN0jQb1L{Wfw3joJs3D|`?!!5S+~V9vUlHQMg~lpy8^}+?u%-C@fP`ab;B;&80jju>dYEn zxL?9=H2W`fo!VoJ9HA}r5uPrnV;#}#-r+dZ9(T$ZG6ss!@I}|A(=E{YxQ`fS?S!|=jQ#F4#aO&|%GlD&m-CA?n;J#9J#w^2#! zkOP0rk+aBy#M_hbmK^G0tdRXDSq~w*7zg->U*eU!8AT7Ql%>|$vMj>?5=SU;d_|ud z0;Xs>Wa%=C`?riG6{Y36xB)qf}k$)+)>raqT z>5n4U8E1aR9PYpX`Vii&zS(>{)kj>ePEVx%6l|ou@qKjbJcBzj!HwW4;{Ge``o}ZW zSo32?Pk%w^cc-0&Y_rln%AAaMD{bZzFS4J_O53r)O8Z*V%nJGLG^^nmE6vTwV&3gE z9lT4L9i=7fL&0UNri|`kvNqhy`i4*fKecyv)hT4^m9^8T^QqMR6#A4D*0P<}xG%!= zR!X>O-!_hPJKcvxck!pJj}|(gNG)`Pb3e9G8F81lLj2r(rE#R?4sTCm|D$c6b6MH< z7kf7{$Lw~PjWpZ-8y=K8cJWQs`nF11$@D*E@OhHVf0wIu24m=R9yjYWGQJA;F+Lml zoX42_oX3wmk6^teVoYLX{11qix;9vMF;brM6tI>faYkU5!x$hJ-i>5_l*67(F7B>$ zvBo3r;R!~Wi*+7(cMXvFlG=CIc3n)?!{`smtL!;+x5{faZA!+j8N*dxvmI`K#^~of z8FxJADI}la);8jS!Fyu?#kl1ta&UQ7sy%Iy!NqpF|uwU{SES5=Ax@Huf|-8926S4SzD9xGq2%W zN6R%s;gi;OZE3CVY=zdcE|_Vp3#v77S(miGRhF`5ziFwGr?s*c_(OyJqpE)^6`2VS z6;LlOct`5SN8J=i`JjQ1y72|oO(Au2@_uNo>&FNErH+KhTh~!=y@>lJ>cvgHY@*DY z`k7@vp&tlds}-8GF0-tqJ7o;ax1PG-eX#Tq2H$jAjJcg{zd493Jh{bykAh(kG`|Kavy!{?tavh-QK0V8SRv}n)ut>Yop5A zHoyO0EGIJIe}6eyzxZF{XD4eMLF*gTt?XmB>$b5~y^iL)U3Z?V^Rb$9O1;XM+2&)Z z*9`bq+D9U8wN{;~eMYU@+5g)-+?sz!QO4NsEN3(S_NP2{IlZmQ`M-mATeLqtj*xa6 zpOsiq@JGJmA$zR-S2F&j4qNZVT1{EfuCY9=`Lslr$HjcSdTz@xiNToUpJ|t^_c_Y= zGZubJ%)h>?wSL7IA~8R{tE^$kUWI~(_`cO7lbi1(E`G?UEPj{o3EFQJ>p^N<((K1x zR}lMP*;Gs!m_s-1k<03OTcG%l*sT;9(;U8|yxM8oZKQSF1wmsrMW2r246Z||OW`4z zBO;5_?6EoXRO=gxi~(E6*SNuP?DDb@*33FF&k1F|6UMy9#a!3Tc^=0$TefFu*^Yjv zSefI>xKsE;qz4T`qg~IEVH0i=08@ep#3M zXW8xcI)#s0XXxVYL$>najACT`7rLIokY`o5Qz%;sWxSKJ-a(mfXI)>;7pe11Jsug?kjQtp0>X|BW1i^j zRqu5fX}@0cNW;^Ojn-P2&{yW1HT_w`+R$?CtQv6H;Wdq=crmkz8QNE#(BnFq_}|NXyk&clahvr$BP+XtH8E?9BX&tf%&{}GdJLEQ zps-I%=%n@!+G8!V%Wc;81XoGtd*a!fmqNX}p?7<0@RFy#M%4!rZ;I*rpw?OSkNBeg86jy2Ft%$C0u$}LgX zLsL0r{e=3M%6bSB^_b~=6UpK3?hMMA2oFfu6hnoTa?1Wv*E@Qg-=*q)R~Li!*6b(o zP|GlU?{5)D;)<5$raaQGw(cuznR>1J_v3xu3n|P|Ttj6|m-QGI^kVKJ^)KfT6MO~M z*}NA1LhyHrwF&&$=kva6f2aNZr9|fRe0K<*Q`hpHxK8%G8GBRX*LyEaBR>(t{6!I+ zxQ~obHO@YK3z|i^RKnjwJTr;!Ze;un*6%u5^Do&?DzY(> z?{K}jm*_6`&2W!&V92})l_#ZdBCa8wCsZzF%&=wn+Q8*V-%}^e$ojsl@ogEmHgJ;l zK3S{YGFsll>oa*jHO&ZY;oH^_%D2N2?SB)R?_*EFGiHRpfW0xYkHYnbEvd5BQpz4A z*Aewzd#7Q2>r(dkVeTXE^3A)HJqy-85%yn_^L(|;S+>vFv!UT`BhVEZ zm$FaCW%fLL@_J*%CwwnZ>o`667TCv-xda}2CMZrY5?^mrqZlb*{ek3bD$sO)bpuI6>w8r!jt~mNO*46K8h`(aU`R_n{ zZ@$UBlu{;rp7i^PZX=MucVqdk`}mD(1O1<6O?JcVhEwdL5LqvIO5__<7Wk_7{AAsPG?&rdsDhxa+} zYxsBS`J^MRzIrrcRauLJkD9*=UdMMeweG+3#@hmGojv@o(qHZ58_VchD_PeRa z+CX3URMrjbeP80=WybksKSv_87ymEOCZP9epX~cf`pQ}pjs22+BvryA1OooQ>rPn3IP(nX=SD`$NIyur7`6l8c}(D&V>mzaCX9wGWE``fO> zk&M06MIv+--WR@4fj-$hQfG;L6D(LG`z>NFxP7dnbB>^3&A7m(u4}P#*BA7Gm!UKJ zSXLAgX7u{PxFqZ39#;Ef{1O{JX+pzP#>%$+OZba7`_%Kj zFD>14T$o3l6`WZ^*^KhK<6E9O)Og?@GA>AG4^}OEG<=-1lKmNi7ug4Qk^NB_=)Wj& z{%m{}bCObLT*C{U=6wVHCEp^4693RRN@TC}0hx@6r4JMNCvr~C0g0blc(9{B^i$G2 zt>up0-fAmVADTpXiCex^1;@+eC01mx$mxERt67H2ez2B(a=~vJ(;nrlgT#}0ko^Ug zZ13YQ=yx0YEq$py`NrK*TdbXw`|hTF1|IB|X=`bM5zQNJ<{BhM7{N+iS2N$>-EH4pb;FXoT97n`>A)CTZT_9(0ezu|lK zKSZE5F*v-o-e-jEjnqB_gYm;r}f9jx74_EXrFqP05~G zhwHE!+f;e#?ra|CoFOmkoZ%^ra$~ zGJf7j`|n;a^X?IpKN)`ov?1hMc@jLF%$!eTToUukLU`(~5Bt9?! znHpZ-SK@}3vstf^xZlPsvdx9ukuoqo?m3>gYF}y`B{Bp1V`4x40kbj=H}TsGw0TDS z#$uLr_Tclp7ukO=dnZr%&9W@U9dDZbMoi)!hESy#ed?Ju|2 z5$$un__2OY`{jRV_I^kBu5q^-#$7+S*YXzKwTBVarMsMJf7G02C`+ZM!Ndde7SXLC zdeaR##{L-36?t&HLPoE^bXQQ)aV4H7nTO5qR~qFpd7M09Je_&sd6Ie7@ifZAk-Uo_L;Qo^|Zqb%t~b4Rg7}BO;@sJI8eC+AX$wkDk4H$6eRwU#{mJ^gLPh=KFrEDXEa5Jh;_~#-5-|zs%{_)Y;tFf+(RRGwOHrgdxOY9>wLTD z=8!V8Pf&EU8wospdHV76=edFBCLZLT=!h3R@}g^A^oJXhcxLg;~-` zl*h~CH|*bJUhva8Iz#N`C%nSXj$WDgKU2T$P}s)3R=W$5cANM`?~MFPtp9D@m)kMo zuf6<y3#=Ts-CwAa|Lc2fMf%_O;qCqO=Hu1|{VDjrIf4SN{ z!}8U({71C=018i5+R*R3G;Dr-aObs+d!#gYe%hf;{$jNI^cLQfLH<9e-5>2B{o^7` z_~r4mm%r&uc;qMaZ_m9zyLUvNt=j!8g=5{IP5B-bLB($;PkZI>B|?m!G(&!E%Acs+ z6DS`tuuc3aA^`bylt264wn55UoA@8q;ZM-m+KYd-C<@`1PssN2e^dl4zwHXz#6Mqz zFTdOl>gT8kW`37=+N1wF3F4~m!vosHe?CE6tvg%<+R$fYU$O4E{eGm~JCYB%{Z#l` zpuPO1^;hm4@$YQy-jRHouiZPUzg+Du!lga>=MMOe{M~SM_whF>ccFiK@ejR8xz_;B zcIX?EsN7F<;C@!SzlJ+SXj4Bsr1RyM#?zkrS?w-zygm2v48G+j`m$i9Ht~B0D|bz% zHvBVRyG#4$*T%g>yKDK=#{KMfgijozB9_dz7eDiS>(>$gRcZIh_-e2GAwyMoP3AWF z8>HP6l(#nS)3tlPa&JSwceHy0a;`o4?$?BSknrv0?-K5#(bKk-K^2h?o)0bpE5=!O z-Vc5d9EmwnW7Sw?o(?XRaAOQ(0hk5O2gfk%odcdAQi*3Tm<4VJ9{^{9cY?jZOz^}g zi?632SLk^ZlyGU_{a_0C1F(inz(RvlKuNcTfuLXkDE=P=B^@s)=|qB(&fekbUclEB zdh$Uj&t$L{_e^;eda|IS_&-j4=73(1G3)g43O%#olOA#}l|s*6_^LbR*A#jZKq-GU zf?V>k6O?>x)MnA+FYaD2R^nCY84AwBJ`xl|<2PktdP~4C7S7wo? zUNQGm`AzGquorkI_OB(Vdd&mFDCcs8o(!-X<_8seLO`kC1_~kdn*z=RkJ2AWxFZTZ zRbW@l`xSbIYWG3fy${HoV|p)zo+I>kxx_OWT*9kY;hX_rI`%PO4E`e(dR$;<%pnRr z$NN}1w+5Gk;(r$yjeChg&rUE3^A3fc`QUx{^Mc}kC>V+RAcdYpFaq-cg&y=3S^oEe zv+oKp~I7(c}RL~q64C7^@wI~97if+pt83Ox^kbMPMmO8uRHsfGWlzz4tr za2c2dN_!cqu^Q$U^G5JNFcDk~o`%V#9;-od&jS~NazC}3bQ2YNBEbyovuFe^?B^@= z^Z~ut$ABSV4a_e2$^sXF13e1QfcYfp>zvK%sA}LQf1aNr%^1qS2=@L!(z?f<{AQjVgbrrdMc*Hv2SY zX!L4K&}e9^fj(CLHI``fY0S{*)tI2s&{zXKt?(L4H2O4VX!L4K&}e9^A+Qx*V~Iwe z#te;KjR_hJjWyI8uc1{MOEmg4W@z+kOwb5e;kb>>wcQow*yt+Yf3^FFn~TQWRs1jQ z{aSB_qont)-rV+f{f_Io+wJz{$FWj{x2P$q2n7Q;fZsmHa{Z%G5d7*ds~@1YyTzMJ&&}+f5P`vdRKJ(UTvPM z-D8uLd#DaSU7K^X`w4AcuJb!o$N!c#d$svFZJw#ki?lgIo41RZ{1j-jUz;~;^W)mQ zRhuhx_~qLCx^~~L&3B8LwwI;p)04PGZ?8Rmr+c%^X*a0yMkALo_{<^-o)LW&#(vDGTZZyPSl6E+w;*^ZU6mM zex~d6?e(hV+H9|XJ*o3ktGpYmhgPj+?mK>jMk5*-e$uZ?czZo%p*Gv=Ycq8I?Dful+H9|H4kH%U&kTFLsh4)Q*RQtd z`m)!%WZhBHv)3R0r1S3`tn%|RAnx{0`cE-e-J)zx0zuBkWWooV&$V*@Pr?VgPlDfr z%|}}KPrxzQ{S5UNZ2pY=2b<+R*!&vh4L1LQ`U*A=ZIynYReb-_%Kgq(`5)LS{uhZn zIR5Zf>5XWW|8cFtk7^bFq*mc4w<=#)tMqIw*jWqa<%pi}?4_%-^H3$ORETp=no=>2 zmhkLlOEXv6E?3bzZfW?oE?BeTXuf|)U9V&Bh>g;?0(}Ht}W~i7|cU zf`v1AvboXCihK6#l+h#6BfbdT;YRu$vu?*n=KL9xmaT45f!?fYyh$}Ws?)8j*P6@C zEpl=fTTqz2|db88S1&gwmwsDtIPo9-Wc9*nqYn5J*a`xs}b)a_L zf>R9g&^E~+N1MFC`rBJ8ezz;7buehh#dfG}H`4E#o<27_4>j-8?U%dn&^8uvn+u?+ zy_B1nX04nj>fY0qO>WYku3bCUAm1F3CgQ5>CChUcOjxyO|U6TgY23zs8S7bA<21GQG)Ci&RdnfFndAn{2;se z+4I}j5w@AF7X&sS&U^}pkZI* zs4pzEF4K@(1xMP~3l6tlxK?oZt@;|lQKRe^`=Y^7!^KAKB9t2jfxgx2CG+lLPCD7iz9n~^`OHk>vTx;F! zr%Y}Cd*>)S_E%Xy+sv>{mdIq8$ogIDeRp!-g#08vhK5FBx?y}Q7H#hBn0M8CHtcxh z0GRxi->c$NrndjT>z<7onF#XB(B-VA+)}pV3)CJXUM0Itzu~;enxe8~@zoJyxeejJ zybWE*dNhvWF8;=3sr^IMEEZQ|$ueuxo)dAGN9Y;P!;C((*ow zbkdN$u&W6p=j-e}ZE}A_L;3{Hv;5xDb1rw?S!d~AR_Cn;8s4$9(NRxTp__<%03cCB zcK(cF{_v}LQ~j#ylE&p(a8(zr)hF7Z30-}@IF!)?j4G2lq{9zXR z^PqF=6dg{^78P)|D4B3I=$w$LY?{}G)f>F9ueTc;b2P9pNPGJ|_eGQM|PAcFnN1BsXUb*z+A%J+TD z``8Q1R4n#h(jASN=23o-GbRTf_g--QgFD#RdpU{yy9*+XQTBPPGIXMdZu+;;`Q#!U z=t?>+D;+nwswMvq5vNuD+bRE*)DH`>V@eq-X9+|PYUC*F=gjzUG4`AJ#5s?ANcr-h z$(PQ^fhQmLu5f+cn07Dwnh!V}^`(DrOj}2}Kjn<`f^cJ0Bzw-MlkO2mq`z45g5IbI z^1^wom6_b(m`RxjQRbV8e=g~soantEXRNY0FPCKYI2?YtI5olK9!T*s!Fx<-G!?%| zJhU$>@8P`rkBM%)h$b55OuQ{{X0BP;-R$At&6y?krdxWLB##BP=m+Qg3ugdFzCF>4 zzASZav%reO(q)mjgr{nWZ~fwj#vmxR+xZZ)0dNcK%jVjz6Ps-ANuJ2)miEnbeP6 z7rBJ3CqMP?dMk6-hkG(Kaz%gYB$9o#Inmz063*4VX!-(AT$%XcX4Bku;P=TF=AkDq zzBBCDQ>c)1K82GBEbZZLAnY!A!oDmV-k5LlnA6I9Gqq`_zGn#)@A*{&?KS zHyX!I&Q*O?57U<_b^GzRlRo?l{MGRJzdqHwapH$!Ke~SHuZ(EdjIoZ+{;xSZm#`su z&sfg1-`~x+@B+Nk=;&2%aK3Tt;?WHs_cOMgbabzO3H$$oM}_a8S!!qUd=46(i!!`|wFKyznS(t;_BSV{eJ-?7zU7-zUweh;!J- z{6g8C$8Iz6Z^kaiE%oE7p9Fo%LX0lqLyc|q@bYxd_C7&;?`JK^{}cP?|CBX2|2@w0 z?#aF`bujVWbs>Jtd({6P(>3j`x8n1k$jTja*Qxk1x%s&j(|=@CvFeXo@2 zm*27aWx31t>qSm@uSdUW@W;r_yr(r&(t7<22x4yS)KXN|iO)|8gXu%eoRCw#Ei-Fo`ij9lGX`*IWN)hz_*a-Sg-J2EZ4eY1tI43l06) zY1|p%pV*VWhcl(3UvT}zmT6vVTAy-GJfwqkO5vM{ogK3zEjh<(r33E;m+e~9*?>O7 z7UkibYAJLrlk3e#Iem?7gE9Ytes(GS%KL8b*poj%H^omk#r@)jKgQ8#b{W#xI5Gk`!yOgVlKTEJ zPSQ?B7q){wZ3=0gA>WhC?*8>3tO=a@i4iDd>~V&3@1Ma(&!gY*@x^NbqEE4ImcGa9 zVq~Qo`C9j5bf@&oyJGB0xTWS!pGWc+PC9${7RC+AHOs@vTkWfnWl!|wejwUbgc;>u zOx@Mb<1Ug1jKH)j`5)FVHp`1o(GC`yX*u*gxpRzr7L%v1$&<{5GLZwdoGa|X{Qy}; z9B0H`^(o9-z81X;oEemH*+uGG_~%pff8WnJQ5kcsM{WuxGM1MyaxLYNcw%)ta(9CZ zoi6`*#g0dfvs0Y0{xf0RC5|51KOzV1da6U8x8%o+4l+{4XHq{o zQIXSfUpBg&ebcD)BTtsXgGbEBh;z=!Y0L$-4s#gW?!MtqMMKsstzdjp`1vx<&T}`( zdCm^kaz6C{yzs})M%oiGMtL~peUfq0RI~HpbkkM8d)SkVHNp>f#{L{-+Z+`>EgxQ} zgWk8mpDbm>K1n;V_!PQt`UG9f%*BK+Uxrtofmhq3zv1*+JeuM7@pw0KHG85F7<6AV z{r%*L`6~2}u;@P+`Y(b0WkUaZHT~~3)6U%r|LZznvS8)wlGDArc9!DLS~FJhCwF?p z_7T1kd7>P z>0@zIkAta4H}$yZ1@0W89`~#VsmDE;AoaLs0!TgX>Ff4e@lua+*WaeQ&5=?D8Bbc} zpe~cB%erZ13Kk<$FKMJ?#;Awh%v3zf$jKq<-B- z*YNBm4Z>^ZpwE})^@l~C)vq!FTaj;F&`o=bBQoNwv-7m+HykOVjc?`I)+K*M#YVGB z;pvBsKqL7nh3|#03h95?gJSX3S&OeCHD7JE_$rcg&ywyr_)6v!XIkawTu^=@8Ec(g zYXr7&7I!H5=13|>l)IO{ordgO zWmL-iihD*<6N#&KgAqXX$6EO$zjsBbe4e-R8Kd*L)yij#@IQH!c*Cg|p-%#FoG0(- z+OooW7Qcz?5cv|!Z!P6Uc9Yx?ejBLyZR!h(-=?lt{5CaH z@!Ql1ir=R8b^EP&HNUyXo0i;Y&1+jI)1TnAvlg#KPIE%fdC+qTytXWVSw)iBxiA}E z6S46m(^ z3h{pr^CPT7US?f&G|y7j+y2Tr>j0koXz$oBBE4g8;yD>^tZ>2$6Z-zCi2Jst^&RkG z(K)k6#HUYkXAiO|$++IX-q|~%uhAo-?#Y<4S@`?X*=O3S8$T}k((Dtl%F<6_tT<;r z9e2O;Cq;ex7+sUmr~WNxu_rQqRq^(=;=O1}JU#u%PVPW;tS6nH#GNyHMO>s^%(~&j zBKDhY{mNl%`||$$idBvtg-g)^bk6MFqQ>O7)r)mbr%}bZlgZfceVmO9ivX6w?x(rKjln$_U!D2d9yPcWc^gaR0qkBJz9pG zvg+K_ZF9ZVHcjgMl&E>F04mR zptJkJh?q_2tTJLPUW~ln-**sWAOri@^+hMcj0;};+4Wk-Sf`Nq8tU&X`)rlBQq~*4 zamMVH_geUzdxdgj3{@X*rq#mRu1s@VEwaD%CU}%}rCQOsG->R%+WtnM#W<-O<`1<_ zBy?beF-QH1HqsOaXU|#hi9G~uM8?~F=KJt?6nBhCe^ZD5&B*cG8)+rK5<1Oy8l6${%hWX;E&4|O&XD3BPOp`V5MBJ-Q^cRR7 z>C4Kf(xHqFzozfBN`XSJafIu+k6P;TI2|T(+d& z9c9ZV-d^U2it;~zJSdC`_s>9XI-@)LGcnJkufGS`kim18>3w7xvLTD-ZpH%BkqwJ^ z;$6mygI$cYxvY6kOgL2ZsneJ?alk?D5H=$2=MLb^@1e7U@z5#eM}3iBYeK`y?&)HT z8s>@?Y1!KoTKQzyw4qj!Q z;jA-aTI7HuMZ51`Tv5oFqaK>1(4S<%EA`JXE;ikM8E?sW5gid}W4V{Bz|kAMOFimk zuD+AGTY)p~a0WWEd=BnZnqXX@KGM!SLx1P&c{m-tP_kBC$6R0&_ZEn`I@C;SVD8w> z?0LA3_S%=V#zgLZ6a5ZT@XMX8+)bu*itOZlEqAHaG6xhH^BMEO)5w$bN$727Zm`LD zo!{v6)_7mo`tilYE$f8RHYL1$uUK%n zrUzxdp828~UjHKBG8V_FcLO)M`!5oHjAC9_jQj3k{y5QHq4+a=S}yXTZdpXx*T}0+ zkO7~PUm260f+zp_6Yg`M%%4$?zfoqHKS?>HJTkBSPr{yL{lFKYY3YVhpD}cO96Wef+I&)!F^zS>*i93SZ8;N+Z5hxwq0{>Dq7OxA zTtI&0ZpTvUYsy#u`k_63o|;`&!Ce(&{Z3=7%x(N(AngXlj$>u+fQyus_I(!U2E?=cAg{7fO=gL zBr`hT6?CfHhmaYf7g{o-9-O6}d+Ypl)N2khLyaZt40#<7-x2x|y-?(JUpQF#_m&mW zT6EM&^3S^U-7STy3Vq1mO@;6&$^R4hcPVv@Y;2Y}F$lAeW+=%^Lrw9brJ%{u|v^=?drcPf0pAN>DQ$I$chnmx)5?;nGn z_!+Vw3*MiBp7=R5z6Tlb6>UF!f}TG+<TeX1o(yDYTvD-HJ@ z;Z2mM7DI$}8z$&?e*&F|?BYyd|9bSoVfQlE1Crgn>#XG}&&!79f0{@xRCf)};-1>hER#Ikb5YV^Sp(1|btd44H7zeoONKLoN(L zE)3+Z+>mC2e=;BS3)`U9)9BQG_~^&z_E&&beM(){BPX)4HP&PcG2}!;*xHGEY)Vc< zKu40E%vDNyeZ?1fQF4U)A}OE93QrO9W5|tUXmUR|m74NGco6%ijlL`IZM;VzFGlH6 zt)qkFMH)O#=A0b#4_UWLi$NU2VJBc z_y%K^NzmjuXt09E4NVIDmGk64k3xTod2&r%MCJh%Z`Q~30+n*??1r;6NervKeYZi`;uTW(94Z=t?5Q+6Bqn6g{^ zM1BX?^J&(u9wEHQ^ZC)Dd+*=kxPdHx6j>fgp1o!Hqoyn`g72N6Pas>ak>T z>ptloE4s%gtntcN_!6|Y2VUX*nU=p_!v->XYK|Qnsqh8=z1`f`7SgBRc*2kj*WzW5~rA!~vS_Mw0sPj>*u~*rn16g0Nx5Xai z&D5d;C;y`5cTn!Ll;4_XV4Vb9Y~W%K!X>2nE!MWZ;8IULOC5;)#c$+(WBPa$WA6ZC z5^L=JJZlumGh%noUC$iwB=>pIFD~83yLfzao3oBD^BgHnF}TcaDo}lktuv z+ZuJ<81GoJt+bbHn^Lc2o5-=g8yasugW649t)qTmsNBWS5Pa+TF2V5@?-$lpiTOO)$`3Jq^p~ym! ziNWJ?haMeu7MVBtw)fHB%6-Vfo^d(jAVWtRd0Xq2gN(~rM>KV`2tyv8K^{&;u4an< z#hCn$`cPBvz`MhCUJO&a}(@lqpW@^xoiM8e)-RmZF zuc3zSbqShPx)=9C(vL)j{=#-s>v8N#`svqKc!KFsXtp~`jm?_~BYZtr=Mp))5&knS z#L~UgIKQuSBy9C_iZ&C&wTcFGNK5xBVLTqJdkr(k>Y{sjc>gxts|0@i*@~#9udf-a zE4@MX3w%tuKOHi)=}X{Mz2T~^Q2I@Gekf%SeL%+T4Kdg115e&SACUSHxgoUm(AmCq zf{E09Z=GO0I*;fB5%5*v$9eRZGW3+3Nm`woIxd9wSLsSmd7iR$a|fa53hiHAKQ{kA zsVg|Cm;aQmz!?8}U18{dT34u`%zvw{AZ-vw_`gM0c;p7U!Xv$O1*vms7i+vPy23iX zS@de@1=Y~LMYl5M7kZt)bRc`>&Cd>!G0alhaK!?kUft&{rEq) zN1l6KTKBNV?*IBq{x6NSda*04`s-@e&U>%XTk`Ly8P`~!|LRKqpL^n}_4yLk=VcvO z)^}vCVXz+2`k)qQSwYCc^*wMqKY zm-MLz4U?nL`&HT*@qS-_q~*FJx|neZI-?xOVC?E#se=?_yn5 z4{LSmo=#_qr?WJQG1I)CzBarzvCN?Iw8P>ToUo^IvkuHtU1Kis2c>?#^>% zGLIK$jM3U3m-mQ(le&|&yeK`gHCm6zt>W9pj7^MVB3k|Mh0j?>72UL*Fg@SUL1k?z zLTB!Ub;2rpXj#sD()sDMrjDP@XgdGz)31|J+hXFq>yc4z{rB$f%9TsE3eG zBBM4TqY7mHcs#n(52zQfyK{r{w_853+j2LI_%-W4wkYNuydTS26FO!weB=|>m{KNa zcS{|naP%NWvqf2JN+%~!cG8Js{o@4l&~dlz-NK%ss5sV%WL@q(y>`oIjN3k79P&By z$NktxK>HYLzHxIZe4p!a#ys)|$STV}dQQ2Ic_Kqr1z1yRHRq5WtQSYlS;TxEbI8<> z%n*Q9AKQ;q(=q58N&K2w$0Dn z?(WRdlUU=945Xg8f)33dc67y7&eHuFQc0DO@Y7qMC{o32xfuBKp zqWiYN8(ZLw?D;fwLb(Uz-)K|8!2u4D5$>i$?sgEoO403P&q^j^Ou29S`|QJ?0!?*1 zDD)B?@UvI^5j$aQiH82?W^SVZ+3L6pKKlymFv!+q<~B+x=P7iW_L0yC^BeLlm37rG znUn59C%%9_`wjSAVjTL(cNoWr+=a$d*iW9y`kT;Z6XW)i$;>0mea!S1={F~n=^x16 zcWpMW^cz3@O4|J^BP~f&(jIE`t1kN2CokRKlq~b)cY+^rL>9L({~_{T^keC7Bj8b8 z_KII zDYWU@GAmu9o%!kD@=94n&*;0XihuN!kv$UaroPd;j9t{*8TM#f^(f_*dXsefsypF9 zR{aStI`HT2{7BZgIfHUOz!)vh9^@k6L z=$g~0uPVX}EtAinO=L8>zPd^>?-(cb=iA4!>3CMy(+Q)6F+S zuF9fzWHxK1W*ZGL`eIKTk;gaR$!@aNZ6$q4TiT|P{W>L_@AzvnjPY_Oc56HTg-7I9 z`vB^jvQLLzKU%AuKS>*kjKy@BA#>lQtg>%E9ruy;)h{na|J%vaW{+=fd#-AS_I&1! z&)2eVe;u~UJ=*yq=<6drKHy|+{-e+}f58S-e#`kaFaM0W;$<>ltjBv<_eI~-S_@fE zX<$8tdGNPoERS9rIDszxG(6%Z@>)zj)3e#%Vei-6Kwd4+W$xHPULUCZjJ)chwHDTv z>LoAaYb|+Av&VUt=*&Os{k#v^`!VkjXXNF4UhDciMdekGY@YNfcH_xw8TkB+XAbX6 zh%=2ia=u3N?2{?l-KUs$JI(so$5HOif0?u3;+uKHz0w|{e@UOV+M-83{-%5EDdb== zvh=iF&n+IY4_T^5J%gUzm)tD=HD?fHeiwNpI>Wp0;&X=1z#bC5$vy=e^B8tLj&-xR zz%tg&9F$4v3~{;BX`2r8p=Z#CK7LcpO&U58_?vjdi|&Iqw*_!MtO;IqxX@ z`D8CvPrad6$y&T0oyuxUi$`1S`9600Z&VkB)WuZl=}fr0Q^rG1>M7H#r_=aJ8Dzcs zzq1T?Q3jdwY-7%|AZX6B-JJ70gUqdh=N+UEYrWPg`;20pBsnug})`Qgdc8vy*r;nZ9R1+>$}$2?`obmUf=y&>P+~y zMK1w+w3^rdusdJor;8KTCy8DyI<@QvD(;%>mGc*}c6;ha?#>0Q<31m^-q;g#nze)t z(RS~KQLG`c&ij1AhAnayrclCq-JQn1AawAP(3Xq=(&2S7o@=zvz3v>u-Z`>Qe{eH< z>g#&neSPWiN!%0nYvT-$>?2yqoX{RyXxYK3T7C#~mezcwv8Q@KSX0klA@s&Da`hI@LE8H(fpW1{DAUbvdddLChhnmp?8WP$2!~X69 z^zA9nU&+r!uPs1lXzi!v&$LGy^U;fVUy5G%8M^g)^lW3!8F&s|gLUF&XV~6}8v10i zt)IbPT9yBNnbkTH}Jcul(8>8(hsi_o+9;ih4<66pH+vVCl^beedv&!2TzdoPAStM z^y5y(Pf`|{KPy02x7B4`YE04cA7$RH8a>2g>N_9N_p3?s4EobXyBZr<<&eEFQmzu@ z>$T+=Wa$4=jswVQRSs(}%uTI-unl6~?XQ>P%~aVJGw8Z~G15Pce#rRbd+>Ej=Vbma zp#a^My&CANG3W!)t<%wM_vo>K9q6_@&~5iX8}EV7bo@E{hJM>Gur6czrQv+rvlcxC zx{y7eZ;_VTd!xoIlIM-;TyUxWq&r{q9&CZd3*GN!+*OON^*JPu4g@ z?;Zu6zNkmG-hu9Q2_Ade)N`eshtSSX(Vo)IAED=pZfiR-^%8r1px0Pq4CG;bTl89K zXKe7I*T!C_*Ge0=!yAHWcmMzH-R5fVQx|P6^wFDk|7`4k8fwt)6~=eh(d{ilx7X{i zQujjZnIGwIzlRR{M|hQL^J{fj)gP`+SK_?!N7XidbOS#+sntfWAMeh;oi<8FznpLC zr$NXI$2-wYUnI{N$p7=u zKnuEQ8@j3JrJ@UGtW$dM9(1`v;_NYX)3?!0r`zM9=Xl0*ab7oiDtWegkk#4-t&9CB zh90~JT1%my$~pHKaOxoZW4ymaoaw|_#JkLYiN4o+{_BM7-Czz(_BMzv_Q^=K_e<*Q zGvYi+9NE7k>(|%mh{QGG!DmEI{M3vqx}xxX;ax%tqVvk$1#1pX`c>)2-TCuq?;Gh$ zP54Rvt4*9Oz42@2=nR}`a|>tD8%1CI)_kFv_0&@)X^Xy<3P1Vp)VFS@&P2!a($=C2 zD;-bmNBb#x8v5UNuGjyPXdl5x&XV?pkN5A~t2P>ZEd5XPC+yuBYu;CNnH%d*a&}PW zV^kT`Jb;Yp1W(cP`oiwxx4z9B%EY8}xVSsRrMG_JaA5AzzMk=l|d6 ztybOqcj|e4<|+K`Pu=;ilc)ImZ)fa#{)v2p+!<|-ff;jn%<(pMRK3i#_Kde%dX2a1 z8E=1Ex@$wa^K&jTE(X_8>`_@ve_(F5*#pmOfale~?_^94-!sPJVkh8gntiXEw}fG* zLwF*xXDPUrGu|Gp_4AI=V!ai}I`$VE{8z^7>=`t8Vin{3Z%cQr9Uc#^hbgm+=g)rK zJ!mv(mXl@---V}SGN!+u{m%i$^q(@O{{p_YV!mGY%-qK>md!1@Xyws+uiU<0Um2Ur z-nvihA-QF@{+ju>kf^8h!CqMt58gjF{MT3VKVjTm&v;(su$A5ew70c?=;~hGtLuMi z?~qyp{I?9HqxRP(-{y)v>dk~tq^kCB&s z|6Ut*N_zN!TECNes)rAhkk-aN#@uiHoqK(-2bE9_Ue%gI;lh}kvq{FhiR=fJx{vmA%E zVCy0NQ_ga9Gj>~x{_!}vYge4t7`GYp6U&*EJKUXBHtqZ~H@&i@n{k`WNy+?BA$u>S zoW1F3@~$gmW&gILQ0`mR1CtZomo#)HNBO;-5eww*LheY^unkahZ>PS1r|{m+7JI?( zgYQr8v5nL#jglqzc9wEhQqnN&$1J+Hv+^;X`S)TMWdQRp3C)~W!Pbsvmh_2g^xG2r zF0Y=%o#O8rW5bID?_je@$8Fb+we#il zqdhWSV0`!%JS{rJ+Rx)*KM%Oj58fk8H2Zm`=~2u-Yk{n_d6$OsO?d7bCeNJ!J&MhV z1GHfW>wn0%z)A25F!!8*pUmTEA&f6I)h`%(Cv@O&%dW8kN8@V*?z$ih$EqGR^rr$yMP5*_Df zF$rwwiI+neK0m7*Hd29bjV(WhV?Ruj3y)X^6 zRZDmex18;jJtm@mo}^EVF#E%eXw=$|awC5SYo%2~6}`&bO)l$#5qyh;US&-&?KzK} z%W2JoUQf|xk?b+e<*9;R-D|Thi5`{-y~>)2*n@czJnPvbrBmM#nyr;H{pTHoN&E8J zJr=pxvS4%UgxnBd2$1#OCf0K=ouMBGNOLOfSxS3WLF+R9 zeL})(oYm%<&n%}_7s0d0sU4AgOWE@opgkmRCT<6Kum`tQzIE_T@Od44MuU&_Z4Y=w z8vR#=k#STZ^j1ZD$xr6*JoL{(`lt0Rhc(s!bJc=V7X34p_L2NXuU)(4kRD^KsmXcE z-|7*qGw4I2cSt`i&|{5$T2K2(KNTHL`spb8P9*&_m**_=2Zi)gH~lmRov4fl-N@)S znR;|U`e~-wPe;>la^Q7QJhAMZoWWB={%vNTsWJ7QOg$#B7kdcOPhTXjpV3dxf!jg z=;b5YOK2#Exdj zkw0Q@tGS2gsWq1Vq$_^C^L&rArjviY&ioU6UglBP0Y8K01n)DXbI_b?lC?D12T*PD z&NH@w2EQz&>}PD@#(OF>)fnaO9FJ`{9lj|1GmX6fnbiGM=)3p&W?%gCOM`z7GV}@A z`}1x5a}Rw%*#&C-p1xHyXY%jeaJ5MP%@e z`I5f)<8g8#!o`RP119^@G&Pt~2v zoPpB9c^o_uJjhxN*`RTjOz!xSd%xsvFvG4Ww8o@>y305b$aP+tlP8sD43C?~!_$+I zWFiU2@i=+hJRTk+fWWK2zVGkVb-|CHq$J50&hwCV^5e zfl@AhVNx#e2o=->O1T80*YmT5O4+EXP^SSyLZy68EiBY+xWhv|21L3Vh(v}d%%U;> zU=-CvM+>p=lzMlPzlA5V#&BDBN;QeUg{QOuzc6V7XAqv!2I98xly-3T;Asz)wjf%F zoBwepbZQp95n%=aXi;I(E|keY2b2vwo#b!fDfKFD3s0$6aa(vwy^34#6lm3}(2C$^ z;VJd%q&^j%s$KHjb83D-4U7rgkHAS^_1qeq1bUX&|T?knfE#8A1PT20BMw>h`` z;V(5m2q+OkLvNPPnj=KYNWFw!&J>t{zeKPZ{UA=W(uoR{@^c#?{*v)cK#33%Ds>ju zlTJ*i+@sEIg!oGY-Au>AJ2F)2k{I|4914^ShX^D>l0ZTX7Dz^k0{?Elf}J2u=!HEb z27lqqk0$gj6EH$I%nN9&kN*GazI34plNW^=r3JdQ01PPP26e#T|P})!+GTeZSAq<#e06lED32!%` zEo`I#?O}HrFeEJ7fT3Z<26TkkbpsEhK81$~zQV&Sd@VdJUL|-s!P}}=#rw4|p*wzI ziuY?_LT}<0S`(=Ho)+dboN=@x8N;LO9{CU(W_JsW39~x|Mu*vrhKdTa8x0y6W;ZKR zV%d2FFX^??PMJJaJl*mTKzft(Ea_#!BBa|23!q!^=sb3waGriV$vl}nRXpAD=sb3w zaGriV$vl}nq@egFO>1B;C%mik>7Rr zZ#Df(@T4iOiyx9>#M8#oMt$W!ewy;{QYl}T{@9tyKmP{t%V%lY?c5hR5lG)29c{Xg zk}kBFcUVDJBM=^tw@pFUP@u%?q5&k{A)vTBC=awdx)4Zx<~^#Q>*5sUzM!D1(sVB~ z-D7}~-zWuLAwbDbQ_!{fK^4DNLDvQ#eSLJC`90eFe(V8F8^QOTK*@i*g04BHJKuDN zm~PESH9^#)AT==rTjY-bnOF5c{txL z(6s?5_(ucbRil~HGTixYRgV|xgSYeD_gw`;OgIrblkdA=q_=XN+)f4Ox$jl^O;pg8 z0F?aV6m)rHLQ(Kl(3KAq|CtK9-Wjj_eG0ndUTN{KQP3rulLX(kdlbI4cPl(bnXuz7 z<<2wVg*#2WO}LB>Anj4EpzF{bD!o;}+emjJP|7uBvd zJNYO14pq>#nL>!WRzX)SjU(&!T?>GM-&phe81s9A`TcaV3g4!n>nH+T@HniXs}U%8$i2%_ zzef?AQlArnlJ3PM1&7kw*r`90cnJ4|;60!iZe6`WUXx;L2aRlr2@oeZREM-Mgq6HWg^ zkt+Uv1?O!y-Rw=1bmwgVf@+>eLDx*vKhN}kCjzB_bhiURDX&pMR|AkN^0q4IdKE~N zyv+)_n2a^zO$PFp=T^`)9_YY5RzcSoU^wnk3c5P*l>CkXL-~GGLDwN*2yXVg3UuuQ z+Htoi=#q&{sed<6+A9$_3>XK5WJh!ETyp}C5gC#kJrkG;yhtJfcLF7z+_NdL5%Hyy z{x$_&ZXEoDIZY@IaknY|uoe?Gn9yUwLKC`8=rkd~h|it2dZW%AwDHH{xi2^V5dT}^ z_sReM`}&95cJ=r1e|!I({BInveE_%P4rt*2<^it`2(ceZIGk`aA>8H}xM5(3y>a07 zfjfcw2Ob)D82IWU z+{(K(xgnW*b)8$C+{Zi2KaBsw{lmj;7e{oBAdlNN+yrH9HmN8Fa)YLoQo z>()I)FX5NWW9jX@8g5IEK5M%3fbuKik?@ut{|DUta9euyzncD*o*s^$gtvZo@Dk+I zMuAVTyP}ud(aSx(mwRik^!`Xk*&bW9l=z77dsrdzqi&j|I3ib*Z6n()$vRSyisC-56HH2J_7JQc< zg+O5jox#4wZFJN@@NIOxZm6Sj7c6|N zh+AI;xblK;qw7`Y##L>1Y6VjsmK2K3I|awiDJWU_*o-CRPZ@Vu z7R+B@|IT;1UJ#y9|N5RUua?i}O1`s=yxjZ5I}9&-;K$~$Asm~msrI-MEg$C&rS6-E|I3;hlE zyOq!vH75_h!92%;JD&0N>c5#I)qAzX;WyrNqXUT^WKE_*Ipr2X%o-YLj^H;3sacPu)@}&GpS3g+wlz8=%F~4Lv zdsq3l-9NVqrB8k$XN@22fH=_RFm7dNAwSE|mFS-7bKbmgA==hAqWhoOTup2y6Ox4->&0@2oP*V8lOw2F6G_l#t{Q0xH=Fosp0 z-^qG&`pRctcCa?5oz`Z^{v91?dqMVpw@okFaALZH_3*NIwf64ho8~+JLkISt<@`h& zX+}jya<)IRbHdM!7=JBx0a;+ynz4spf5 z+pZr8-WvsOwJU6#HRoxI(k{1!YfZFUZHgvmB^}L_esdvy);=lX*YPc3io+J=cq(~z zgyY?$Ox<24zgu`er*JzVikm$O6azQCT6= zUEAFh=T58VjQ)th?x%;vPr9_OcIwmZtkouS4<36t@~!d}QwH#^<*6%TKlm_n&y19f zZ=3H8^}O5yzOQh<*;nkd$n3J8mpvttPA+FQtaK#2!^s_2>_fNeN#e_Qf&Y_zW#}0R zlHPWp#E-(xtrb5Q4?XmHJT4LcpNS_nP%p4g;7j%ie9gLg+ndyT+>%>P;65pKBiOqp z`CYw|N z=-efH>`x?nW{FKJ`CbBEVuwR;VA`N|F8O4uyo@$k%bi+Qo48FJPCNoG;qD!6;rb3O zs>Rhdz~hp7IGL^2eY{@IPKdonb&tVG_5!k(uGKLBJHR8cHGR9*B013hq8#xanyij%DTcXr?1h^&f zAO{kx_owXI-9!0?v`&b4^4XW0!CCR~2!oGj1@ZBmKKOVNIA`jK1|N^)Tw7Btd)mPH zY0hrtn0&ll>>PJ>kIe)R;g=#qBu}Qs64J@@E#j21zeDnsJ)Tl$^Mmr0y<0WC zt_BA*$~Dp+*(&w~B=2X5$ts;`Ua_(EZX-l#`Bp`cHDSv;lwec?T zrG6UWi|}^tDx*(Bquzw8G+IdCVg9D4?;w?_Ke^A$ZQLm|vNtciod02a_u5&~UxWFO zq$B;LjXv=LX$+3z9z@Dog}v>}dDk0dL`a3VWx-*1x;6jYsUj`OCDX z6aB8@W29-OeWD+{+$x8}r+nAOAKdI;aV=hwuB0Jxi)Oj{>NwP31w5+JJq`^36yO!_^wwt8+>*WcgR_M)?=3C zN$g(#Aza&4h|TNnVeC7W^Eu>4yU%)vy6;I>+L}7*E8U_#(p^P*uNroe5)GbLMI96l zyUGuxjD@!axv7&v_H#M8qgCppiaKe7F9{6=;}8W77Edgs3@l0Z(3{w|5}N7jKDe%> zSv_~cNxEMXR?5s8mGtphQsy&Z*y|0|GSD0Ib1oNtm~PX4;s48;m)qeHk<_P~Gpmv4 zZ)-!)+d_q3IhySoUZ3uMA#;be;f?7=zh|$V*pujHuXzi7IAT?*FO_zZG~^pL!Oc3< z>RyS2*Uu9B|LV>^$sXL8ODzY;+VvlH0^Ez;o`l#kkUQ34d)dqWmWT?k%+mgB2 zO{Ju7rHjsM*c8ts{UDp|Wzn?NdtHTamUCWj3u_e^+Mw_EG zjeAewWwW?z<`nDXiH-7ncxZgB5hgCsz};;x>QMnV@Po_QuN=NS>BEYcIIr`0_r(+T zG0h)vb~0J(7x*BfcFV{+y<1|tyA8ZF>Y@a?6k1&hp55<>ybDF%g&_~ak&6!GV+49> zWYgvKwz^K_-KhZ@X8=V{)0-XR!AryLVnjq#4*GFg)PbatYizSKTo<(=?GXd^+|%Rk87 znQV6i);YO@kvmJyB%@139NcpE>yfW@CTqL=!`+=j)(jl-wA~Tq=ls}!3U^uydG3#O z@3z6mMBme--`bPP)^Y!46zy&Ht6A&|pS8#~Go+C^)_RjjH&BQ!J@cv6z9W(7xz8pHIggEhZN!nJ zBh#GT3~Z5ShG`id#WG_DZ=GU@ERPw~;LGG~I_EKlV zdg{T=9`AT~cm{VIxTQR$g}!9ca+0r`Jf;4Iv7FRG91R?k!BOtYWlAAIzKMSp;f86p z$oFtBAPjroGYV;o;nW8-o523#cnOoG>6`Y&*t{FeFmBr5SGfO$e)F4W5{7(Adp@Xz z=YFc`k?iIwllx)We3T~Xv@tH~D-E4611O`^J7+p_MGlDlr4IJ`R_PIC($3SrU*`La z``_yIsIq-&?x$;XZ_-DctJ@LkOvfc2xuR)bunxDhgdHDwDJ8!`A-OmI96Qsnp{HyRB916FE6&*AA_t`(U%o;g!A- z+;`uiZsnWB`%^7yfj_oI#xBtFQmVR|V{!cdzB_4EnXSkJRax2F}{% zZ%o()95&p&TgF~rBSS@wN;nzU%YCr-p$iV>!M!WEoc?fVWl}vl`b`1GzWKkGeBr@z zKWIDS=Lyi;1$2j0-iwjdAwaP=aRPoTa{6O)TrP754*F<^!_o_ZXd)-gFO{V>_X~!(ucM|QZ8|{06vh+@4$d&G2oEJLf zE~DfwttmNLnjb#yZu*`^osti6jz}CwD!L`Q&%4yGoLMa7JXZsGl%Ss^ zbN=mJgRZ0w)OoE!bUFCUuJ2JMnRl@4imv3$=-PGe9qVi@uK8h%UHW@mPo`@1W?iZlTdH(D{7W z;Az86dIlZK{Q>eW?fYQ;E7BEM$X!3iJkXD9Y91(=c_8M1P7wAa{N>auSxq0KtB9@C zPtExtxj%Ou=T#g-+`Ff6M=En&ogamToL|q_<`QwLvHSAXn|hP>$D(Ibqx`?q^~cqm zsmv3k7Tx0KjET-Xn!{0a%OI!79qF5L2Zow&^26V4`kZE7DrWihm6^mEBe`hemIe?2^T_le6h#Qzw+Q29fxN!|sYv$(t0haCxw z?7z5u_yA})f&2%8$4%ff2)q)Z;la!kht<9Aa9>V!7TzLrrKvTAw=_B3Y0VDzZg_D; z6mw?J=$n|Y)+%zidpuX)m=t-*GwWgCfiFC>?$Ya%N+XXaWirma=ehbM`cuUL`q905 zby5WScRDZ?_e}$u~GN=CHZ=W%Ik^&O6I?V)7ue)QXECv=@u zUE9sP9d`x66Qfj}weT+Em)>Ky`E#m|HkX?tP)#5 zY0SfezZSZ8hbK(ip+#`tTcpQzV(GM|HpW-hU2FcwNprN4XHtK1o=RxTk8Q#<`m`S& z#hpQ&k*wj|#rNcXS8?e*H*Mh{^(nZ^9J#Sh08bR&s!gIlQ0Kxk-nd=MzsshLleGd_ zC(sg0nHODji{qDSeL&KZbpdR^bPA8VQGH3>?BHCx{~q^lkqv(e)$V3VEn$UiP*jy| zU|9)vo+K?~YtP(_;2_@wUPEi>8t7P%fT^=sicg>%kYgrY~$$0W@g;roNZ+AdF+dq?5=wG;i+Zwcj+b}so;Ybsj5 zZlWiWdoI?mpSYsTo>nqQyZp>>ZFkjO*mpL^>Lr=l?%Av@aX)5yram~}hpr0j1IikK z1}&hcAUitSzC-<>ljy`tVXexd7pd<6<3{#*8f!`;iT7=FX31x(yiJ@fTx0NECASUw zW}F$+9?-d?HG=WSRAc;s-8Hd4#hh;xW6TEH;0xOO5_gscw>9_LT*KRP(RIHKH$(R` z>EOSsJ=urY)1EEdV=QedZKioto6#0Vn>kK1r|VH|rg_#+98h9Ub0lb&hdQ<0$z!gz znPcLAz0F#Y!T*&u+en);?|`hl+GZnZ$G_HQau2<jtReiBHP=|R=DJ$jJ=j=tRcnbdrerUEXCZU=hTd(gyDGo$F~2*O$NzkGlZ|{V zz33(&Vc1oJFLs`S-@w~G2CB7FxtB=RPZhpa_yer9+U#2Er$3(5TDt9*(?JG|*;_0iIy4`YY&mKTyVu`&qBppYHC=WDa#0^AF6m8RJFfM;V(X28|b0 zeYS{xOZ-1DW>mI3)OYr+cm8Qfh|xYR>())&yKenNX`}t0Fh6XrVgCd3=&~*njAt-j zPkht;3oGnO==rK%uf`BcN1qAZ&VYWWL&wvg=REYn+$QMT*yAB%(f#R+o506P>pw1c z|Kf9aid82O4>8wWar12EdS70Ya&Sv#3iI6hraw8Tt4QYaB1;!tOy{nHk-R&&hnYPE zo7f)`@c(0dk|U*luGl9;r|X|~_r|%RZ#&VkW!xHUFX#(&Xz3e0dCL4cPZW6+>Ji=| z=5lP{A$_RcHXs1r{T+8#lQuTYbWc*SeIUn7)>^=qwTPFerX1Lk48BwJjem-uze<|v z;EP?7-D%*;9afvLMbv|@2EKw@D*jV>C|{@S!I=aOHqtfs%OrqD&ks4ylz`gD^vI-H z)2B{<@Lp$r$wKGiqE)4fS1xugC@romUAm-b`BTmnrB5$(-n!CRy24qxbg8qvX!+v{ zowCcXX!*hgd~#M2Z^hD;%ZmXjY>M%csy?QDN4}=OYW9%Vo3S;)P2Hw7jTv z30usZOP83atZ)`pN&+h?NpsaGXAz;6EnoN;8{6iWKgG8(s}$mb@zTeP_p2zTI3u-e z+0*AF+oHt_*$=nES-PZn>2mU{wEPyYB=wS_Cl+4KYw41Ogj+_*9xE+h7^z8;YAaZ%+$=Yqv#zqn}Gz0Rd12a0SaUAp|K zNG)q+Y54*v>{WaPqb2OJTUlI5Dp$WtG_y@2wW${VL50F*%krlrEgE=1>0?yS!X-4x z!papA^=jcMiR)TCsqf0tqH?Le;-yPu3u2^J)+s5Ni@Eil2`k_j z`AaK{%AG5U7B4GbxI&?P4ZWp;-?Xf>Q9T4|l!_q6cS>QzU$S!Xd@45{KHz-wG_*s$!^K09Iyl-xjWq)?ZP)yr{Ccgia!j zvLdK~1wZOjDxZGw*kh%uoJGr*7eN@=bjpED76GV;3o9~BLpUkwQ`4|1KqpGuWq$PL=BW0>+URN%V&^M)&rIe@Q# z!GxPlxWR-aCY%h+CEh6DRNx5ndq;{&?=bN=s`g7fwRr||t9;H}`}Q_!_j z`Im23a9#t@hW}OtT{6I@1LxH!=-L33bUg~rs{~5;WeOVSIVGPtxT&%{CesAE@_-U= zih}bd0wvyf1zkgdcM0A!03^GuiulysaxDNh2Bs?Lj3&~=R2DoJO*34JDPG+`x> za%>Z5)Q{<2rrfS_AhbAIX1yr-=qW%+&kdA(#{(tbQ9wKSrYh))29D=@2#|Prnu4xF zH>-U1E9j~QN_~|8CA~u61Heat%(3UqQP4FWD0m7KJja@Dfw)~`fP!Z#(5MffQ6E6T zD^5X|9HN)_T?~9AUJFpt6)4}|G2H^i-v^X*w*#g8n}JgP8ldE}0Vw&b0!luW3cBV1 zrTjC2QvN&zU5P-+CqY5iF*aXII){N$?n6K!rTq%Jd_bw6SAi0)7AWCrfKorz3c9%e z$S8+ENq>Rq7AWN?1WGyP041H7K*4)5Q1Esu=o$l*_@fkbr2-|q6Dau(1qyzN3cCCt zA^vSZ@jnjK@js@ZYb!8}@6P}wTqRJ#EdxTLdF2YaQi0NsE)G`V&H*J{2au}D^DF3T z1Cn*#aRpsIprpGKDB-sQA+@|l1zokkZ2EJJg7d0@Qoaoex>A7?hmy z6FRw|psNEY_32k|-f^JR=P?Cchk%maKA@!60)!;<-ciuC6)5pvRdC*BpaZ@kP|~S2 z-2!pDYJk$;CYt`^&F^E)@2TeZ5$1O%Q0h<4)r%b2iUK6~Yz7KGwLnNUuSP+aoVOQz zHBd@Jsk&1`f~mr=-LI8 z_}hU~fDJ%MC~vERuFXJ+U#sA}4M2(SQP4FWIF)#EPF7%o30d0!sW!1zj_N5`PL% z;!g&$?wRLS&@~Du@lzF?HxwxG6BTsD0fiK!6?BQRF5$bv3^-55ClbCxL6;Ba^&t5) zn($Q8AoEUH0u55F48YWZm9?*vT|t z8&J|e29)w11ws;ehZS_~17?$ci-PlZ0VVxyK;dtzfHd9cJfNgA87O#;2U7IBu?o6a zn^yIv;Jl$gquzjdgco5dbg-X~l1>Xy@OcL)a#7BW16_^4Z1CBp;Jgh${_>Xbk18GQ z21@+#K*2-KPlw{4s-VjWl=wpxoF|bb{6!L%{4M~gs=RXwy4rve{BvaoDzoG=zlPe4+1zI&9lQ8$+>b z5#|eX*c!vOhhYaH{Ajqt))Kxi9D5TEzr$fW<~Z)K**ap*#b8|_wl>yb>x%Kj+H9}J zZjBAMwZ!h@OJnSIzFdgC$p2maeEqN=(XXMO!?vMcHSfpz9p`;tzy1AeHc$KptZ8hG zZ-{r;YU4M@+ib_;kMre1{6)U_<2(3rynh?^HM;tH1~_aT{m=Ec+1du2#>&RNg#B38 zcqgHS|JxIG^8aE&R{~)Y&hfr&VB@*?oa99d&8J*W5{vL{xJ^Q&M~`qzcA(^ z@BT3zV{EpTjC~o{j>u@saM%uK9OZw_UA1>n!*^}xzyGcd{vW@qjsG2E&yA%9?mm9E z&9;5q&T-h<7`K)GE#vm_zxtk^C4ET*WetX+%9p`fvt1FXCyq&_*<2~@GWt#G2Op0 z-T9{bM+6gl5Y~Ls4^96gra!uo;cv|+O)~vIG5yz??w^=$;iF=|BF@B*{i4^nlg#iR z-=^@p$BJ*d|AV-K@2uOEzfRf2z1npD+zkI4)BStX{if*-H{D_vN`7`pkMddbDSp!} z^A_@}HN&4V!w)q5t@)F0Ot&@Pve*o-r>gi{Ot;vbl3%{*-e$U=G~NG~=^kOaBTRR^ z=}tD?A4z_Ed(d=?yb^br>1LmeQ64{oBKiF<)4#&>k233Pn&}>G`p+@(%`x4hP4^Dd zZPC*vaR<@UzWAFedr)_h;1>9*z%zh}m` z<|C((mZaA~IQfZO5w|tp_hU1>HGj9t^ta~w_7X?JTl04!FT`#A#5RezH<%wv7J=l5(cx3`yD!Ul&g#S!fOS+DRP_6k3}m-}olcR??AcQ3cd@!2cRxDj! z*^{zFT~WzugY~7jauwE6N_*V%CB$_fo-C3OmT4D-hFT>MG%X}dOCid_M|QA}LNDlx zfuC}#)SfI_zGTLd*^60X%3ivB*-GreD38kJPtELEv%@&Vw1uo!E_kqf>3mGJSb=2u za%JVh`;7$*$0T)upnGMcj(Xx1QRG>R(=im!eNdR?({u~tyb z+99TE9($~O<%$w3n$bj1PpN!*Y#F9FDj&1_ip6S=v3MqnN6QzkSg~-yY)~WMlglv! zq7}@XF(c2U7tOZu zs6vjnK2gf?R=A9wx1N`bdo0hMmpjKzvd_QU|37<`PSQB%}g^r8RW{Zn)J*fs}H^Yf17=onLkhw zTFiLW^NjJMwrsvJewDA+Bro!V`6%-vGYh@!%QO&g4(%k7B;3VfW1dJ$U8rgMaYymY zOtE4Kl{7%_%GR~qqASf#=afywpty+elxv}JwW}P@p6tuZ$1&H?UHjF zZ^T`^=N`^3USQu08*$hN!})+P&H=J7o$#}`^K17rOoLtIOu^G?AJMF8n|s2>F!uz` z`j`1{uTN^fr#`7_ZkjKR@TGH8eamFp%%k?p%APZi)-a1Q82i}9gTp^WYrCG{k-bGH z*rz1tU)26FwdV*mv0^cMuS)uBM-H+_4RtPo^Xx|sglfCmv1P#CulV$NiL<$jD!uhj zZ~4k1qrBTABfK@Cp54(A(cEbh9jIona18shxW}eHb0rDfSCRhypLX~bN5*(RuzG52}Q;_Pr)A!*WY5}4e1 z$dgkzo^!!vV}4vaSNuGr@mW{*Jv;OPtvfiw{DB@xes}mzvInX<=jrhqbDkQX$vM@j zxBuhZ=BFEdiy|YvQ^~8Fyu|)Y>A`!vx$&H(r9NnL?nt>KJ@0w3V?hfk6!9B+@ z&E=$-Lq3vjF*r*bTl>MT?)^&SEWl)FCmR~df|e$+=j&IEvZtwQ>Hp+i=bH^bJ7J)$ z)ttf0*6X_MBaU3)oU`0pBWLgAoHKjSd-h|=Uav-b(#1CXbdx?L@3kgCGChaAic1zP@_hhJELoK9 ztCGEe?5RsHEgN43&2YEfLwh)Hv5~ebC(MqN$n+h=`C!pvBhCklM*G;4_fUX(s^^?^ zE_F?~hjPox$8!fw*_-qS$!9TfrmcD6=Ld9)wIIL;w|PF~5jkc!udD|Gk;XSf4V?8ns|x$T)+r<`AT>n1(F{oqJ%2Y2{w z0+$JTWa|}Mq_>nhZoggIy_qmfp5+_y*0^6ebGv(Y4)Oh*Yjvn|t===l727K9^uhFB z)%$Yn*jUls6LLa%NORH6PyD=fQL|6lqm?$2`u1}sl`&044Y;NLV3%(S=Mf`_;~OUilGa&Gn9TPcY^25a0Ckc1HOxt~;SlccAuB0W| zmldKt#H2`BGr0Mo->I6O-twnXUxcpZWzL7w>obKEl4n ztNO+*Ro(q6+Gc2-CtuNacVugwpLw<2%q?m z-XsToVhcFP9_oOc(Pc09Hy>)d&#sPc>cU+@{Nzyg`HMf$I%BY<^2LMNZW!&XK!G}& zznL}<6u3LTT*^5v?mfB)UzPB3j;#~E@&f&0K)9Rzwn^~a!SLNg_^z^b5 zz^DB~&X&WgzTh0q%Kx!x%NIucq~@fzHzr-^3Lke9cuHUWn)4j{pMCa~A$CV=2<<0l zcojdFGi`~S9UaJ?US-$m_fIF!_CKr7zDSuKxzg{R|9~E^;vxKhXtT(OMAH7|2iZ+u zvyb(Q2i?2FsE@?e$+N48TMus#`x6Jar@XQ|eB3G0|HZ-at-CY_JjGyVx>ko4sI zK=LGa#kxT&J3PAS9lm|Rw_w`K>k!)q!8D}W?gBQfuKY5m=}Y$fUo6J%!(h%+_Jor& zoNC{)gb|xNBG>EL1KlF`2=1--ow28J=7lqNZ*SbW7R+Z%SB)T9tw=hO(!U?}XRHGw)E5zH`%-vNh`>%XBU3 z^S9R}RgwPt$dy9cbJA3&cNqI~vz}R(GrV<}cYd6^v_K0fJ4KqFx2HYbN*ljnvw1%z z&(qw$Zok|uOVEd;TC00JR;%mQ;sU49 zkA5Qed1_&SQ@qb7P5C|?-tq?Vvm9F8Pe|h@K>MZcvD>gMmCb#J+j!<{A-RWWyJ58d zL12MCG?2yqb>@EVhQDH4U}SnOI)v~wsh4_tY@igq$!m{nT|z(pNccPF^P=^ktu?so zMK@I*;C=VJk)M$UMp2@58B|kPixc+1Rugu`d}r z88Lw@_#evb?ixKliu>BiV$8UI0mt+9QRxlv!V~mEsoOX9Ci~vNJ#4b(OSb8O65=-S zjeDD;B=7CigWxH9?UADuHQ38wFqQDx57;Y@eire0_qe-o3+^xeqCV+GJvNtn?ke7- zzx)=|9W?p&|oDL&9A;B?^ID)&}rzpBKGP1RcdX};@`MbwOykFpJ@9w^bIvnKpa{dvSaqf0^ z=lL@Bh0kzzo+#$L<15sEYj^&B`oyVh&I@XZfh^oR0rWZapp_biGH93cN{taliX3?x({& zAO2>=``?s#>aAkm=PK_NzPsosVZl>m%2{N6U~Y*o%69P{(VNcL9qDJ#m80Rahkh^; zn;i6Cf>0i0H_zXV6Om)J+Fx zjAWd&7`Y;Cvq!%r@W$I(eiQ8>dJQ(n?wJCgEBRxpZ~qUPd^;QtZzKIAc_4QnQ?3Yj zRVH<~j`vKF+4ObCb2%UOl=G<)Ujl6*@)dh0=luif=ZdYUH0rxu)9!XWH|yH4)VK6s z$8&S9{Z8GA&gXz8t+dCIcK3VQZqeIiyd3yEE67k8jX7FHycuyNr5c%xB%m!bLd5)>%0XG%Q)~HX;$g@V+T*h82#>Wr+vQ0{mQ66m-$AoUW)CS zn6lIKxwG)-@+;kUZ>MfM;Jw?SKRI`^gs{TT#lK%9=M`;30`Ji`&w*dRUl;gh&<3Mc zFY#rNmhew_W7!7$R(pt{%%6+7M_}bVg#*ye|=<9~A7zWLs ze$!s}@p?OYrLt!rXHid?dfyf7Uba6e`fgG{+OrB9I9WhR1Kq_)L&gJVh?9H2$0vPK z#-?&Nsegr>hf?uGUzT`sc8v2jtvS#LZSj!Ym0M1{gR4jRst6Jd}-= z`WxDuLw%IdZdJExO(kperpm>(rl$vMotrqTIEr>IFA7f^sSjygjDDU+*R-F7cJ?p53EYm-{lIiUaSeW9WG|1W#* z0#;?UHTu76EtJJoh>N&%3wQ^wC@SvF$^}}fka?kYyJ7>91uPIRXkJQC$xDk;)XJ@X zA68aEva=o5;_SX7UUq4?OVi4eolm?}7?_$`V7Ax!jrV;QE{SUI^Zn2BfBw(;uZPKa z=lwS3m}8DP=A7_9az9ToI5I=`* zGo!zqamHOZ{wb*@zxt=Zr^$>@>F>eh`!(x_LClF4sE^>zDf+*u!PSOG1>n)n!Ad1> z-nKsFtpwL(nPSo?>jiLLd%L+?)ep>Pto4pDN3phmmcio+EsL5U>kspE_| z&lC@iw6Et`&hC1aw~F(@EBh$%-$0K)F$Vg*%Nn(#@(&`M8{hLO=bym$&3ZZSri@Qh z)@HSS{%ZY>QceeSEg!m85bp1&e8I-OaDkQ8-E6gkhb!^24!h2p(y6qJzF>-o6f=;-QbLMkD zoZz|OT|PLMZ&GUQ_88~AjKcu#)hkRH?AXbCIV)w5!#&0>QNFk(vl(N-_MpA%EV=vF zZobGEi#s{nc?7><;%5x9SCiLy>@iDn2^(ZTzxSEV@Q(E&cbaVos4p}GI0B54R)I5< zaM4RLiD$6CvA4t$u+OpCzIPpI&T&e9o@o|fA55G*q*<_c?dDV;|N6;3fer`mLmA&v z1}UG5@)c4(2l*b}Tef+~$wKEnYM4T&&<~MUUZ9K;Pv}B1b_aAI5BrcMc}}4(v3w8W z`*q+j=70hD61xn>1@LOH)FwSOx zM=C9TNTaK6T3o-#?zDT*;@67Yb`@}l~`QTj?l6~`n>SuFlQ995=n39;>Zz~SffaqP4JULmfAL)rkw(T zr+|+QRYo%w)jJb&!DYF}uKJ0k8w%|O&O-1x*FM;p%lt4HJe2f4;7-NaltXZ2_Q^j} z9>G_|e&mg{mcU)*mQ#iR`_r`Vtdp~yvrZ0j2I}SLLOGh@Bi!ZiC;ta&qmF1oXY#qQ z-&R{~@b|m0A6^Ffny1s(_B2wZt?+ODZ@JPQ>Z6Q={|()-a{Muo|K%c2;>>Kl%vCb4 z%>oW(EVI6|gVUA*a4-g3^TT}zI4t-rczOyvZQARwOy0YCvl*O@J(=$uc+w7T`q_Pn z^BQAf0DjUggJ_q#)%XSAcc6Xe-ZhrH8P9q9pV~Z#c#`K3@)SQwt4hyL#!M=HGKUr$ z{Ozfv*9d+zGe@mqj?1O(*_W#ox-o*e`6hoP@-dA!a^ErD*ne`RAN)!l{D#Zu+jN>W zz$s%_yYNk4G8TjfZ(&?iFl9^EXtT@orOaV*@XyfwrX8%QdqLMboWppbL7l_MQ|7Qp<>nkF`yn!iAp>ZQ zGX}8)lKC-6T@RpGeU$W%$~`@i3OqdPYLik@yKhOhvk!G1tEN*54Kdoc!M}gFq_&I%+&2(@mwvXzDrxofubWSCKtGG<-wX7q@WT`` z&5awqh$FaR<4&X3I*8LqocC{6;)Ms_b%S*pYgTu<8{vQ2&j-m)SK2DdR>mCS#y9_9 zWn?Nc)LZb4zHsB4iFXY)(HCDYz5mw!qVDr>mvBO_zJ`a%gD>)XVJxrmWQNTD~B{=KkHfL|41U3G5BYx5xkoKo=)Mj|M`6S-0tTsm8=kWM5Xo#1KYg_f`Fp+xoKzTBh;X z-QmmqtD1E_Hj4gAMgDyYZqV;;+~^Iia_%8CpLM=F%!8$0Lrm7JWu!0J46BKFfaWDeim|T7IXG5-)ph@bEe> z@0T)+^`{?lgQ=`P2LYK|n*L7NxG%0rc#R5ytRvj~y{u;!!T+#V*E$Qneo?fxrlpRd z683K5NZbqU&teVSq_!#YKWMP5Wv(IzKKO&H>)3F3FL=GL@fTTq z6LXRs+_Z=LIr3ypO?rh=hIZ+FN?9U_``SMfS1-S;$)zm2iK~rIKR+3p9gW|iwAmEe zOX$7uP?DFOIkclTlC|0_#{3(k>k?kFeK@^c+~GD7PS$|3j%{Xd>nd_kN!JC>l0Vy( zwuE-k@X>sWf8=fW7rN5s;qL(F+Grg#^xsI+-p*~CxH0Yr-lmMKhsL?Xt27r{u?~NS z?*H1)_?IEua?==My1jT&3brmZTSaOL}sTw`VzSmZ66lr#|E6 z+FQnETUo%nLhx?kZ`jM#@hRjN(vtev)Bl;YMBZ={_x|&`t7|=U@D}``KixPga?IB{ z^uLMY!fT?x&{f8r?B{(kg8Qi6o={nZ9NWy=Rb(v9qiwAY!pL0dmJj?SJa_sxuHa5p z19!l$Kqih1SmM3N7!?`v6Yb)A<~0dhTxl2oYO6&4KIdE{vPNiX6Lhc9WNS^a=Oj!(cjRkRm)K@iXT+~6gK$mEiGI0x z36q#Jh1Tz3Z6Ir>!OS_S$ZIOtLwJpH-h*E%`X|EUpM_>$AUygftb>%x7m=e5BE0a< z;wE#Qe5V?^q*Rc{`2S&BJjw9X12Lp=_++K?>&1hdAO7GP)i}22@Mm(U({N3m%_04P z>8!QUW03W!=y6G%#xi##vu3=&`f*6Kf9waOzcyzU=?!tl8M~xBjqcRbC#sp-LhWVn zjlkNkaV56Zv z$S24Vi2ULVJjLE+#np!8W2=LfD@Q+ArW{R2`77Rb>Pho(*rFU|Et}p4xTh6(Vq?A20BXRKQ6fgy`}N!FO5Tw zDH(mHB<{0%SJiJ4odMVWU%qF&2mM?(M5(z(xt9S&-)Y>CmeF$0_%U=84udO(K<;BV z*~V}dZcHNh&YkR`*SX*92lS(^!pooG-c01EySWQ2*z;aDx89uG`&**xMYZrv8n?yA zDm7g(Eis}yCu>Q0OQWm0ukL=|Y71{_Eyf&N>NVXQa9^z1XF{ww7!y4q*5`@f=>s|e z!6AF#A&cFljNyL%IZep-pJI*icU{-+Y1%Zki&7&xP0tYaY4GdMhQQcBWCqXB_ZNNr zd3&Lxc8X6I2kUKB*AD*jql>KDS;rj}`5t?Gp`5>q4LZ5YxegqP-JB|D**BPB^2mpvdd zi*4Yx@N~=AlW#CZ$CeP5ebufF(5K%1hb+7yFx7Ulx3hg(?-P&p)igb=T-1 zV`zO4Ybn-=lsC@#5$Vv!(|#m<(G?oZUd?LS$5R(^4eirj7t!zJYfcBch|T|mE@HsR zYUk6xpo^&LAkrp7Q|2Tzn1;rd7?j#*?4G)aLOZ5FXWe>>vW67h#Hf^8brTipJWJJ0 z3~kd*3^+N&Df_Xp_IU{x9;kAbQI-#gJJdca;SxGQE`zWA40s%A2+5QEZ#y*Q?vn$Z zn*Y$)b(H56xNZl}SxZfOn)Q1#`1lQG?fat~vR1i7zwBaf`~&=mAB^0w$;cg`M-8^x zVZ=?5J~x{4s2hDgJE4v=GYpFTGxw1><+vZo>TCQKP^ zA^kLoJY>)6YxE2o(Z_sB*TC6A00>;awtO#b*Y3d zMUu}X@(CdA-%v*BKSu{)kjIYHJ^2qwzQ!0`;q{381?v4$|&-zv{Agf_)s zm%Sbb?Gl6BZ<6kpj}8d;EhI1>s_#(B`l1q?7;6Z(Pom!J3&+0C+?Pr{D$ws(K|Xn; z`DdfAqrl+rsP;j}0~{3`T232RqyHmqzlZgW4|bWqUnBfej6cy|IArumNhQyGEzdbG zP=_Am=e|=@?yVGB-{_659CX-y-=(}i@Z*wSey3pUGUc+7@<~0pk0JCa@~wD6nUl;q zTy#Ef$1T5G^08RfhShf}<fslN z<5Ta>W0}w1o#?Mje&I>w#s|^L8_TG>q+`G0+Ra{A*VKX~>u248o{KTiA@tlG?;?E6 zzfv~o|4ho1(dmhAGojIw=RdLj@02OV5a{Os7E-R)p!I8gMfWDKK8Q9@c-!E#UcM~K zSEiSby@Mvhx30A3DPN4i-w*!8QO37xzc+WNc4LmVFjseH&hEk7{TpcX}Z)mg*T z)vEE<5te=%Pd)*4gB`+?4cn*9dlFaPsF8i0%dD&WOtv}u!YB1R*vIke{kGg=5w_eK zgSTV1A;dA-5bj7Ybaw19Ml^ki-dKgPYtwhY7l+?}eK~D?jk)=iyhjsi^5!L+Vi_lT zXRjE&9iO0U_KGpYaU58o$KB0%MW(FApFusuUG&dt2)~=K-$6^>g}+{jUVjbdT_rT- z6mzoBXOV68S&rV9(bTjL{;SWOHfJgOwIY+0d6IY`om*Wa&zbtf_CY7?Z@688zj+dR z8ch8QyC@@NF8Gjkx)_Gel!xEiKxO1iqcZvxXgM;~IW@ahT2?JJgoJ$ds&nMRH=U#M z<|l-G_KI`#!rji%d5%k^ zk-KD1!8@cdCciRy$BJ&n{H3<_p^Ha4U0%xNubATspc_}bl%t^!kQX?xfPIcr#?JQl zX`7J?Yn&s=e-V2i-xD^0^<=lt-g1svSnC{^KH&!vpvpDlETe>U3L4c%2qJA6sxi0@_Zjr3oGE{Cz_`afx_Yt-Q+uu~^x z^m~sl-4K%Zs56xHO$conn%CdS$?aOHhXWcJPCB8O5er{+j-cO1^8E?2ysn?oJ`3M= zCeTJ*DWiOk;XBgQ&Hm}|XDuT~Y_jYzhS)19SJ-EdIm14ScYa5`B>pw(EN{2$(aUv( z@`(Hn9psOUzzx{F7mskh2R;N4KZLS{PU`P?7Rb3{wQNb~SZv_i#e6c5Hg)&IyYxfn z_xm~aDCRjk(L3Bzg3bc>y`O_$-f+0W*@?RKU-+`KKX`l`nYO#lT}qg}reveVd-=-E z@h57W@p(lF@0P6FJOZ4MdUJQH{d}~N_8xKi(-sonrG(huD=FRV1D`E=i}#E#aSmPh zy0c$iVM2ytYLYSI1unBL2)&5^7vN7adUB;0!IevlM>QOEyoYopZeQY_QaY(~?kXjy z{vx>R3Q{iL4Su-OQ1@sT?s5hhgB?NNW+d$4Ev&!8FJ6MrmAz@vtq~o<<)rIHefuta z*!i*&;J1?Zt@@pK#JNZ5tnL@f`0=_^K}RxJ)zgvjCEqW?U-kK!co!&##Op*~^rijV z;=@-9Pw?jBrs-jmMmQd&ucgnVz5CGKeexboc#H4R3&9oeq(bRxf2-s_64j#=nb$BfI^&HmW(O_pQy>$@e-k+-UUSwYLXYW+x4|`<29hb@_(PA+&8g@noEDC;Z>kykAz^y#jjra>)iu-xDu09%m*ThYnm*{dcSW zox!C&(2G*a@g(kj!S9vywS;x|rR2Lu@mA@>7tnI)_db+Y#=#Y6!6S^jFBGL#_GHDs zn7J;Q_Lp)dU}Aq+w(XzpBQ0wJlB#^MmNU99L|$HI(Z;bf>QpKNsHmDrrf4 z-ncxv2Q`I#>?=I0j-TCX`=27u-ndEp?}*Rc1GU%C+m`rWnmWfK8w~vpnjXfyDKz_C z=D)9)e>5tm-x17(md|R{`R{kkf9|<3k?&$t54(kVaMOq_ z7TyN6S3-*-K3nXJU@p9$xlrQ&L7fXNRbx%w>RfmqbK&QdPuf-5b0^=@rje9&w^|2V z?OU|Lvy}H{+l4lrp^tXccB9}Aq-;|63Ajn!KOx>*wB5&g+sPW%vpw3{5BK)%_ac2F zZQGl&pQ2xpm!xqQO>M7*FFL0x!HziS{8;ibqAO;kA1*RxWG>0zolv3kQz=uQg>#&x z(D}Z|clw@~>8xNLsg^O%c%Mp{B&_ITiabQVjm(>u;R`#8Cv;xoji*lMgjTrY>vX<` zHrof?NQUQnMd@7s?v3OP8H}%w>y}&Ik~KPb^Wub&^{09&s(!QJ{1Dn!=1HLocU)d5qU!w+FEFl3m%&L5z{WhL%V5>#F4##dA{L}PmN*qZ=1V1zE|hDu1(3q zreBxz_cK;nO=0z)z)fI?zOhK z+PHRXb=0b{)e*c4Fb4BLZ%q$h_}3`uZ`yMKW&AyD@fhRwKH37ese7oW$p6kLq4m?L z{|v=jZI5JdI|JU}u3Z}}SK(ohL!`}^(7(Qdwm1thr;j=}8Uy^Uk;ljr z^PNIR1^>P>^^fgOUJ_RD5B}$_NU;I(3kC60~=1A6Xw3I?vLsXON#i&i?Ydq<_?`RK7tO zjs_{UQjTq;b(wMmFPh_wLohyqa+LdOVajjQ!ql}36Hl0->}lUjKd96Orao_WTS-W=*Phx$m^7YW--58EI##iM+EJ@P)nx>k4t zY2z;8O0Dn?$>dv3-K9R~Xv0$arj&HcLzUWf8x57x4^Jm{ib?o|aLdbXhLgF*ou0&% zyinS&9pDjP+-H!-RPs0rUJFf<{rrw(_#*%OamgQ9|ED7dcnEpGgUAIQKz9EJ-jMWG z-zF28&M%%(UJ-Ou*8S91-kQBdc5smTRx%!ZkqNv8J>SXrZbnXj0U8xZT|^cr`WQRI z{2kBTtyGq>=Y7f0^R{)Jl@ZdG+{ap5X6$)eb0=lQIdsoOo>ERamzW=9{cx7CP^_4n zQZ1tUkA6}n@>RxOGVn6Adn`1p9-8xrtcQCjb41qm9(-Ad(c3YP@m z;JT4-S-X5io3R%Xe2npT6>}Uqas`9iR7q24Nt}<#(Hpw(rKxvpG3(@acdgi*{dmT) z5$4X0&hXSRDUriZ;rHdPm77=nWrZakzc1OZ*nc>~x!@67?&evxm?g~BzsF>l zy4$b4TETj^o4o~Sd-K7?Dd5|E$Srbxms4=qU<*XU+oYbZvWB5vmoF@LC=zihsA4Ia3G!7kViGPjq zRv48z8(-Y`z^i{*Wr?2?G54k0N z^O>|wk*Q^LJm2eS4|h4WwhmU? z`c1u@($?;By306`_EpQMhZ$)`MnpcNCX8}?uPDQdjfS~z8lT(zmGZo0vC6B($4cAN zU&9w*4`pm!qfez@h34Lk{{JasP*XE+j;`w*Ijy2oTAu%H8!bYEc7+8wjL_*Y-nd!L zSUSVlk+Ed7Fo$4Yi(U33bQ1xuH1=xPjiIGNwS&s*sHe(Cd* z#vB~&SfGU0e`1Wl*lIsn`rM>Z2S+(Z+ikfg(RSO)I9P}gp7%S(fXisHw*oWYd}y)A z&K_hO{N4~Dco_fO4OjdE_5+(43!=9le#13N zFmg^o_7M$Lmkh-7pLiXBskKoexOYGrf!asPPXb9U3g13K&j63Uba*C$Ry~X3)yEDdp@60_hwUk zY(6+H<#~}htK}i@vb`BnCRvA!Mi%M_!>xw0xbb=CdEUpPEb;B|ycs?A$8h)Gm*EU% zY?eJyHfhkoL5|+$V8_a-w%q*tZ80LVTqQDP_C4gBvB++(088GuZ!!8mY4;-Yy%YL= zEL4dme91SY>UgTn zIiEgi3-b@xXxX>?H5wlcANRjY<6C$eU1NM zZ|z^HxPI^Af&NNbn!)542VTAxsKf{J=4Cm!o)50aK_AP}HxgOxBh0yN_zyj6W*zKL zdkp0siYc^hGk5l+DoX9^or6Y|QI@qHWf^Af;wbNC%k`gZi&57DYFR?;mng$n^Z)X`*d^K%`3i08-FEh0+@);~6Mq$LdYCj2N|$F-w;*tiyOXHn z(AYBS^uaJC{xD^JhJB`;VS$e2>@$^e=5#G*v+`Jf3qNrldE+H;#~YqO=J{j9>kp6c zC3H~yCqVNarR;OyY2Edi@}p~{><6b&mwVYqly?Xh7){_%XU9?M(@cHFy5}D1^BwhJ zU!&9nwLTF!mW1g?pLGVG(T%FT2OX-l zCiAGN`k0DaT0SZ6V4R~LW2*OLTki2ReM~W~WIZo;U$l*>zuddn5BZ{dOufom=5f$2 z8B^Knm@=iXrW`5x%DB12xS2v7Uhf(-YMnlAf|jnE#1+PlUW}Vyi!JxARBhaxVO`{6 z?0SxyhFu+un=ozM#Hr&ZIOPcQRTTOC+_;&qj+-!*$9cAcakGatj{D9G_qaJKG7xCO zQN|5A9XH1f=L2L9Q1HAh{~%?g$g$!2{J=SO7#4R{dj=hU_&?sq3Pdq7vzWzUjCqX)Wb@vnZ8ISD$Pv8mn zIH&zr@dTZ;_(0aCvaioNCosqQ5+{SO&CCJO#EWGwYZCZ50DY!wlu^>oBd(nP$WVOMJM6g2 zqbh|nAbtzw%&C@NftnZo@Ff$_gOa@D{FcNKeVPH2*gMo0x-C>-N_Se z_JL0O))TMwC5+rwbu zCLQxfOeQ7^!`z`{V`gF=!90q|!Q^4=n0!nDW)@~PW)5a9W*(*pGas`6^B86!W)X%v zew8H{?)+7rz%0d-=w=ykIc5c>6!RozC1w?7H3pp)Scs+vCoU&rjy%^SeK=;lpe4Q97)-U7a@8z-<|&CZ*+4Jcu6;xfnvVZ%@v@StGc-c6!V>Kz6XkF(M>B* z%yr%T02FgWH$MW!{6jY`p!@fbQGKV(%WZcs98eyRE+plrry*{SOb( z{NsZ<_LoNj<<}#)WB(pgG=G1S3iZ-xZt_0@|3JtTce=?tYx(Cim_CSf#XdlT={IiI z!W{;R`#}w+dks}9m8xjadWrIif3pVDw*q}2Wt%i;jR%^s57VIa#$e6AMT6E&K=I$G zL2Ck#stq2X!zdlL&~65Ke+zgUuug|HI!pux;%){?dAu~3-iLmmlTzh<7@*Zlw=24R zQ!mYbqXw<{x?SG)mGJSpeVA^)5v_%9(V$h{_a$6vgKj^d+v{}u4&A<8w{O(#>vX%k zXDji0XwZ72r=GqBt;coy5#7G&H=6%O4O-K5`xM>orQ79EPAPvw4=w!X8no`v?b~&` zyz46Ym+1B>x?SFJmH1}e?xow)x@+O(T~+ay_e~|fyl<+8*X>@qUEU|{Ouqzm)x!B} z(7G{Pv!nN+#@i91*|%%ZS_%?Kx+NO4u4X()Itg^HxW{YI8l~HN=ytPi_tNd3gA@|} zga)lw12p?34O$QA_B!3ZNw;s*?NfAns&3!zuf^Y{L94u_mR@J|fV1 z!&?jAqCxdNMDag>UHrG|{+o3FRLwn=v#P590NuZj?r#P%vPYYw(^Rbyx-<+Z0T}?{~Log1@X>Yx9#0{vCMMBf=l;J z-Mx)<-4F2pQunL;Kk=K-f8%W|?YX)qud?;r#{Zh0PX3qlF6~YF{de@|#j?R02lGzb zkaa@{8NZsB(zN%|j*L3a>u9A3tJ_{gD;ZNdhFD{E@c;8M4g4?pZ7DCJ9r^8X{%`!< zCSF84Vm)r9gw`AU-+KEt-a=b9ZX>UoZ6CLT|8?UI@V{Z)IsUhdyTSim<7>wATHE+$ z{%^cv(;cKap>zWEny`I>m#K8}>d9r3d6(_MO%EE4Ef3z{|E`B>9^&P@^sVWH{bT7L zy-dyNm(s7M^Tyf}ho3m|gqLZ@(w$3pE%h>;t1vWH7@8|+RerUYK?_xaYw*OvyHNI5Rch4Kb6UqB$!=|g=D$n9+e?#}T>HaT^ z-6Q=9u`}S@?`BBRBl+i^_abyVK>bbRzen{{UeePZ-_Cz&JNxtP>`Df&r_6YaSB&P( z%P8P6owR&GAg=FG_`%9@eS`#SlLy1&R~-eXy_3*;dq-sMpr+3}3}==`j?9xv-? zkJM=I(D0vUWq3zscm@y~Z^)>&VZZQx%;>17k;C{GHGCNVqT+@P8xC=5Tyv~t3;;Z@h0|01C0@>?x&_?dM(juSLq^8Cb#|cqN?`x zP5x`|(d6jm+oq>>iSkK$C;F-Lg}B~Q+~GJ#lW2}iVn(ebuw&{vRu--U@DzUuHy}oR zr&^gXSW!wy*blcU82RQly&X?GabSiayc+w;9_ z-2I@;i3#KFnO;h50y+gZjJDbv>?>SBFGbGR%h~+z*w<{_XS?x3KlX|DCAVA{Wvh+s zt<1?}i5=NpnUjSKyuuXLGyoWh4o@R8N-p%8IuN;G#v)|1f10#788bK+tQG2C|81J_0;Ya2;p7jesk9?N^{@X=*zvE)Hf>n z;N~Q~-g4i;=@eV*jqFY_jopsI?n^ehKptZHwH%7{M?j@yM^ugr) z2f2$|$|3hupMS|#YktCKi^!>So&Wxk&y%lxdw%MPZ!3F#@vZrZLt8rIzR##QUNS26 zEsrXd^7hPW(m2Onvz+^obVO%Rty6>H4fCIpRowu0yJpg7_;=XP4U}EtyHb?a2JTFe zZ}%DFXK3rTbVX)<+0|iRz0d(36iZzC>j|LX(h{I+g^}@OVmx_)51qh^&ftePcw%Ne z`7oZ0@J-?$5TNo3qjEU1-xYL{Z$$fLDag#Fg)^wF(%I>L{b9$PUMLnhe zmFUc#K1mgp?k1lU`3_CmWRd=nx19RY&+fY?B)+&u-4pMSdkw`dVH!>0v8Q@*f3cxt znq}FV%@ycX_GTP$@5^oS-h=GHRNSV_*}=He@DCi4api%-nY1-JENyKK6%+Pa^mWy+ zcJ^63`(?E&?bvzOvT2R|mCBCBVFPUkpA-6^uM&@d;-1XqhU(1a=!O|ftZnIn$4!)z zX=&Y7aJ2*@chWaz+?uAiOI7qN?aaR>D*V`Zb! zQqP`Bd5(p5YItjD!F}c!CfDG{`cZeg?p$8@-XXK59F%i~_IGu>w8Cu|7`Qv^=`4#3?d6!Gm+0@?Uk~?J#%(u(g zD-~MaTgMflQ|IeGL= z6@BCG4?BJ1pl|HbPll&9YklIA!hI0-iOkDBl%KQv(*iJZN6K>efndsXn)?uKB5J3- zeaWA5DZ!D(0DIZ9%PhUn7YbPZwB`J=H5R#F^OV8ZWMhxHS6)_2^5ec+lKa$e&R#rY z;aPMdxKAcHdvULYMx~1%_a50#qhClHg`QhH!?{ig^81ND`@y{?6}F`_uBG_c!+F^Y zOW`jb&g;FfB2k$udgy0}A7nJ9h(4;6UFslpaTx;bLMMZ$!$t0|`X*^eeHZRhxD;k< z30msAB?w#!x?X&2PTuT=qO&{ zc}C(%IEkaBQSuRI5xNSEj_j*kj`>>dUsXc8fyWZYT}DSc8Yp=SU)4VCOwyKgHQw$n z#_64m*E<-u;~Br>7~{#6jAwOxyH<2&o)2N355+HxFyVylN}LGBb~onv&gEJ@?)xvg zwQTdZ{b-KL>}d!p=xHv?>RB0Oi+OE8QcMQ?vOm1w^Mce%kkX$+QQ6 zhpuq1fT6;Y>-(u?EOYUwCo3$GggXQ-CK^^-WG;^d%G?qbw%SsR-)iOxAA`fPBZ_v% zZiXNKD>TIn&v2ghaPtynd`Ave=9rl8i@mvrhdy`D7oL2uLDHjbC5!{Tf{wzx;4weB z+j0cFGJJPv1$qhac4-+Ve-*z=;oDnT2aLU8y4*;flCFLD&2%NcIqK(m_cxihSPQA; z@tos4%e;(sk+Z8(h8?Cq{N&!T(<6jm3W1OR1O0YC{r7wN@jm+VUivx>dOjU`-dXK$ zDPI$Mu^rXBBR>8La+dG{bo=~3pO`aUOqJ*EQfjva(I<@43nB0uBRa+GXM9A{ zcY=E*Y-wF#UOY}*1w3xx9hV&6Pb}bg@b0DN?hP-o4j|kfQ-pK%ZQQl@s8U<9d|zV2 zh!Sh#xYU*%<04ubKji(r{SnR_Z}iSh%A7_MI0DWOK;Nd=4<5JOcz`c#2 zN@#&Twk3|4byFjAoQ#XcflBL0U~!k9)*n8*!}>RvOy#Hj##NquenO1AU8f{v_WTkW zC2b^Yu3k}c##tG4dI`F)tOw<774egHx&RM+ZK(D}p5U0c?>2_}?cp5C>TZlbAFaQI z=VzV2E&|%BVA}VExJle&-ISU}%A@kA;6TooO64}wiNwt06^X@nDz)?HnqqRol^+`) z*mbvy zidWDdsb?F_WDfCR4wgwD4%(kJF~TAwJk6^W7K zmD;e`rWkXm)+b$~yka7U-Ru){;=k4>azENX+b44O&n9b-kBBGnMfUbfayN}OkXOmxL_^8G#BJV*)k?|! z#BHX{iCxX$FZ_56_4kz3$k`INY(r#h7Nul;BkK(2lJ%0O#92d}G01m>W{I1~1vsmD z`GCcC-7CU&eHrcLrX4~L2C~+3k4qD4E8?j1APJr&k?~@P6#5qi{nOIh=KXkegSWAI zo9VN}hVCU+=K1n-7`fj~WICFBs4H@vP&I$=ElHJTX#c^-%|9z2>V$mg5b~ij$cKD4 z*eZoCwa@44CZ)1*g;FkZpayv6)4Ker!LQER5Xt=59U3tXzYx9|_YIUu>L11V7SH;= zcC-Ez{bqfadEkq?JrvC!o-tglOD5~j_T_4bB!54dpUJED-E-d^)1ufq$tsvxG;{8}sN7lEg>&c5%r5d(Zu%F@ zpH~!>leJ)GR8~<`{!I2Lq6%ltB<$?@MNyCB=Fjw19?s1!N}m77BQxiY%bJ~)om;dh zN{uzcBTnD>B+7Qo%!i|j3W?2bO;&#H;+avr3tu#IAz3ER&&_|B9fK%Ij;)-mxr=<2 zaq?>X-1*r>#JlMpRrpBMJc%r+PIQ+#YToSp+@dLk>>hc#W@Ts3%=dJ#qdz})k>u2O zK8Z@XXU-~$nm4m(o+Ny;L8zHka6**4+#` zZ)QH@!2|Vt73EybbF}|9|sP=oqIynaN~i{{=tdBG3YZSXfQnA zCsl*#3BZwr8vvB{>Y>9>AbW6w{ehC67f{k`xlK_}6-vFTL2ENm(reUU`a3{LXO|9l z00&~{PGdEl5}+0P<3OsEx=@4G`+<^9ng-LS0BNu70;L^Nb-O_9)(ODhkP zquT{axG1263k9OGG}sIr0aP@5>J5+{cPn?DiGJ$49Uv&0>eQfh7f{N% zQ-kS6K*@Ku2CeZx>6a*=w8PaPExl%-)VmQV^**OT>j9voSEs@BcYsoFfs(FMw+oc| z)BvSE8+CVq5^kMt7bxM%fD*0*DD^G^O1<+nduombtq%dE-ibh|cRWz)Jpd^6?xR6# z51^DYLWAiIDE&yj+-srcw;d?)w*sY}oQGC=;J;CW*403XU#h|M$AQvLIY4QrDZt@C z(Yu2742}RwdO<)*&mY*G^hEy(XuScElJr_Mn0_1x?MgkOL2DgQ@Ry5M)$#TaP|`~S zN_ta(sD`DcYS20!$dFC7Y0#Pgl=R~@m>vOC%L7!)17!a=)vQ754Tz;$9u1~91EqZ$ zHE68?#*l9rFkZ?5l=MXJOWK?Dmk`m^L=9Twfs)=Z4W?g0A1ncTvj)>o0J{tY+ z1l@hS?w+8##{-k39wa958kx_eJPkl8$LByuX6gwIs(N`+4$;d4raOU>e~kvMJAi|T zR|N9Qc*xNq`*CWz_XDM0Qh|`J)Cn52+JKU7q6X7NPfyYv0E7gk_R*lVhvuIjp}};2 zpoBAP(AvV_VQ8mb)u6QzDCwTlVEPH5gg>r9>pMWT{ee=h9YBU=>UIrUw*n>pCJm;u zm!-x>PAbs)I8f%TL>+noB|Y>i)b?uuGPF{!YS8*QQ1U;aLF;j#gg*?F_C5$?sH7gy zptS}l`R~$T`gWki-=;xp2~grM1WNoOAgG%=NBEKPomlzfRf$`pw#O)5Ls#J5e-@o042RT4W{n`N_snhqX?e~1f>T@0i}Pq z|3FR83n=AlAuy<#dR2qgW}u|ksKNBlf&8VO;GfWQ?$KB6bwE&U@HQY_ow`+nR^;_+ z`lUcge<6@2OD)o%H6JMH=V&l}7*Ohm>{ktstW=$+A~a|X0!ny)4W_p!TKG#qX^&b4x@AkkZ_&R)-*I5%*Nw}69)8Bja!Xo zW0`TC(c5^{*ur2$o4*;v=<0Gh4aI&bf6Hg4>^iOYtz znzxy~jqjN2%x2?G^DeW|c+h+pmrLfWxSTM5j>|Tm?LJ2137^k>(1GJhOif^tP1~^>tnyTF{b-Rk&JC-^N zb+FW+wY$1USr4EY!2$Zw9L!MnO=Sx>_J<_6yzBR55w zjir&RxwPx^$c9LAi`o%I-J-Te(Kk`eQAXps=#A0d#&gk)(X@Es_Kz0Zz5=;HRd(T5)GbAs=yeOvlc&%VulX_tPV_cI$0_dC)L zjoSfR2cSPZz&QX7+W|WV>>5DZ#+JpBf9#G}FVm*jt+CsH&e(Ti>tb1pF@8PuZ3THm zpVh6$C-gw{VSmG1H5|gF;{u3kmv2MM>hs91D_xkHw(vtMD_4MGK zRDbvS>qFgtq3$21r+67IUZ20{ z_AR=7xo+R5+Y5F3hq}E;&+mKPK2^8>sN4TW9i_e9>(L>)ze&&kuX=j>_45C_ZvRHN zC+Xqc{o7OQ7MJG;L%*wsIK2kD7$E3hR8T?iTM+U$FgpQ z&;(7Aw>!>bS#xLIGi!PQQn7J`b7#+==b1s#+(lE`1h<|Z56^rgYkq!Fk_bILLuA}7 zn(hzZm0vg`E1#sa8fB|uwV8LQ(lRAKV+eI~`-!-9x+?xc7&~#^9dqXvcI>D7Xbr4N zDDRwGSfB}nQNf>AG_G*LOrmN53TGGP79dDeg^e8t6g~WhXU?5F+g+4-^9wps?9WCF zYe#wik^K4da@=9nPNJK0#p#jR=%5!p!pCECbBj>)pK{MVsTtWw4cjc5>c7?;w^aGg zpF0zvrjhmV&C>xH*{bAHZ@(dNW87bDA8`q8r?!u{(Qc=H=(zdVvk}MQM-&B0tRKi(?j{RJw&g@U$hCWmX;X{cR zM*MK<(v`Y&D&G-;PAGf9G32p@zRH;p+3Wu6mE@R4+SE*&Hijs*nl5}e?{}7bB<<0-!tj>?%DQjO zj~`^8_>afVj}0DfUgp!?9Bm7(K+nl+*lKC)s_8hbKu@FxW!uU=aR~-o=uqcR{3Pua z8eGSBx#+sN&qcWX**7j%qLuO=IR9ecEygn@3+>%aJ%b_V9OS;puk<}&!c>`~&`AVu zL^rQ%+?vgTPdn(}Mq{|6(%7{=k~eV^>F=F$}$aZU6iAXq&U%Ic`=h6!Fd6jiRqSs&?`-#ijcGP9M{ixih zDR*Z6p!e@t&Wc>4kL4a+tsm$Q@MXt}ZY?>>ZNcW?x(6bHm1$of>TNW2j}<+ieYAt< zcl%(F3x*10i4f{Lf9d)d=?l&YG;x2G0oaTCh-{?|le0=sPmWo}dxpFLorQjD6mE+1 z@*jPaNB%v7`=1&}(`@jqkL}{?NO{uMl56mBpaXRI1;R!y5q(Pa9Vc{0x#JTv#;asK z>zhzHn<2Q~i0&Tu9&*QFK)LAE<#gR*8Rc_uvxaB%0e;H1!RSf#s4|vl`dyrjsWm^= zbGk8D30D2r`KkVDKS_9-w`yN*(ZZ`?Z0May8n@|gy^ZAtMWYD=7+a06DYcDTFl%|c zmwS?Pa8nG=S{g~uyexpYLJu^ZvxeloiKmn65;;>+eJy)>ZR0A;3f>*b4yiqzT$0$x zJHn00h8EY`=2q96n7^7^&yh}PNkI7*z?Vxpfm@-_n2;(N6TKxb@IvU9S_kwKZOXPb z8dY=~dTQv$u)kEpJ;gQG{gl@4(0RCuE`yZa9R0B+a+z%odR^1ZZ>+JLyiJMkL0d;) z!k(~gkpAbKNUPe;FAgrhLK)_*_ba~+d|;e+`BmV?WK(%-SEZHtJaoX4$6F$}$C-26 z<&FJqt#XDXiZ`CbUB=T}%U5qcU3drQi8S3j8IwkK#83AgS7{=ixF=H<_ZzL^|04dj zA6+$WJs+XH<~~W4(D|lbjGY(*`psdsgh||$)s&a#w4+DXlo#ua0hdIt`cN-hWp{&5 z{UX=6V{W~W3($(zuC~^vue;(8S!}I2j&aB2PPBofV<(;bJiFY96`Y?p(8*Z^EuC;% zt7n*0yA~$Z?h%GFAF+B_9C}$Cltu7b%Cd*^Bxk>Kr5$99`k;dy6QE2r`=A>M9Q$LJ zIpVmU5Sn85KXx1iVtV( ze?q$ZHN7T{cA~rNrk!<~`$oBARN_Pi@OC8c2qIHyJFh_7-l}`4)8nV~ILI;M8UH^y zmzU*1qcin8a$1L7Mx+O$cBDPcsaiz_%#Dv+Ogbu zeHGpEBzOU}tn^bhxYc1f(P?zIsXI?~TrfT|xwjG)OFi4TyjaU;HRG(&-n};Gqb}u9 zUTiZMlplYsTxRMViEiu+=1rls&|dZ3Q7PkhqBlkVt?t`#Ul=-Q$7uS%CwS?4q0?W} z7L&^T%H>V=q)vMiqe{0;78)&dS8={E*;nb;Q|{-BqW(>UwG&p}&^9xMa__J?V>z4P zrqgFlzOiauHu;vHA#5`ARL)|_S;)RRZEDfmxmPf4OBi#Kclr5aQ)`u^xQj8&29!e!28j%o~$el)f}Msq}@((ZCmgQZK2GnfjOvK7Lx8chbrUosXuR zrHl`EIfbsM^afqwhS3MCr+;_AdS?0o>)Cq_SkH|+VEtn30qd8)KVbbT<$(3lT?ee4 zSH3d&CGyHHeP#0U(ih41RgA!w+VVD$x0-L1QP0<;=KC<`vfTN~JS_R1fe%yL5Pbdq zo+M~wSC!761!vFcx_oY&U5@Vi51avc+}N#NjYInj?UZ?HKmG4Mi|TIsGlXBlUH4l} z5n6b49?` z{|7z(>Ys_fx5IobRVixx@T!S={6=%f`Je89HnImq-j*uXL%&$(qz?0rs2Zrp`?)%I zT;_1~&QGZ`G+)d2XXtsyZEdMyB}lo$d74BC%`sUVKG3=-gY8G5X+Df6A0O^D1<%Ze zaE!&#Elw$yIY4Mun@%NbB*7i`T)bn&PtSs6sXTGFDtOp(c-UXRtF?D6XMYUv%tm-; z6FjsRymTklMx5{}=rZwpFN_49itLrn3 ze(_#28nCU$XWj-L9|134`bCg1u<{8xixd*M%{zNa&|+qGSuKAa10eMl=cvJHx#_t!5P%{ed`Xhhx4c7IDeN&oi~%`C(wVPxxKYCRGvNyKR5sBj;F4- zNL$uh&Hg$ z2fsZ6IYR?)hHJR2$#6P4uefZZ$ZbNZm|K{i?qt4!H~;;3=AUuQL&?Z)k~p*YUcSg4 z_xRr56!p90#b;>)_d2ak)-cS4joG*2i?~bPX1@qWvA0>hM|><}R>rsZ<;XZ~M;qL6 z8lgRrVVrkktVx^8*=6xNC}*pFCXVNNG0E>HZn@KxbR<2qcZ5Ucu*~01TwE8Eytp@b z#yRS#IsHDcNS-e-Zls*DzVoc(HIMpTlRK-an>%fR(BxazoNhnQa_#lt;oSah_{Eu+ z^OQyKsVYe+7o19g&TtQbU$G&g$xOQm9hfA%h{>-?bDlqZt`RH?O?3weDq9C;yQFB{x%nQm)E zY#+)zzJ3dyFP56PQ-_<8llrh z+Tr|KM0Zh82O;4J!gL{un=uFUALB#&}oD{FS({inojt z6V4lZI0hPQiw1PEEh;vc)wB5ngKY~3#n~1X8~p7h)OnzfZBeAb&mIkKF}EyQO_|KR z!8mYY4q<%l+h~V1;|4g+8+{zH2HTjJ%NYq7@LK12yA4_sOV~O4!M!!6U`G`ACo)$f zFq(7}#(>mK>L_)UI-gCEv$SDVGN(#CZ$Pu8FAjhQl(jV#9Jq#zwP74MM_c^eQ1@AN zaNTER0e3r|hPL_Rzo(mWnR8j(6X0LPTd%=`@89kGCa<8SnYI202EX@tmtl8-65tT~ zGf&%EUWW($z|iIWKf7Eta-YUd(kvs*Ndc*jRMMQpndJ&elX_ey@52vNIWOepw|q_f zYSMd}^zyk|U+j02-ZP|EZ3uk7n)EJ^-djdX$|<8_Kf~CPv$o%H-%m`dEA70=5?f{T zsn76X|Az9Wv=*-y`EN5a!?KCLbzCr+W2=*XTVIX)GyhSX+b3^GLZ7^$31!IqtBI3E z+h-X3n(C0HH4;{6dyF6ba7PLK8=Js9tj^8U>wh(NHegj;XCA*FMhS?YBpS4OC6WNw zuP7!ywl@+Wig1ZROm!RY@*xO#2_PSdMmMo)yO>M5CS_AMx+^v56Ht=48@h!}c4O4; zl0Z^ycsA}+yHZ0N2&9?~>td>p`+Mh{d%1JP)cc6PdEay9oHOUloS8W@^FP!2s7KK{ zxR7IMdxf_2-v?N!(7pG^ zpr;WQaVfoG85%5J3w0`am5|qSh*X*;2?`^a1$Q*pXF#D3l_mip;)~Xbuc+GW+tymt*$X#P_(_cVs%g z^=4nP__EEu3(WI5xW??;Exru1FGhT;%s$>HTsN3v_T`Ijh1q8l-?z=aBX{6?!0byF z-x9O$LKMD*X5Vh{InBNp@!f0ookUk~@NTm&UwpI8KAZSvn0-f>J2HZPz<-?PV>^TF z80?H7yMzAEyVMt5M?dDHAM+aL#|&RO{a6nDSUUY!2K`uPv3efVS?p}hp&wiR{g*XEJs17J&p2wh! zdHNu)=0thn{V)Uas!)^@E`||s5gcHgU;*ra^WZU<0DUkHdf`1V1I~p`7y~1q0}gO) zI}3I|o(o4EgVE3j<(|_EIgdwWz$oa1Q(y#~1P8c2;N6|54mchjgW=Ezxn_v+Lb=w@ zfV}S$<%BjE0p*-7=XbfT3%#cOEM-;G_++QAj(KnExNeLLx16cr+Omvu^Z@rrZ-iU2 z-mp&aUEET(jO(~8{8`sIr^vihZ(?6(4xvxqV2;*t*0w*MvcKTunjpPqR`%x;t!?k3 z-PXW0Rs8L@N5oshBl@S}yN4+)@O{9(92Ofsqi-6%1oS<*-srbSB&1Vb|2~|_v&hCd z<7c#&7|%6dSb=7g++*}@;yQ|FhSZnwobloVmMi;6^L^%jm+Kveb;m# z%zfy+^}S|ky2N!8-`iaa9utxH7Ot5t9$wjWX@#mIJN4F1TJlZ)7ddi0G|qeSHhEVi zIzj68KY6Zqo_X%LmR{Gm{mIsT(w}XeH1Jcd)f3RlLfguFf$Jb?&(F6M5BuzBSQ*z2 z;U5g3#UtFJLT<1EdpYaiYx-n>!eNI~@F@_XJTLfRn2XBLv zTjtgN`tg*e%YT*o7Vo7bTrHPzvzd(F-f4`R_3HORhInSqM$$c@#8pe31fBi_- zj~rQ_{ABEx>#{bkADN#j+ACvQOMWdepC`@LTVFTEl~$0)1!F%8JDR>V@}LvHo9#@e zyjw_{Z)C0{+-}Lr!0*K;_lD21&MUN~97}SK9#0tWSzTF9e9AFdww>|7wlMDTX^Skm zSCV_&J&c`wO})v!CGP|ox-w|j#P57@?LMttV;q;CxSK{hE{CQiU9mrp)*SZ=l12~j zNd(^;NS+pBA1|I27t!}?tF`DKEMa-GdhF)?J9r&Q=P>WkyuyAO>X-h(cz0=(K4K;F z>;1X1x`-p=3`2j?8YU^$GuL*EX}Ts_8r)O%?6JIlo#Uy;j&>()vXA4xZyXv<6D@7U zvb@mJ_ItY;C%?8Mwds0_Rq3gb&Tx*E@hltTTD&W#)BK9M9pd?jjH6q4r|KbxWsQvGs`sO!Zt1-;#!lOYZBf42_NfC$ zsLux8z4E_rC-p4;+}#IWmTehY!!d|X=y1+MBinV#$~$y7e@=9nccnyMd6?%xS9nJ* zcze&G^?2T*-tU=V{b97RztR73KH2|0yNrVy_vNF$*Ct_7z7)m%ESuhI^M^Mu29g&e zbvo4-O`W2J)h5q;%A46v4Zd&oUlj=Q(s3<3RD$sG zeU>w2)?F`FEIWEIf&EDCS<|^+4P)Hyx;1Q|fqVDawy=R*&P5aG%TGlnwQWUTGJlu* z>2~^x_dc|=w!fYfABIljyk~4P>d>3o7rC1LJR+j~XO{R5`kWlb3UB8*yYzW7-=g$Y znbzimuhZ|mOCQE_gmr1w@dqP`EB$FZ{mq9D&mK1BW@O!3@`5HdB>ab@FYiNL=X7r2d_|vSj6X7-G(ca$+>zXa%XV1D9{kvv)OL|` z{+F3at@qldMm%e?dV3Z<^z;S#vijnx;hN%#Vdk(M5FO2N)ZNx=*Awe!S*9GeE%jQp zugWsjmpImP<}l~SU2H?mmZD|K z-#cmzKe(E5%C?1;lWP_W*B%yO@bAKs|10_J@Ag&P_RhpLzxb-`%!L2Wned~JW~ZF} z&Fq&qwEmOv-ZbZ~0A>B)KZI)*iEs^Hv{aWM8nda&v0%XhN4VvY@@hw6S$R!Su~tdJ zw`TKZL~|S|agAen>;p9{I?^6nsXnbqStrvlIQX=qypjO< z!bQYemLsH7U0hyTUR1)D6pn(t>O4tzxkJAzU**WFEOeAn0{LF0vaqVEM3Pw^P^eMC zB~{xUq$}z=m4!t$DD05O%It@8%E~MGiiU4aQp#)4h*8-;RZ?Ay=F8?ylwSG&T;wN2 zxW-*rR#aVlYb#KEb9AW)<3p5?ijYDY{zq<-3*Wl&;j+m_THyVco`6 z=r|RMQj?LFV{KuDYHShg8ELlfWv@%k@wZ8GVNpq0PE8dpnj+sZIW{Y~r{EPfmDiLO zm|9Y|pY!5T!wD`o$bsPe%N;!Qd}d)|_o<@`DF zw^y?-B3e~Zj!4q7nt5dPB!$CZw^X4!EjvkRj>9psP3l7|zrjdo0A5s*Cf=*v;7It3;bhgq*%ZY2sCKtXoyWCrs!O$;U=gC#=Cf98hxdiz~$i zNh>Xv%|Q;zDA8z1sIrwjDGFP(O+{sSK@H7hXh~HDi4-~F=2r#ELaWNL5*kSkRR3XB zR=A0SDzCDH4a_gB<^z>^4k`~hC0d|xGaFxGs4=1Un zskR5dx!UC8AEgFn>?7<7m4OW}C@ia@l7pfIZ+KqmQ+eA_+ma0liela#T4zL{E8Ll~-9P6>UhD8BHXeB9WPoMp_n^2%#BrQ$fuc6qE1K3~4j^IV!iz->EXu zjiLI5Bvwf|*|0`9+F^+c7H@XM#yTP`#@_Fcz1cXpRTk=~bv)>D#Fhm1Z+2;iMSZSE zIc0NokTER|DI3R-UX^^L^L&!@Sd25WCwH=qcTj?$A7cyhlT(K2r~RVsAio>J8N)Ds z^Av6fPu1ZkAlD;uDl`4Y$VrKx=p)P?B|MCqa`{b<7(Lv<;E4Rf?jn2?9j!QC+>l?I z!VURX=RIdr3DNo`dUQ z7i3f2or$fX<-qHRR?LV&l%}~nI2p5w7tBS5&D!g>3qNf^W5pR)ZYOK-` z8>87ZSJ?+3??KM*zFU_AO8V_^KIxxUbnS;yzB)xu3pYJd?q>KHY|!=rDDmnQJ^P`g zR|nZg-L;CYY8758^WzcUBlF`)dIj2_tNk0Hr2nY)yS2ZI9bd}b%10 z?qNmOB`D#2P|DK_X=-bXB(9CDxl<70BJ&ZuA*y$ z4u2F%er`yUy3-V0b9K1ry-B#}z0rj3C`H$$C|zDC;k_`1@E%217nJgJDtc-mx2$()!{9W@m}|FMOOoq@*h(4hz^>RS9H*XjhgjvE%q)b`H3!? zQC|2hDX*eyBb4+uD0;?2i7#SB68|cL#*8z&hZS9-mnQK=FAaLy;iJS8-7{IQ7Rq{i zq4Y1JcLrVCp!6@Idj?&idnV}=D|*(!4Xl@{xlB`Z&Um-iEjnk=<({bGIUt9Qd#0jG z^vUS5-BF4z(I=DrTlB~z-83leEV^RQGZRXG)yqAL_UED3YW6;$HC4R4>NA$)d{nJq5x56mmw++w<>z-A5D-KG0nYR>r zL>DcCcwsONijG+_6uq*C;36o;!8%*E=`VkLnkbW_4zl}p(%#gJ%kHSXf|l>(#+LN(sXD7 zV?9)rY|XagqN9as7Bj5cdqg$M-i?CRX?weUjJ@+V(a;)WKRfo^*dFw=8pk%H#wD6r z%%eJ?WkQ&qF?MjmC6>=k=+X1WqR%yPKPp$Xlj@i;_T0oCw63})cH`MQxpp$TQj@OY zZ=QU7GFnbk8mFKoHRaG06s@MUPeaja+B?%wi<-6zb*oF$hFETy-pX?0^k$T#jbQ8>5?1yE+uzfZA1G^dQUUqI;rIl$vp9#u$6g zjNTc2@bt{~nVmDy17n_AtF3<4p;^rPI_m)cD`l%)bM~TXHRKpZ>q=CvP_3GC6@4pF zxnj=Rm`gFJg~gmh<*GaOEZSAaV_VR%IuzT$^4@!D?;*}Tm+oOs+4$P{x_D-xjqi;| z+bX_`<@$sJ=veh6^zwguLMQ(ZJFhzNID7g3g!8l$wJv81%iRmkE-oQa>4Bxx@Y1@aW9*j_hZ2Vq*|Nmm#6H-W*p=9wNGq(`zlvI2)xK(s{di_e zW@{#CWHx4wv0ruX%G?XvGdnZ8@OEZ(Wl^75t^D7Z)ttq4WHn^To?@~5I|q&EF!_J~ zDmH(g%u}AJgo)jyZ8^@xKC5lc^~U=C`?VJ&oCC?PbI~1QKPEN@U()$s*Y;c5UZB^1M%yd3ov7`t+8(Fv25qkrn>_qF9-_+_Z2v^t4Z8f9 zVpG4nw4JES*L9!T{&Nz}-!*MNuEVqa^{MS0+HTVE-_!OVwEbgkOMfou{ZiZVKwRvo zMJm5*I=%JU{z%&m+WuQ@|4G|r+Ro7BGjzd7@9(tT&$h|_=)eCytn<5fvC40Ywjb2? zX`Nnf#v?q+?lr)yhu(Zn9F?P!+8_W!E2?H{jj6CmOK@s0mwIVig2CBlR4qak)Z`2~mX z39;it(&IZMQ~XmQ>HSwo{4F8jABCj9BP9Gni2XkycBpQc>;}Q*mwC~HZCglr-VCu% zgxI1Z7rZ{taZL70A@;9B?B_zt^F>Jb?IGz$h1BQIL&6h7>=_~PWj`W|z|R>H|Lu_Q z!y)VYRk%jeK~wr%c?hI!f4cBtnSxuS&x|qt((=55lz_{xiZ;69S7~8U-X{NwN@^=5 zrFxrAN#wSa89cN1jZbONG&4UGvE+~~}?1&L)*nP;D0#RvT()D4!V{R8_cj zUAa8Cs?vg0_zi)$62H*dEvuF3YAYzv^N{-VL`HFmCfTqA{r4$DZ6lMc6E8Bc#KN*=luvX zjaYI*o`NZdA-$&_i}}|=;NDL^) vEeR|w4lIqLD`%t>(2`pcSo%h#Ib$Vs=HSwtfZp7az|!KtlA%E-o#y`o6IiQ? delta 182370 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 KFqZFZ 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 diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64_x86_64-simulator/libsession-util.a b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64_x86_64-simulator/libsession-util.a index 65cfc8dcf46d452982feac7e9c24573b67a5d758..de85c6780954b6555140a492a4b4815bc1a7b18a 100644 GIT binary patch literal 4484472 zcmeEv34C2e+5RcAl&$GPp$JHU$|l9!G)WT?KDwkNG+k&?DC_CX%}sJ?k{j;5X;V}d zStOQfT@XZ6R73<+L;*h(D6+_+QuqK-T=)?DKoE+eqJM$^^Ugfy+_TI%_aN;wt0}}7As+?Z@C2D8a)>c+lR@Y9SUQ;<60kt(XvudU%Dl2QNtEW#-%&M-hQxR6~ zWZQ?goVaLy!ve)z|L+G!S=KWAc~QkS)?bMWkO6Ujk+|zN+C76PF(v#}hF?YZ)!SOu zhC{cpp4eXFwJ4dmk3>Ad-x3c+X~o?|ym~wBuHMtK20FH}E+@W0YRxKOkyi>SK?ErYWkaq>xe1hk;G}lKb@lKe?mNm*g`y*_{Pba z?s4J`#C61##19bvaFV9)AwGJdcHg~1-#lcLHy(4+WqECjb9};5idPV`=^Q3#E0Lf{jVgp6YGe(5dZg3O?NYK zJ+YlwPplw5cZjCHlXy8XPyFP;%n$L_gS7iI#P1xa-P;l$d#`q1M?9B!&kXJVW#U@m z65;{GyARNGtBAW0zqP;ipH6JuPrLs-UE>AB6Nxj4f81Ax-$lHFcp7ng;&bP5k1{+I{aX^xswE?GrS1 zRcKs4QRBBKX?$q1#^yW8omBR;uW`+uF- zPh3FUgZNy(rmG>|c!_rZxJ%=`1&w{ghkEEH{=Qqgcj(mE(WY?^`roulyI&>0M!L^E zUHdO59zy)vXSM(H#7BsC5GOK!`w(XmuV(n?h-VRZC}@85q&t-UkAFt{uOrSU{%>CU z|ABJdkN$uAwD!M){+sB&fcOF8Up}Sjt|wLzFJru2=ze~Krn{YZ8Sy02EuwpS;&<4N z?)apR*Fb!d{x{m>$NcZh@PB<9~fz zu)gQBUN2|5tBK2qFXlA;Cy7M~UZZ>}I@W=V- z>DJTzWxDS|4IBgaW3)bujud#F4g#Mx?lOScHd2WhT-Qh-lg>4_exFIK=%t@((W(O|EDOg zcx`|VXw_c(BUnbpw%*QeGzn}h(QBISH7u{)DeFu%VE+bw| zyp#B)t91A#y0_h^-H+U$@w>!@#Am;w{Z|reiQ5pb`?d~WOPovm`M0$HD&o$>bFbI_ z`w@S9op!eq@48mI|8TX&-`=5d+ASI{r2CGWwY!7m`OVj~`@|n>JdWWD=|1ACq-XpJ zx}UpA`=7=5?6yj|+YPydTPayu~yV}2#`F!px+Ixn-i?)NM0efrlagewN^Skd$n(mc9 zX?%cq4exiEso<=;J_%7manEv~TONa&H6~sF>Th=w3cCwED zi^f}C(b!77g8pB@xFU3S{8{70^xuj2VPYjQPr7##-^Fx}B%fas52gPNhCwvyrJ)QX1@jCn=Vl8pS4%+|Z z?KPgVoyLFSWKj6Lgi}MouM*pddk}v*PKUR@rqg}*KQ#WFc(0|y_aPSE)Zw#;GvCne zb;O5W*X|tg$ISo1bYK539sZ$zYJ7xv2g`i`!|(rpI{X~sILh@6;=tc@_@(5(C*40} z_^)clSqFb}XX|?v8uy&2@z7ZsH_=@;NxQEk{+hVgWbMC{_zB`e#QG^Zyq9=8al&re z{|Mq$#9gOq{{(S4@fzZZyX)}FiOZSKooclI0Nsbu{ZG1|B>gA%&~$GSv(vQum&ECN zYWK&94-*^7FGW0?cscR=#6^^&{cg=?n_7*ZpnD&>Z=?GNx)%}uME?W!Vm^rD_tx$# z@hal4h!gkG;l~gkB+h-0_P?39fqw_@gy_CH*JN(e7jDezs1#|BHNfBVKoe_WwEgymX9q zZ>E382etcdVl(+ZK)Qoj{x8#gIP-lOaV`BXWcs(!|5e6&3Ku^j=c_5dZRp;vLHo~S zyvvyG_Vcy>AoG7J%D3rfyILQTaz47N^;zPL#9uEQXEi>tt95g;#v_S0(tje|4}o6j zUM0T!SnX~iW{F=S-befkao0tf{vh&wmV7Uy`%`qEPFzO+m5BGmpLew`CVpqJcK?QW zC;k6K+@JnSh-VUO7(Q*uIP0=^O|W(*zK_^VTuz)rycsxtrwP`h#K}vwyOo$A=82b) z?i}JP#A_w|-4m=&6E8SUyE};Ui0>lKAl^s%KM=pOY@GFjLnm14iC2M6+;!&N7V)#h4&r&lH;K;^=Pw^;ZCE_P+J|^Jaq^0BR@dr;Yv?~f_frg?$8?(LpCeuMNyt~%1Z&)h8f%HyfL{DxATFVQ zns_F$!0?Ua^D(+_B7Tkj>xmyBwi7QUpX-Qs5FaD%M!bRa&(nP=`8U)5b^0Gb_wK|e zNcVr@zI~##iMa4&?S3C|M`AB<(>)Wd^MOY{IMKS7_;up5#9L1pXFc`ML~94)-o#nN z<-|3_ONpC^uMj7ns`(^{EyO(WGsN!_cd*eOemBv&_%w~*B3i`v5PwcQko12*e2Mow z{U4$GgLEHH%n~0(ycK_(XuVE6aHV#q=x(BW4&8aWzeD#R@$>ZG2)ysl6RpR^J!z8l zJK{^ke-S4nb@*w-xx{mbml6*r?$w6<$RU%gONhULzqr3a{2=}RLH8=Ue*iqVdXn`7 z@gCxiDU2_5ldPBF7W^RG6F)e~I+%DoF-!b7&^mdNbrtay;J|5&cPIXh;oqbCXu97^{66s^hJTK@hB(0R&k?UfKAyN@ zlGQ={81YbIBk@&W-Pa~r$8~Bvig*ZdSK@C;cOUWd@E?EeB&(uJ<9&4hllV08aE5o% zeIfDN#8VM|!!47nCgKM&+PyE)B0kOVpAfGleu}t;_*2jyeCH%<(&-v!6Td-R&hT#% zPp1FHbe~7}%XB{och`fHtk;1TJvqsmxk_Ur@kC-b@qFO4&6BLli8lZn-Sbl*=rg#Jep_awFh>kgl6eH++UGui6OYHTDPNPMGL`~RN!W8#&> zvxu$4_Yrp>{=P@YzlFG-*iJl}xHoY#^YajKkeGAa%-7*(KpqPxTQ|ZjczvJ7nw-Wd z#GjFFGx1}jyM*D7)7?gFAzcmKQ;4rHod<{mq#H*(j{FM5uhReO!Z_=iQzu&+iN7TF z_G|wS689!<=eYAayoPuS_zItUiN7ZsG_pc{g zzk^%w72*u~A4&WWu2;B<@1I4(Uw)$7E{(F-hFz9PR%S-478zM!b>X8;AwsQsQC6 zO~4zronqCTt8ohPPxL<-?x&_qvA#e&mpJaDnD_RcV*L(o!QJWJNcZ{YLGA}lv8v%O z*g-st_yyvRh|dsrV!XqNrx3qRyr1|-;*RHQ{#C@~#50H+fSYTlSXCFG9F0?~2jCW5 zNcRuvo<{f6biYRYr1&qHVx30Z^QWmlHoq>?5uqe)^(u*3!%r>u$IOA0|Fc9LMn8h&99};!5H<#OHud z6sA~@5pN~FM*Ifx@IlP8mrb$86Zaw>OFV-35^&-*Q>-IDuJIAN|C{&~;_(ds7~T8R zJxI(F(+uB%?$(RPS?7FziuERbrtbK;*W_h0SA6D#rh?2r}f(X9%4PP@=sH&cHpL$ zr&yl{uKfEH>p|es*QZz;K7oDLHoIB35oZ$LK)Cp?r27S8^Cz|cKE#Gk!99C7>;HXP z<9moJh)o-`|3u<9B_8O1M_f(+0lIe~zZZ#5GWk729FYadjig*^WiMSu}FJI7f zcM~rmo<^KW{MY50?k?iR#FL4K5??|3lI~r^ONeI>$A1y~fSY!+YJpw%?`G{q{4w!( z;#tHm6Q2jZ_SkOLj$hJvII)#j`DN@!f4iHNCVrN9FYz4EN%*&jj}za;@K=F#FYadT zaizv1f%mr`tO;^D-_#4CXJO`d9fpE&WW zXs_>?YP|xt;3i@{{kQuX#@z#^TKg0G;ViMJ5{mv{y#ps&zHo zg1cR%@kP4VGrXHPpLij|yNKU*!WlnB{5bJO;?uz zVArpwS~G#uUY%++0Dmxbck4{S!*;hm4XkY5-MSw5RC0IgF7eOpZv7p&dBg73q2GY} z+q+xG0B`vD?p8N&)9-h;E(YHB?C#cP;_r!X5^pDdllXb!nZ$>QQ?9|h^RL~lHsEF3 z?_qtMI6z!PtRo&oyqWkc@j>F{#1DKE^K?I)E?IT z*J}JV@htkUB(@UoAYM=0j`Y)rGl+AED~Nr>^N61(ewKV5Bi=&)_g#ne&PjV%(}+6| zUq!h1A4T_Z!~*dmVh1sKJ?gV<537RsLE;aHmlH1_o6_OPBMzUN!oeLvlg5qG733$X68J*>lscYIsB)5LR$mlMB3{1xe+CtCF1 zn|LH~8Sxq7{}3-?ysL=!5YHeciANG2yaD}Z`)St1n>4=oeU0}JzfJrS@#Dnxr2jtg zUgBfKKN9~*+~sD?cLs4bv60wG{1Ew{Mm&~y7;z%;uiw-Beowrgcq8#E#7`5~5|1Z- zkhmA|UBstv)O>$RyoLB>;(Fq0Vv4wgcm#1D;$Oe3`8`g&f%qBX*~BbyC26+pmcSQ_kO}`$A$b@l@h`;(^3T#Mi&0^Z5(n_lcJiKT2#TE+QUIoJRa7+M%?k zb6KBXAl^j0hxiNP^Tcg$(ed^r))S8>ewcU}@ms{Zh))tWCr&261BfZsLlbd5{5N#&Y4s9MAU*`N&e+p>miRZ~)x;kV zD{jR&zF|-6U|`)h_q6(nR}haO-VB^R_-kUs?uUpUy&dE1ioLAeex&gz;*Fpe z|4WG%+@al_#CqcYGJJ2k|4x376Y~sjBpycWW;&lF-b{Rq_!4paZ93grVi)mK#M_Ba z5w~Z$Gl<6#uO;4(eAd0Xuk|@%(?;z+lsJX>I>Y~$_+8?mccR|*nQkRPC-@=aRm6LV zPZQrHE@r$pfR(52XW7KFh?f(uBmRW=3~}<0b-ecy?**>7Vn6Gmdo+HG?kusJ{>zE; zh|`JN5Py5Oj(-R7>%>nGPjUFty^Qz);z7h_(v7G4g}XH0{~>N9evx=KF-2@9{*3wh zF7a~WOomS+zQ**PC0<2*O6bqr-#YeQjpq=j5-Wa!b>qkOw|)+{U?1H#)4h`JM&j@3 ze2Z+xQ$3Lj!A3&T#Jb~Cj93Xz0 zcpdQ`;_ryB5-0yu^PNGQLp+t3BYvEC1@T(qoy13p&k?u(ndZAMv5GjK_$K8%mF0UL z{*B`guudfQ5Fa3Zk$4{Q`^1``V;{cT3~L5)67h9}i~sD0G)^Y|gW)d`e@)y-{04D7 zae%mjcnq->{5H4EuudU%0dII_hV?1p5u4EdC%xBt32wm~iPPwR81Yg1FQa=0Vjs}9 zPg*=XpU&qq+1|Q=bbD1zO`^U!pXuyvSlm*WW!s6w?6zb+ld|)LT&B0vUY$v=X-X|_ zNgyQEmCV_NTryM0H>DczYpJn^$+0WUu`8BiZC`)es`T37GJ@gibS~2| ztQ`ZSyohvjgUEE?P zYDv+XZ5Oh9x@qZZVYIr{tqjepx;v9EG#T>d&3{Xkj?H9{Z97qA>vGwJ zwS8&r6O>fAn;~LJIf-cOM7?dJ3YbJH+tZiKrJGu2i;NIVInG8to7)n|>|$iJNy|@i z){*ILx6^0zC%Yw1L&LHdDS=&8g7Qmcd-DZ51M#Yg1Va_k2^R56DlNx?XSc()hLO`q zQ&Th}xMh*OY+odV5=-YYDu+%Tz{&dtMgbH6>O)Qq6yZcdR3dV6P5kF znKSy+c3&Z<`arOyNH>%QhBm3Xuz&{Hw;l8i-O4s&4 zB^#GlkIpN&qFZ|L!4Rh845+uCAoWz5@>xgG1)rMfk% zHC9k#T^f@`Rl~9Gr0yJFoVQa~Mo!0CXQ^&HON2DGe2HG`#R+I^e8c!wgYj)_;cM6> zm<02m)?)OAsJMKTy=y-Xv#`O@f7x=%j74|sJinc^%s#IoVrP_(T;DBtBbQ)@lv=}R9cZ(K&d+$-5!EntS%%rrB9m4D!D5%r49-L2w}QrN1!Ko^_IR~=Tjo^M zu# zx4DDhzq6mk)V=CxhPqb+iaT9Y=2vBS+#Vi}{G_v;86ai^i-@7T&4ZXfM!l1U=jyIR zzP3#NRM~KPL?YpwoAmT`Z;9R?&ArL#58)v(CX&;@OGM&0v&Bo`j9+Eu{cYA|Ohc7V z)@V`s-Y9f77e5gg&jf=LK`5n`N}LW4c`Pi+Higs0k@2m<{iuyYeq0GByEA8|af*l= z%}o2erbJ62QA?Md%ynYn*;{B>x-jL*RaETxmA?KQNtW=F%{J#n-9na`p*R-7T*cD* zv*uh%RQq#?$=$PfTHTQ9ZosKA?1;3bYgLwjkBrF5~gn4YLuVCzi+a0 zL>RlUY-t=qS<2qMlt?;aakLC8S{<`R8^!^(5gaM|*_~-z?Bhz1b5$!=s=l77%IK&Q)nfV4k?BmxW*2Cu zbGdA8UP2Ti61py9Dcw;dp2`_Nf>g8SsZnFURj3(l~m9XhP|=Vdjap& zB{p(eNhNA?C-;`}YcopBpZDtaOh*R?@c2?zcO~;(Wl`3<;wUQ%sI{2k7V1X9K~d>$ ziFaK`e{ZS)<+7#)Z8AK{LK?iV-Cdw!`GwwvO_q3j_Rd-hOhwC-BQ4dym|3VwQ9-~w z&c$WilLe*yqPO9`;y{Z*^kPt)`BBBGMRDrvCS3?coN3eKtEB~2qSbkOhct;YS_I0d z&wt{ZL;PYsuJFEMz9qF*)vzRLRPgr(6w2+p@7VgSDyx1YM@YZK=9kx6=9iafvZdUb z0%i12Un3J5fMR67Q-0UO#@V15<@6NO<%6RH$9YM zzny0$vrv%ZC2HjN_fj>gVLqtNiCR`KRB_Mf&DsUybP&mybBdYs>|)~@HgmPCe#Z>E z5ps+@qyG0!n~^ylr6#2(TQtkjXj1RQncQ4KQ8Q5lwKHifj9=f?yB(S%W{}>@J{(P- zq>PGcb+81L>bzSk^UKOPMoE1x*3Qm8DyXEc_D|KKp{{k)K^dukKG&pw4o@i6iu$>8 zs?XyjJb6({#+Zh)tMLRtvPj7&P3G8_0YXW&SseB$_1Ze<;Db?7A6h9aSK9FB%Ngj` z8aAJ%OGWR#NWHg_n5G*hbyp?2wT^0Sc#DdM6H0qhRf1No#p8+j9+`U{Sh)9+ibQrP z>enpuSgqYEFMt~Gg1}uuQkKJ##=!8!flQUi(!c2$NCK@&wSpv;sCZhnOzm?~cj|^d zm+aQfL$s~i^ji$I$kJjQ!gG^a&0mg7r8}tOaV%hc&oQ7ZiNL#9^009yX4lrOX;&N%^YtG-!o`)ku{ZE1#; zTV26BcdxlaBLFQKDB}ws=N>_a#w=~v`cO7U*5qog(O9+ zvi56&NcB@sz8zIl?uZ&>2>~CsbqlzxN2Mn@cV&9ttD5Q-dzL<$yZu(aZz1U?}A2Ut?_n}WYp4A#1S}7+4^KLlx1i{v+*rMmyY^eH>yp;vzmxl zlG5IWd5Vjd&|9)39aZs^u{`z2C*IJ3K9ZCvDc;b*GITTfZS~UI@=Mi4Y+E9i(eVQC9O`X#Gg;i~PY*TamQF9R>Mb=`ZmL}Obsob*&QFl-105R6!DY6vdP)yt`vn>3^p*+H94w|C8Ui@ZsUS(+b@ zVARr3MzGjsz7|@7imc#VcbwI_kwNJzArz7^qcmm2ON!6Q(&a zzA{S;*666s<&4#D4~e`b@;LTwA&in8yA5^X8ct<%;!gz>dj&DjHW4{<*ad_4$o~@1%V8equ^}1K-@D!=y z9>O}L!F_NQ?q}WCb5TSA?OfS*BGn+P;8ThyZsF$0-9sbE3;GC@_V0=IDAA72?RRq4uTDUmQ!4d%{N8DUoD$_Ud%RYsUHvNA%X zah1{Hya*XtWgK5tWw=zOE93ZbDkDr}s*Es|xiZ3ZQI!#?_5A$z}$l~(7u zyI3M{pMT;YO}*(TPM2ayH!vW>+`r*Hy?A8o2~si544euS=wT87S>PF$)2W^ znv1bSfuU$vmWA4PgPiZ-9;FQPz$QDQV{#q2T!Ypx$7DKknR;zOj>&W6@)Qe+V{#ml z9QBQZ!JwkHkk#K?$ZQc)Q0g&}C@_w$wML(9!;I`j)ST(;am~gBFlh#@BB&{{v@ykZ zlJ!A6aBvosriV((Y`br(r(~q=%{-0BeaNEfd22JX7PTQM)6`OgAxoa+8b-?zTF45X0#(ja=XP_UfRw0Ab*J&AJUkZ2n{8Vp zeAN2sv9dWGGj~QABIr?#ATB%A>PRuygU`O@yI^!oTRw}6QPZ7G;#**1t;|-IN13lJ zFe>Y@-{0d(i~x@DtWpK7Ztw5uD=qM95kqORn)Xbt;8+2V5mrs$D|kL@8KHyj;1W@t zbo&NL9(BWF7N3-qDt2rJ42!U+h7=gA^>ZL~)EYX9^EBVt$@cc)HeIuQFAwuN)eglN z`!G62DY+Gws&&~uFIQ|2r`Q>-{mQSyFiL*LhhO^jwZ@nQj^4QDjb#et+liL^874v&O;+L zqyeqJrCm#nBOWZ=6|`xohNX#FyhO>ioeqm|PBQ*dV83RVRmIb@04VNG7Wx*gRh4q6 zsty{9XRGpMI#r-8FiZv)B_)PPXOYy9S}mi7Ho+7$58|J7JfnL#D6h+bXHuf*)V`L+>uI+C2`E_#8jqd!jGe^ zmsfZj^-KApdW`ge;Dx2r zS!V0bqE|V(D~cJTKtsTi4s&34x{&@)X%TIP7V&Sg?}V)b+Oy9H5C$&kp#LeAVd|a` zfjl?EDi{g{V_!O_!m{&5VC+lh^6^~H$KIngtGAxkBXeS$UV7Bid3iRqRhA!p9D6m) zyq+(;&#AH(%1%XXd$lPPWmpi}{P1;_3PlqsqO2)nMEvt;ST}+WDdL2}3>Xcs8R7}KNY)vphhW`kt88$k_4;8m zDx&?388%X9;?BEZd90+)-QqOi^(f}q!xXOv%=@6C?4?=y&vD8;4st$xTinD+ch|3_ z)pOt3>_F%=+9s?dg?=TQF|~PbTG}Fni(8JB*WT*yU~9*YMAp0iG5gWe_3k6MC8Zdu z|5MieMKMH3qUJwR5N`J)O#}?Q`yzWHiTf8=jIH-%X58ojdu9 zOS$alGj0Kfd+QL$&4}885~=%CCqe^GC+JeeshT;YfvZRF4OkS3sj}lA$&6-Y$ERF& zWxH%fR@p9_kym!B(|<Lc@rH%`3&r7npjXAJTPl5QJUd3IkelXU}%3RXogiQ+0bx!!VP?K*HI zFXOn}rnF~!@Yx+Vqyq<+u7elFt{eBY-oN$Tt?Q%oe_U7bo;=_scj2y=&r#I>a>GTL zp*!2zna&lx3fUaKY-sl;p`OtPo6%}hp|uhcm4&RjN1au5raP|t7xW92LWa_+2yH5Y znikM{5W>I(N`tby?@865RQLA$#;wzyoi~bcBY?BOA?=u2CBF5MZz}4+_|;&-Bkl;* zDmD9hHs{*vnAR*!6nZ$gn&;hE@!okv&0IFi*zBRTFR-!oH7cp$Q|moFunApQo2Zn& z+SlJ!LQ4&M_n_`yQK*4s%Fww->GY}I8qK{*@{T*zwB4U8^+0DhxmWx<`n$VFiT|ur zvNzkCN#TPkqf>zDVgk8rwgW==$EE*(5Z%@&Mu|PQnZT_UNsEQdWw4#5X;b|id**S(*Spm7|YP5 zD#b8jvtv6NTw@DpcUmy$++%2+6;|xRy}5qB;#-I&g3q4TI_hX~^R<~h_Ihp&Im`cA zr7fM3?|{e$-SCK}M9!nTxi8;_7BIpEbYNWYo?IlV=Vwxd1(|er`_f!?U~OVHKfsjf z5Y@Hb&UCxG+zzb_$}|X~zQtr3;o~wwyUoTA2gk*$7o}_UOlQRThN{7`R(m8lz(c!X zvA}1u<5!i&RytG>$ESkjL@*U~Tu?HVt>~dxLhaL%nVfw0kRMJ?^yRW08GH({FOz~k zj#Cez#$OX2hvdsN!2=TE0F%^^CSGqT75jQF($FoIwA%Wky`Bq)V2^B&ZzbUiBTHHn z#^qx|EMXO(Vu~GaM>e6hSJ?ink~1N<6@_wdScsLosP`vWEzhxva@rElT=1!3?Ch$&6wE=vyQ~yy~62+%(VU2<<);g3@Y?-;M zPC1(2XvvuZhHrc!cOJ`y^Owxh;3F=qBGIdQmZnUlb))LjnDjtjCKvl=FlyVTKGa^L z;wRNHaZk2gdRCQPo9fDCd$V=z{W%rV1WooTyGA*hn!9T&8=AZENnHHO$Y58dy*=IQ z_E_`c$51g14aU=`V)4}=*Vdg)t-=z;8P2vGdo|!dVLyilHlxNrG@uP3|57KxCXRtx zyn};vd^M?(%9TS7XMDqvr1|Ni)5H@sG8;LP#DmM>BvGO*s3g1-+%VdM>JPqIGFEJ} zP%1S)U|9DJ#qpiO{cZ;yo;V74WyXkG-WxkmZ2sNU#Xh(^ftP8P+v32&+U;|0oW6*+ z=S51XbKiXQZ)n+`iOP_A)H$+rpyDfOEOJT=bDjxj6jO2Ub)xFVz03)ot@w5om81F; zcJ$u1`g&M3zEYWOSQat=2FvfZ$waN}p|ZK6R`+m5BGsv))0iE)ca1ohDrReTK6u?( zD#|PrWXtEhI*&v1Qc2ger8_gdrKsG2qPjiXTau?sojwC2D!QMR@v=Zu_ci3e{ysC{ z$|JNU4Q}_;m2cG3cYWzGD$jdD#Ze~S^cUyl7CP#aG!{-w|HHH@D5&M}FfrY3 z_Z4!E?tECu%{Q8eExdKAsasuN41OX?JqeC!$Wafc-U;6vjs1kg zus+#6_8mkFPx@QYhLPX!xTnl9Uw7QCuCuxOsp!wF#S1{KHMY{i$Gygh`wn8!BKjzO z(G~|?{uwnG?a#b+Nm|va*mPZR6r|G6R7Dv-J5K~Q8-S=S0t19 zoU%E9GseC?Tr_^$XvstIEjS>Jk#tbzUcnC63;#ML@sm}(0i-HXxD zeE3h$Nibc5CT8PYX?3;&;CW%Tl%G-%THi7ll7P&YvC8IYV zI8Ua6{BY93LQqW>!6Ub@XcBe0RNd*$WNNLLFYNCdPKsqF=egjj!3@4So2bN;YU8SGvUuZx_X&)=Ux!h>#ItvpLjr8y4SdIl}3rWdix??rvMQ&T=Z3}avtD{=`gt8V`Ae37Pqk5ZaDAcIls~XaC zm%pXkl)I`9kXBVngLh93?5=b-3_dDVGNtyT>&4UgI5&Aa6K<=#<1D(3QdkUi9q7kE zFBjT+mBky6CaBbzJ5{)Y##d*QLb9pD8hJziPfxp{6CD&VY{4?(IxZkzV@k^wR*BXB z)mf#%V6WyvLQVc2}7347IRg@TO|q^hlV8zU)$Q^?IOs1G{q52YuUnCa@QAX z8{RcYOdIjD+tRHh$o;JojI@-I2u95;W(`?%mErbyJyCfs#bw~*rMJf*FZ|R8xP2CR zqbspE+NuRW72M(W;I323;-flR8GM3TB0dF%tyL2zyjD(#I?9&W1cfAKNDY$!w_A3H zyzlU>d5X2&fE(AyjUP7LwSj4<_z0k!Oej?%?R6!vO|ym93Cu zSP~`5R!uTRu2Z@83TX)Wu1W-zwMMp^FGI{#A^k8{nRTo2s2dw{|tT^Wk<)p3- zyu6BX`0&IQICCf?={OxULmN|{q8QOBPZ@b~cY1NkY^KaPR|tN!w!oU!5V2l$991my z=SsrsV5&rxSpEeIOFq7Z2sxFxJTP-Cj>1m9%9$<4^!@O#8u!WnDf)hix;ChtJD~eC@JdED6PkcaCdVQN8~+1GSkV4pPYI`hn7f47?hWc?{F(VO&@6)8Sv4U z&W)?u8j@`Wrcd%Q0@2|ma-@mM$c9O!){rGM2qe)cLF;}H?FlM_tx6y1I z_1jpASSc~fVC0_X;t0J&&V<@4wFr7qyohc-5?Z2ugv6VUIa_id7#$LqgTTc>3=?lI zP+Hgp3l8!L`9$g#`(U>5)7A;UNqVg#Ju8Rr2WNWH&Nr_Y8V#caqPiys!BY5Ba1We< zWHru*5Hh{`)4;A3H2?i3u9AWV(3CkOpG|3Xxugh9CIWE2BzqkgP*Q3a9t*^wO!0L& zR2|+nnZoK^SsF(P;_llFLV}4*ZLg$Mxt%+#Etv!}8>-CkwV?lKPpUG`^rIt%VKL_G zC47e3_W|l*@d~YmgN@1G5u#RhCaUnVCq#u?cCl*DwxNa{akKrpZj31Hc&+V@Z;B?D z!}BUN0>wXW7<)mHNw3j;C!|s}W;;>@6C8Ak7M*p=s_V#Qd!VK_fKPuZvk)WGS%V$* zIc9BcugYma|8nes#@qja#!%ED6IDIQwQXrT4^!>Qob&WCtQBQ2*<`Za)c~Wyt+r?w zSMjT(bd(R=arx5LJpruS9(C+%%by$GHVzRWR&pO|KNk-kbo0TW<3Ii zA63yH;QA5*mMIbz_R9Nyf+3$~jnf2bMp|1xrp&EC2+B|!zGI*atH6wtD94JZ=SA9= zY0xH+gNql~C`{vP7{#ZTacjfanIObjwq(d6J{(zCYeTGM5;pe8+7V>L zu+1>%CZbiTwEMF8Od+!x@8m~NQT4?tlD9)`YYR|cbL3^tX4#D&`u3_7U{#5p1*1RZ ze43qs@^)z606_l1Whu-j zbaTD9l}<=oxhBcjXN5Ao$pUV|+>h3b8P{<)C+58r-s1&d+m0O#j~xx0u6JV&VPejx z#Y_m!?W#RG^)M_wCneX^-FhOj_amjQ}5_dKjpUQuNRi(=1Fzk4YSOUr? zbSp0^oRm0aGu%H!ezP*+O=mRC5P8z^*dI#3z=QMVSAC|!+)#?L@W!3(Rcui`i@I#0GAu~K>X98woD01)oHGF=NW_LV=iMNq~aVOZfsQ**ehef=7ZFFtmcuEwh zE#;+g9zO+dOpK=8JQ2~RhHBF-&|J;7CLra|A|ohyb*mr#B9h@Ya%5j)3uIW^nyBhm zM%u(;uy^<}w(TGTWn^L~48ooH?Ju=(o;~AWgf2#zHEa}eI6X(>Dsp{SW^%@>k3={< z&(Ja4wp(V&bQ7#`nqX)aESF8u?9FIrST*^fWw?~ZEyKL0npJUj#7Z<~F4R`GuyZk6 z*$O^?_bpbpRtqnlk$W^vyLgj{u;~)2y%IKEf~qKL+Ewj5Tv5fU`m8(P2|!^e=-`Z7Pe`0w zEsBUzJ zhK>1sOQQN)@c?w}6Odz{fP@l32A{2ik?BmY`Z%3_4c@f4jL*h7q`_N3&(V@Y^6%R^ z=^VTThLA#~9*G5{k!~1usa<7LgB6`a)x~hoYl-!$^II+5SdO;$iGNP9~+S?Gd5n;lh;Mkr^%%WwhY}@JS zR3QV4-G%z}Kq}o=fQp8yuh1p6S21;KRSNP-Fi|7RUvJ+tf2Jhii&VZQi$7W(_9l~^ zX9lD;P@6^JVc(##=qDM9ML64c1=BY>sbCtdeqtQ z*f$TxzIlKb)i~DWxp45_My-rWp3D!a+i!Jd|Hp0}bJ?}>8N=Xpn|_L~)*7K{?QL?d zzvW-5H-+X|?|lE3o$rPdeG86PgNeqSZaT{{S)$1Th-X<|Z|t7}zMdmQhfvnMhWKh9 zCh_qE-(7*#x8T!VSpGK6p9Nc*vOa5Wn%mOQ($chKvAw*tso6fhVVO801MCIMo13FU zTUwXFWNdD2A1sI^)38FF>Pq6js>;4>_u9m)${IVV42zW@Dq5qBV?`?1t}P&ubbAS= zMU%F1OdZ3`WirjA3mxT^Nl}(pFfVDjrBiXrE25WlNbFao*OpUzia%&O)7WRlHU>pW zL*|vBjfTb8XSQoZ=R^;0{IDi zgGwNNz5t^$fu0^9>F;Y-hH1=2wqqnxlc+M53jQ$?k_ZAL7a3Xyp(`Q6C)#`C@m(TikFm71ccR=Ely_m+fH`i z%C;BZ(_rfpzqWKoHkXd@^tWY2o*fXAyx{-{LQEX{c%v(a2_wfdVGnb}kv%cTnuK`<3-Dpx+8ZVwmCESJzaP7-xIjYUW&^Tjby zsKvubDPnPX`6TZPiH;jeO2vyzFQ4{|tXg=kW$=LmpD&&$E2#!O<<+vC?{8DaFp7bFiN1uG z1nk!&GPuo5~>vU;x4851`4G=xgf$S#xDB7W^djjJ!Ggz<+93UV6ZG*C}c6D zsYU5@f4xy)1S1R?Jq>I;8YOE9DSqn<`6_?%^`@$nG+nD zF%tOmMNiK{T$R)Hj?8b7A}Yg03UiK&;ia-%XUub5sl3%iRx^{X3$Jy(rLT*wWHKm~ zgHjoYPv4k4mfhH5q6bLNWaIYu1@;G_5Z~-+2+b7Fm%O3=O@%kax0~<=geu(>7?~X> zw%rqvBM^Y0Q3T-yvo~qO#>5*O*pYaH+Pd*kgsQeU5ql{j%jV+~SwM%%k%Y0RL86jr zI^4=hHcGxuqJ8OHK8uqH|43_+7`jVfv^7$B&KjIFptahU!|Kn@r8_K}zV08+X4j$C zjnV6p9#$lmaPhGvL$;IuKsbyBB(Z?-afzVEZNe&SH^qW9p01>O(!D5x(uHwiM?`d5 znXw165k)gMb$cBtwvj}4Vkfteo!Lte6kc?0hCt~Egv@-yn>YfUPI-m1iOCv*Ls^q3{#x}iGiFVq1hR(D zli&;m+g@jNUg;(j3X%^L>yyS%kXpAz1*UqkxEhNL&Gr?~2SSWf`8CPD=*XV!MihT% zAsQK;S33cG?IkELOt`cHxz5{LV6%D+ItDGpOWt?sok0yoDCUpW+}Jeih+DSDV!_q8^}#$ zF5*%*rV3B*JSZ6nk2|Yy3elax=F*zElqb%ta*_Gu7pp>|z&F8U1D?d$zgsMiUtmb{hWN)PZ-{Sv@&?4EE|TZxyynhuk>&TMa$sLn?$)<^(t`)+*WyC<7l zi=i-^T7@3$24Q|wZ=vWRLehPW6P_rUV)Lv0M+rN-s0!B=G=pYD{E zq+aqey;K4B9{gJh9CACvXM^>%SYiHBKqc%Yl=RBcW+IWS()`x8K`Nc6YKgnFNlzSj7RN@HAv5@QnQd0#s(VT+^aT2}#n>ON#{cS7fMk4Ost*c{FTcC8(m;$5Ym0AKws zFE2#}tRMqV4{DF>LhX89juE*_5h-$1SoyukLmb`yyhP6O zD~5%$R}712$1Xu+_M9deZnaKGIN9*Ro((;+@fxN98FDdd23u9BkyygbEgT`nh7JEf zdoEtYxv3YucsIvZ>GhYbttmoS9G+PEPk+Wd!Nt2@Szjh9BNxx+IMIvfaQ=%)nF>)1 zjTC^FBuW+@8e_?oE)3KJidk@c%!&?rn=N_;pXk!SA)Q>5$50aFso)CRZeVJ&@ z6&Rw7snIZP=_(GziLLVxbwncw(M8ofhNg6~#||sa2K1VBwM0^P;T=LcF!+^-A{%BX zt#udmSj&sxx#lbpA)@{x3jz9Y8;GQi)bT5ktDzLR4_{Xfbe_okqh%L!aPD*{e}rN>et%nDP{jt?xH&9IU=P4J zoIJxcr>;^lEZz-y3Em}fvULj{|CY{3ZCcKmQbrl#3s9nv;n6XW3J83wn?P{%P!%97 zqd;+GDNzhG+0X{mSFJ%IMU~rJ+)|~CE}UW7(t=Ti4KzKRLWuHz*&T)S!Lm7QqRQwA zH&m??TB^(rU3A$U3PZhmf(%{I*@grQ=J-eUN-u$-Xl`(+vbluOg>y*^rSpb)i^tJ9 zQGCDW`UKWw^!Mm`xISje+GS4l(=PZ z^aiK8v!ea&_44bh@wK~`K>zKJfro|+eA)qhStu^ko=)DQ$+NF|=vGVz}IS&ZH1+F0- z1XM#?%ow8LI6-)3kxk)-HiN0(@oVqz>4BE!+Mc#-w_3*L(yQf4T+~66cmmK}fF@Qc zVjPvHdiu(yl`|7f%slId2wF@klgV5zi9I)inHSfs&AT!%(~I4g+~T{zcsimL$Veg2 zOS0&wj#FjU>-1BLK0+}RmMRJ`!G_0$$y5rbPT>&j{h%DtUCb`@XmuP3S%dyiY}-Bk zy*~DR$zmhG5cLIph&D`v8&&jd9LG1#f|pIrJ$sPxnC} zR9}1B*H#S-FH%RkCV?J-vZMW!l}v}$s0(aPV{@hw@oFuaPyC&NDDvL?8U5&gWoHV- zfZ00F9yB&b^7IGeYMBKFZ^?c6)K2EhkD>Bj7CE!-R94_!qO+rLb(=~(r}w2I8Wx+SqNux) zA=i#C){v`6#U4^UxiN;ER>T=sDX~NxdzFr6I20@=($IQS=&g8Ma}{F$acJob1)tKW z$7s$GkypLm7vL|1FUa#smIFXI*j0&N46uSz;IcV>4!Mx+>k6VxB7BH67S4t3fFIkV^b-kJf`=pPK`4oUNwG0 zMq;WbGA8TX5Y_jlHzaIdXsOr{jnBs;qJ8;j;u@Kcu#A@Gqc>HLkAM{rF9s4tH9QkT zQ|mL)g_@v=$kYx^bmf-T)FMs%LXFEnRH})1c{mz9s`GPsU&lvQa%-Hk@zmL8`u8CU1bpe70m@%5G zZ5E^I79X+P61gcEv^?We7+Q%O9k{S|oG}%Lyi74m$MN^cxb}i=a#?K_bg_EX#+*we zed1Ye8bivsqk!jhYk9ZTo5h)wo0=ZCF~s6vP+#rF1VyT-Idip~Ml)xRv0!^FOQOB`YN z@+2k;p*><8=jh9am=p|q!?-kjJG^4$#U$YF(TdbT)s%tKx+y{umYji6F%^AJzb2V$ z&nv|U{kFL+Sx9+wm|}<|J>Q0&_vuEC{+*Qn{Lb?(Iv60Qdc_>;3p`Gtcm^FVuz|vG z9aZEI-6VpXOzpr&Qx}oXx|lK;?3ef@pa`N%P=_T!gkh~9pXmI%eKlABHy~2bJ_^a zu*%p9{do?b!24?Ca(3F01^+1&&dF@>ybs-8R?vKNP+LqfJ z>=4j03{fKTr7u>!gMiv&h?$JwUAH_fYU}Up#uX(tk_%cI=0T0~T-pEABUyufDTYf1=^52j0pd ze`ay9t{jEj+BssL@T8X3`SywxjiTLA{JUrnZ~^QdHmbMT-cXn}kxGHYyuPTbLOSf< zp6P@&As?w^F-#~2XOiB$qBTMfdz2;Z=i=y5Iz*y7QT#iu4A4hOh07qmrZT`kSO$0* z$^hS(G9WZi2KWTa5bPqVJgI(sGz((Um+Ky{1&|}ltZ5c`X`CKA5AWJY<#_2%tT;Jd zPWMrsZXZ;xK)3b!IkBWc>+XMoT*J@BK!Dce_NRHOD+YR=p^z`&RUoUg|90wG~ z=}FJ=(!Ef#(>ZZNJj_*ndx@l4y>8swd8{mR2-CHC7h?UXYYt;&*enD0$zDub*lf5gyE^ z>T&bt6$X}y9k;w>Lj5J`aqQWiP_1?pjHAwbi&I4~*gG_Ncwx*nr4*F|94IH-=VxG) z0!H+5P}Fgg350kitw@r#wad~SX;?iMW2r?d=-1AZBm#a*;V;Gse1zK5m>9@gyQF@W zX0zy|-e|>%MkFDJ@J1PU{qxIZ&Myki%TVaL)9syTM!A%3NyXF(FyY}A52GobvNiR3 ziL~F6{({Fd*?u~vgn7C(Fe%mB=Zel#LvJC+*$vAkl~hWRO0B78?s|8nQ>*-JoWAKq zWEXIj2Tt%@TsuG)PctWt@(aq^7r_nmjE)8tx@UXp*w3UG?D8|_MA4bJXvHvp!g|8; z`AKaC7|(+xuuN~iQ(;cN8f3Eb#cfHS(Ju|xl{96fYcoi!sy!%TO$z&YYQ&$D>kRUW zD!uC;C1nns2GMvzTrPpkY7$@EzO`Oa=_%DCJJ-Y4UiDH-b+Evw#1L;1nuYj;vSkQz zCFuzZ4<<~;Un4q(_6sGoHr1V0^S!ES+FqE~toKODFWrmX3V6WQrgl3s3B=*)zwQJ> zZ;Bl*pNJyJ%rqh#QxcAUurw}noncedVo0eBhA4e`?C8cLP@i1XDy!H;9H&2)ti(Kx zKoR-4;6y`NBMNpl8Rn#qd&&ea|2S9lP!oAj^nqYcHV`7NruoX;pErXDS%B#|y)>8Y z#B?fZp=<^S@f8hjSw-$03-<ljQ6!KSw@(NU$@^ja+>2}@ZJlo=Yx*Ph;%6lG% ze>u8W14BN2Mr+nv#=P2{+aBRLJ|4+AUOurEvJ0etEzaUpL-AG%%4X~)8hZK)YgPK* zkd{@MzCJk_QH_-$&P>*f&07vne*)giFz_+N%dZzj&`V?|l<|C950$A@4mXvo!+BZv zJOFbfDmqvL9r-kt*d8)1$_63EGOKlMpVM!ge7E$&^07B-PJr^m@t%BrLGQ41cAPbf zj1!(My!qLKyzi?au1o6okVs2pU@~wOMFMCv$ScW{`;HWFoh7Zi6?c zP`V-==03_&d`p+xshLuoq6#3@WE4hp{izU>l%n~Vs3UVs{yss)vGx&`;2I}Toh~U# z@QD+rsj!jxHxb9CT~4==k_71Dd6p7O$yTOPEDFWY<7`D@+_GnIlanjV>1|(-k$<^- zp{ciB9;2x~3a%kFku5UNe7TIa~QgfoRmP@9ah^El#=hxp5Y86334KWbs8!!SM% zEN;V`;o*c8l^Sn7OFkjaW3F44T;t5#bG6ckDuWXooO}F(Nm~KuI>wfwLIO1!Ll{?` zZ3SrTZ;-h`sAk{~{KFy~t*he9uPevk}2AOJ@n>ZXQ4#TmivL7mQ1OdreoS+da)w z`jm2FSG<7Hhjm4FVg+9L`AOhq&sT|>{XHj&K0Ydv3iKRA`YH5M+H;iX4MxQW6Re8J zi1v@j2rT4#Z>54fBUl!5AI?SzhMwe%C9e+47+%JFIR3_>NuVdKU_Pr?gd_;Bh-6|U zD3M~5iC}4W^>-{$Zx;O`#~Cz3B1+{8j*wu99C`BVVSoc=1dk4dm*ja8;{X+Q26xAIsQ@Mp2TGg_a=ZL zT=LH24sS?t8}Iey`0VwPVbgSBp!|)i4?{>`w$THVKQa_impOZTibpZRhCh@Y`1vjj z976Mj0Yi)T0?Gr<1#&y+tPdPdEKg3rswun1BUUHOtx_k0dYbhe`OnvW{v&_YDBMZU z(MK?O1SQ2r<2y6(`2{1it4xM23uIQ3-S^!74jiCJ*|e6vYRJVYiv=rsc&`cqz8t?i zTWB)@8eo{M=fu7kD{w$x43G*4@$ku{NUOcW2k%9SxQsKbm}j@EAq$=+;d<-j;zQJ7 zqxZlDM$g*kyDBKo4W+6J2~woairXJ5N0g1219TlyCaT*PMWmcQ>Bu->PA)J)6wB=o z@Maz%%%BLwb)-xT3;=#TYX4)@)JoETK%cM(*lR(l3p?K3Vj10LRBy9Cc*3 zpryfgY^Cl8^_^Fy;2cUwH)uDW2kH>)sC4x?TR`#Fn+mVA0>19FTdeeyBG;{B-jd@R zmg(=3;b0gSoXCyG4{bgph9*5;Qg1$wd;wov_4%wx7D-Z4*4Atx*&XaQnx_<@zmRU| z%ci%<8r^5DGBg-;syuNhgBR zXE1P;v&VUHoI?b7YsmJl+LqFs&{=fSLhUIX8<_}P%>h+5jv>DB${Wx$-{ApDkA9+- zjH?c#odFp*F$3PP%hQ=mt+ftFyuYe)dhwU2on2d7Sy?%&YWnn=%Gn6e!HLSs+Un}* z(`)N$NxpKY0luL*Jk{5Cc-B(ChyMAeH+HbBaqog_9R6*Kf7fh-|4qj~{Px7ZgT)>3 zdunGLa35(LH+Wj|Z+phGZTVOg9RS;IZCKG-bUS~YHD3QE{8U~hSXQTaZEKyQL-hY` zyIUXq!&a31g8a@QpXn;pvKo<3;phCo*7vjR&QuDgSE+Pg!7e1*g8Z%|zrz@-3;bfl zg8bmiSh!GdenEaeCBKOz_$=k<&<9cY!6FaV>;1iJFqPVN^L#JOQ%-rM67jnR{G?8w znq*nKDj$D#Z95IilWJm6p~3uFJLyPIk>7p0Xnu{8bvpt3E={5TUj>+ke^Tcv#cg!S zL`LGj6!8uMZrshXUfa&Hq_|7*Pkxe5`L`qfAsK5g{2S_TdE?--b559Z{G8=;T3Z^= zduC(fxi4-+;)@3FX&QW}aqurqgKsphL&9hZ>#WAX^Jkoc(8l#2p0V;d@tpth_8Di- zAAD-Y*%!^9adySTcU4SWII-f8`7@5LIHY;TQX!n)xV|vsXhmIda5Dl9o{$54YREySkOm80id*k45RU{_^pztNyJoq=CTQZ?#=Fdd)`X8yn zX!T&=pi+}#1aeXT#h|(jsje}3CP*2Sqp=+jpa`WKk#`WnZPM&khoKxqVHpf2o zE=5|guawM^`bIs!*w{Gu|2(k=8xZLuHz>+Gq}M>K5i->Gr%iN^EpZamq|gG#^H z-p+k-Jo-uV;QCE(y!mGH-~~?rng;*nVKdmUsd@dkCcY`bFEc7%cuaI=(x|<|N zjq5Ja`FlmtU9hp@vl|=N>DISy$HpTTZR(%gc<$X&l%Ui%`1{wnFTMuZ2D+4#@HSn- zH<^(hWaMT3{dHYDX%SAlMfRn5{&obH(5@%@3#3avhS6|N#iZ@gC$B`Gyus~*)BSxgwsI@> zJ=!}SY*ItQRHOtRlN$$@Z=N%FpNt9|M(NSO5Eg%#OY;w7KvL6%y<((%u>eRsU-oyiNB;cZ@+*%o+S?aePo58sCb+p>ZAa za+4kzo^Br0gTvsRiH#TBUvbHujq6-V9u-{P#`Vh`zuQ(Sx7phn2G-wZDXM-jxI`k5vl>0XQ$m$j z#l#C_K&hCxezQPCRUd{DO1?4Nm#pna6<^CbPLVbZwq(eI`^#*Pf{tRDJ z`Wy7Z(s~Ku-uT}iulZnEsea?mTxN|Mm^g0YE<23h04d0NeQ*4G>aS=6_=)_~Z_b2? z7j83e*X>)++u`Ev*KhZUZ9l0bFav?j_;=;sHND7F{T%v+^Aw%%mDT-gq!YQQpF_9Q z&sXTL!oMH9rumAT)h~wrQ4lo#L(`YTe_NDo{697Qi)3%b@UI5_ivOqSMegbsK`%0t z-?%fEHY*A5@C9iR6TNa4*$ps#sb~3(-{EzgfWy}#zgC1l!StnG+d9d=2epU*Pk=eSnVu_XFMzM44vZ z1bh$ht3cs<7EsbTh3;nHd*Pl16uvWn!nXn_e76M(-KM_+AVY zzGu;$14{mTfRg{^^q&cYU}sLITlPv4?=hh8xdn)>JoS3uF7W>c zQ1X=oO1_$blCPtIlCML7!f$7w@P7e`iu-rK_riTIQ25>g6uw^t3g1hC!uO*<;oAcg zzDc0)Z3GIRy@0~^HS`v7zXB9~e*g-QSg0Tg~4fWq%gpu|5JDEy9~ z`~5)4=k7q^y8}@8{&^eC_y2&x_Yt7*y$vXQzY7$;mjQ)u1}J=wqk9Gr|4rQ!{}cIE z08x~gZ;sRS&jN-1PP%UePKNt>;1u8$Kunl3KLy0u$;`8Wdjb21rx6ze-wpqxfHQ#a z2THvCfV%^C1Wp6~!_xQy@t43o;C~wszkP1T{|gaiccQQ5PuQCngD@0=NsQe`f`}ZiAH~fcz@Vf%#IU|tYeFEv-72tPuAp8@7`2PyfKORW`=s^6# z0`c1e^f+ic>7>Q8`8D-AXXyl^BgZr^nLDT1winxpgzK+P(QsgH=NgtRT-;KaW!s4w zWo^2ttGQ=ZRYOC=;+85qF&k!vGAVva+FqSWuW9O10SP?L&)IS~TgW$cfxMw1k${$O z9#6|~B8l^VX2o{0;u>EH1;BPEEF`$b6C8abST)xlzzJBdlUKa9HvWQZ3ArQSBm7Xr zLe_7yC`8bK^LRgr$E=ar(o!h*vN{#%_pZTZ0CQrnny5?%JK>>>D8qZOFD|yP?Nrw) zy8|XATy{$1E(3|9;EuHeDLHIDcjFsplzJ?=I>dJ=ydq=>4ps+}xWd89F+9#zHg&WY zUd=cjUgHGnlK?Tr4Oj^@6~TF4O)lO{1%e`K#VwCX-i}#8uT(o>OB#>Cm|$7o(=oC3 z+Gh+^Wo)3bKWG~|YOOX+hF+)oVnoMFpbv*SUQ3OQ|D*|q`+pg1Q;S=IY*W&m{u68$K{0FLSo!J=oD$w8_u82Xoxm9t`Uq>|iXW zUc;ggOD6Y+J&5mD>K<$<`F)nLreX1mDLL{Nz6YB> zr*%$|{5kSlNv|8g4^zH%B^Jk6e}{gh?!g+dIG5i_W@;i9<-$)E;n;7;&zId&_h8o` zKk~awF~PbLi*)((HSohyMeeb>B;5H`;Sa3aBk#dv6EXwgM`IJR0viVfIwDG)q$2XmK zH$3}qXsZ8v|Bj35pRG9mPKc;k&W8un_j!-?=3CYeWp}QKZbZJCkNC(973W`szZxoT z?w>yAZ}*NX?A^4kzjD#K{)6XK+-y((+g;-ddo``I4{lm_cI69uH?8mAuj%2JoS^<^ zo7SH_z474}8qfR5xTgBw;*7tkV(#OO7d%qf{W&Rj^SXvj7i=u-IC$3u8_z!Ag*h7% z8$nj_={uY2|Dv*zJN%;iB=D>QJ1X`?{+iY`jBmuv#`*6;?NscGGuON1zVCvK{eOL4 z_CQVRAOUdPgZD^HgLgaOFU$b_qII1U7uA1oMq!Vp^~X%?*wDCc{Qqa~T;SxYiiAI% z=kUty@EqQQ1eplp3=o(mh%=dVay#jUfe?+4m|!FX1&O2^f-x|myCIir>#Qpp*TtP( zSB<-?oBc2`sDTNAnSdx0bpb^sfI!-d5)eZGA>Y61+}rndPv_qTrLEFu(7d53_%B;>fJGHSXXTZ+4Yn!u26(6^X z^Qh39AB7-0j>~(~&OoqNW4lQd&ADF1%@`Y{c*;4)b~8h^8#C?7@-~)od3i%)1th6_ zM?5Aqya9|VUH&(DaPANKQ{Ji{=f=~C)1*7exWL2o$9CTzw-X2Dwx{y<5AV*l+V13L zR@!ZL9RDNjPC368y2;Ki|0}v7T2BL8vJ+`ZJnd|YLJNFW;D06X&oty-@D8DiVWJHC zi0bC{Jj!_+D<(PQR+`!~H|gP{VS7d%b~Wu=y62*R5UGAEhT9Pw$nT#<*SF zk$qpVlYGv$rx*yfyQGn=wr!7a?{;`BWM6EjeoH7!=b`C(4|ZLJI2IvBlzUr7@u1Prww6qbzg4ortpQN-`jI# z-?H1z-xzPmek|qeu-!4X`{730J;-*uVFwp;+rhQb9-O8u)lU2|+u8X;=|j4-e8@Y} zjJKczX~)YX{**m2?S8z`?P^IUwq}0|5WpzfeBELM2HqC>*%;ZI3)Jh??YG&KcCTx! z?Q4jSaG(GA_LQ?F<@`zb8;otUo!D*_z5|GC=SMJ^Wb^7+_K1voxiF)~9UEi0qip9V z_Le_*hcWO|Tb}n{XUi#fabqg+GLXnvQ(n##ci2v*FMAMKvhkGL9rwni6VG1zzH&-< zNU1xvI+k`a>tT{>YzKmJa{bvc8FzWB*O)0SZ-z<4*mzoPJCEvi=SzxgVM{-xZE2UB zE;-%hLzc6NH?T|?u9r@{%PP#G1|hXGY-cUAyWMu1Ofk8;QqEv%%L{%%+3%&@1udBb+p|x> zf**wiTZMZRkjsYz3fBqEdP`;CgWRev-284K3J~q)R;QhpGR{vj{w5i3U~kKZ;E);T zMcD2kOXC@5V%j;aJXZjjczMb8j61tA1H=DR+8Ofu>+|bgslK$VStbV{yxG7!;|yfS zfUdYVDpgt>^C2q@%CP-QWmLL3J9g=~^3TbiN&MN$|40e~qz9**qwV~rM&*p!4zK+0 zd`r28Nxn^h7{X5Wu`2NM`K3L1?q;j7icHqJE}^b5X?IzRH^u;yDb#je^5K_VETpj` z*%!+mnR1uLg-BM%az`>O=+Z#Ui>FFUrDMF!{ti~snsFYYcWGz#X6;qloxPbpWt=CA z3Ppit_SmH-q@ACnoqHKd?cq>TzK~A5x#TH<D*OO0 z*Mz*FJZ_f&>Yh1tRNDg6WqM0FV^hw&#?FE1aWJ!#nZuZpbPh~wZ|EGHrYx@0ojRel z!U|8O%Jvj?A59@Y+>A8?>_8KCI@7wPJjT;by3`bQX%j=V)14M_)$w5oh$dP>Q00zk zuvMUZqhYJwK>=rxVLm8AIOFRT16AMd#_+FNd8sMwUeO4_L6X1C9%-#R5ZVVtL$Gvr z20ZbSxM$hU1?A&plx`{8`Fr`C+3Xz?$P@0U31V>}WBo~Ll|ESOrcMx2IV|I5C$xBn zh7`82SAe?Z52?gYU|7)2Z|vgZPz~}4=}*qMS50X3PKl;ZCtkevE8dkfz95}y$ds;{ z0K2(AmU13dZc{N6VYFdG^BTg$W^3i;pxir5=l{%R%HV@_vtY|uqXf1Ul9*3q9lP{E zua)$)Y)MNB>!-#PXjRdw*g`(|061v&j`DBlGe9>l8HD}zFl>hK7XI!$V%WRD--?gG z1Yqu_VBgqwN7&Auo@??)>6iK75xX?a2)ol{PJlTxM$bzj>1GnIScNBm8Wir0H}ax0 zn3g%?0fpr?&0>Ax=7tdMFd4}7XPiJ5x=%zqnF}rg2oL1m=UuM(k&EE+zo}TXxXeMY z!KgfU_5|A<=?imXZD(Z4l{d?L2_)YYN4(QQvxf@*BmV(X!=K8sla0IzOK)ddkca$W z6?;;7*GiXeh}&)_{Jk-^y?mM=r;0T&js`?f38!i^5bDmSEw=L>+qsEppCQg)+FeM- z+_>s1sV4fO!p!XO%^7Ra!wGs%s8KjXWWlM zKbWjdzI=T)6B>cY ziyOp`O7`I_BY=Xm);l<06Ou1e{C)QADVMpWo*b0&y?>wZjdIYOPBY1 zM;LnWt>@Ct>rh+v5oQM?4{ZzNq30sa-xAbc8P75v?8KYa7p67kj+wiu7Y5Tb(Afl& z8L$&OtuJ&m4UC9opS9f!<2^StHROJ!n0G<`gW1Dm-L#5z|Z&Kcxuk_IqvTY{uY)eE%fxr^N@RHN^%xnwVN`nlRpW zkT=Oc%GRp8hXGYBU>X?S_jymga$22@Q>R~v)*lrvW8#QI@;+Q^}=h2G~`E1O8vFj@* z#g1;cprPS{#;vi2YY${!@Vl`CAB)BAj-4Ouz)-o)SB5$$__y zX!v$)Jk>lGJ8)Ml_G0WNw#P;svTH<8*FH4+KP|~QC_Avn=nM)B^grFA1mSNf|4(Wd zQTe{e*XIoR-%*#ypARMTAivxF@BX&iu? z9VUOn>fh>rw@v?teQ#|v8DHZPEq|q7eybQW^Y<}w*0X2n^X;pBb=9!)*ZALk9oVq% z{d@U7w3qK&_fr3M|NCUq{!Y`IhK7F$`sX+7)|&4>4B)G4K9pmo<>ogigsbSe9v2w`d>I{g!my`_R6FQ8zZeYWm^R%dqwdO~~Kw?2pdooAkB8 zGvx^1lE23H_y$RPfcu9?*YpRIPxzVqHJ(_LZ+d=|>}5+mpW**{KfUl5`D@(ecOa-| zMm2sV-+lb=eaX}#d_(>kAFat(Ll=_o&-`!l=|aY%{55Xcdp@!6YWCqT<5cr~WzYHU zWZa6q$~U-@<@f?u^ycsUI9G7+&a?dQr}*y=<0|Q%gRo#3$A61!v)G?-mGmca6+4%I zLD9`ve&;f-Viz-utJtwTPbRib-uW+*nd^C6Pv$ztzrGzlD&?HORoZbdS83k}|9LBX zSnB^OS1ICJu2RmWT%{eavi57@x|yq#`w|R5uDAKuRoFYo^N&ZFcK#o(QvMgYO1W9C zQty)^&GkO6^8HT#S?x`D_U#;wpU!)+FPg&jB(7(2mG-}btH9xl(7M230at;;bglx2 z-%y#n|7Wi9ekoUZFRF`^xynKuUNf$nD`S0}%@tNVu7fLkIOE#6f{JmiTmkvG7OsG3 zTr*d?J`Na&$;&w56LfhT;Ax<1WYztQaUHaSD#x^QrKw|DxxS6>&Hno)|9#wlFS{s` zek(t6?f0*J{$J=~6Y%(P4Gk|bTf|Gv+^_W0NN z{ta{)ey z0y*arK?2W#Tr(s{kU{{m{9OpW%X1*NJS52Bx!M2z3`8RJ2lB}b5G3FG{qGLe(=7ra-9jpslP zc*SpDF#rG5e-7sRM<6b-y9)lUhv-g>^*8iGl=8R9e{KaX^0$h<$oH*&{~m!T z76J=>7DAOj>XYYg-pk(w{`U_5`Mq2t)i=b*7k$2rhv@UQHP1KKJfBum-f_S?n*UQZ z=^L(MuUcLH&cGhQH)c3z9;dcmd*x-Eh#|+STzM5{Nf;9?xl%$>Uh@e)%V}n9ZT`!7 zShb|c+&YghA0-ZFO77cnoiye~GcKGpukFmJWm1Zb_Zrg(Y#4Z#(2)C=Nf~O?Aj#_3 zG%c9bJ};SEdIO)&zUoTCX12C<}D{xT{5p0f1v5Q zOJ>cQcM%u^eY4tHxos6B&icT-S?@P!ym}Ut9pdq$S6+677|DrIM?I6Pt1kP*6&PGy zbrqh%K9Wf~+jzTPa`}?n)r8lqj4eh8A&oB=lG8yY#5yla2u}0nFB>Ga#IGbT@Pg{i zcmObX;a+=rYSy{)W_3o!z`67}{Sio;kuZr7(aJ zNoXo+<+NY|bkAFym66Z5HhnZ1DY5%HO?M_tMEb7p%{ZcX=3O%L|9|Ki$kqozrT>9#?U^6RPExa}&~jF112v-?HY?RJt&@bq z=B3&pCyCC!N~aZ>nl7Amj!Y}O^XHXz8Fo5ik{~j|5Y9Z;$K*#YgOe<{+?-#Ap{#_A zOznY$U}cw93<=Fp+3j2yZ9ithw4>`xz!8`s`qTa}VC>5j-PJa{AZUJHB|A_)s`s&>aRd1oO zRu+cuprQF6hsLuQjk}l3-a_O55;UF(8h0<=y@kgAC1`8|jk}i--$LX65;V4g#@!2= zZ=vzO7LETa^z7#b+i!JM>)SvpFeCbDn7jWqUNXZ39}2l z)Zcdwp4r}3pU+^s8fEl8#k*a%AS_R|!h4>IH4Mle{eJrS_WR|CU55O%UlF5-@J|gM z!bfnj=XkgMmz#FoCx#RJwO<+2qSs&`;&3eq$aZ;7+u^m5lz)@baqtkQ+a5%4_ut>A5|o z{oG7{m)91?vy``|c)aajH0AYSHz0p&zHG`{bN+~iE#{MtDf4}Ae#npA>9_ARlfL_V zro8r?U!qN!(ML z7%#HBjiKJIn>4+(ZuZcQdt=!H@$T4ayR#qj#@btcXP0tYXhBb;$kJl+X{kl=->5FQ z^LsJ7v{v2+-bi+7w6rhfj7eheUYcauiX~X+uw>!Pv}uQy7RycCksONUx^^UcV!03O zNcP2YpWKmLAIp6zS=eP2SCK7naIoDC(iCl)WXk?X+#NR21by0o55rdfV^1vm-S8t% z-{51f{}K1KJHn4VeU6W7{EzEn*;{xt{%SNKy8@3OT1%6%<0>D#k-V$)*!PeKy_y2Q zAMxW6Pun@RIBDm1j<9a|ympB2kycOQN~`#f@>seUH-N)%3?Ojqtj3-jV(-kg+4+t5 z3>{?W`|%R!!o>dxZJ>RZ88r<(c;#C8DNwup%47d0=Hbs^QndZlc4vx5k5xE{w6=Rz z&jpuT#dG8yA3fVSi=wHBH2M4H3?oYV1=7B;976P69?P$iwTfz^{w~R_1U>Da9}@~C z`0!}q4%YaN^G+Hx(z?498XGbla+6!}Rq3}{$Uc!c#`yG8l0BH* zDw#;P=I&Fu2@>~{xR1nZNTp8ZL7_KMR;O znPJxB@bRiU9t@MlGcbrxeZ;+3vI_ZDGiicv3mqu^j*l|w2TgLrUwn>EWMD~bf+SQD zD=)1_zk6Y%f`p?L39X8R1Z*q!!2lxFxcE)T_&~-`H8O|+;e%Qj-`zgr0|&zR43_FL zzTtj%-L^ZgFPrs+F!b2bdFFSEqq$`~p7ikX^z@y5fg;C38<_&vP+vB%PS5-k?#> z|Jp%xB?grN3$IXeNHW;<=+Mr9m1qKK6$rBCO$ z=#LKZ1`gn-z}uOf2aMXy*zzIrTOjgG1tQM?kp}{Z91`)<^zOD@1B|r-jKJ@%8Xn%4 zaRa_@K3W3 z%@B{jT|9-`Gq~w?WD;-WreqQ?SjBJ9>++w-ApK3{r%tf(CM5+Pl$tSBT5c0~pj#h; zIPDUgBk_9nGV%6Zlv?na2|^F{#LcNhviYW&eyEY`_+DXseq0srWbqUBUFdfgXC#w2 z!zx}6O(5Zwui=_;&yZFYE~R{&CB3O3GHcpf!Bc6OjUTINZ$Jv#@pQto3I|C;akRyG z8MnrJqs1{^Y-bD3+u~@Ld)anV&8f-h35%9bR`-9Y&o0e`cxOS=$K;HkG_IrK&v-4b zj+D3wIDPurtk3+N3@Iv_2(k;@OZj1(2A@cBlf6{N zt;4lFp&ziqHz2X2q@gS=^be;x|px@0rh4>7bLi=tN|XX9$Ac(M&k@R)R`2+m79la?=##*w9RRLJK$D zDVO6eaNnFGzL$I=B*DGi;=P$Fks{@g9{Ggu?zjkxNB%}cDZblaF*D=~#*WDBxJ1J< z#p{{K4i2}xfKc&yHH0d(8xsm%6ibypG@&IZbuncZCiKvD851qRY?gBL_zk~i23Rzx zK;--g^&7u*R!UgG>spg9jnkrZ=}sAI#A;{^xJ~ukPGxNmm`o5t7C(PTGH7wS^aBYt zWV?6DB`6@}7A9z+PTq{;!URp_O>?1|cbg+|LWjoWEp000Bm#%3c zgcN|jhWnd3xQCnE)Xvm3hx<8c=ZbE7@Ya41B7fX&H+Kkn(--l7sWtxAn|U>OtFRT3 za}f<>Nk`Za71ez#EL&`?%dD5MBdoXZ#CJ3Ovfe5~$9LV`Og2tNN&4W|wT5_Pn?(rd zwyPb&3Bkc?#Hyt5@H9>i+3pXdads&`B$vd_9G=8ucXc;Isl{DHU6O|uyCjQ=WFOad zuB*91!4kUV2mO+5#il;Z{d(Qp#?#+lET!CYX~Jql(Hif?xZx8tVU_K+@~zMGo)9K_ z6Xjqv=Z)kg?#&Lc6bb4zWP*Ct#5s8VtvCnO@|KKug8RI#!T}-|v7m@rUK+CnDNpoKw>Ts7aR=v@kP;49J}ZJM3zv z92jL+3+1#@PHRvO3AG$ykJk7~IgFzP9hAeEsuRSQm|+(z_(I0%Ogkutgj!C! z&k_9gaL&*I*3e5BkCk$u+AbLOv^iYoP|keHnIDuxLM^8|(jL+-BJCpPf@$#C_-A_{ zbZdOqVr#rBx1HAaZt3N^(8T^|=a@ksU^#1!z3Gvm=N)nv$VoYQ7MZiHv0orLYSlN46CtGkJI=y>)=_h5cI zMao~DcE~5%A(x;X`l_^UNL~$ZM|FlhwxiR%MOd7ZSNX|#tiqKmLawhbh|EW`KvESG zzE#`d-XrzGp|Yn{!7<+_uYE?u*SE{pQw`tpP7HuolPdSi;KQLF_g zC5YK)MoZ!n2uS8O5me1AgZv@$haaG8`1pDSVbw3#*KUoPkSTpvC$ALZ>OM`WP(C`N z=PFZb;_2+sOsG16uGr+yt3lwJlnJ{70eA;j-y6*WR#(L-_q#g#29tA; zG#E2c?u-0bnJ?vZvF90$f2=+I=rNmkA5&AM8<|H4wo;lE*Q$9T;B4lF0Vyv8*o23h zWdP0#K}9z&d~~>7i#Q)=krijO#r@GXUPj?~GYY?_LvCTGqa!d=`bLxzAOb=y8MH$c zM=HKyXUTrADNy}*<52zdN$4s4%BYbudQeK5a8Arc{k7wTS`0OsX_H#o!nrg1u1GCR zWipQr%fNW?HBa6lX<{6a%pbGEYd%D189`9yV9(6O&906mI;}%0di2v{IXLrn2(+Vu zXoY=-lplo?p-4PfDCF#4rNXZBIFG*kO}J z{xJGBChaO=tGF83-A3Nxt;$-qQ*30p#4AW>x{6%jauv@|&~z2MNV3M{>NYag^hJnX zE8!0Ew{Y;*UcIqPxAt*EYB~Idm3p%*VU9+h+H5B_W}8z^v0uIqq9sM+a8|11Kg(l- zcQU7lNye13wx4I)JrDE{=#8Ld3_F695#%jZT5E`;OUkyLwd=_sRm`M{O4+m2#+VgHpUcJ1tP*ag{@U&a zX{RZ!w4qrr9I1hGisThaFDjn92FSNc3itMQx%fi8ZJKdpr5SIxiU;rz99}p+J%L5s zC}K_NIY@q8M|^{wJ=*mEm%X;DvVnk-sPK|hlwZCwmdnuXo}Tveb5neuE0Xu0P=sBXDbKxR;)Nl=j>+}$C>R)M^$=t(({p}SV1R;3(CR1_@n7C{eq ziEFrB@}s4d%pfMnl4DRSl3zpHj`DX&m0Eym>lgOKjkZ(jm3m}JknfkYx>r=Xyw5v3 zN_7QQY8l{N^(^;={vrF=yS<;RtvN=;l;h}4~e zzJO8pw6p1Q7V9+nW>OYAB!gR&3}iPASl$aI>4(zoON08X7ENnSG&$X(e1UcK*@?%j z(j9`J$wld{_1UBpX3h>Y-N?VzY!xM5foOi?s0d2Q)&9|Nsr+)gA$tWxwo2l}!NCqS z`V!Q!ew<8i60!Q?G*WGOgFF)4yC+)T~q1Sfl3vk&?>3Y;)k4r<+*3_N+CkXHb4HACTRo-Y7|AFwip-M1H@C_Ip~&SYW;n4`-uLjH;Bkndpa*%-?Ej*TB~l^^5F@e(!X{C#^p0@T(!<3e0!rw*3AgU*;@HZSiMQ|`ez?DD8t+XZMyi)!RSfVZEM+950 zKO2vPGMXvf;JI|fr{^nIy`IbdaMc{#xwx2el5IO6lg@kSYr_xZr}KyMYeJ1um!#3U zq@ypuGsvLKcvzO)m7umq%e!&-$8>F3rzqQ;+f&Oe)9%+-(c$#OrvW@i=N9{vSU1_$ z^vBX{00SMkd{w)O4+%Fn{ZX2iKtU;YLq8osp0f&~`XRJ#b_s#S`)p_KYNlHOV%kwA z%A9YDSX#PWUhe8{?Oa*xB@3vYS-W5GVKx$Ne+gv=q7+DpZlDM3y8JMG>{$*gDadg; z5jku!yQsy#m3J%d6NxRNII69%Z_$2K_+cunE#N&L-H!r}uT#JvA#?Y+%fEs|YFqG~ z{VCupbqe^({uJTU$Wa{uJ<~It5%Q^4z`>-h1m5 z(6T=T96}&h+l!!|E+6*!5sVyM(q5UX8jIC-5n7=0Z(s<-Ol~50PxqrhDVeH5kNoq#f@}?@%x9z1=(5dmA~*@0CiJDkOW5%^T;9 zq1X|@`_BBX5mxbZQU(it)C^(Qn0D`JhUFNx?i7b$dRT0H;yq%vQ0$kd_-F}f!vuIp z)KcSk*a}uIPwXOoWod&X3}iL4iCtQ(d8L&3a;M^7V4NhD7;EKqpnUK-8sV0Q8)KMh zz`{u2K8%9M6w6sVv}v*2v6ux+^U^k^KYY+m_@A=R@KSnXCXvT;Cwix73DVS9c6{`! zH=SG>TlG#sM*)u_||8I{_Oif9g=SDVz7daWlRw2 zRTffWTvJ-Ujd_Xd?5$d;8sCg3q;GPo9%CHuVzcA{IZak_8%;#BM)MB|3o2uBI`YHO zL050Cso(XZ`M-vG2Zf4l=gasGI#z{~L^EnPGcBR_>arD5`QiOg*i0=&v`LA$lJP-RWKsDAF%ZB^1);x?(t+j3f#)Sfz%%@^NOI%{OjATd^b;o*+(b&`cX?J zmqs1Cl5q5;%QuR+cTw2dg}t2vqmFBs%Aq9vdML@oh%B4Ei^CLY&L&9ZpWPm)|CD7U zhf?{U5QrSLm@{`Xw1j>t&@fgPnEO{GL!#%WOM0w19TxdFl|W5%+X$)l?UMK3F$W?CrsmGMx!UO0eNd zh|3~F8k%J_o0o_D^2QO?%}crMS&I4KwUJtpYkNQ!%dNH2K8{0R-+|oGR7XvIEJILlIeli%K!*MU97}@$5OovKaV8YLy--bO65){e~%pgcz8k)k#_!= zNjz&6zRHI*diZECaO@h{%}}k7OyLc3BFSJd{+t&yztGFu>6^jFN3Ehrp^8n@tWt6% zdK=UjJzx;R@A0X^e06R&jZQ*Bxs|HyaB`~|&b{2P5oN_OwlZ7x7b!`KF_J*r4i9~V zu;C=@pNc^-WGH*%kYK4rh%ul-(GDmT{u6@CKBJw5d>>dwUn(o&4pb=A*Uc53owJx- z#T{1RC<@d--kfVBN1(7hSsp9WQeA$z#^;w5D~q{d{8ESs4(pF47Iuo-n$Gw`Dr<>3 zvsIMwQGSA-J@xAWYa#^Xk1qN}2W(pv6yrPE`6e1HAwz=7cgv+h=8Zm?@14Y@HQ5h9 zuWJ;=-bD&JckU{Rgdc@P0xuhd!dTmdR*eyh?HrV1G3zd)I%C6POPqfoy;|r`3*3?H zk6DF(+$oIR-|;VhQJz?eQv4p4qK9HLRS}~1w)X9#tXeVT>+cT>@J^vc{o111WH&~V z)n8fH=2$f=EZCP}ZD%*Da_@i&D6E6ZCqQE&`xr)Wm{8$9G6K6~#$IBRRd}4vR?aBu zi=LbjtOrnY`597)`>n!1ks)$q(JzEUh~6wbvIxzr5hcAFy-(9ZB?}J23CQB((rO*c zfdJLx3sBxc-r2gYIaZRm=Cdn8tcX{JsNN}`YK(LuuRQDw{n`aOLNaoAlus^!>>~Ql z%KoIk|D*;+&Kt~-a2j@#0`X3TzGmU9&W0u~!G0HmS6gJ{yHOKVzWe-vK&SG-DqMb$ zFvbsYl#5QwD|wV7O^h`5Zkdx673tE3Pf88QWV3_v5uZ@%fe#MVAOvYXw*w40Hq*#1)Ckf61u`$RpZKCXWM`jxWy#C+J5xhQi8U zT7{qN2W1!B7o;v?aIFcHqjEx8D*tCBsV_(ljuGMsNvfAgva2*|!pdYXJHUU%kvCSc zk9Xy7@e@T*M->_L17xX6cLzjXsje&6FH@ttR*_Jey@t7-;uSX9k%|b_)Cg-@M1J$y z%`kFAgwmuHDN3JU=RG8(8H+mxJ4Eb)v(KlMU|TfO%7bAw7%TEdnJ_06$vv-@SGX|y zCde**EFuCrlli`%VoZOS?@fP_>&?6@({@#FdYOt_!B@0HGGJYdgb+l_Nbiv@IT(c^ z3pib2ueywESk)^MF@be;a9z*UrceFY=X9%9x?2_m><;BO+A`6=Ye>U3&G?3LM`ZmF z8K!^MC6nOiDy#Sc50Ym;`U8T}ZYzpftN0T}YP3IeRQfY5?9a5__6M6K?aws7KNp(* zoGHBBd9hx9-o_^}@Pwf}y**%yh7Sk*;zqA`eXo;&|{w55?Uk}J?)VlR7fqZF$RBRxK*JA!P_~^lWfG|_} zH;-C!I4txWWuceDLU-ie;hiJvp+AZc$5^`;h*b3@x1=mS%J5{MgJfS3Wu()C2Bgux=eo-Os_9h$ zQIn;n9q>wFP#{$2RF%`ZB6|nN0iyvaU?eK@H$ElPOY7S;d!%NGP}`^5J=E> z2Dhy9HpAwNM}ggF$F0Km_$V#+4(z2Et02x2vPe5t&f&VZqF-@mwC7*ll{>e5tESJ; z5mlzhQHz{^W^`!7nsaJ`WlVk7F7MqH_<3j6W{gKZeS8Uq2d(ylCf8Io%&5SEfcaWJsgDlhyNnDtTZ`cYj_O4wDFt_ge$($2eB zG%nSubQ#&9w@Iv)l=EU*C+s~~Z<#I?`ax|xcQAYVx};p#=!^UCwSpY-VsqrG?auL+ zl+PXj^KciN73HykmUp>md2{dbCWlP0{(2F8l&lvoXL1yM)UZ_|%v#$ox^>_cw+g=( zj0v}c?MI4-|3gQ<{SD^`_nE~a2Rr&J#rOLwMHaX{6hRp}EeJ?tT4<&wxsS|m==VD5 z>BWJ+7y5Q7wsx`j&Ebp1E?F#M46EArzbDI~-r?a&F~=KSCK^2zW|wpu+5zKFW*0kyj9tiF2T}8}pZpn1-NYcYdNggeBuDvt<0Rq9oUAmW+Ro;A57I?7J-A zs7uCw<6Zgt{DkNMeh+IorW&j8E1i5}x+EEr`$%0VeR|oH9Z5w9mn*$-q5P;aL@C*W zfvQy~qKZYBjOTA?86_&R2HO(*jft|taa7#2^Pl28YgRAUiVsJlofyil>vS({LHuk6 z8{XS0_(&ywVdbwz+SPQfewO67W@Mvm0MpQTr}I!}{m`!avq~p1B%0J`f!ruQA z!`{fzHGOqCxgVDAAVtTz81<9Glu{t9f`ZH{-U4Chu6tnvcO3;*l}oq*0cqz!_R4<7 z=Ac!$O7?P{fllXP?1>f1SQJlnUKK;iZjlpK&f>TH19Bp{#8A@s4ZIy|zr5FRd-BJJ1PY^n?~r}5Rc2MMT{3q;a)bfknC$C z3(2N;C<}>Vb^+&*0n4SdMuolEJ&23*<%z9YEm7uZ-3<6O=VTp4vQ%QgDjZ0v+C7~|{XL!j zaGm3A+Kmoj$k!qCGec~yqeEb*0g}!3mTf&xqN|r>?;|WSw#xyj;|mYKZSZi#t3FwN z*kH7uF)?^AxDbVL965zCsI6u)nGSpJmqMJ4QrebnVF6b0bL8(7gEOIvs#1Q`6>IQ| zu!@V6i1vhgVIkmNG#czC6hBLe<)``C6B)?fNt6u0x9*+D#-;3?RQ6RWdncJ5Ydi>4 zC5Ept-Ix6nl-06-GNk=6`zMvX6Dbnfl8qBAF;%NuyMMy_;rl0>1z=jBOmm`)w+g+l z$Pn=bK6zcfQQoY$PlTAd%LF6VK0YNPn3{bQX-$18Uk;uMMN?D2&-SMPt4;yi_oslL zK>oEY=-HnFN_7e-b^?PypEA48hwP+pg|E#k6E2C6H4=%V-&ZF3v6VEC41>z|XRSbT zxb2cjQ65D@z@w?HyaOzS{W5EYkr-T6=El8kajmUWz=u2bqkwFk0;Wm{e+IP3ae$jb40o?FAH-hf63FV4aYHCfetE9*bvh?y&40E&2_ETkl=~?8Y>fpr#OiqhInzq+3B1^C< z-R;ts6qoy;;g?^g(6USI0j-tK@|$An&Yl&`=$%r2hxuH7pSiNs3>i|Hu^3$qN)@^D z%3^C66i-vjqRf%&;nk_jqs+55tniAGO0X?l=1H0f_gt^pX!kC|t!5L-Zz4q~|` zYym&+;rig05&&ph6hNezF5-7XZf-g6mtyjZOJ4{`?<5JC6>dcidRJ(eBJbkBxU#Z{ zHN}nd&J~kO%oHdb$JKon22RxtR#A1D^$e{L*#|dmqr?uDufaBNuHPe|3lI+E1@?DE zUbsKIF0!$^)okp(5|I*q9P~%!XIcN&+Sf&&yOnhGXSeA!o`k>uN*F!-{~n&|6purs zOF0jT*ZDp<05*``u66)ijhxA=X1n45SR691+Idy+0W`0=_t6JXQoB=(pWX%$u?gW( zn$c`0?#2Duy+hvEPQD+aum>ScMqit{C*Nvb$(sfbFBFp!-$1SYPW`I>dwA~Iesl#K z^!O?nawEGq%iK>=H=YL3RF;8Zz$Nbs>mz79VJtRaiQX_MqI=LhZ#Y_4+7@ z#nQtVR!Sn&{~HpYeY#!#q2E4kg6xH}?!2d;0>yvzSTTPkPnJQ5BWv6{IFxa9S-6H` zyrYf2ytKAgYI4@fOHJQIQQA$40xcV)=e~b;+#4SjW!8WF^tz|2y!oa)d1=bSNRfL7 zkHEk}S>kT6oY0y}cSwem^Vn3kBs8nE&gj$pgao=;x`a$7F1ftcW|q`}OI*_zReimq zT`N`aBH=Gt!0@z7Yx{#@2Q=b|gwJS(v~a(a^yPl;1AT?o(>$!69Cmp$fDbiQTzdyA zw^pfj;?L}jua8by|N3j(PGk1n20>3Z*ohahB$z`I>sEqi64YqO-ndf|-k&a=&wBLX zv^C{b`>szOHz@x|zd3Dny_uocC#2XNxb?5elqQhX#;v36()mURWr~&nSIlq6J#KG& zdUU2V;e?Ay=kH2wd~S3mh7$a_Xtz5D3MYu;I-8uMPOwKy-w~&^)uun9rBNdmkhu+X z;~x{32A|lFah@Tx!B!qpEAEqv**Y=|X$NTQ^mtXe&4%(IOXa(gs7AWR!40k?y z2`$Rl^d?t7F06Wt&~-gm^I>$C)l_C2w|K`GBJ>V5Rr$k78`7D76IIq&ZA~zQL^F5; zjBl6N?pSC!c3s@+1J1yy0)D^8<$LKNJ7hlI6k(zOdj<_zO8vsXP!Gs!lynnWrz%IX z3cmop0Z~f;&F>4bkxr=Iwz`l!Z(j*np^02f;7g~tiU+S#*V}HfmxqA{7QVL&#@LwR zwy0F1lxk1byfjfLlhz3{u?l#zNDZ2NLYGQ!DN_?cJICi9ZDp`z>HXCx#{VGPOt-Dq>;6%RSEqh2ebx%avZFoo)8S?e@E+ zT&uWR$io>%3Fsx5x+dQtK*^rgqXEAfa_r|qW*NN_vAt>CAO!4T5DsD+PK$q&YAc>NacDv-=V_Lx4A-Pn>>r6pI_DG-Kqo-Co$K0R1uSxlS2+}W! z)sa3|#~&>(1JEHjdYOXqLHyTjr{9cV))O7v^s8VOM7bPhd<_?r(9vo-bi>jF6qMm{5oL#f>_z~?g(swu)S;b|qg8bX~3H5mb*3zV#PDY;>Z2Q%XbnBg8o99kuj!K@2UX8-E zU8N)R;%h*xgS63b-Et4_x$3ibU8R7aa{nsA^85J-;Co&azTWBHsgj_bpI|cePSXfl zmB#}rIv}R3Jz!%>fDOQ#_+$3e5FzZJ?H(W1s4xriiX-Yy2w^vlsJ>$22mL?+s_XVw z+Tti9XjOv3CMxet_4fbU``lL`??WZHEPUNi&7URlV8P9wk-3d*Em(R#B9z1wwp?#e3Shn&=gmuhbPZ z=5uEBU`59+rco~Em(GuUpK5`j+KX6)m)X3rOBrKDjl?C1mv*=v^RXGQ3KRGWZ?uYI zxX~alx}k;7lrVWaAOyg;#+Z3yg6qgPb_fZ}sFUbFHcHE`W3Sb>@Ad-(F=m$1lJbY7 z1*SXRRMsDCDVA|Itwu+-T>vm5*G9B!!c0Hj_CN@O!k-~g`ON<84+Lz0z_UVrt&lP> zF{D0OO=+_dPp={YxdaL^=Otv1k0OkwZ4Tz6vMl5L`N%5d<$*aVnp)N?>0)= zk)Z??WGBl0vhJ)~7lzJ^!DRoA-3?K@gTesjDVvb6)KoK~K0<`!E0M)6o zatn9RT!%nKMNsq)FKzoWmJ|Z9@;Q*2O2Rs_C=9$;6^03!hasz!;;11x)ZCa~+JZCA z+#cj|#dArz!m5KBj9GJOaXjrVZ9+B1j3rx8xueP-p=jm*8TY0}Rj$c~SEh7LywjcD z;%v-6`DXsr5xJ9sr7uR^fsOV+awt~*DJ|3dB6_8TIA6dD1!ciJvDnF-BaYf=Lc}rp z+T?JXI5>H-&$o%|HREaSD(^JvXRS2Px4D~1i5d55Acjx<57g!kf1TW4VRM(|pa)r8 zMqI5kN@T-zw9LMvc8wRW1*AWz{kH^nY_n07?-{X>=4Za zFw8jJauMes=MfzfYNy|`e29^{1!}~=5joi^-0xE+8;ff~S}K1>UZc*@var&_Qcuuh zpuY%K=5p)^(T<|qh@Ffw4taTswkD!YdG~4b6d~`{;qQ3-Px9tI2w+%;@ z&-&D@sqjsR7L??7B0bzpAALL;0P!~4?apjR<&zzi*ti7SL;}XT$BNBnmv^0kM`Gjk z#6yrUsL9n(ORw+;#eYdBLSGUqS5g?Z+2bUFbY;GEtK;`WqcSAYiMAZ^ z$n@TJ7}wJ{<;cAe5yEi)PInq`-ihmAExTr*>Ohh)BtuOH6nJW;R7~4od30IF)OeYZ z75r6W>NW8TFteBNSW-l1tFOU4%LfbTIfOAcBFO{18}6+vMQck2@W5u^M{GNcK|^`? zVrKVdH?mv4yMjKxKLv?M7%rRtQQ*D`doTz0o zmzpwJqmNoEc-Pgo|Etlj zb85f#M8BR_`}HHyugBGXof`RC?FUc+c+JYxn};AFr-^a+j#vbkJvTAf&rasC&ttX5 zS00HM>zfmKXLu8_qd&zv**nQt*9Kbw8o_@y?FC^xomUFZ!4X06YA; zNd{i_QU;lhJVqM6NyA2&cUG9w&M}1rV%97LvkF2k1*NZzrv5_4k3y^?}G%L#HN0>q@x z9WS)4Hc_1_hvMXuc3w24mM&TZtv6ehB)a)zzPZznda#XSQJ%JsV_o z+JM}pi`)As>~$n^yL3k*@BN)DgCiQ*M|=mkkqFPi;h0j^4^hj5AKi zgT|zVMHpt@OEiJ>`9s#U?9?x64^j2Oy2Gt0c>Qk==N0As{uggyMO< zoo5fBbjbQcDwwMv7ZbG_h(No~=!(m+yM|a3NB^U>PDVlyv z+lQe^wHX=r8ct8Tp2L&=gjb*v``5Q2V|s@N^ELYIwD|A+4IfcqBR)g!2o9w)t^i0dVNL+yq0-s|OG{Oqk*@N0vzItlmv3@9m5h1!O`8gjJZ$N=6}KUK`q7 zNyOvk0M@BeuI2m9(qdmP#tWoqGY(zt59D-uNM+N_B3P^8%b^uQGXf>FhO3NEA4)q6g5*RaRTF@WJL;n6+{ed|8qri%X`u-1)P-z~aM?|S8>0cyEx!sfgJ@}4Q z@ij!Y4k5UJ{28rK{HidSvZH|YF?enYKLuRr)q)cxA{QX%u^YBsLeLWC=m8ejmg|y-gj5;uGFHzXwG#0yIqP?>)x!^3@&0 zzLpoNYKn5P4ymU!>5&5}U1dK)uoSZCVU~BLc4^gV>f>_TNF0w&!aHSaYRmH}=O>wo z&sw*Phbr$rl9HW!a}&$gprT5<3mSv3uzEW9eo;1 zY;u@JPlgM<2c{j((E z?-i_QqgXlWo}&Z?R@bYrenqdNNKkgp_!eKL!Yn40 zw1Z~tY4&<>d1|z7{d*Eozr-2FijO*%pY*vBg>dR5lmz3`lemvh zJ-1okx9WTDdCxuXJM?|GzUQ9z-1C0EzF(~Gx##_2JO76ssvnj6lIUj6Pn8Zom&bTI z_6IN;7xM~shR+DN^OQ=O;tPmfKN&VEl=n9hGG_izGwTsj&p1tTrwkGn$*xax;dTF>*7Oo3W;`n{xkV z=Qjw&9Vv!VyOiLB6{MYy!@!K~J4VKG z@5BA>Ju63c4NQGEi*gObUd@5OtaN7LR9AE0W{TF?p5rc7+$SPq)&AA8#_B?I|LpyH zD8w5cc;Rk^_+U~f=NPJCHrY2SO?uy9GM|~9DBCxKMMa}pIJ1dm4torsQ*Ny)#MGU{ z?K>xnVZLh6F+uj^!6z4vUgg;87EC-yndJWPVY4FAHT0WNyeK`+vQL3P+nds7JBxYjMKK%UM>r}s%l92 z+q??uDEC#qt0-d?WFHXW$|{JXh=}n8@$ssK;%R#)x+iFL`B(BIccu;Xh6q}kEZRl zg`C0WUI72yWP7R(%w9p}*h@Asz$vemAH5T=|MK7S1181$p+ZdBw+bt6sX*p@04a-C zS#d=K;R+i@mf6t|!?sj|9xsh+IlF_^^u4xM4vIG{R#s@fj2`MY%qL%8Sc1d# zmvkOtn;2!n@WYGTPpLiYk#^#>>W1OY*-i`8P>^Vkj|s$%1`43SvJ$2cDXBx+8wG%=4QlqLT@X1%mA^)nX16 z3-sevm(--;H%J*nNY4auRoIL?&>xa^%%aOG25MoEeuhDs) zBK(%3#x|k*Qi(USldA09X*(Tht$x>!gkp|tJ6-LCTj-c?ysqhqC>=aX(%irMkH)@3 zbg(IOA2L`=*ev+y?CjYfMt=n9nZ?X6ao-NWCU5jUkP!Dqf1By!R0m6X zF@?Ne$q)vLX{82Z3PcFi_6&s#LC2&yn?^Q(a0$`;=TFEuxMuLIL$4n3L9d4Rj*SxU zxCrseK~(N?rMII}i8rz*vMN=wO${amBjTkJp<6WcQMx7fZ4fMO!lY!C(?P=^Grc%> zC`~%YO<;M2KiS$QKBh}o14>f_c1iNa`5i0Hscun#5&%|nLVLCJC^R~p9^cq4J(9kf z9^VM)G2llLm&0!`zW?2n(QQ zv>h#20kwq0EgryK&9<8(%@V#PxAM$j?PoqDW7@e9)`g*=ZviC7%w9lyhfE(_RM#b6 z8rIe#K43r*^v4`OFo)v@I1^fcQDCGr)a)1JpI<6-I>usJiNuE;J21To(~}F*?%UMb zEPpRAHSWAPKi$45cP_3xqCC%?VZyjJb@nW8YRHa9TUeQkItP+XtXu9|8sQ3Dfc&pz&5TTTOCO zBQ6(2wKoyb?a~b~6i(aZy?cGD!oJv#d)#fB8OsRw>Qy%!QGWUWlneN0CH^EYtebzv zvj$VS6{yoLTplUr81W5+1aP+T0|*fmSo?L9Ou$=cUgcBv8Pw4&RZX@E?_`2jTRPE{ zW!&ksnTRJFx|ybGPPApr9M{OL-VumN^nIFR`66 zALV>1GKto%_$)Um^y-3-4#rK7Sk4KlG7$<@5`kUz)Hjo?{0ur;wCbW7whFH?8AlP` z5Y-P~+(=4v)qtSzhU$v56IvskFOH;PAVm6~Z{S|`>+wLKne%lCwUMetEjJ0+Nb?v3 z47?z)yJ_F-o^6|DN1UxDWFb`OcEwPTc{%A1MmgSd05hX@x1&4Q_p=JOQf_`Hwrp2( zlP-O{gYn-H8UJe-?-*06F%{p`3eaZ!V|D^0(#|Ik6soNNg|=j{Tg9amS^o5%8!n*T zUBg!!E>geYmukbYqd89+j<|){jixtse?jWr$Pb0HOI{5XF`w)S3Xgyz`yN>&5@me! z3s}-?&j-6YC0QBT+rG?~0 zN~KcOhXeFd7XS|cFHBue)3ypHLvZyB&d3i+*v?^Ekbva|92BvV2h-h3H#U<{BQgzZ zMcP@?&i@X3u*)V9gBF~_@7cDNiDveC+dUH>K{&3Q9xPXkEI19x^H9pKmp$#%tDPdjTvE&(&6&s{78hZ z;&xb~Fs$MUiUI9Nbzg-POkc;5X^)-UBgcA=c9MZF%B|r8|0dp+|Aik6bs{6eD*g@@ zS^k6YN!WeiZB=c67NdX;`2$dn@CU2#9j%;h_Hx5$A9zhFn%2%aOdZm}@`Jq3IA?@R zaEVlVJwH&4ye-d_ni-IP(byLg^x{x0o)1pagQgxm{4 zN#aV145#O7Mw3sakkW@HAV453sRZ$3U@F`v2_>j-Q%{5-aDVaTzfgo&MZwB-#5H2S zM2N*Oy*-N%4n>wr{Dgh%-BTxI4DL>x*dtf@fOZ0|BGdhFuJSk14w?4Fer10oiVl$| z-VUeu+a1y}rgbC=X{C4xB0z!|VbPQLF`104Kguhg#__0<$ z+z}wcK_kPWW=ak^B6f>4&@HPkmATk zlohI3>srORFQRWV>s_nZ%|m%CKOtu`vvmiHkMdN0swSs*$lEhyT_RC`DqN*_7bfN2 zmwZV;EMBAY%5TKJ_9=csWv1D^9LG+1pw{2b?uMLWP@C=1-|a1%x3D(P*Y=)gSajxV z_L?W$m8f*XU#FJkkS_1AvK+Ho{=EE1ggUcZR@uKOf0}nl3`WYih6hM1RCnmiU-{fE zpG6q93Q0}5kF?9ei{wE_DGW=SLVA>VQpmd;7W5M1ITBOqSHlkrdLZaxd6RGt@klF+ zr*QCkU$2!8bxCH^p?105*THBNu=5E!G~`8`EzNkS$n##O3uAgecM@b}W`}tr+|rWxFHoL|0=rrB;5}4T*Isx<>rx>mE?L3*WZ@kM+4tEz?-O*Br9B zT^Y`S79`Nmc51w|{PQeuaZez|EU>~Pc*ssXVij8H7J5q068R99babMHCf=b^ZDy-v zK{FOavE1RQ(oICYcor?1SMjf4?m)A;tQG8SX0a-9uKaUI36GNN-v+!8;IfTN$?&lID2I8I7l9uvHvKGI3HD+sQab~XzS0pi=kN5;$lv;O`15BuA{5AHR`zV;7( z(s`-uXmkrtu!?e&Qp$PRCMregh?jmZ10&a~ktu>9Aes%zjMi|4`*|+dvmMHIIwD*c$En?oj+7|Qn{C(XrB6LqA z<_&~`1J>>2byp_c&;(X&-9+|dueM@7jl3b_Q8+ydk=HSY z@TI!%!|)r3ze%L}t+IWItLW_Q>>+H!;Au-4f7&Mz6&9QLInEQ&*g46q#)WRX5kXW) zXrfZa<&N5KM$;c{j+tMj{p=sUBu%PcjS)ViGur;Jzr-EWP6B#S_n7kR()7jA{d$ED zS{-8|GuB=)DArH5duN=B-|lERVGASh-CO=T_K$Ru|p3cG9*^V`W7;LAG4fU2!uTQ0GvZfa^E!b{zbTo`Fq{WVFQ znN8<2!(TM=GKaw+7fA4|f{>uh$btSz^vxeeU3^ydm{e(6++S}ReUa_HA2+*$a@*61 zb8{nY=R`X{J&v$)6DAK8kL>xu&M)Q!rRzF;?G!_H8~fYn#YM5St8>S+W`CT9A zs1J0Lo0N9OW1)#cTgOpnLQxMxZ{1-Msi5#NGT})kg!aFl)-2)s$#EaW5Y+5PRyn->nYsta` zNMo3K9+)If*wLK>Q<`G1hhA}mrmcyX^#wXIYKuHu>$=Gn%N|zvsYD-_5_3z-%fZ&d zUOAuTYl1g7F6YG*pW?p!96vrh20EHz1DzP3xEHg7%9bhHr5xKqgJ{3|N&0ZDUOAH> zG0yaX6}aC42HC?jb|<@wWY(S>*mDCES&*oEdU^>sII!na^6JODqBdT4KpKP)_!cij zL0}b61<&41VN>MPlksA4LNf#@0L$stE4xaG13RqSSr?CjpGt(oPD=AzL97hvWl$sQ z2Wle?5cb>k*O5l3a^e7KO7R;k0;PH&+}59V9`(_PrGw}s7t0dS*(ruT!ujP;maq~L z+5lk8bPXUn+5}oYg%;ClMRj*vT0X+G{9sxRz&OMMU54?sro1r%rtG`Jvc}VCt!Zy% zp_S3)&ppy{^$` z`;pn3{i1#OF8uS4?zE-~`(R7z5YQky4%^Y;z&!@L#1@okLM4P6{s4bPLI6!Wh~OtG zCV~^5v+XHK?=FEsy|V2Q?%l5TN(GpxNeGUh65&r3DJxJ31(k$-t(qU*>3?y1YvH$J zOe{A-2=T^vEewslXV^cy@OR(82>+#jHT-wbgfQCCCEQmuLV*BauC=|c%ntzdykHWk z)Gf}20c?LM_oZbv&)q?pm6=WDpU|Ej{g!%M{7FTpn+W|-m?E|bB@7YK&`Crs=P-M) zQzV}THOcGZ!(v9w4*1e`!EiF|E{x-%n4PMTNjX4S+>w`3N@{TGghnMB=7-aR{g7~f zCD;_pc^*9biSj1Vj5{w9A}sCXHml3?4f<;KYNBK!ZLdcb=ZK_!ObwbdrG?Fbc`m_6 zx-li|{ysft!+$6@T0*Iu%$z4NRnU*q2;BTJTTv6mK14BT7O`De-mdnK$S&aW(SV^n zu~>H8kI7nY4Z&aCPqbv*g{|Ji%KVJUA9{!UlK>6qlGdODz~|H()_N@>Z~3Z@@Kf%Ykm48i0+uDN;0B?kmpPg^ z%aH-zLH;i4&aLqh68=WCu0!Fjlu1VwHF&-hl3NjV4~PHSb4O{37F zDL#lZxa9<-K+v%BU2%9Ijuf0RJRj`&c1Kx!)H_fE-5m!K*}n_v zt9&J$Obw2jAQ%fftL+PRW(!G46H=!K8%48i)*M3L9lDhV+yU?A>|s%QX2Jy>=)0Z> zPL~Buxzpi}QGKW6llO;v&xUO<{`=XV<4e&T)D}3RQwJmkkqYREr}{IVegE?;M?Bvz z@fC*on|t35I;#4v00>s$n`)L)+1-@>8P6N41?wR?#b!(P6#8WsBH$o;r9NRjd*hF_ z{B&>66@Juh=Wk@M;A1Kovk&=Un9XpLv1!VUY$A1bR`z3t&We6zK8M2QB(Z}4bJxywG zuWjs_#5rvn`|gp<$wJ?BsYS&LoKkRYmj_+l4)0qqfBN$^GXaf=R(Yb>GaP`^^%(d8 zQ5wPtx=IQ#38a2lsMKG*Y6yYp)6Z_}JApM>j!4XKnK9zEQjrHtb^1Z7VOFS9P3?Zd z@cvN#$bBiF$!kyLNANX@Q7bz_pOYN!6sAj{KG60+6z=@&SSsFM{M&Vg(>o3sPlH7TPx)Cm>?e_>hED z^pX7re}4}5*+!nAspOzTD*LQA^iEd%9+tni0;l%ylX2gPK;Q}HboGzwC zab$viYUO_*6J*?vVyokRkg$k<&c2JnazF4;7MRHtFDNBS6@kTxi{nnimej)xHX}W($1^F$&TQwDYCZ~ z4EEq!kzZ=zXS282r$&-E&@)>9V|xHw<=YwKK=C-Y)x{L=|kzI4^6yOtf7CRSim38kF5A=D=)Drb@GEjO>VFCLbWq`--WX1~aO9 zh3s7kt|yCK1iv#=GNL4$M{FtNfef%6;Mx)3Vmu=jcVTsr_0eY2VG%5SNR{uTp8*~; z>$JK#*Bd5>jHUoa0$uH~5HtgTd=eHlxjG0$VCXwtdbi+(osS{^ye8T32xLcM(n_q% zCT*-lb$TjAM1Q8hS2}kcKIr}2%;^#5Twfj_T#Fc$yUUGDeo$r5mMF$5>q+MkGs_5l zS@99f7jp>*eRr3a8X&v0O=bXB(O?qIWE{oLin?)?Ro8|E#exPw62%g*p;%%=#<9ePSo8hQ zx%VkEnGoF9eZTMb{WiNeGq>M!&pqv)doB}E+iz1oU_Kf8jW2W|h@=c326+(67Tn-3 z{900BPy)Qof}^3>@zg)hJv0G`cM_y|%$*d_asJSBM1vVfAxg_|tAY2hAJW^n@(lqS zhCwV!hWYlTzlW&^Zgh}522{iPBh0OdYSm0f?E*&02JCEO=TN*vW2>~HrcLjq(OU1T zW9c{G?S%kd151Tf5cruW7r`l+xdss|T01GZ8E8`86zh{9AJLmkfmh)>;AT>@C!pk` zk8Hz-Rb3HaL;p;qAJ89htHE|(i!PC+;l37V0VpV5ZNeE5(Eo>6HuuktXHXEIK`MoN zCE+uSBiqPXAgyIhWuZML&7t>W@jNo$ya}EiE0^PEj|RLCrlJ_ajRN&=XKn(q0q-f} zzBKEu5;?ZPXfQ~{?IoBLacEME6?{Z#J0i5ql9$HK4sx@x_IWMYiHo0 zFZke4k_)PgfJZ7h5cNYV#~#3Hl&;pJ}qp;P%9ou8Z$w|^2V z@kdH9&@$M&hX?KNZNRt|feDgql!Y0Ac4+IencyQpCI9x%RYQS?5#ecASs}tqzlZyD zuaPmfmfU7&dIW`~WMhdf2VrgkUyYl>MJHkn$yES|1c#h51d%dSBT{SeP-R(YUs>o2 z;8W)Etg_JX($L}2X#~i^udCY2!e^10JMfp%P^+4+@*iDA-BTPrhGwuu!n`cBHGn<5 zBrE2ki+zmlq*Rt|CXsc3y)JqStJrh5*u@{!l-#HFvZP2|SKil5X6r_Kv}OF#GADU<5bh6|DEq4o3-fmD` z#y5B!sW9pg;a{E+8$tGw!WXJ=?e=_n02Ag&nimIO0386s^~PX_<1d&qfG)pd6GGNX zTumFir%p*cqMY*tQ5q#bB@GcnOu>}(4ThlPqr#u{t0(0J^XdGl=BYe7qJS1jCnr}I zg?0V} zW<%#sAgp|hQq0FEZh3styWKp18lT0!LTDwB0>*#cY)hc=!f(c;1NEXnzD!U}$G_EjB40Df1eJX&dh(`PV1y z(}8ozuq$oTJ=r$hQ{v0N(>{GE*{2b6fGyG#HUMU2qQ8*B$M|th%W@ZZVF-8Dk#K?A z^xA>~43jd2fQ9+WlAaCwQG))@^!{g@)zkUKQW%H)k*Q=w;#laRvzq)NkL?TtgO~&p z`3$0vxLe*vgz`58W};R&$@mme1o4O%dxbSpW6dRQqQ4|g9s+y5Y0pL}mTNwNY(*OR z?&vp=37~wy2NyKz2b{o?j1YRqPXEe6pTR!v^+Or%EWb&8nl7(o+>&*DklYs%aYd7IQAT9a$#nzRRo+Sj8d z>3S%0e+jKxoVlOncV0By!@%aXEoN*tn*&yWDac(&ofea|9qW!A*%C1H7p%Xs_rsu% z;#hh4$RUCrH?2U6BsDLose z_C(4pR!U~W)J&vYVWsqHnA!`EkY02i7S2l58>Z@%N&hN-!fni-ecyJ2c?O8piHYW3EqVQL>reK#)E(=gRTsoUaG`!-DN zOR4MQQu7+7=27b3;!^uHOzlUhp}5rihN=0KS{0XC&@i=tQm4hG_HUTlpHfebOC8WK zbpWM~j7uHZFm)iM7R03vYM45RQd8nmy$w^nl=?Z4%!0$j(?5Cukfs62X>Q_ngT&v6!cbv((5%?8SRauF_W_^{u=n`MA%gBzcK*FSOk z>y7osW~(LY0{%)zv4KNP^81k@zrha`>kY!LGQk}~@K|QpU9myfqreeI&?A386EBoQ zBt{6u!t@bGH|Oyfb66)yD#eh+6)GHt>Wio%p}fH_J0KV*Di`S3sR`yarM?QthVHGo`lHuTZ&HH1@ywsL&+ zNptYLGu`DtR?SmbinH18a&w|ab;W(aDYZT^+nXJ{(T*Ks%k?GIx01VwCi*)k^$(pg z8JmPrpRAKL$(cLlH`<6nn&!%66j#Hyc|6gvNPJ+-{oi$NXSeqqt}{d;?@|}%s7$y3XeYr zcB$LWbbhcu3$cyfGU#l-L63?b!d(~0=Xe>6oU|`=ivV;$_!BqBZP{YMj~Fl-ffMfm}Ag;q7OFwNa}A* z_(5l^;2|{8lbPhEH3(Ph2^m#?BAzq6gdf-e(tvQm_)QNUoz*DeHVk>$@V!D_7#3JQ zdP=w-B=^9mZf9c69ctc_L@*Urm$czom3MJyjghqKSt+F0f(d5gE56|?j`{`&0AGJ% z1dUKuAdp|lv=TD7P7cr3xP>uF=!jI?Oi$3cBsD0M|E1Y09fgYHcqeI6?ik2!xfQE2rSdlC`C?2RFH4kmT91q(Voe# z4S9gp=N!13xHbLaYK$Jj0q()dHPUMU8A0v?3WspX3|qdah}N9G;Wz`Y*>VoBmg3!6Dq-Xi3M)D+g2ck;aCNd zt>qz?nQQqs)Wib%9`Vjy99%TVI}|6IS<-g$OO_Lhwur_6qNY}ad|=BO<@->pn7|8P zGfsr@$7{dJSXp(|VZUo{O-BOc1lxS$@x@pKUi4LrEx?&!j58yF4`XBh1vHSGhhJp+ zkYXS=KcMjsI8N9x=|vM`fNg^f>@e^t%gl|X#9)IabmGYy1&K63#xV$_#8tZh#^<4O z_j{$h2@W7Jw<&;ef|^wX&yX@;s&Ql|DFVpAiappu_&iG@HE%Q~1#Q48xcR`<=j>{GK~DM*z86}?Hx;-tGQ?CA=cNz9SZkB$9d`d8p6gdzflTG>i<7DiVlt^3Ltkv4Ki zl#hTyTP$lk)|6G}(1a1NZI>@!Y<(xO*_!T~koh`<9e65gz=$F}t-~#=D-?_g{IxtnllAm)=>ipC<#U!Q(QCNWf?sYgN`F zy_O^@9Hv$mmdkTGif;}35Yv^CyGWiwRmk)|<3NvLZaIDs#pvXa0&Agp&~lh{9$yXX z3_TiV4gu$Ep;L#&dMf#Z>kxi~&H+v#t^!V{s@MZZBPXF;91nB&uH9A(hQaGFHGKKp zXCheRVvQzJyUIJdTWzZb9UMfC-En(YH4GXRSHli0Y=vPd$J8@}!1I170|0q2`vLe$ z8xdbYS}eYQB$g#j^8F?TJZ&b(maBl#Bb3@i$k#9_1M7i>xO<+pTqf z#D9r#RP(h!)dq8qb&`8D@+!x3k49r22f{s=U^i;PfqUyH^s5#;es4*&7M!pbuT%v~ zLE0;#&tqsH=aPA;Vu9CLNC0b))dJ7>!W);P0UjeY5!i@2tJKdXE$|?Iw(?Ij+BK7z&10&;L#!}3uY zzMJ6VulcE6uNjV3@DL1Li$_A=s@w^YiARe161>V=eg*8M!9!vr{gG)Pa__erGbHCR zQhaK*fxJ$Gh_G-qj@o!zOAn3-TU|$phH0xNHk4lhOuG`Lp}Ihm36(qqyK5s(P%R(W zVz^l!Q4IfMzOa_X3sYP_u%v!29T%Wp*+bxz4nIzx$`Gv>=T+vJ4Z3Dew606zFjhLy zS`ehj9(6~;9TaqXzU?5QX5Rw6_8MSV&Atk~mg4l)?3<(4Wuqjtz}1q7VWNnqZ-f*- zYn1_0`;6rK-yBfwX`;`t5&3a-$J`6y5?Hvwyn?I!p{3w~C5@OLnWXhvdb`QTKKgj5 zN}jEZ+#AhDDu#gB^1s#CrHvRTwj-|_#(?ut2{#*H_1dpcre+^PqEWCNKe=J!MO_-v z0Wb!=_8gQn92~hH+j)Z_ERlbsUQ5r?*?h9wUTSUjhNQt6)?P&A@UthhB-sM1v=rH7%Re6LG9m+z-!OGz_BryW8 zp^UGB`n9^V%Pj}FVV7Gvw}K=>3qm6xxg**HV`Q>^r~tMNTugXD{Sh!}b5YB%s5p(q z3BZ|jmP$)!E@*6`8k1f-huRL89q_ocNmp$csd1xsMD zi0F-iMS;_A$ z_(Io^%dF3$+r;@yIXDK|KrVJ92hV{oOub%5uQN$OxD1~p376nVcs;dJ7P%mf1eb<3 z(#}Pwd$}hkAy*wCo=y$py+@W|=?!XMDG4G zpN1ve01LpArQx;YDT$R_2X^9%Ttn01{wCJTYIJfRE_)JLTs?FO>N-k8vRZS=1|T1V zzY=oKQ!n3&=8|f((h+81y7)=@j&o8QKm%|~0%olzc~A9e;hC%kTHp)wYrU8lEgC(x z7m`opgiET?9>S>B2YTBZGBgv_|6Yc)7U+qFXp%fF**YED_QslF3^ZY@bA=Cq2?j3I zr4hGbg}_2D8+Ni(QBAaA<(3wJz$REML5>u*9SV`C&Gw!R3%oCg{mjz+B}q~;L4 zv8yHk2d)-ZBaT8hf?HG@FlU%BH(t&IMNmcAliBHQt5!dGJopwg?-e%2ugu`%Lw3Hx8_&eLr;{AIN z=Sdl#po^DDc4sp_UCBUfjx#>leLJ^K7(-F%u?`*ao}GtCD?y@jXDf)Z;oKRTFd6vU z!lX1_~m zoJalg`xZEbfi_{>7(jPqbUV%$M%l{8yL->GwWI603pySYO|BN&! zAxTCg?PYYM21*OdxMZ}0D`5C9m=T_jLBNe%U!)R=l?uS;z*{jI+)wC7ierR}F z3)}A`d>|;n+TZC-1R#OBh6l@9Y3N+HU&Gm_ag8tDq-gBS`13KGMtGCi7w7z1_Qf0E z@@;etu^4*>WVR1~C=5)3jF2zb9<2N&VM}mSmUS{~k7G;doZx6SP!Gr163INUb*hFz z_S?u{O^OokR^>^P(&1lX zT1jp3)A2&){vn?Tg!XH@tI@>~rLljoUSXU!!pc+qUHY=PCQJSS8H z3(UmMUlPZ&r`z!)#hq&E(!d{cJys#nT#p5M9hFMD9s}doBVhsXHZysyAIJjux*dMv zg#B=6+5eJtu>eJ#?D4oC^`HAa|GD1-7t;US?}1PC|NMT>f9|*Z+wZrKxHry$uy?qR zLi8KvfL>*xXZ_nhE(^65(`7nWDXc5{Lk?W>L8Oqs;O61JfKCp;pqf6qMz$~b1@f}! zzL76-fku^rSuwmd45F=mBluYOf~??h5J1J=m4=hY55E2TG@MTJ!^M6aiM<3_mmYe=JvGGR z3*fx=ImiHb$L2H{vB?b+7-9zGJwY?Vo!Ef85K+)KLI=kkfwICNESQZ11|TYy$9BOC zrzKn9pU?L)zNQv%91uJ{8lu98p@B4GiEBM?AF$gBUbYJ~_pmw+#Sp3l3K}tkjAPxPamD*PMir{(#O) zL%2t{#T^|;hR4t}SOx~+CI|8`QM%Ju7WzaTt{-F}2kCdEk=alnu7m` zo(K3Mj#eHA%{m8tAw(P6#$5ue0v6z+#q#r#6KEW9{U92}_x67Dx(O&7%|*e|&}b}L zbO^p;d?T%TYDa2PbkL z0%*XGczSFP-Dce9j!i*-hbHvL&x@{TVKRcH+5}?+;y*sbgFI$PlwLLRBcvon-9-F; zx+8~Mn{?0ZcfgNa+*gUr1G!v&V^XQg;prACe)C(LU;|PE(*#NGMV}s)=+d`oCwANC3jpHkS;qiLy zCg4R`=qI}JGaVj5&{Qq@N4J6$z2v)|usVW6aEoYdJhg-!*+Pty?`pZQL(qV6zCmg` zE*OA*d=sT`$guigvtZxg%4k3I6*~&+it#v0!{xBzxrRg=Xp4ouRrZfW`b&mAa=LEz z`Y<0g4D6Xe9*zDyGzL(>J9snfQ*TC3LR)2U=i-rV!7LRmr3+gz57AGs2)b5AdyF;} z6jdCR0CZ6N<=qbKe%x5bNsN3115~H>X#yAaxgsyYXJrT%?s^1*4e`@5iXyQ z=iZ!;8K3ukoEq3J<`FOf*le0kKurfzi7i$0z-tHS2)FQJbK=Gi8OazsmfaltJ3MxC zy)y0N#=#=BG$NL-z5o&kwaD|pY0*;v;mdNn#reG15K`&02V(zO@T=H=27*%?TkL?% zR2G3S?LW-I9f2GQ_=Sg6(trTVf5WmDe(-0a&VN$1t>>gS3gT-RhcZcvc3~a>y(amT z{l_u84VbLjx>AU!Y0AP*0r|s?gCy^TgHA%o{K4i?iX$h(uY8QMy20(y@)RN?+!O@N z$U2!kM`46J9e15zt|mXnF#tonYcde5lau%#X7TAyD^7oiUw3vAH=V^=)l}%!a5~zD z>r7`S@#2TO@Diu<&^WWCagIZA`wbxi2hytyGS~@tZ=MHbn>FlB({H`NI9T z(GB5Q>Z*4(%-h3a*|z#bL?|BV*ANV$-n>k1?RP>|LmY(jz>>6o*Q}Ih| zS(isi9;BPC6tV0?goofTH8={HhCm|35Vj){%=_eZsgHk&I_QlK;4K+M2l6ls9USE^ ze6tEK$bVBB+QS%cUGwHxnBd1&>A;TVuf$$8&O6NeH~)d}zYo6r-bxldU#bEW1b3

    fJ@1O%XFCoMtmVH_aUYK)BM@tp&Pr56y8m!ln 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�=J{rrTsn4_ceYWY0WbHDr% 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 g3(8Ca7 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

    >aRGuobY2U~hw+13L@$$FR4+{s?voEY}zL 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

    0XR=Pl(bXfPq6+TZs3vT@MRG35|h2pGifH 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�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

    fAGk88-wk0~LNla9obK^Wmb@cnBg;& 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

    9+<#0of2Y{>`)X`>BM0XtG=Jhy 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ℜoY&7SsW&&;;ZDz{)@Al@8l`ZD*VQS+pfBtx| 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𝔓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 z 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 fblTJ9N)Vi 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

    J3lV)ZnrPtkmE%0nntdp8G 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

    7bPt(3w?V$(@K%5B)`R9wGpu|P$~(-R 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 `G7_`|<)0 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

    !HP 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 diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/module.modulemap b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/module.modulemap index 37ffdf02d..8d442880b 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/module.modulemap +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/module.modulemap @@ -1,8 +1,11 @@ module SessionUtil { module capi { + header "session/version.h" header "session/export.h" header "session/config.h" header "session/config/error.h" + header "session/config/expiring.h" + header "session/config/user_groups.h" header "session/config/convo_info_volatile.h" header "session/config/user_profile.h" header "session/config/util.h" diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config.hpp index c59809861..cd6e851aa 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config.hpp +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config.hpp @@ -225,7 +225,9 @@ class ConfigMessage { // Constructor tag struct increment_seqno_t {}; +struct retain_seqno_t {}; inline constexpr increment_seqno_t increment_seqno{}; +inline constexpr retain_seqno_t retain_seqno{}; class MutableConfigMessage : public ConfigMessage { protected: @@ -292,7 +294,14 @@ class MutableConfigMessage : public ConfigMessage { /// Constructor that does the same thing as the `m.increment()` factory method. The second /// value should be the literal `increment_seqno` value (to select this constructor). - explicit MutableConfigMessage(const ConfigMessage& m, increment_seqno_t); + explicit MutableConfigMessage(const ConfigMessage& m, const increment_seqno_t&); + + /// Constructor that moves a immutable message into a mutable one, retaining the current seqno. + /// This is typically used in situations where the ConfigMessage has had some implicit seqno + /// increment already (e.g. from merging) and we want it to become mutable without incrementing + /// the seqno again. The second value should be the literal `retain_seqno` value (to select + /// this constructor). + explicit MutableConfigMessage(ConfigMessage&& m, const retain_seqno_t&); using ConfigMessage::data; /// Returns a mutable reference to the underlying config data. diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.hpp index 86cdeab24..deb7b16f0 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.hpp +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.hpp @@ -90,6 +90,7 @@ class ConfigBase { // already dirty (i.e. Clean or Waiting) then calling this increments the seqno counter. MutableConfigMessage& dirty(); + public: // class for proxying subfield access; this class should never be stored but only used // ephemerally (most of its methods are rvalue-qualified). This lets constructs such as // foo["abc"]["def"]["ghi"] = 12; @@ -271,7 +272,7 @@ class ConfigBase { std::string string_or(std::string fallback) const { if (auto* s = string()) return *s; - return std::move(fallback); + return fallback; } /// Returns a const pointer to the integer if one exists at the given location, nullptr @@ -297,7 +298,7 @@ class ConfigBase { /// Replaces the current value with the given string. This also auto-vivifies any /// intermediate dicts needed to reach the given key, including replacing non-dict values if /// they currently exist along the path. - void operator=(std::string value) { assign_if_changed(std::move(value)); } + void operator=(std::string&& value) { assign_if_changed(std::move(value)); } /// Same as above, but takes a string_view for convenience (this makes a copy). void operator=(std::string_view value) { *this = std::string{value}; } /// Same as above, but takes a ustring_view @@ -391,6 +392,7 @@ class ConfigBase { } }; + protected: // Called when dumping to obtain any extra data that a subclass needs to store to reconstitute // the object. The base implementation does nothing. The counterpart to this, // `load_extra_data()`, is called when loading from a dump that has extra data; a subclass @@ -429,6 +431,11 @@ class ConfigBase { /// to use. This is rarely needed externally; it is public merely for testing purposes. virtual const char* encryption_domain() const = 0; + /// The zstd compression level to use for this type. Subclasses can override this if they have + /// some particular special compression level, or to disable compression entirely (by returning + /// std::nullopt). The default is zstd level 1. + virtual std::optional compression_level() const { return 1; } + // How many config lags should be used for this object; default to 5. Implementing subclasses // can override to return a different constant if desired. More lags require more "diff" // storage in the config messages, but also allow for a higher tolerance of simultaneous message @@ -463,13 +470,16 @@ class ConfigBase { // the server. This will be true whenever `is_clean()` is false: that is, if we are currently // "dirty" (i.e. have changes that haven't been pushed) or are still awaiting confirmation of // storage of the most recent serialized push data. - bool needs_push() const; + virtual bool needs_push() const; // Returns the data messages to push to the server along with the seqno value of the data. If // the config is currently dirty (i.e. has previously unsent modifications) then this marks it // as awaiting-confirmation instead of dirty so that any future change immediately increments // the seqno. - std::pair push(); + // + // Subclasses that need to perform pre-push tasks (such as pruning stale data) can override this + // to prune and then call the base method to perform the actual push generation. + virtual std::pair push(); // Should be called after the push is confirmed stored on the storage server swarm to let the // object know the data is stored. (Once this is called `needs_push` will start returning false diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/community.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/community.hpp new file mode 100644 index 000000000..daffb7886 --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/community.hpp @@ -0,0 +1,239 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include + +namespace session::config { + +/// Base class for types representing a community; this base type handles the url/room/pubkey that +/// such a type need. Generally a class inherits from this to extend with the local +/// community-related values. +struct community { + + // 267 = len('https://') + 253 (max valid DNS name length) + len(':XXXXX') + static constexpr size_t URL_MAX_LENGTH = 267; + static constexpr size_t ROOM_MAX_LENGTH = 64; + + community() = default; + + // Constructs an empty community struct from url, room, and pubkey. `base_url` will be + // normalized if not already. pubkey is 32 bytes. + community(std::string_view base_url, std::string_view room, ustring_view pubkey); + + // Same as above, but takes pubkey as an encoded (hex or base32z or base64) string. + community(std::string_view base_url, std::string_view room, std::string_view pubkey_encoded); + + // Takes a combined room URL (e.g. https://whatever.com/r/Room?public_key=01234....), either + // new style (with /r/) or old style (without /r/). Note that the URL gets canonicalized so + // the resulting `base_url()` and `room()` values may not be exactly equal to what is given. + // + // See also `parse_full_url` which does the same thing but returns it in pieces rather than + // constructing a new `community` object. + explicit community(std::string_view full_url); + + // Replaces the baseurl/room/pubkey of this object from a URL. This parses the URL, then stores + // the values as if passed to set_base_url/set_room/set_pubkey. + // + // The base URL will be normalized; the room name will be case-preserving (but see `set_room` + // for info on limitations on "case-preserving", particularly for volatile configs); and the + // embedded pubkey must be encoded in one of hex, base32z, or base64. + void set_full_url(std::string_view full_url); + + // Replaces the base_url of this object. Note that changing the URL and then giving it to `set` + // will end up inserting a *new* record but not removing the *old* one (you need to erase first + // to do that). + void set_base_url(std::string_view new_url); + + // Changes the room token. This stores (or updates) the name as given as the localized room, + // and separately stores the normalized (lower-case) token. Note that the localized name does + // not persist across a push or dump in some config contexts (such as volatile room info). If + // the new room given here changes more than just case (i.e. if the normalized room token + // changes) then a call to `set` will end up inserting a *new* record but not removing the *old* + // one (you need to erase first to do that). + void set_room(std::string_view room); + + // Updates the pubkey of this community (typically this is not called directly but rather + // via `set_server` or during construction). Throws std::invalid_argument if the given + // pubkey does not look like a valid pubkey. The std::string_view version takes the pubkey + // as any of hex/base64/base32z. + // + // NOTE: the pubkey of all communities with the same URLs are stored in common, so changing + // one community pubkey (and storing) will affect all communities using the same community + // base URL. + void set_pubkey(ustring_view pubkey); + void set_pubkey(std::string_view pubkey); + + // Accesses the base url (i.e. not including room or pubkey). Always lower-case/normalized. + const std::string& base_url() const { return base_url_; } + + // Accesses the room token; this is case-preserving, where possible. In some contexts, however, + // such as volatile info, the case is not preserved and this will always return the normalized + // (lower-case) form rather than the preferred form. + const std::string& room() const { return localized_room_ ? *localized_room_ : room_; } + + // Accesses the normalized room token, i.e. always lower-case. + const std::string& room_norm() const { return room_; } + + const ustring& pubkey() const { return pubkey_; } // Accesses the server pubkey (32 bytes). + std::string pubkey_hex() const; // Accesses the server pubkey as hex (64 hex digits). + std::string pubkey_b32z() const; // Accesses the server pubkey as base32z (52 alphanumeric + // digits) + std::string pubkey_b64() const; // Accesses the server pubkey as unpadded base64 (43 from + // alphanumeric, '+', and '/'). + + // Takes a base URL as input and returns it in canonical form. This involves doing things + // like lower casing it and removing redundant ports (e.g. :80 when using http://). Throws + // std::invalid_argument if given an invalid base URL. + static std::string canonical_url(std::string_view url); + + // Takes a room token and returns it in canonical form (i.e. lower-cased). Throws + // std::invalid_argument if given an invalid room token (e.g. too long, or containing token + // other than a-z, 0-9, -, _). + static std::string canonical_room(std::string_view room); + + // Same as above, but modifies the argument in-place instead of returning a modified + // copy. + static void canonicalize_url(std::string& url); + static void canonicalize_room(std::string& room); + + // Takes a full room URL, splits it up into canonical url (see above), room, and server + // pubkey. We take both the deprecated form (e.g. + // https://example.com/SomeRoom?public_key=...) and new form + // (https://example.com/r/SomeRoom?public_key=...). The public_key is typically specified + // in hex (64 digits), but we also accept base64 (43 chars or 44 with padding) and base32z + // (52 chars) encodings (for slightly shorter URLs). + // + // The returned URL is normalized (lower-cased, and cleaned up). + // + // The returned room name is *not* normalized, that is, it preserve case. + // + // Throw std::invalid_argument if anything in the URL is unparseable or invalid. + static std::tuple parse_full_url(std::string_view full_url); + + protected: + // The canonical base url and room (i.e. lower-cased, URL cleaned up): + std::string base_url_, room_; + // The localized token of this room, that is, with case preserved (so `room_` could be + // `someroom` and this could `SomeRoom`). Omitted if not available. + std::optional localized_room_; + // server pubkey + ustring pubkey_; + + // Construction without a pubkey for when pubkey isn't known yet but will be set shortly + // after constructing (or when isn't needed, such as when deleting). + community(std::string_view base_url, std::string_view room); +}; + +struct comm_iterator_helper { + + comm_iterator_helper(dict::const_iterator it_server, dict::const_iterator end_server) : + it_server{std::move(it_server)}, end_server{std::move(end_server)} {} + + std::optional it_server, end_server, it_room, end_room; + + bool operator==(const comm_iterator_helper& other) const { + return it_server == other.it_server && it_room == other.it_room; + } + + void next_server() { + ++*it_server; + it_room.reset(); + end_room.reset(); + } + + bool done() const { return !it_server || *it_server == *end_server; } + + template + bool load(std::shared_ptr& val) { + while (it_server) { + if (*it_server == *end_server) { + it_server.reset(); + end_server.reset(); + return false; + } + + auto& [base_url, server_info] = **it_server; + auto* server_info_dict = std::get_if(&server_info); + if (!server_info_dict) { + next_server(); + continue; + } + + const std::string* pubkey_raw = nullptr; + if (auto pubkey_it = server_info_dict->find("#"); pubkey_it != server_info_dict->end()) + if (auto* pk_sc = std::get_if(&pubkey_it->second)) + pubkey_raw = std::get_if(pk_sc); + + if (!pubkey_raw) { + next_server(); + continue; + } + + ustring_view pubkey{ + reinterpret_cast(pubkey_raw->data()), pubkey_raw->size()}; + + if (!it_room) { + if (auto rit = server_info_dict->find("R"); + rit != server_info_dict->end() && std::holds_alternative(rit->second)) { + auto& rooms_dict = std::get(rit->second); + it_room = rooms_dict.begin(); + end_room = rooms_dict.end(); + } else { + next_server(); + continue; + } + } + + while (it_room) { + if (*it_room == *end_room) { + it_room.reset(); + end_room.reset(); + break; + } + + auto& [room, data] = **it_room; + auto* data_dict = std::get_if(&data); + if (!data_dict) { + ++*it_room; + continue; + } + + val = std::make_shared(Comm{}); + auto& og = std::get(*val); + try { + og.set_base_url(base_url); + og.set_room(room); // Will be replaced with "n" in the `.load` below + og.set_pubkey(pubkey); + og.load(*data_dict); + } catch (const std::exception& e) { + ++*it_room; + continue; + } + return true; + } + + ++*it_server; + } + + return false; + } + + bool advance() { + if (it_room) { + ++*it_room; + return true; + } + if (it_server) { + ++*it_server; + return true; + } + return false; + } +}; + +} // namespace session::config diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.h index 0046bfd4e..76fd8acf5 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.h +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.h @@ -5,20 +5,27 @@ extern "C" { #endif #include "base.h" +#include "expiring.h" #include "profile_pic.h" #include "util.h" typedef struct contacts_contact { char session_id[67]; // in hex; 66 hex chars + null terminator. - // These can be NULL. When setting, either NULL or empty string will clear the setting. - const char* name; - const char* nickname; + // These two will be 0-length strings when unset: + char name[101]; + char nickname[101]; user_profile_pic profile_pic; bool approved; bool approved_me; bool blocked; + bool hidden; + + int priority; + + CONVO_EXPIRATION_MODE exp_mode; + int exp_minutes; } contacts_contact; diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.hpp index a4d33234a..01bc8ce55 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.hpp +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.hpp @@ -1,16 +1,20 @@ #pragma once +#include #include #include #include #include #include "base.hpp" +#include "expiring.hpp" #include "namespaces.hpp" #include "profile_pic.hpp" extern "C" struct contacts_contact; +using namespace std::literals; + namespace session::config { /// keys used in this config, either currently or in the past (so that we don't reuse): @@ -18,30 +22,41 @@ namespace session::config { /// c - dict of contacts; within this dict each key is the session pubkey (binary, 33 bytes) and /// value is a dict containing keys: /// -/// ! - dummy value that is always set to an empty string. This ensures that we always have at -/// least one key set, which is required to keep the dict value alive (empty dicts get -/// pruned when serialied). -/// n - contact name (string) +/// n - contact name (string). This is always serialized, even if empty (but empty indicates +/// no name) so that we always have at least one key set (required to keep the dict value +/// alive as empty dicts get pruned). /// N - contact nickname (string) /// p - profile url (string) /// q - profile decryption key (binary) /// a - 1 if approved, omitted otherwise (int) /// A - 1 if remote has approved me, omitted otherwise (int) /// b - 1 if contact is blocked, omitted otherwise +/// h - 1 if the conversation with this contact is hidden, omitted if visible. +/// + - the conversation priority, for pinned messages. Omitted means not pinned; otherwise an +/// integer value >0, where a higher priority means the conversation is meant to appear +/// earlier in the pinned conversation list. +/// e - Disappearing messages expiration type. Omitted if disappearing messages are not enabled +/// for the conversation with this contact; 1 for delete-after-send, and 2 for +/// delete-after-read. +/// E - Disappearing message timer, in minutes. Omitted when `e` is omitted. -/// Struct containing contact info. Note that data must be copied/used immediately as the data will -/// not remain valid beyond other calls into the library. When settings things in this externally -/// (e.g. to pass into `set()`), take note that the `name` and `nickname` are string_views: that is, -/// they must reference existing string data that remains valid for the duration of the contact_info -/// instance. +/// Struct containing contact info. struct contact_info { + static constexpr size_t MAX_NAME_LENGTH = 100; + std::string session_id; // in hex - std::optional name; - std::optional nickname; - std::optional profile_picture; + std::string name; + std::string nickname; + profile_pic profile_picture; bool approved = false; bool approved_me = false; bool blocked = false; + bool hidden = false; // True if the conversation with this contact is not visible in the convo + // list (typically because it has been deleted). + int priority = 0; // If >0 then this message is pinned; higher values mean higher priority + // (i.e. pinned earlier in the pinned list). + expiration_mode exp_mode = expiration_mode::none; // The expiry time; none if not expiring. + std::chrono::minutes exp_timer{0}; // The expiration timer (in minutes) explicit contact_info(std::string sid); @@ -49,20 +64,13 @@ struct contact_info { contact_info(const struct contacts_contact& c); // From c struct void into(contacts_contact& c) const; // Into c struct - // Sets a name, storing the name internally in the object. This is intended for use where the - // source string is a temporary may not outlive the `contact_info` object: the name is first - // copied into an internal std::string, and then the name string_view references that. + // Sets a name or nickname; this is exactly the same as assigning to .name/.nickname directly, + // except that we throw an exception if the given name is longer than MAX_NAME_LENGTH. void set_name(std::string name); - - // Same as above, but for nickname. void set_nickname(std::string nickname); private: friend class Contacts; - - std::string name_; - std::string nickname_; - void load(const dict& info_dict); }; @@ -111,13 +119,20 @@ class Contacts : public ConfigBase { /// contacts.set(c); void set(const contact_info& contact); - /// Alternative to `set()` for setting individual fields. + /// Alternative to `set()` for setting a single field. (If setting multiple fields at once you + /// should use `set()` instead). void set_name(std::string_view session_id, std::string name); void set_nickname(std::string_view session_id, std::string nickname); void set_profile_pic(std::string_view session_id, profile_pic pic); void set_approved(std::string_view session_id, bool approved); void set_approved_me(std::string_view session_id, bool approved_me); void set_blocked(std::string_view session_id, bool blocked); + void set_hidden(std::string_view session_id, bool hidden); + void set_priority(std::string_view session_id, int priority); + void set_expiry( + std::string_view session_id, + expiration_mode exp_mode, + std::chrono::minutes expiration_timer = 0min); /// Removes a contact, if present. Returns true if it was found and removed, false otherwise. /// Note that this removes all fields related to a contact, even fields we do not know about. diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.h index 9ec098ece..94e103ea5 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.h +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.h @@ -14,7 +14,7 @@ typedef struct convo_info_volatile_1to1 { bool unread; // true if the conversation is explicitly marked unread } convo_info_volatile_1to1; -typedef struct convo_info_volatile_open { +typedef struct convo_info_volatile_community { char base_url[268]; // null-terminated (max length 267), normalized (i.e. always lower-case, // only has port if non-default, has trailing / removed) char room[65]; // null-terminated (max length 64), normalized (always lower-case) @@ -22,15 +22,15 @@ typedef struct convo_info_volatile_open { int64_t last_read; // ms since unix epoch bool unread; // true if marked unread -} convo_info_volatile_open; +} convo_info_volatile_community; -typedef struct convo_info_volatile_legacy_closed { +typedef struct convo_info_volatile_legacy_group { char group_id[67]; // in hex; 66 hex chars + null terminator. Looks just like a Session ID, // though isn't really one. int64_t last_read; // ms since unix epoch bool unread; // true if marked unread -} convo_info_volatile_legacy_closed; +} convo_info_volatile_legacy_group; /// Constructs a conversations config object and sets a pointer to it in `conf`. /// @@ -78,76 +78,75 @@ bool convo_info_volatile_get_or_construct_1to1( const config_object* conf, convo_info_volatile_1to1* convo, const char* session_id) __attribute__((warn_unused_result)); -/// open-group versions of the 1-to-1 functions: +/// community versions of the 1-to-1 functions: /// -/// Gets an open group convo info. `base_url` and `room` are null-terminated c strings; pubkey is +/// Gets a community convo info. `base_url` and `room` are null-terminated c strings; pubkey is /// 32 bytes. base_url and room will always be lower-cased (if not already). -bool convo_info_volatile_get_open( +bool convo_info_volatile_get_community( const config_object* conf, - convo_info_volatile_open* og, + convo_info_volatile_community* comm, const char* base_url, - const char* room, - unsigned const char* pubkey) __attribute__((warn_unused_result)); -bool convo_info_volatile_get_or_construct_open( + const char* room) __attribute__((warn_unused_result)); +bool convo_info_volatile_get_or_construct_community( const config_object* conf, - convo_info_volatile_open* convo, + convo_info_volatile_community* convo, const char* base_url, const char* room, unsigned const char* pubkey) __attribute__((warn_unused_result)); -/// Fills `convo` with the conversation info given a legacy closed group ID (specified as a -/// null-terminated hex string), if the conversation exists, and returns true. If the conversation -/// does not exist then `convo` is left unchanged and false is returned. -bool convo_info_volatile_get_legacy_closed( - const config_object* conf, convo_info_volatile_legacy_closed* convo, const char* id) +/// Fills `convo` with the conversation info given a legacy group ID (specified as a null-terminated +/// hex string), if the conversation exists, and returns true. If the conversation does not exist +/// then `convo` is left unchanged and false is returned. +bool convo_info_volatile_get_legacy_group( + const config_object* conf, convo_info_volatile_legacy_group* convo, const char* id) __attribute__((warn_unused_result)); /// Same as the above except that when the conversation does not exist, this sets all the convo /// fields to defaults and loads it with the given id. /// -/// Returns true as long as it is given a valid legacy closed group id (i.e. same format as a -/// session id). A false return is considered an error, and means the id was not a valid session -/// id. +/// Returns true as long as it is given a valid legacy group id (i.e. same format as a session id). +/// A false return is considered an error, and means the id was not a valid session id. /// /// This is the method that should usually be used to create or update a conversation, followed by /// setting fields in the convo, and then giving it to convo_info_volatile_set(). -bool convo_info_volatile_get_or_construct_legacy_closed( - const config_object* conf, convo_info_volatile_legacy_closed* convo, const char* id) +bool convo_info_volatile_get_or_construct_legacy_group( + const config_object* conf, convo_info_volatile_legacy_group* convo, const char* id) __attribute__((warn_unused_result)); /// Adds or updates a conversation from the given convo info void convo_info_volatile_set_1to1(config_object* conf, const convo_info_volatile_1to1* convo); -void convo_info_volatile_set_open(config_object* conf, const convo_info_volatile_open* convo); -void convo_info_volatile_set_legacy_closed( - config_object* conf, const convo_info_volatile_legacy_closed* convo); +void convo_info_volatile_set_community( + config_object* conf, const convo_info_volatile_community* convo); +void convo_info_volatile_set_legacy_group( + config_object* conf, const convo_info_volatile_legacy_group* convo); /// Erases a conversation from the conversation list. Returns true if the conversation was found /// and removed, false if the conversation was not present. You must not call this during /// iteration; see details below. bool convo_info_volatile_erase_1to1(config_object* conf, const char* session_id); -bool convo_info_volatile_erase_open( - config_object* conf, const char* base_url, const char* room, unsigned const char* pubkey); -bool convo_info_volatile_erase_legacy_closed(config_object* conf, const char* group_id); +bool convo_info_volatile_erase_community( + config_object* conf, const char* base_url, const char* room); +bool convo_info_volatile_erase_legacy_group(config_object* conf, const char* group_id); /// Returns the number of conversations. size_t convo_info_volatile_size(const config_object* conf); /// Returns the number of conversations of the specific type. size_t convo_info_volatile_size_1to1(const config_object* conf); -size_t convo_info_volatile_size_open(const config_object* conf); -size_t convo_info_volatile_size_legacy_closed(const config_object* conf); +size_t convo_info_volatile_size_communities(const config_object* conf); +size_t convo_info_volatile_size_legacy_groups(const config_object* conf); /// Functions for iterating through the entire conversation list. Intended use is: /// /// convo_info_volatile_1to1 c1; -/// convo_info_volatile_open c2; -/// convo_info_volatile_legacy_closed c3; +/// convo_info_volatile_community c2; +/// convo_info_volatile_legacy_group c3; /// convo_info_volatile_iterator *it = convo_info_volatile_iterator_new(my_convos); /// for (; !convo_info_volatile_iterator_done(it); convo_info_volatile_iterator_advance(it)) { /// if (convo_info_volatile_it_is_1to1(it, &c1)) { /// // use c1.whatever -/// } else if (convo_info_volatile_it_is_open(it, &c2)) { +/// } else if (convo_info_volatile_it_is_community(it, &c2)) { /// // use c2.whatever -/// } else if (convo_info_volatile_it_is_legacy_closed(it, &c3)) { +/// } else if (convo_info_volatile_it_is_legacy_group(it, &c3)) { /// // use c3.whatever /// } /// } @@ -169,6 +168,8 @@ size_t convo_info_volatile_size_legacy_closed(const config_object* conf); /// convo_info_volatile_iterator_erase(it); /// else /// convo_info_volatile_iterator_advance(it); +/// } else { +/// convo_info_volatile_iterator_advance(it); /// } /// } /// convo_info_volatile_iterator_free(it); @@ -185,8 +186,9 @@ convo_info_volatile_iterator* convo_info_volatile_iterator_new(const config_obje // of the `it_is_whatever` function: it will always be true for the particular type being iterated // over). convo_info_volatile_iterator* convo_info_volatile_iterator_new_1to1(const config_object* conf); -convo_info_volatile_iterator* convo_info_volatile_iterator_new_open(const config_object* conf); -convo_info_volatile_iterator* convo_info_volatile_iterator_new_legacy_closed( +convo_info_volatile_iterator* convo_info_volatile_iterator_new_communities( + const config_object* conf); +convo_info_volatile_iterator* convo_info_volatile_iterator_new_legacy_groups( const config_object* conf); // Frees an iterator once no longer needed. @@ -202,14 +204,15 @@ void convo_info_volatile_iterator_advance(convo_info_volatile_iterator* it); // returns true. Otherwise it returns false. bool convo_info_volatile_it_is_1to1(convo_info_volatile_iterator* it, convo_info_volatile_1to1* c); -// If the current iterator record is an open group conversation this sets the details into `c` and +// If the current iterator record is a community conversation this sets the details into `c` and // returns true. Otherwise it returns false. -bool convo_info_volatile_it_is_open(convo_info_volatile_iterator* it, convo_info_volatile_open* c); +bool convo_info_volatile_it_is_community( + convo_info_volatile_iterator* it, convo_info_volatile_community* c); -// If the current iterator record is a legacy closed group conversation this sets the details into -// `c` and returns true. Otherwise it returns false. -bool convo_info_volatile_it_is_legacy_closed( - convo_info_volatile_iterator* it, convo_info_volatile_legacy_closed* c); +// If the current iterator record is a legacy group conversation this sets the details into `c` and +// returns true. Otherwise it returns false. +bool convo_info_volatile_it_is_legacy_group( + convo_info_volatile_iterator* it, convo_info_volatile_legacy_group* c); // Erases the current convo while advancing the iterator to the next convo in the iteration. void convo_info_volatile_iterator_erase(config_object* conf, convo_info_volatile_iterator* it); diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.hpp index f6de101e3..16e256fa4 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.hpp +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.hpp @@ -7,11 +7,14 @@ #include #include "base.hpp" +#include "community.hpp" + +using namespace std::literals; extern "C" { struct convo_info_volatile_1to1; -struct convo_info_volatile_open; -struct convo_info_volatile_legacy_closed; +struct convo_info_volatile_community; +struct convo_info_volatile_legacy_group; } namespace session::config { @@ -29,22 +32,23 @@ class ConvoInfoVolatile; /// included, but will be 0 if no messages are read. /// u - will be present and set to 1 if this conversation is specifically marked unread. /// -/// o - open group conversations. Each key is: BASE_URL + '\0' + LC_ROOM_NAME + '\0' + -/// SERVER_PUBKEY (in bytes). Note that room name is *always* lower-cased here (so that clients -/// with the same room but with different cases will always set the same key). Values are dicts -/// with keys: -/// r - the unix timestamp (in integer milliseconds) of the last-read message. Always included, -/// but will be 0 if no messages are read. -/// u - will be present and set to 1 if this conversation is specifically marked unread. +/// o - community conversations. This is a nested dict where the outer keys are the BASE_URL of the +/// community and the outer value is a dict containing: +/// - `#` -- the 32-byte server pubkey +/// - `R` -- dict of rooms on the server; each key is the lower-case room name, value is a dict +/// containing keys: +/// r - the unix timestamp (in integer milliseconds) of the last-read message. Always +/// included, but will be 0 if no messages are read. +/// u - will be present and set to 1 if this conversation is specifically marked unread. /// -/// C - legacy closed group conversations. The key is the closed group identifier (which looks -/// indistinguishable from a Session ID, but isn't really a proper Session ID). Values are -/// dicts with keys: +/// C - legacy group conversations (aka closed groups). The key is the group identifier (which +/// looks indistinguishable from a Session ID, but isn't really a proper Session ID). Values +/// are dicts with keys: /// r - the unix timestamp (integer milliseconds) of the last-read message. Always included, /// but will be 0 if no messages are read. /// u - will be present and set to 1 if this conversation is specifically marked unread. /// -/// c - reserved for future tracking of new closed group conversations. +/// c - reserved for future tracking of new group conversations. namespace convo { @@ -71,96 +75,34 @@ namespace convo { friend class session::config::ConvoInfoVolatile; }; - struct open_group : base { - // 267 = len('https://') + 253 (max valid DNS name length) + len(':XXXXX') - static constexpr size_t MAX_URL = 267, MAX_ROOM = 64; + struct community : config::community, base { - std::string_view base_url() const; // Accesses the base url (i.e. not including room or - // pubkey). Always lower-case. - std::string_view room() - const; // Accesses the room name, always in lower-case. (Note that the - // actual open group info might not be lower-case; it is just in - // the open group convo where we force it lower-case). - ustring_view pubkey() const; // Accesses the server pubkey (32 bytes). - std::string pubkey_hex() const; // Accesses the server pubkey as hex (64 hex digits). - - open_group() = default; - - // Constructs an empty open_group convo struct from url, room, and pubkey. `base_url` and - // `room` will be lower-cased if not already (they do not have to be passed lower-case). - // pubkey is 32 bytes. - open_group(std::string_view base_url, std::string_view room, ustring_view pubkey); - - // Same as above, but takes pubkey as a hex string. - open_group(std::string_view base_url, std::string_view room, std::string_view pubkey_hex); - - // Takes a combined room URL (e.g. https://whatever.com/r/Room?public_key=01234....), either - // new style (with /r/) or old style (without /r/). Note that the URL gets canonicalized so - // the resulting `base_url()` and `room()` values may not be exactly equal to what is given. - // - // See also `parse_full_url` which does the same thing but returns it in pieces rather than - // constructing a new `open_group` object. - explicit open_group(std::string_view full_url); + using config::community::community; // Internal ctor/method for C API implementations: - open_group(const struct convo_info_volatile_open& c); // From c struct - void into(convo_info_volatile_open& c) const; // Into c struct - - // Replaces the baseurl/room/pubkey of this object. Note that changing this and then giving - // it to `set` will end up inserting a *new* record but not removing the *old* one (you need - // to erase first to do that). - void set_server(std::string_view base_url, std::string_view room, ustring_view pubkey); - void set_server( - std::string_view base_url, std::string_view room, std::string_view pubkey_hex); - void set_server(std::string_view full_url); - - // Loads the baseurl/room/pubkey of this object from an encoded key. Throws - // std::invalid_argument if the encoded key does not look right. - void load_encoded_key(std::string key); - - // Takes a base URL as input and returns it in canonical form. This involves doing things - // like lower casing it and removing redundant ports (e.g. :80 when using http://). - static std::string canonical_url(std::string_view url); - - // Takes a full room URL, splits it up into canonical url (see above), lower-case room - // token, and server pubkey. We take both the deprecated form (e.g. - // https://example.com/SomeRoom?public_key=...) and new form - // (https://example.com/r/SomeRoom?public_key=...). The public_key is typically specified - // in hex (64 digits), but we also accept unpadded base64 (43 chars) and base32z (52 chars) - // encodings (for slightly shorter URLs). - static std::tuple parse_full_url( - std::string_view full_url); - - private: - std::string key; - size_t url_size = 0; + community(const convo_info_volatile_community& c); // From c struct + void into(convo_info_volatile_community& c) const; // Into c struct friend class session::config::ConvoInfoVolatile; - - // Returns the key value we use in the stored dict for this open group, i.e. - // lc(URL) + lc(NAME) + PUBKEY_BYTES. - static std::string make_key( - std::string_view base_url, std::string_view room, std::string_view pubkey_hex); - static std::string make_key( - std::string_view base_url, std::string_view room, ustring_view pubkey); + friend struct session::config::comm_iterator_helper; }; - struct legacy_closed_group : base { + struct legacy_group : base { std::string id; // in hex, indistinguishable from a Session ID - // Constructs an empty legacy_closed_group from a quasi-session_id - explicit legacy_closed_group(std::string&& group_id); - explicit legacy_closed_group(std::string_view group_id); + // Constructs an empty legacy_group from a quasi-session_id + explicit legacy_group(std::string&& group_id); + explicit legacy_group(std::string_view group_id); // Internal ctor/method for C API implementations: - legacy_closed_group(const struct convo_info_volatile_legacy_closed& c); // From c struct - void into(convo_info_volatile_legacy_closed& c) const; // Into c struct + legacy_group(const struct convo_info_volatile_legacy_group& c); // From c struct + void into(convo_info_volatile_legacy_group& c) const; // Into c struct private: friend class session::config::ConvoInfoVolatile; }; - using any = std::variant; + using any = std::variant; } // namespace convo class ConvoInfoVolatile : public ConfigBase { @@ -186,33 +128,49 @@ class ConvoInfoVolatile : public ConfigBase { const char* encryption_domain() const override { return "ConvoInfoVolatile"; } + /// Our pruning ages. We ignore added conversations that are more than PRUNE_LOW before now, + /// and we active remove (when doing a new push) any conversations that are more than PRUNE_HIGH + /// before now. Clients can mostly ignore these and just add all conversations; the class just + /// transparently ignores (or removes) pruned values. + static constexpr auto PRUNE_LOW = 30 * 24h; + static constexpr auto PRUNE_HIGH = 45 * 24h; + + /// Overrides push() to prune stale last-read values before we do the push. + std::pair push() override; + /// Looks up and returns a contact by session ID (hex). Returns nullopt if the session ID was /// not found, otherwise returns a filled out `convo::one_to_one`. std::optional get_1to1(std::string_view session_id) const; - /// Looks up and returns an open group conversation. Takes the base URL, room name (case - /// insensitive), and pubkey (in hex). Retuns nullopt if the open group was not found, - /// otherwise a filled out `convo::open_group`. - std::optional get_open( - std::string_view base_url, std::string_view room, std::string_view pubkey_hex) const; + /// Looks up and returns a community conversation. Takes the base URL and room name (case + /// insensitive). Retuns nullopt if the community was not found, otherwise a filled out + /// `convo::community`. + std::optional get_community( + std::string_view base_url, std::string_view room) const; - /// Same as above, but takes the pubkey as bytes instead of hex - std::optional get_open( - std::string_view base_url, std::string_view room, ustring_view pubkey) const; - - /// Looks up and returns a legacy closed group conversation by ID. The ID looks like a hex - /// Session ID, but isn't really a Session ID. Returns nullopt if there is no record of the - /// closed group conversation. - std::optional get_legacy_closed(std::string_view pubkey_hex) const; + /// Looks up and returns a legacy group conversation by ID. The ID looks like a hex Session ID, + /// but isn't really a Session ID. Returns nullopt if there is no record of the group + /// conversation. + std::optional get_legacy_group(std::string_view pubkey_hex) const; /// These are the same as the above methods (without "_or_construct" in the name), except that /// when the conversation doesn't exist a new one is created, prefilled with the pubkey/url/etc. convo::one_to_one get_or_construct_1to1(std::string_view session_id) const; - convo::open_group get_or_construct_open( + convo::legacy_group get_or_construct_legacy_group(std::string_view pubkey_hex) const; + + /// This is similar to get_community, except that it also takes the pubkey; the community is + /// looked up by the url & room; if not found, it is constructed using room, url, and pubkey; if + /// it *is* found, then it will always have the *input* pubkey, not the stored pubkey + /// (effectively the provided pubkey replaces the stored one in the returned object; this is not + /// applied to storage, however, unless/until the instance is given to `set()`). + /// + /// Note, however, that when modifying an object like this the update is *only* applied to the + /// returned object; like other fields, it is not updated in the internal state unless/until + /// that community instance is passed to `set()`. + convo::community get_or_construct_community( std::string_view base_url, std::string_view room, std::string_view pubkey_hex) const; - convo::open_group get_or_construct_open( + convo::community get_or_construct_community( std::string_view base_url, std::string_view room, ustring_view pubkey) const; - convo::legacy_closed_group get_or_construct_legacy_closed(std::string_view pubkey_hex) const; /// Inserts or replaces existing conversation info. For example, to update a 1-to-1 /// conversation last read time you would do: @@ -222,31 +180,35 @@ class ConvoInfoVolatile : public ConfigBase { /// conversations.set(info); /// void set(const convo::one_to_one& c); - void set(const convo::legacy_closed_group& c); - void set(const convo::open_group& c); + void set(const convo::legacy_group& c); + void set(const convo::community& c); void set(const convo::any& c); // Variant which can be any of the above protected: void set_base(const convo::base& c, DictFieldProxy& info); + // Drills into the nested dicts to access community details; if the second argument is + // non-nullptr then it will be set to the community's pubkey, if it exists. + DictFieldProxy community_field( + const convo::community& og, ustring_view* get_pubkey = nullptr) const; + public: /// Removes a one-to-one conversation. Returns true if found and removed, false if not present. bool erase_1to1(std::string_view pubkey); - /// Removes an open group conversation record. Returns true if found and removed, false if not - /// present. Arguments are the same as `get_open`. - bool erase_open(std::string_view base_url, std::string_view room, std::string_view pubkey_hex); - bool erase_open(std::string_view base_url, std::string_view room, ustring_view pubkey); + /// Removes a community conversation record. Returns true if found and removed, false if not + /// present. Arguments are the same as `get_community`. + bool erase_community(std::string_view base_url, std::string_view room); - /// Removes a legacy closed group conversation. Returns true if found and removed, false if not + /// Removes a legacy group conversation. Returns true if found and removed, false if not /// present. - bool erase_legacy_closed(std::string_view pubkey_hex); + bool erase_legacy_group(std::string_view pubkey_hex); /// Removes a conversation taking the convo::whatever record (rather than the pubkey/url). bool erase(const convo::one_to_one& c); - bool erase(const convo::open_group& c); - bool erase(const convo::legacy_closed_group& c); + bool erase(const convo::community& c); + bool erase(const convo::legacy_group& c); bool erase(const convo::any& c); // Variant of any of them @@ -261,11 +223,10 @@ class ConvoInfoVolatile : public ConfigBase { /// Returns the number of conversations (of any type). size_t size() const; - /// Returns the number of 1-to-1, open group, and legacy closed group conversations, - /// respectively. + /// Returns the number of 1-to-1, community, and legacy group conversations, respectively. size_t size_1to1() const; - size_t size_open() const; - size_t size_legacy_closed() const; + size_t size_communities() const; + size_t size_legacy_groups() const; /// Returns true if the conversation list is empty. bool empty() const { return size() == 0; } @@ -276,9 +237,9 @@ class ConvoInfoVolatile : public ConfigBase { /// for (auto& convo : conversations) { /// if (auto* dm = std::get_if(&convo)) { /// // use dm->session_id, dm->last_read, etc. - /// } else if (auto* og = std::get_if(&convo)) { + /// } else if (auto* og = std::get_if(&convo)) { /// // use og->base_url, og->room, om->last_read, etc. - /// } else if (auto* lcg = std::get_if(&convo)) { + /// } else if (auto* lcg = std::get_if(&convo)) { /// // use lcg->id, lcg->last_read /// } /// } @@ -302,8 +263,8 @@ class ConvoInfoVolatile : public ConfigBase { /// /// Alternatively, you can use the first version with two loops: the first loop through all /// converations doesn't erase but just builds a vector of IDs to erase, then the second loops - /// through that vector calling `erase_1to1()`/`erase_open()`/`erase_legacy_closed()` for each - /// one. + /// through that vector calling `erase_1to1()`/`erase_community()`/`erase_legacy_group()` for + /// each one. /// iterator begin() const { return iterator{data}; } iterator end() const { return iterator{}; } @@ -313,12 +274,11 @@ class ConvoInfoVolatile : public ConfigBase { /// Returns an iterator that iterates only through one type of conversations subtype_iterator begin_1to1() const { return {data}; } - subtype_iterator begin_open() const { return {data}; } - subtype_iterator begin_legacy_closed() const { return {data}; } + subtype_iterator begin_communities() const { return {data}; } + subtype_iterator begin_legacy_groups() const { return {data}; } using iterator_category = std::input_iterator_tag; - using value_type = - std::variant; + using value_type = std::variant; using reference = value_type&; using pointer = value_type*; using difference_type = std::ptrdiff_t; @@ -326,15 +286,15 @@ class ConvoInfoVolatile : public ConfigBase { struct iterator { protected: std::shared_ptr _val; - std::optional _it_11, _end_11, _it_open, _end_open, _it_lclosed, - _end_lclosed; + std::optional _it_11, _end_11, _it_lgroup, _end_lgroup; + std::optional _it_comm; void _load_val(); iterator() = default; // Constructs an end tombstone explicit iterator( const DictFieldRoot& data, bool oneto1 = true, - bool open = true, - bool closed = true); + bool communities = true, + bool legacy_groups = true); friend class ConvoInfoVolatile; public: @@ -358,8 +318,8 @@ class ConvoInfoVolatile : public ConfigBase { iterator( data, std::is_same_v, - std::is_same_v, - std::is_same_v) {} + std::is_same_v, + std::is_same_v) {} friend class ConvoInfoVolatile; public: diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/expiring.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/expiring.h new file mode 100644 index 000000000..c98653f13 --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/expiring.h @@ -0,0 +1,7 @@ +#pragma once + +typedef enum CONVO_EXPIRATION_MODE { + CONVO_EXPIRATION_NONE = 0, + CONVO_EXPIRATION_AFTER_SEND = 1, + CONVO_EXPIRATION_AFTER_READ = 2, +} CONVO_EXPIRATION_MODE; diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/expiring.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/expiring.hpp new file mode 100644 index 000000000..4c040b740 --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/expiring.hpp @@ -0,0 +1,8 @@ +#pragma once +#include + +namespace session::config { + +enum class expiration_mode : int8_t { none = 0, after_send = 1, after_read = 2 }; + +} diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/namespaces.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/namespaces.hpp index e96a12ab0..394617c0c 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/namespaces.hpp +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/namespaces.hpp @@ -8,7 +8,7 @@ enum class Namespace : std::int16_t { UserProfile = 2, Contacts = 3, ConvoInfoVolatile = 4, - ClosedGroupInfo = 11, + UserGroups = 5, }; } // namespace session::config diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/profile_pic.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/profile_pic.h index dc9887dd8..590df2117 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/profile_pic.h +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/profile_pic.h @@ -7,13 +7,12 @@ extern "C" { #include typedef struct user_profile_pic { - // Null-terminated C string containing the uploaded URL of the pic. Will be NULL if there is no - // profile pic. - const char* url; - // The profile pic decryption key, in bytes. This is a byte buffer of length `keylen`, *not* a - // null-terminated C string. Will be NULL if there is no profile pic. - const unsigned char* key; - size_t keylen; + // Null-terminated C string containing the uploaded URL of the pic. Will be length 0 if there + // is no profile pic. + char url[224]; + // The profile pic decryption key, in bytes. This is a byte buffer of length 32, *not* a + // null-terminated C string. This is only valid when there is a url (i.e. url has strlen > 0). + unsigned char key[32]; } user_profile_pic; #ifdef __cplusplus diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/profile_pic.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/profile_pic.hpp index 0a076ab97..d450d074f 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/profile_pic.hpp +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/profile_pic.hpp @@ -1,39 +1,57 @@ #pragma once +#include + #include "session/types.hpp" namespace session::config { -// Profile pic info. Note that `url` is null terminated (though the null lies just beyond the end -// of the string view: that is, it views into a full std::string). +// Profile pic info. struct profile_pic { - private: - std::string url_; - ustring key_; + static constexpr size_t MAX_URL_LENGTH = 223; - public: - std::string_view url; - ustring_view key; + std::string url; + ustring key; + + static void check_key(ustring_view key) { + if (!(key.empty() || key.size() == 32)) + throw std::invalid_argument{"Invalid profile pic key: 32 bytes required"}; + } // Default constructor, makes an empty profile pic profile_pic() = default; - // Constructs from string views: the values must stay alive for the duration of the profile_pic - // instance. (If not, use `set_url`/`set_key` or the rvalue-argument constructor instead). - profile_pic(std::string_view url, ustring_view key) : url{url}, key{key} {} + // Constructs from a URL and key. Key must be empty or 32 bytes. + profile_pic(std::string_view url, ustring_view key) : url{url}, key{key} { + check_key(this->key); + } - // Constructs from temporary strings; the strings are stored/managed internally - profile_pic(std::string&& url, ustring&& key) : - url_{std::move(url)}, key_{std::move(key)}, url{url_}, key{key_} {} + // Constructs from a string/ustring pair moved into the constructor + profile_pic(std::string&& url, ustring&& key) : url{std::move(url)}, key{std::move(key)} { + check_key(this->key); + } - // Returns true if either url or key are empty - bool empty() const { return url.empty() || key.empty(); } + // Returns true if either url or key are empty (or invalid) + bool empty() const { return url.empty() || key.size() != 32; } - // Sets the url or key to a temporary value that needs to be copied and owned by this - // profile_pic object. (This is only needed when the source string may not outlive the - // profile_pic object; if it does, the `url` or `key` can be assigned to directly). - void set_url(std::string url); - void set_key(ustring key); + // Clears the current url/key, if set. This is just a shortcut for calling `.clear()` on each + // of them. + void clear() { + url.clear(); + key.clear(); + } + + // The object in boolean context is true if url and key are both set, i.e. the opposite of + // `empty()`. + explicit operator bool() const { return !empty(); } + + // Sets and validates the key. The key can be empty, or 32 bytes. This is almost the same as + // just setting `.key` directly, except that it will throw if the provided key is invalid (i.e. + // neither empty nor 32 bytes). + void set_key(ustring new_key) { + check_key(new_key); + key = std::move(new_key); + } }; } // namespace session::config diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.h new file mode 100644 index 000000000..a4897da6f --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.h @@ -0,0 +1,181 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "base.h" +#include "util.h" + +typedef struct ugroups_legacy_group_info { + char session_id[67]; // in hex; 66 hex chars + null terminator. + + char name[101]; // Null-terminated C string (human-readable). Max length is 511. Will always + // be set (even if an empty string). + + bool have_enc_keys; // Will be true if we have an encryption keypair, false if not. + unsigned char enc_pubkey[32]; // If `have_enc_keys`, this is the 32-byte pubkey + unsigned char enc_seckey[32]; // If `have_enc_keys`, this is the 32-byte secret key + + int64_t disappearing_timer; // Minutes. 0 == disabled. + bool hidden; // true if hidden from the convo list + int priority; // pinned message priority; 0 = unpinned, larger means pinned higher (i.e. higher + // priority conversations come first). +} ugroups_legacy_group_info; + +typedef struct ugroups_community_info { + char base_url[268]; // null-terminated (max length 267), normalized (i.e. always lower-case, + // only has port if non-default, has trailing / removed) + char room[65]; // null-terminated (max length 64); this is case-preserving (i.e. can be + // "SomeRoom" instead of "someroom". Note this is different from volatile + // info (that one is always forced lower-cased). + unsigned char pubkey[32]; // 32 bytes (not terminated, can contain nulls) + + int priority; // pinned message priority; 0 = unpinned, larger means pinned higher (i.e. higher + // priority conversations come first). +} ugroups_community_info; + +int user_groups_init( + config_object** conf, + const unsigned char* ed25519_secretkey, + const unsigned char* dump, + size_t dumplen, + char* error) __attribute__((warn_unused_result)); + +/// Gets community conversation info into `comm`, if the community info was found. `base_url` and +/// `room` are null-terminated c strings; pubkey is 32 bytes. base_url will be +/// normalized/lower-cased; room is case-insensitive for the lookup: note that this may well return +/// a community info with a different room capitalization than the one provided to the call. +/// +/// Returns true if the community was found and `comm` populated; false otherwise. +bool user_groups_get_community( + const config_object* conf, + ugroups_community_info* comm, + const char* base_url, + const char* room) __attribute__((warn_unused_result)); + +/// Like the above, but if the community was not found, this constructs one that can be inserted. +/// `base_url` will be normalized in the returned object. `room` is a case-insensitive lookup key +/// for the room token. Note that it has subtle handling w.r.t its case: if an existing room is +/// found, you get back a record with the found case (which could differ in case from what you +/// provided). If you want to override to what you provided regardless of what is there you should +/// immediately set the name of the returned object to the case you prefer. If a *new* record is +/// constructed, however, it will match the room token case as given here. +/// +/// Note that this is all different from convo_info_volatile, which always forces the room token to +/// lower-case (because it does not preserve the case). +bool user_groups_get_or_construct_community( + const config_object* conf, + ugroups_community_info* comm, + const char* base_url, + const char* room, + unsigned const char* pubkey) __attribute__((warn_unused_result)); + +/// Fills `group` with the conversation info given a legacy group ID (specified as a null-terminated +/// hex string), if the conversation exists, and returns true. If the conversation does not exist +/// then `group` is left unchanged and false is returned. +bool user_groups_get_legacy_group( + const config_object* conf, ugroups_legacy_group_info* group, const char* id) + __attribute__((warn_unused_result)); + +/// Same as the above except that when the conversation does not exist, this sets all the group +/// fields to defaults and loads it with the given id. +/// +/// Returns true as long as it is given a valid legacy group group id (i.e. same format as a session +/// id). A false return is considered an error, and means the id was not a valid session id. +/// +/// This is the method that should usually be used to create or update a conversation, followed by +/// setting fields in the group, and then giving it to user_groups_set(). +bool user_groups_get_or_construct_legacy_group( + const config_object* conf, ugroups_legacy_group_info* group, const char* id) + __attribute__((warn_unused_result)); + +/// Adds or updates a conversation from the given group info +void user_groups_set_community(config_object* conf, const ugroups_community_info* group); +void user_groups_set_legacy_group(config_object* conf, const ugroups_legacy_group_info* group); + +/// Erases a conversation from the conversation list. Returns true if the conversation was found +/// and removed, false if the conversation was not present. You must not call this during +/// iteration; see details below. +bool user_groups_erase_community(config_object* conf, const char* base_url, const char* room); +bool user_groups_erase_legacy_group(config_object* conf, const char* group_id); + +/// Returns the number of conversations. +size_t user_groups_size(const config_object* conf); +/// Returns the number of conversations of the specific type. +size_t user_groups_size_communities(const config_object* conf); +size_t user_groups_size_legacy_groups(const config_object* conf); + +/// Functions for iterating through the entire conversation list. Intended use is: +/// +/// ugroups_community_info c2; +/// ugroups_legacy_group_info c3; +/// user_groups_iterator *it = user_groups_iterator_new(my_groups); +/// for (; !user_groups_iterator_done(it); user_groups_iterator_advance(it)) { +/// if (user_groups_it_is_community(it, &c2)) { +/// // use c2.whatever +/// } else if (user_groups_it_is_legacy_group(it, &c3)) { +/// // use c3.whatever +/// } +/// } +/// user_groups_iterator_free(it); +/// +/// It is permitted to modify records (e.g. with a call to one of the `user_groups_set_*` +/// functions) and add records while iterating. +/// +/// If you need to remove while iterating then usage is slightly different: you must advance the +/// iteration by calling either user_groups_iterator_advance if not deleting, or +/// user_groups_iterator_erase to erase and advance. Usage looks like this: +/// +/// ugroups_community_info comm; +/// ugroups_iterator *it = ugroups_iterator_new(my_groups); +/// while (!user_groups_iterator_done(it)) { +/// if (user_groups_it_is_community(it, &comm)) { +/// bool should_delete = /* ... */; +/// if (should_delete) +/// user_groups_iterator_erase(it); +/// else +/// user_groups_iterator_advance(it); +/// } else { +/// user_groups_iterator_advance(it); +/// } +/// } +/// user_groups_iterator_free(it); +/// + +typedef struct user_groups_iterator user_groups_iterator; + +// Starts a new iterator that iterates over all conversations. +user_groups_iterator* user_groups_iterator_new(const config_object* conf); + +// The same as `user_groups_iterator_new` except that this iterates *only* over one type of +// conversation. You still need to use `user_groups_it_is_community` (or the alternatives) +// to load the data in each pass of the loop. (You can, however, safely ignore the bool return +// value of the `it_is_whatever` function: it will always be true for the particular type being +// iterated over). +user_groups_iterator* user_groups_iterator_new_communities(const config_object* conf); +user_groups_iterator* user_groups_iterator_new_legacy_groups(const config_object* conf); + +// Frees an iterator once no longer needed. +void user_groups_iterator_free(user_groups_iterator* it); + +// Returns true if iteration has reached the end. +bool user_groups_iterator_done(user_groups_iterator* it); + +// Advances the iterator. +void user_groups_iterator_advance(user_groups_iterator* it); + +// If the current iterator record is a community conversation this sets the details into `c` and +// returns true. Otherwise it returns false. +bool user_groups_it_is_community(user_groups_iterator* it, ugroups_community_info* c); + +// If the current iterator record is a legacy group conversation this sets the details into +// `c` and returns true. Otherwise it returns false. +bool user_groups_it_is_legacy_group(user_groups_iterator* it, ugroups_legacy_group_info* c); + +// Erases the current group while advancing the iterator to the next group in the iteration. +void user_groups_iterator_erase(config_object* conf, user_groups_iterator* it); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.hpp new file mode 100644 index 000000000..2feafa693 --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.hpp @@ -0,0 +1,335 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "base.hpp" +#include "community.hpp" +#include "namespaces.hpp" + +extern "C" { +struct ugroups_legacy_group_info; +struct ugroups_community_info; +} + +namespace session::config { + +/// keys used in this config, either currently or in the past (so that we don't reuse): +/// +/// C - dict of legacy groups; within this dict each key is the group pubkey (binary, 33 bytes) and +/// value is a dict containing keys: +/// +/// n - name (string). Always set, even if empty. +/// k - encryption public key (32 bytes). Optional. +/// K - encryption secret key (32 bytes). Optional. +/// m - set of member session ids (each 33 bytes). +/// a - set of admin session ids (each 33 bytes). +/// E - disappearing messages duration, in minutes, > 0. Omitted if disappearing messages is +/// disabled. (Note that legacy groups only support expire after-read) +/// h - hidden: 1 if the conversation has been removed from the conversation list, omitted if +/// visible. +/// + - the conversation priority, for pinned messages. Omitted means not pinned; otherwise an +/// integer value >0, where a higher priority means the conversation is meant to appear +/// earlier in the pinned conversation list. +/// +/// o - dict of communities (AKA open groups); within this dict (which deliberately has the same +/// layout as convo_info_volatile) each key is the SOGS base URL (in canonical form), and value +/// is a dict of: +/// +/// # - server pubkey +/// R - dict of rooms on the server. Each key is the *lower-case* room name; each value is: +/// n - the room name as is commonly used, i.e. with possible capitalization (if +/// appropriate). For instance, a room name SudokuSolvers would be "sudokusolvers" in +/// the outer key, with the capitalization variation in use ("SudokuSolvers") in this +/// key. This key is *always* present (to keep the room dict non-empty). +/// + - the conversation priority, for pinned messages. Omitted means not pinned; otherwise +/// an integer value >0, where a higher priority means the conversation is meant to +/// appear earlier in the pinned conversation list. +/// +/// c - reserved for future storage of new-style group info. + +/// Struct containing legacy group info (aka "closed groups"). +struct legacy_group_info { + static constexpr size_t NAME_MAX_LENGTH = 100; // in bytes; name will be truncated if exceeded + + std::string session_id; // The legacy group "session id" (33 bytes). + std::string name; // human-readable; this should normally always be set, but in theory could be + // set to an empty string. + ustring enc_pubkey; // bytes (32 or empty) + ustring enc_seckey; // bytes (32 or empty) + std::chrono::minutes disappearing_timer{0}; // 0 == disabled. + bool hidden = false; // true if the conversation is hidden from the convo list + int priority = 0; // The priority; 0 means unpinned, larger means pinned higher (i.e. + // higher priority conversations come first). + + /// Constructs a new legacy group info from an id (which must look like a session_id). Throws + /// if id is invalid. + explicit legacy_group_info(std::string sid); + + // Accesses the session ids (in hex) of members of this group. The key is the hex session_id; + // the value indicates whether the member is an admin (true) or not (false). + const std::map& members() const { return members_; } + + // Returns a pair of the number of admins, and regular members of this group. (If all you want + // is the overall number just use `.members().size()` instead). + std::pair counts() const; + + // Adds a member (by session id and admin status) to this group. Returns true if the member was + // inserted or changed admin status, false if the member already existed. Throws + // std::invalid_argument if the given session id is invalid. + bool insert(std::string session_id, bool admin); + + // Removes a member (by session id) from this group. Returns true if the member was + // removed, false if the member was not present. + bool erase(const std::string& session_id); + + // Internal ctor/method for C API implementations: + legacy_group_info(const struct ugroups_legacy_group_info& c); // From c struct + void into(struct ugroups_legacy_group_info& c) const; // Into c struct + + private: + // session_id => (is admin) + std::map members_; + + friend class UserGroups; + + void load(const dict& info_dict); +}; + +/// Community (aka open group) info +struct community_info : community { + // Note that *changing* url/room/pubkey and then doing a set inserts a new room under the given + // url/room/pubkey, it does *not* update an existing room. + + // See community_base (comm_base.hpp) for common constructors + using community::community; + + // Internal ctor/method for C API implementations: + community_info(const struct ugroups_community_info& c); // From c struct + void into(ugroups_community_info& c) const; // Into c struct + + int priority = 0; // The priority; 0 means unpinned, larger means pinned higher (i.e. + // higher priority conversations come first). + + private: + void load(const dict& info_dict); + + friend class UserGroups; + friend class comm_iterator_helper; +}; + +using any_group_info = std::variant; + +class UserGroups : public ConfigBase { + + public: + // No default constructor + UserGroups() = delete; + + /// Constructs a user group list from existing data (stored from `dump()`) and the user's + /// secret key for generating the data encryption key. To construct a blank list (i.e. with no + /// pre-existing dumped data to load) pass `std::nullopt` as the second argument. + /// + /// \param ed25519_secretkey - contains the libsodium secret key used to encrypt/decrypt the + /// data when pushing/pulling from the swarm. This can either be the full 64-byte value (which + /// is technically the 32-byte seed followed by the 32-byte pubkey), or just the 32-byte seed of + /// the secret key. + /// + /// \param dumped - either `std::nullopt` to construct a new, empty object; or binary state data + /// that was previously dumped from an instance of this class by calling `dump()`. + UserGroups(ustring_view ed25519_secretkey, std::optional dumped); + + Namespace storage_namespace() const override { return Namespace::UserGroups; } + + const char* encryption_domain() const override { return "UserGroups"; } + + /// Looks up and returns a community (aka open group) conversation. Takes the base URL and room + /// token (case insensitive). Retuns nullopt if the open group was not found, otherwise a + /// filled out `community_info`. Note that the `room` argument here is case-insensitive, but + /// the returned value will be the room as stored in the object (i.e. it may have a different + /// case from the requested `room` value). + std::optional get_community( + std::string_view base_url, std::string_view room) const; + + /// Looks up and returns a legacy group by group ID (hex, looks like a Session ID). Returns + /// nullopt if the group was not found, otherwise returns a filled out `legacy_group_info`. + std::optional get_legacy_group(std::string_view pubkey_hex) const; + + /// Same as `get_community`, except if the community isn't found a new blank one is created for + /// you, prefilled with the url/room/pubkey. + /// + /// Note that `room` and `pubkey` have special handling: + /// - `room` is case-insensitive for the lookup: if a matching room is found then the returned + /// value reflects the room case of the existing record, which is not necessarily the same as + /// the `room` argument given here (to force a case change, set it within the returned + /// object). + /// - `pubkey` is not used to find an existing community, but if the community found has a + /// *different* pubkey from the one given then the returned record has its pubkey updated in + /// the return instance (note that this changed value is not committed to storage, however, + /// until the instance is passed to `set()`). For the string_view version the pubkey is + /// accepted as hex, base32z, or base64. + community_info get_or_construct_community( + std::string_view base_url, + std::string_view room, + std::string_view pubkey_encoded) const; + community_info get_or_construct_community( + std::string_view base_url, std::string_view room, ustring_view pubkey) const; + + /// Gets or constructs a blank legacy_group_info for the given group id. + legacy_group_info get_or_construct_legacy_group(std::string_view pubkey_hex) const; + + /// Inserts or replaces existing group info. For example, to update the info for a community + /// you would do: + /// + /// auto info = conversations.get_or_construct_community(some_session_id); + /// info.last_read = new_unix_timestamp; + /// conversations.set(info); + /// + void set(const community_info& info); + void set(const legacy_group_info& info); + /// Takes a variant of either group type to set: + void set(const any_group_info& info); + + protected: + // Drills into the nested dicts to access open group details + DictFieldProxy community_field( + const community_info& og, ustring_view* get_pubkey = nullptr) const; + + public: + /// Removes a community group. Returns true if found and removed, false if not present. + /// Arguments are the same as `get_community`. + bool erase_community(std::string_view base_url, std::string_view room); + + /// Removes a legacy group conversation. Returns true if found and removed, false if not + /// present. + bool erase_legacy_group(std::string_view pubkey_hex); + + /// Removes a conversation taking the community_info or legacy_group_info instance (rather than + /// the pubkey/url) for convenience. + bool erase(const community_info& g); + bool erase(const legacy_group_info& c); + bool erase(const any_group_info& info); + + struct iterator; + + /// This works like erase, but takes an iterator to the group to remove. The element is removed + /// and the iterator to the next element after the removed one is returned. This is intended + /// for use where elements are to be removed during iteration: see below for an example. + iterator erase(iterator it); + + /// Returns the number of groups (of any type). + size_t size() const; + + /// Returns the number of communities + size_t size_communities() const; + + /// Returns the number of legacy groups + size_t size_legacy_groups() const; + + /// Returns true if the group list is empty. + bool empty() const { return size() == 0; } + + /// Iterators for iterating through all groups. Typically you access this implicit via a + /// for loop over the `UserGroups` object: + /// + /// for (auto& group : usergroups) { + /// if (auto* comm = std::get_if(&group)) { + /// // use comm->name, comm->priority, etc. + /// } else if (auto* lg = std::get_if(&convo)) { + /// // use lg->session_id, lg->hidden, etc. + /// } + /// } + /// + /// This iterates through all groups in sorted order (sorted first by convo type, then by + /// id within the type). + /// + /// It is permitted to modify and add records while iterating (e.g. by modifying one of the + /// `comm`/`lg` objects and then calling set()). + /// + /// If you need to erase the current conversation during iteration then care is required: you + /// need to advance the iterator via the iterator version of erase when erasing an element + /// rather than incrementing it regularly. For example: + /// + /// for (auto it = conversations.begin(); it != conversations.end(); ) { + /// if (should_remove(*it)) + /// it = converations.erase(it); + /// else + /// ++it; + /// } + /// + /// Alternatively, you can use the first version with two loops: the first loop through all + /// converations doesn't erase but just builds a vector of IDs to erase, then the second loops + /// through that vector calling `erase_1to1()`/`erase_open()`/`erase_legacy_group()` for each + /// one. + /// + iterator begin() const { return iterator{data}; } + iterator end() const { return iterator{}; } + + template + struct subtype_iterator; + + /// Returns an iterator that iterates only through one type of conversations. (The regular + /// `.end()` iterator is valid for testing the end of these iterations). + subtype_iterator begin_communities() const { return {data}; } + subtype_iterator begin_legacy_groups() const { return {data}; } + + using iterator_category = std::input_iterator_tag; + using value_type = std::variant; + using reference = value_type&; + using pointer = value_type*; + using difference_type = std::ptrdiff_t; + + struct iterator { + protected: + std::shared_ptr _val; + std::optional _it_comm; + std::optional _it_legacy, _end_legacy; + void _load_val(); + iterator() = default; // Constructs an end tombstone + explicit iterator( + const DictFieldRoot& data, bool communities = true, bool legacy_closed = true); + friend class UserGroups; + + public: + bool operator==(const iterator& other) const; + bool operator!=(const iterator& other) const { return !(*this == other); } + bool done() const; // Equivalent to comparing against the end iterator + any_group_info& operator*() const { return *_val; } + any_group_info* operator->() const { return _val.get(); } + iterator& operator++(); + iterator operator++(int) { + auto copy{*this}; + ++*this; + return copy; + } + }; + + template + struct subtype_iterator : iterator { + protected: + subtype_iterator(const DictFieldRoot& data) : + iterator( + data, + std::is_same_v, + std::is_same_v) {} + friend class UserGroups; + + public: + GroupType& operator*() const { return std::get(*_val); } + GroupType* operator->() const { return &std::get(*_val); } + subtype_iterator& operator++() { + iterator::operator++(); + return *this; + } + subtype_iterator operator++(int) { + auto copy{*this}; + ++*this; + return copy; + } + }; +}; + +} // namespace session::config diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.hpp index cb3b1eb32..bcc8afa95 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.hpp +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.hpp @@ -44,9 +44,9 @@ class UserProfile final : public ConfigBase { /// Sets the user profile name; if given an empty string then the name is removed. void set_name(std::string_view new_name); - /// Gets the user's current profile pic URL and decryption key. Returns nullptr for *both* - /// values if *either* value is unset or empty in the config data. - std::optional get_profile_pic() const; + /// Gets the user's current profile pic URL and decryption key. The returned object will + /// evaluate as false if the URL and/or key are not set. + profile_pic get_profile_pic() const; /// Sets the user's current profile pic to a new URL and decryption key. Clears both if either /// one is empty. diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/version.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/version.h new file mode 100644 index 000000000..5574fdecc --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/version.h @@ -0,0 +1,19 @@ +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/// libsession-util version triplet (major, minor, patch) +extern const uint16_t LIBSESSION_UTIL_VERSION[3]; + +/// Printable full libsession-util name and version string, such as `libsession-util v0.1.2-release` +/// for a tagged release or `libsession-util v0.1.2-7f144eb5` for an untagged build. +extern const char* LIBSESSION_UTIL_VERSION_FULL; + +/// Just the version component as a string, e.g. `v0.1.2-release`. +extern const char* LIBSESSION_UTIL_VERSION_STR; + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift b/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift index 7ac0ad123..b0c22ea07 100644 --- a/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift +++ b/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift @@ -373,7 +373,7 @@ public extension ClosedGroupControlMessage.Kind { let addedMemberNames: [String] = memberIds .map { knownMemberNameMap[$0] ?? - Profile.truncated(id: $0, threadVariant: .legacyClosedGroup) + Profile.truncated(id: $0, threadVariant: .legacyGroup) } return String( @@ -396,7 +396,7 @@ public extension ClosedGroupControlMessage.Kind { let removedMemberNames: [String] = memberIds.removing(userPublicKey) .map { knownMemberNameMap[$0] ?? - Profile.truncated(id: $0, threadVariant: .legacyClosedGroup) + Profile.truncated(id: $0, threadVariant: .legacyGroup) } let format: String = (removedMemberNames.count > 1 ? "GROUP_MEMBERS_REMOVED".localized() : diff --git a/SessionMessagingKit/Messages/Control Messages/SharedConfigMessage.swift b/SessionMessagingKit/Messages/Control Messages/SharedConfigMessage.swift index c6172c79e..2e67ea96e 100644 --- a/SessionMessagingKit/Messages/Control Messages/SharedConfigMessage.swift +++ b/SessionMessagingKit/Messages/Control Messages/SharedConfigMessage.swift @@ -15,6 +15,8 @@ public final class SharedConfigMessage: ControlMessage { public var seqNo: Int64 public var data: Data + /// SharedConfigMessages should last for 30 days rather than the standard 14 + public override var ttl: UInt64 { 30 * 24 * 60 * 60 * 1000 } public override var isSelfSendValid: Bool { true } // MARK: - Kind @@ -23,14 +25,14 @@ public final class SharedConfigMessage: ControlMessage { case userProfile case contacts case convoInfoVolatile - case groups + case userGroups public var description: String { switch self { case .userProfile: return "userProfile" case .contacts: return "contacts" case .convoInfoVolatile: return "convoInfoVolatile" - case .groups: return "groups" + case .userGroups: return "userGroups" } } } @@ -82,7 +84,7 @@ public final class SharedConfigMessage: ControlMessage { case .userProfile: return .userProfile case .contacts: return .contacts case .convoInfoVolatile: return .convoInfoVolatile - case .groups: return .groups + case .userGroups: return .userGroups } }(), seqNo: sharedConfigMessage.seqno, @@ -98,7 +100,7 @@ public final class SharedConfigMessage: ControlMessage { case .userProfile: return .userProfile case .contacts: return .contacts case .convoInfoVolatile: return .convoInfoVolatile - case .groups: return .groups + case .userGroups: return .userGroups } }(), seqno: self.seqNo, @@ -135,7 +137,7 @@ public extension SharedConfigMessage.Kind { case .userProfile: return .userProfile case .contacts: return .contacts case .convoInfoVolatile: return .convoInfoVolatile - case .groups: return .groups + case .userGroups: return .userGroups } } } diff --git a/SessionMessagingKit/Messages/Message+Destination.swift b/SessionMessagingKit/Messages/Message+Destination.swift index 476b868f4..a40b6ca5d 100644 --- a/SessionMessagingKit/Messages/Message+Destination.swift +++ b/SessionMessagingKit/Messages/Message+Destination.swift @@ -39,10 +39,10 @@ public extension Message { return .contact(publicKey: thread.id) - case .legacyClosedGroup, .closedGroup: + case .legacyGroup, .group: return .closedGroup(groupPublicKey: thread.id) - case .openGroup: + case .community: guard let openGroup: OpenGroup = try thread.openGroup.fetchOne(db) else { throw StorageError.objectNotFound } diff --git a/SessionMessagingKit/Messages/Message.swift b/SessionMessagingKit/Messages/Message.swift index 7dc1bbcc4..22643545e 100644 --- a/SessionMessagingKit/Messages/Message.swift +++ b/SessionMessagingKit/Messages/Message.swift @@ -361,7 +361,7 @@ public extension Message { let blindedUserPublicKey: String? = SessionThread .getUserHexEncodedBlindedKey( threadId: openGroupId, - threadVariant: .openGroup + threadVariant: .community ) for (encodedEmoji, rawReaction) in reactions { if let decodedEmoji = encodedEmoji.removingPercentEncoding, diff --git a/SessionMessagingKit/Messages/Visible Messages/VisibleMessage.swift b/SessionMessagingKit/Messages/Visible Messages/VisibleMessage.swift index 20b01e9e1..8f70c54c1 100644 --- a/SessionMessagingKit/Messages/Visible Messages/VisibleMessage.swift +++ b/SessionMessagingKit/Messages/Visible Messages/VisibleMessage.swift @@ -218,8 +218,8 @@ public extension VisibleMessage { recipient: (try? interaction.recipientStates.fetchOne(db))?.recipientId, groupPublicKey: try? interaction.thread .filter( - SessionThread.Columns.variant == SessionThread.Variant.legacyClosedGroup || - SessionThread.Columns.variant == SessionThread.Variant.closedGroup + SessionThread.Columns.variant == SessionThread.Variant.legacyGroup || + SessionThread.Columns.variant == SessionThread.Variant.group ) .select(.id) .asRequest(of: String.self) diff --git a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift index 3dcf881fa..ead83d7ed 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift @@ -363,7 +363,7 @@ public enum OpenGroupAPI { requests: requestResponseType, using: dependencies ) - .flatMap { (info: ResponseInfoType, data: [Endpoint: Codable]) -> AnyPublisher in + .tryMap { (info: ResponseInfoType, data: [Endpoint: Codable]) -> CapabilitiesAndRoomResponse in let maybeCapabilities: HTTP.BatchSubResponse? = (data[.capabilities] as? HTTP.BatchSubResponse) let maybeRoomResponse: Codable? = data .first(where: { key, _ in @@ -380,20 +380,15 @@ public enum OpenGroupAPI { let capabilities: Capabilities = maybeCapabilities?.body, let roomInfo: ResponseInfoType = maybeRoom?.responseInfo, let room: Room = maybeRoom?.body - else { - return Fail(error: HTTPError.parsingFailed) - .eraseToAnyPublisher() - } + else { throw HTTPError.parsingFailed } - return Just(( + return ( info: info, data: ( capabilities: (info: capabilitiesInfo, data: capabilities), room: (info: roomInfo, data: room) ) - )) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() + ) } .eraseToAnyPublisher() } @@ -434,7 +429,7 @@ public enum OpenGroupAPI { requests: requestResponseType, using: dependencies ) - .flatMap { (info: ResponseInfoType, data: [Endpoint: Codable]) -> AnyPublisher<(capabilities: (info: ResponseInfoType, data: Capabilities), rooms: (info: ResponseInfoType, data: [Room])), Error> in + .tryMap { (info: ResponseInfoType, data: [Endpoint: Codable]) -> (capabilities: (info: ResponseInfoType, data: Capabilities), rooms: (info: ResponseInfoType, data: [Room])) in let maybeCapabilities: HTTP.BatchSubResponse? = (data[.capabilities] as? HTTP.BatchSubResponse) let maybeRooms: HTTP.BatchSubResponse<[Room]>? = data .first(where: { key, _ in @@ -450,17 +445,12 @@ public enum OpenGroupAPI { let capabilities: Capabilities = maybeCapabilities?.body, let roomsInfo: ResponseInfoType = maybeRooms?.responseInfo, let rooms: [Room] = maybeRooms?.body - else { - return Fail(error: HTTPError.parsingFailed) - .eraseToAnyPublisher() - } + else { throw HTTPError.parsingFailed } - return Just(( + return ( capabilities: (info: capabilitiesInfo, data: capabilities), rooms: (info: roomsInfo, data: rooms) - )) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() + ) } .eraseToAnyPublisher() } @@ -957,15 +947,10 @@ public enum OpenGroupAPI { timeout: FileServerAPI.fileTimeout, using: dependencies ) - .flatMap { responseInfo, maybeData -> AnyPublisher<(ResponseInfoType, Data), Error> in - guard let data: Data = maybeData else { - return Fail(error: HTTPError.parsingFailed) - .eraseToAnyPublisher() - } + .tryMap { responseInfo, maybeData -> (ResponseInfoType, Data) in + guard let data: Data = maybeData else { throw HTTPError.parsingFailed } - return Just((responseInfo, data)) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() + return (responseInfo, data) } .eraseToAnyPublisher() } diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index e12a0a1ec..063cbd3ce 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -230,7 +230,7 @@ public final class OpenGroupManager { // Optionally try to insert a new version of the OpenGroup (it will fail if there is already an // inactive one but that won't matter as we then activate it - _ = try? SessionThread.fetchOrCreate(db, id: threadId, variant: .openGroup) + _ = try? SessionThread.fetchOrCreate(db, id: threadId, variant: .community) _ = try? SessionThread.filter(id: threadId).updateAll(db, SessionThread.Columns.shouldBeVisible.set(to: true)) if (try? OpenGroup.exists(db, id: threadId)) == false { @@ -249,67 +249,64 @@ public final class OpenGroupManager { OpenGroup.Columns.sequenceNumber.set(to: 0) ) - // We was to avoid blocking the db write thread so we dispatch the API call to a different thread + // We want to avoid blocking the db write thread so we dispatch the API call to a different thread // // Note: We don't do this after the db commit as it can fail (resulting in endless loading) - return Just(()) - .setFailureType(to: Error.self) - .subscribe(on: OpenGroupAPI.workQueue) - .receive(on: OpenGroupAPI.workQueue) - .flatMap { _ in - dependencies.storage - .readPublisherFlatMap { db in - // Note: The initial request for room info and it's capabilities should NOT be - // authenticated (this is because if the server requires blinding and the auth - // headers aren't blinded it will error - these endpoints do support unauthenticated - // retrieval so doing so prevents the error) - OpenGroupAPI - .capabilitiesAndRoom( - db, - for: roomToken, - on: targetServer, - using: dependencies - ) - } - } - .flatMap { response -> Future in - Future { resolver in - dependencies.storage.write { db in - // Enqueue a config sync job (have a newly added open group to sync) - if !calledFromConfigHandling { - ConfigurationSyncJob.enqueue(db) - } - - // Store the capabilities first - OpenGroupManager.handleCapabilities( + return Deferred { + dependencies.storage + .readPublisherFlatMap(receiveOn: OpenGroupAPI.workQueue) { db in + // Note: The initial request for room info and it's capabilities should NOT be + // authenticated (this is because if the server requires blinding and the auth + // headers aren't blinded it will error - these endpoints do support unauthenticated + // retrieval so doing so prevents the error) + OpenGroupAPI + .capabilitiesAndRoom( db, - capabilities: response.data.capabilities.data, - on: targetServer - ) - - // Then the room - try OpenGroupManager.handlePollInfo( - db, - pollInfo: OpenGroupAPI.RoomPollInfo(room: response.data.room.data), - publicKey: publicKey, for: roomToken, on: targetServer, - dependencies: dependencies - ) { - resolver(Result.success(())) - } + using: dependencies + ) + } + } + .receive(on: OpenGroupAPI.workQueue) + .flatMap { response -> Future in + Future { resolver in + dependencies.storage.write { db in + // Enqueue a config sync job (have a newly added open group to sync) + if !calledFromConfigHandling { + ConfigurationSyncJob.enqueue(db) + } + + // Store the capabilities first + OpenGroupManager.handleCapabilities( + db, + capabilities: response.data.capabilities.data, + on: targetServer + ) + + // Then the room + try OpenGroupManager.handlePollInfo( + db, + pollInfo: OpenGroupAPI.RoomPollInfo(room: response.data.room.data), + publicKey: publicKey, + for: roomToken, + on: targetServer, + dependencies: dependencies + ) { + resolver(Result.success(())) } } } - .handleEvents( - receiveCompletion: { result in - switch result { - case .finished: break - case .failure: SNLog("Failed to join open group.") - } + } + .handleEvents( + receiveCompletion: { result in + switch result { + case .finished: break + case .failure: SNLog("Failed to join open group.") } - ) - .eraseToAnyPublisher() + } + ) + .eraseToAnyPublisher() } public func delete(_ db: Database, openGroupId: String, dependencies: OGMDependencies = OGMDependencies()) { @@ -961,14 +958,14 @@ public final class OpenGroupManager { // Try to retrieve the default rooms 8 times let publisher: AnyPublisher<[OpenGroupAPI.Room], Error> = dependencies.storage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: OpenGroupAPI.workQueue) { db in OpenGroupAPI.capabilitiesAndRooms( db, on: OpenGroupAPI.defaultServer, using: dependencies ) } - .subscribe(on: OpenGroupAPI.workQueue) + .receive(on: OpenGroupAPI.workQueue) .retry(8) .map { response in dependencies.storage.writeAsync { db in diff --git a/SessionMessagingKit/Protos/Generated/SNProto.swift b/SessionMessagingKit/Protos/Generated/SNProto.swift index b10ef5fac..ed766ca8d 100644 --- a/SessionMessagingKit/Protos/Generated/SNProto.swift +++ b/SessionMessagingKit/Protos/Generated/SNProto.swift @@ -3716,7 +3716,7 @@ extension SNProtoAttachmentPointer.SNProtoAttachmentPointerBuilder { case userProfile = 1 case contacts = 2 case convoInfoVolatile = 3 - case groups = 4 + case userGroups = 4 } private class func SNProtoSharedConfigMessageKindWrap(_ value: SessionProtos_SharedConfigMessage.Kind) -> SNProtoSharedConfigMessageKind { @@ -3724,7 +3724,7 @@ extension SNProtoAttachmentPointer.SNProtoAttachmentPointerBuilder { case .userProfile: return .userProfile case .contacts: return .contacts case .convoInfoVolatile: return .convoInfoVolatile - case .groups: return .groups + case .userGroups: return .userGroups } } @@ -3733,7 +3733,7 @@ extension SNProtoAttachmentPointer.SNProtoAttachmentPointerBuilder { case .userProfile: return .userProfile case .contacts: return .contacts case .convoInfoVolatile: return .convoInfoVolatile - case .groups: return .groups + case .userGroups: return .userGroups } } diff --git a/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift b/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift index 3b649effc..6f209cb67 100644 --- a/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift +++ b/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift @@ -1623,7 +1623,7 @@ struct SessionProtos_SharedConfigMessage { case userProfile // = 1 case contacts // = 2 case convoInfoVolatile // = 3 - case groups // = 4 + case userGroups // = 4 init() { self = .userProfile @@ -1634,7 +1634,7 @@ struct SessionProtos_SharedConfigMessage { case 1: self = .userProfile case 2: self = .contacts case 3: self = .convoInfoVolatile - case 4: self = .groups + case 4: self = .userGroups default: return nil } } @@ -1644,7 +1644,7 @@ struct SessionProtos_SharedConfigMessage { case .userProfile: return 1 case .contacts: return 2 case .convoInfoVolatile: return 3 - case .groups: return 4 + case .userGroups: return 4 } } @@ -3343,6 +3343,6 @@ extension SessionProtos_SharedConfigMessage.Kind: SwiftProtobuf._ProtoNameProvid 1: .same(proto: "USER_PROFILE"), 2: .same(proto: "CONTACTS"), 3: .same(proto: "CONVO_INFO_VOLATILE"), - 4: .same(proto: "GROUPS"), + 4: .same(proto: "USER_GROUPS"), ] } diff --git a/SessionMessagingKit/Protos/SessionProtos.proto b/SessionMessagingKit/Protos/SessionProtos.proto index 2014274b0..429c10b14 100644 --- a/SessionMessagingKit/Protos/SessionProtos.proto +++ b/SessionMessagingKit/Protos/SessionProtos.proto @@ -277,7 +277,7 @@ message SharedConfigMessage { USER_PROFILE = 1; CONTACTS = 2; CONVO_INFO_VOLATILE = 3; - GROUPS = 4; + USER_GROUPS = 4; } // @required diff --git a/SessionMessagingKit/Sending & Receiving/Attachments/SignalAttachment.swift b/SessionMessagingKit/Sending & Receiving/Attachments/SignalAttachment.swift index 08881a282..9b00ee6c5 100644 --- a/SessionMessagingKit/Sending & Receiving/Attachments/SignalAttachment.swift +++ b/SessionMessagingKit/Sending & Receiving/Attachments/SignalAttachment.swift @@ -918,23 +918,25 @@ public class SignalAttachment: Equatable, Hashable { let exportURL = videoTempPath.appendingPathComponent(UUID().uuidString).appendingPathExtension("mp4") exportSession.outputURL = exportURL - let publisher = Future { resolver in - exportSession.exportAsynchronously { - let baseFilename = dataSource.sourceFilename - let mp4Filename = baseFilename?.filenameWithoutExtension.appendingFileExtension("mp4") - - guard let dataSource = DataSourcePath.dataSource(with: exportURL, - shouldDeleteOnDeallocation: true) else { - let attachment = SignalAttachment(dataSource: DataSourceValue.emptyDataSource(), dataUTI: dataUTI) - attachment.error = .couldNotConvertToMpeg4 + let publisher = Deferred { + Future { resolver in + exportSession.exportAsynchronously { + let baseFilename = dataSource.sourceFilename + let mp4Filename = baseFilename?.filenameWithoutExtension.appendingFileExtension("mp4") + + guard let dataSource = DataSourcePath.dataSource(with: exportURL, + shouldDeleteOnDeallocation: true) else { + let attachment = SignalAttachment(dataSource: DataSourceValue.emptyDataSource(), dataUTI: dataUTI) + attachment.error = .couldNotConvertToMpeg4 + resolver(Result.success(attachment)) + return + } + + dataSource.sourceFilename = mp4Filename + + let attachment = SignalAttachment(dataSource: dataSource, dataUTI: kUTTypeMPEG4 as String) resolver(Result.success(attachment)) - return } - - dataSource.sourceFilename = mp4Filename - - let attachment = SignalAttachment(dataSource: dataSource, dataUTI: kUTTypeMPEG4 as String) - resolver(Result.success(attachment)) } } .eraseToAnyPublisher() diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift index 3b11e0003..1f4e1c678 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift @@ -70,7 +70,7 @@ extension MessageReceiver { // Create the group let groupAlreadyExisted: Bool = ((try? SessionThread.exists(db, id: groupPublicKey)) ?? false) let thread: SessionThread = try SessionThread - .fetchOrCreate(db, id: groupPublicKey, variant: .legacyClosedGroup) + .fetchOrCreate(db, id: groupPublicKey, variant: .legacyGroup) .with(shouldBeVisible: true) .saved(db) let closedGroup: ClosedGroup = try ClosedGroup( diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift index 467a41930..6e733d227 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift @@ -168,7 +168,7 @@ extension MessageReceiver { // past two weeks) if isInitialSync { let existingClosedGroupsIds: [String] = (try? SessionThread - .filter(SessionThread.Columns.variant == SessionThread.Variant.legacyClosedGroup) + .filter(SessionThread.Columns.variant == SessionThread.Variant.legacyGroup) .fetchAll(db)) .defaulting(to: []) .map { $0.id } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift index c69eed681..d0f9e531f 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift @@ -393,7 +393,7 @@ extension MessageReceiver { ).save(db) } - case .legacyClosedGroup, .closedGroup: + case .legacyGroup, .group: try GroupMember .filter(GroupMember.Columns.groupId == thread.id) .fetchAll(db) @@ -405,7 +405,7 @@ extension MessageReceiver { ).save(db) } - case .openGroup: + case .community: try RecipientState( interactionId: interactionId, recipientId: thread.id, // For open groups this will always be the thread id diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift index a8b71888b..e94804030 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift @@ -40,7 +40,7 @@ extension MessageSender { do { // Create the relevant objects in the database thread = try SessionThread - .fetchOrCreate(db, id: groupPublicKey, variant: .legacyClosedGroup) + .fetchOrCreate(db, id: groupPublicKey, variant: .legacyGroup) try ClosedGroup( threadId: groupPublicKey, name: name, diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index d1da2bc22..dfde99552 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -284,14 +284,14 @@ public enum MessageReceiver { // Note: We don't want to create a thread for an open group if it doesn't exist if (try? SessionThread.exists(db, id: openGroupId)) != true { return nil } - return (openGroupId, .openGroup) + return (openGroupId, .community) } if let groupPublicKey: String = message.groupPublicKey { // Note: We don't want to create a thread for a closed group if it doesn't exist if (try? SessionThread.exists(db, id: groupPublicKey)) != true { return nil } - return (groupPublicKey, .legacyClosedGroup) + return (groupPublicKey, .legacyGroup) } // Extract the 'syncTarget' value if there is one diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift b/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift index b3db30c7e..0502a5d89 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift @@ -68,6 +68,7 @@ extension MessageSender { } public static func performUploadsIfNeeded( + queue: DispatchQueue, preparedSendData: PreparedSendData ) -> AnyPublisher { // We need an interactionId in order for a message to have uploads @@ -95,7 +96,7 @@ extension MessageSender { }() return Storage.shared - .readPublisherFlatMap { db -> AnyPublisher<(attachments: [Attachment], openGroup: OpenGroup?), Error> in + .readPublisherFlatMap(receiveOn: queue) { db -> AnyPublisher<(attachments: [Attachment], openGroup: OpenGroup?), Error> in let attachmentStateInfo: [Attachment.StateInfo] = (try? Attachment .stateInfo(interactionId: interactionId, state: .uploading) .fetchAll(db)) diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index 754ecb2e6..7c03c95f3 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -236,8 +236,9 @@ public final class MessageSender { return PreparedSendData() } - // Attach the user's profile if needed - if var messageWithProfile: MessageWithProfile = message as? MessageWithProfile { + // Attach the user's profile if needed (no need to do so for 'Note to Self' or sync messages as they + // will be managed by the user config handling + if !isSelfSend, !isSyncMessage, var messageWithProfile: MessageWithProfile = message as? MessageWithProfile { let profile: Profile = Profile.fetchOrCreateCurrentUser(db) if let profileKey: Data = profile.profileEncryptionKey, let profilePictureUrl: String = profile.profilePictureUrl { @@ -597,8 +598,8 @@ public final class MessageSender { // uploading first, this is here to ensure we don't send a message which should have uploaded // files // - // If you see this error then you need to call `MessageSender.performUploadsIfNeeded(preparedSendData:)` - // before calling this function + // If you see this error then you need to call + // `MessageSender.performUploadsIfNeeded(queue:preparedSendData:)` before calling this function switch preparedSendData.message { case let visibleMessage as VisibleMessage: guard visibleMessage.attachmentIds.count == preparedSendData.totalAttachmentsUploaded else { @@ -674,7 +675,7 @@ public final class MessageSender { }() return dependencies.storage - .writePublisher { db -> Void in + .writePublisher(receiveOn: DispatchQueue.global(qos: .default)) { db -> Void in try MessageSender.handleSuccessfulMessageSend( db, message: updatedMessage, @@ -701,20 +702,22 @@ public final class MessageSender { .eraseToAnyPublisher() } - return Future { resolver in - NotifyPushServerJob.run( - job, - queue: DispatchQueue.global(qos: .default), - success: { _, _ in resolver(Result.success(true)) }, - failure: { _, _, _ in - // Always fulfill because the notify PN server job isn't critical. - resolver(Result.success(true)) - }, - deferred: { _ in - // Always fulfill because the notify PN server job isn't critical. - resolver(Result.success(true)) - } - ) + return Deferred { + Future { resolver in + NotifyPushServerJob.run( + job, + queue: DispatchQueue.global(qos: .default), + success: { _, _ in resolver(Result.success(true)) }, + failure: { _, _, _ in + // Always fulfill because the notify PN server job isn't critical. + resolver(Result.success(true)) + }, + deferred: { _ in + // Always fulfill because the notify PN server job isn't critical. + resolver(Result.success(true)) + } + ) + } } .eraseToAnyPublisher() } @@ -762,7 +765,7 @@ public final class MessageSender { // Send the result return dependencies.storage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.global(qos: .default)) { db in OpenGroupAPI .send( db, @@ -781,7 +784,7 @@ public final class MessageSender { let updatedMessage: Message = message updatedMessage.openGroupServerMessageId = UInt64(responseData.id) - return dependencies.storage.writePublisher { db in + return dependencies.storage.writePublisher(receiveOn: DispatchQueue.global(qos: .default)) { db in // The `posted` value is in seconds but we sent it in ms so need that for de-duping try MessageSender.handleSuccessfulMessageSend( db, @@ -831,7 +834,7 @@ public final class MessageSender { // Send the result return dependencies.storage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.global(qos: .default)) { db in return OpenGroupAPI .send( db, @@ -846,7 +849,7 @@ public final class MessageSender { let updatedMessage: Message = message updatedMessage.openGroupServerMessageId = UInt64(responseData.id) - return dependencies.storage.writePublisher { db in + return dependencies.storage.writePublisher(receiveOn: DispatchQueue.global(qos: .default)) { db in // The `posted` value is in seconds but we sent it in ms so need that for de-duping try MessageSender.handleSuccessfulMessageSend( db, diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift index 735b96503..4ad5a9396 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift +++ b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift @@ -59,7 +59,7 @@ public enum PushNotificationAPI { // Unsubscribe from all closed groups (including ones the user is no longer a member of, // just in case) Storage.shared - .readPublisher { db -> (String, Set) in + .readPublisher(receiveOn: DispatchQueue.global(qos: .background)) { db -> (String, Set) in ( getUserHexEncodedPublicKey(db), try ClosedGroup diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift index 1878f6a60..c84258b4a 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift @@ -82,15 +82,12 @@ public final class ClosedGroupPoller: Poller { for publicKey: String ) -> AnyPublisher { return SnodeAPI.getSwarm(for: publicKey) - .flatMap { swarm -> AnyPublisher in + .tryMap { swarm -> Snode in guard let snode: Snode = swarm.randomElement() else { - return Fail(error: OnionRequestAPIError.insufficientSnodes) - .eraseToAnyPublisher() + throw OnionRequestAPIError.insufficientSnodes } - return Just(snode) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() + return snode } .eraseToAnyPublisher() } diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift index d420e709f..e4b07e678 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift @@ -9,7 +9,7 @@ import SessionUtilitiesKit public final class CurrentUserPoller: Poller { public static var namespaces: [SnodeAPI.Namespace] = [ - .default, .configUserProfile, .configContacts, .configConvoInfoVolatile, .configGroups + .default, .configUserProfile, .configContacts, .configConvoInfoVolatile, .configUserGroups ] private var targetSnode: Atomic = Atomic(nil) @@ -89,11 +89,8 @@ public final class CurrentUserPoller: Poller { // If we haven't retrieved a target snode at this point then either the cache // is empty or we have used all of the snodes and need to start from scratch return SnodeAPI.getSwarm(for: publicKey) - .flatMap { [weak self] _ -> AnyPublisher in - guard let strongSelf = self else { - return Fail(error: SnodeAPIError.generic) - .eraseToAnyPublisher() - } + .tryFlatMap { [weak self] _ -> AnyPublisher in + guard let strongSelf = self else { throw SnodeAPIError.generic } self?.targetSnode.mutate { $0 = nil } self?.usedSnodes.mutate { $0.removeAll() } diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift index 557642d1d..cd02aa5df 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift @@ -91,7 +91,7 @@ extension OpenGroupAPI { let server: String = self.server return dependencies.storage - .readPublisherFlatMap { db -> AnyPublisher<(Int64, PollResponse), Error> in + .readPublisherFlatMap(receiveOn: Threading.pollerQueue) { db -> AnyPublisher<(Int64, PollResponse), Error> in let failureCount: Int64 = (try? OpenGroup .select(max(OpenGroup.Columns.pollFailureCount)) .asRequest(of: Int64.self) @@ -225,7 +225,7 @@ extension OpenGroupAPI { } return dependencies.storage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: OpenGroupAPI.workQueue) { db in OpenGroupAPI.capabilities( db, server: server, diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift index 5bca3274e..230218237 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift @@ -200,9 +200,16 @@ public class Poller { poller?.pollerName(for: publicKey) ?? "poller with public key \(publicKey)" ) + let configHashes: [String] = SessionUtil.configHashes(for: publicKey) // Fetch the messages - return SnodeAPI.getMessages(in: namespaces, from: snode, associatedWith: publicKey) + return SnodeAPI + .poll( + namespaces: namespaces, + refreshingConfigHashes: configHashes, + from: snode, + associatedWith: publicKey + ) .flatMap { namespacedResults -> AnyPublisher in guard (calledFromBackgroundPoller && isBackgroundPollValid()) || @@ -322,15 +329,17 @@ public class Poller { return Publishers .MergeMany( jobsToRun.map { job -> AnyPublisher in - Future { resolver in - // Note: In the background we just want jobs to fail silently - MessageReceiveJob.run( - job, - queue: queue, - success: { _, _ in resolver(Result.success(())) }, - failure: { _, _, _ in resolver(Result.success(())) }, - deferred: { _ in resolver(Result.success(())) } - ) + Deferred { + Future { resolver in + // Note: In the background we just want jobs to fail silently + MessageReceiveJob.run( + job, + queue: queue, + success: { _, _ in resolver(Result.success(())) }, + failure: { _, _, _ in resolver(Result.success(())) }, + deferred: { _ in resolver(Result.success(())) } + ) + } } .eraseToAnyPublisher() } diff --git a/SessionMessagingKit/Sending & Receiving/Typing Indicators/TypingIndicators.swift b/SessionMessagingKit/Sending & Receiving/Typing Indicators/TypingIndicators.swift index 97d5957cb..2d40d29db 100644 --- a/SessionMessagingKit/Sending & Receiving/Typing Indicators/TypingIndicators.swift +++ b/SessionMessagingKit/Sending & Receiving/Typing Indicators/TypingIndicators.swift @@ -39,9 +39,9 @@ public class TypingIndicators { // Don't send typing indicators in group threads guard - threadVariant != .legacyClosedGroup && - threadVariant != .closedGroup && - threadVariant != .openGroup + threadVariant != .legacyGroup && + threadVariant != .group && + threadVariant != .community else { return nil } self.threadId = threadId diff --git a/SessionMessagingKit/Shared Models/MentionInfo.swift b/SessionMessagingKit/Shared Models/MentionInfo.swift index d603f17a2..bbf555030 100644 --- a/SessionMessagingKit/Shared Models/MentionInfo.swift +++ b/SessionMessagingKit/Shared Models/MentionInfo.swift @@ -34,7 +34,7 @@ public extension MentionInfo { let profileFullTextSearch: SQL = SQL(stringLiteral: Profile.fullTextSearchTableName) /// **Note:** The `\(MentionInfo.profileKey).*` value **MUST** be first - let limitSQL: SQL? = (threadVariant == .openGroup ? SQL("LIMIT 20") : nil) + let limitSQL: SQL? = (threadVariant == .community ? SQL("LIMIT 20") : nil) let request: SQLRequest = { guard let pattern: FTS5Pattern = pattern else { @@ -57,7 +57,7 @@ public extension MentionInfo { WHERE ( \(SQL("\(profile[.id]) != \(userPublicKey)")) AND ( - \(SQL("\(threadVariant) != \(SessionThread.Variant.openGroup)")) OR + \(SQL("\(threadVariant) != \(SessionThread.Variant.community)")) OR \(SQL("\(profile[.id]) LIKE '\(prefixLiteral)'")) ) ) @@ -83,7 +83,7 @@ public extension MentionInfo { JOIN \(Profile.self) ON ( \(Profile.self).rowid = \(profileFullTextSearch).rowid AND \(SQL("\(profile[.id]) != \(userPublicKey)")) AND ( - \(SQL("\(threadVariant) != \(SessionThread.Variant.openGroup)")) OR + \(SQL("\(threadVariant) != \(SessionThread.Variant.community)")) OR \(SQL("\(profile[.id]) LIKE '\(prefixLiteral)'")) ) ) diff --git a/SessionMessagingKit/Shared Models/MessageViewModel.swift b/SessionMessagingKit/Shared Models/MessageViewModel.swift index 129553a42..610a8052b 100644 --- a/SessionMessagingKit/Shared Models/MessageViewModel.swift +++ b/SessionMessagingKit/Shared Models/MessageViewModel.swift @@ -304,9 +304,9 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable, } }() let isGroupThread: Bool = ( - self.threadVariant == .openGroup || - self.threadVariant == .legacyClosedGroup || - self.threadVariant == .closedGroup + self.threadVariant == .community || + self.threadVariant == .legacyGroup || + self.threadVariant == .group ) return ViewModel( @@ -741,13 +741,13 @@ public extension MessageViewModel { \(interaction[.id]) = \(readReceiptTableLiteral).\(interactionStateInteractionIdColumnLiteral) ) LEFT JOIN \(GroupMember.self) AS \(groupMemberModeratorTableLiteral) ON ( - \(SQL("\(thread[.variant]) = \(SessionThread.Variant.openGroup)")) AND + \(SQL("\(thread[.variant]) = \(SessionThread.Variant.community)")) AND \(groupMemberModeratorTableLiteral).\(groupMemberGroupIdColumnLiteral) = \(interaction[.threadId]) AND \(groupMemberModeratorTableLiteral).\(groupMemberProfileIdColumnLiteral) = \(interaction[.authorId]) AND \(SQL("\(groupMemberModeratorTableLiteral).\(groupMemberRoleColumnLiteral) = \(GroupMember.Role.moderator)")) ) LEFT JOIN \(GroupMember.self) AS \(groupMemberAdminTableLiteral) ON ( - \(SQL("\(thread[.variant]) = \(SessionThread.Variant.openGroup)")) AND + \(SQL("\(thread[.variant]) = \(SessionThread.Variant.community)")) AND \(groupMemberAdminTableLiteral).\(groupMemberGroupIdColumnLiteral) = \(interaction[.threadId]) AND \(groupMemberAdminTableLiteral).\(groupMemberProfileIdColumnLiteral) = \(interaction[.authorId]) AND \(SQL("\(groupMemberAdminTableLiteral).\(groupMemberRoleColumnLiteral) = \(GroupMember.Role.admin)")) diff --git a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift index 4c5610e2a..be9c267d9 100644 --- a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift +++ b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift @@ -103,8 +103,8 @@ public struct SessionThreadViewModel: FetchableRecordWithRowId, Decodable, Equat public var canWrite: Bool { switch threadVariant { case .contact: return true - case .legacyClosedGroup, .closedGroup: return currentUserIsClosedGroupMember == true - case .openGroup: return openGroupPermissions?.contains(.write) ?? false + case .legacyGroup, .group: return currentUserIsClosedGroupMember == true + case .community: return openGroupPermissions?.contains(.write) ?? false } } @@ -161,15 +161,15 @@ public struct SessionThreadViewModel: FetchableRecordWithRowId, Decodable, Equat public var profile: Profile? { switch threadVariant { case .contact: return contactProfile - case .legacyClosedGroup, .closedGroup: + case .legacyGroup, .group: return (closedGroupProfileBack ?? closedGroupProfileBackFallback) - case .openGroup: return nil + case .community: return nil } } public var additionalProfile: Profile? { switch threadVariant { - case .legacyClosedGroup, .closedGroup: return closedGroupProfileFront + case .legacyGroup, .group: return closedGroupProfileFront default: return nil } } @@ -194,8 +194,8 @@ public struct SessionThreadViewModel: FetchableRecordWithRowId, Decodable, Equat public var userCount: Int? { switch threadVariant { case .contact: return nil - case .legacyClosedGroup, .closedGroup: return closedGroupUserCount - case .openGroup: return openGroupUserCount + case .legacyGroup, .group: return closedGroupUserCount + case .community: return openGroupUserCount } } @@ -1355,8 +1355,8 @@ public extension SessionThreadViewModel { LEFT JOIN \(OpenGroup.self) ON false WHERE ( - \(SQL("\(thread[.variant]) = \(SessionThread.Variant.legacyClosedGroup)")) OR - \(SQL("\(thread[.variant]) = \(SessionThread.Variant.closedGroup)")) + \(SQL("\(thread[.variant]) = \(SessionThread.Variant.legacyGroup)")) OR + \(SQL("\(thread[.variant]) = \(SessionThread.Variant.group)")) ) GROUP BY \(thread[.id]) """ @@ -1435,7 +1435,7 @@ public extension SessionThreadViewModel { ) AS \(groupMemberInfoLiteral) ON false WHERE - \(SQL("\(thread[.variant]) = \(SessionThread.Variant.openGroup)")) AND + \(SQL("\(thread[.variant]) = \(SessionThread.Variant.community)")) AND \(SQL("\(thread[.id]) != \(userPublicKey)")) GROUP BY \(thread[.id]) """ diff --git a/SessionMessagingKit/Utilities/ProfileManager.swift b/SessionMessagingKit/Utilities/ProfileManager.swift index 9d13ae126..def1a651a 100644 --- a/SessionMessagingKit/Utilities/ProfileManager.swift +++ b/SessionMessagingKit/Utilities/ProfileManager.swift @@ -34,7 +34,7 @@ public struct ProfileManager { // Before encrypting and submitting we NULL pad the name data to this length. private static let nameDataLength: UInt = 64 public static let maxAvatarDiameter: CGFloat = 640 - internal static let avatarAES256KeyByteLength: Int = 32 + public static let avatarAES256KeyByteLength: Int = 32 private static let avatarNonceLength: Int = 12 private static let avatarTagLength: Int = 16 diff --git a/SessionMessagingKitTests/LibSessionUtil/ConfigContactsSpec.swift b/SessionMessagingKitTests/LibSessionUtil/ConfigContactsSpec.swift index 9012b113d..e9aeb70f5 100644 --- a/SessionMessagingKitTests/LibSessionUtil/ConfigContactsSpec.swift +++ b/SessionMessagingKitTests/LibSessionUtil/ConfigContactsSpec.swift @@ -32,25 +32,22 @@ class ConfigContactsSpec: QuickSpec { error?.deallocate() // Empty contacts shouldn't have an existing contact - var definitelyRealId: [CChar] = "050000000000000000000000000000000000000000000000000000000000000000" - .bytes - .map { CChar(bitPattern: $0) } + var definitelyRealId: String = "050000000000000000000000000000000000000000000000000000000000000000" + var cDefinitelyRealId: [CChar] = definitelyRealId.cArray let contactPtr: UnsafeMutablePointer? = nil - expect(contacts_get(conf, contactPtr, &definitelyRealId)).to(beFalse()) + expect(contacts_get(conf, contactPtr, &cDefinitelyRealId)).to(beFalse()) expect(contacts_size(conf)).to(equal(0)) var contact2: contacts_contact = contacts_contact() - expect(contacts_get_or_construct(conf, &contact2, &definitelyRealId)).to(beTrue()) - expect(contact2.name).to(beNil()) - expect(contact2.nickname).to(beNil()) + expect(contacts_get_or_construct(conf, &contact2, &cDefinitelyRealId)).to(beTrue()) + expect(String(libSessionVal: contact2.name)).to(beEmpty()) + expect(String(libSessionVal: contact2.nickname)).to(beEmpty()) expect(contact2.approved).to(beFalse()) expect(contact2.approved_me).to(beFalse()) expect(contact2.blocked).to(beFalse()) expect(contact2.profile_pic).toNot(beNil()) // Creates an empty instance apparently - expect(contact2.profile_pic.url).to(beNil()) - expect(contact2.profile_pic.key).to(beNil()) - expect(contact2.profile_pic.keylen).to(equal(0)) + expect(String(libSessionVal: contact2.profile_pic.url)).to(beEmpty()) // We don't need to push anything, since this is a default contact expect(config_needs_push(conf)).to(beFalse()) @@ -68,14 +65,8 @@ class ConfigContactsSpec: QuickSpec { toPush?.deallocate() // Update the contact data - let contact2Name: [CChar] = "Joe" - .bytes - .map { CChar(bitPattern: $0) } - let contact2Nickname: [CChar] = "Joey" - .bytes - .map { CChar(bitPattern: $0) } - contact2Name.withUnsafeBufferPointer { contact2.name = $0.baseAddress } - contact2Nickname.withUnsafeBufferPointer { contact2.nickname = $0.baseAddress } + contact2.name = "Joe".toLibSession() + contact2.nickname = "Joey".toLibSession() contact2.approved = true contact2.approved_me = true @@ -85,19 +76,14 @@ class ConfigContactsSpec: QuickSpec { // Ensure the contact details were updated var contact3: contacts_contact = contacts_contact() expect(contacts_get(conf, &contact3, &definitelyRealId)).to(beTrue()) - expect(String(cString: contact3.name)).to(equal("Joe")) - expect(String(cString: contact3.nickname)).to(equal("Joey")) + expect(String(libSessionVal: contact3.name)).to(equal("Joe")) + expect(String(libSessionVal: contact3.nickname)).to(equal("Joey")) expect(contact3.approved).to(beTrue()) expect(contact3.approved_me).to(beTrue()) expect(contact3.profile_pic).toNot(beNil()) // Creates an empty instance apparently - expect(contact3.profile_pic.url).to(beNil()) - expect(contact3.profile_pic.key).to(beNil()) - expect(contact3.profile_pic.keylen).to(equal(0)) + expect(String(libSessionVal: contact3.profile_pic.url)).to(beEmpty()) expect(contact3.blocked).to(beFalse()) - - let contact3SessionId: [CChar] = withUnsafeBytes(of: contact3.session_id) { [UInt8]($0) } - .map { CChar($0) } - expect(contact3SessionId).to(equal(definitelyRealId.nullTerminated())) + expect(String(libSessionVal: contact3.session_id)).to(equal(definitelyRealId)) // Since we've made changes, we should need to push new config to the swarm, *and* should need // to dump the updated state: @@ -144,29 +130,24 @@ class ConfigContactsSpec: QuickSpec { // Ensure the contact details were updated var contact4: contacts_contact = contacts_contact() expect(contacts_get(conf2, &contact4, &definitelyRealId)).to(beTrue()) - expect(String(cString: contact4.name)).to(equal("Joe")) - expect(String(cString: contact4.nickname)).to(equal("Joey")) + expect(String(libSessionVal: contact4.name)).to(equal("Joe")) + expect(String(libSessionVal: contact4.nickname)).to(equal("Joey")) expect(contact4.approved).to(beTrue()) expect(contact4.approved_me).to(beTrue()) expect(contact4.profile_pic).toNot(beNil()) // Creates an empty instance apparently - expect(contact4.profile_pic.url).to(beNil()) - expect(contact4.profile_pic.key).to(beNil()) - expect(contact4.profile_pic.keylen).to(equal(0)) + expect(String(libSessionVal: contact4.profile_pic.url)).to(beEmpty()) expect(contact4.blocked).to(beFalse()) - var anotherId: [CChar] = "051111111111111111111111111111111111111111111111111111111111111111" - .bytes - .map { CChar(bitPattern: $0) } + var anotherId: String = "051111111111111111111111111111111111111111111111111111111111111111" + var cAnotherId: [CChar] = anotherId.cArray var contact5: contacts_contact = contacts_contact() - expect(contacts_get_or_construct(conf2, &contact5, &anotherId)).to(beTrue()) - expect(contact5.name).to(beNil()) - expect(contact5.nickname).to(beNil()) + expect(contacts_get_or_construct(conf2, &contact5, &cAnotherId)).to(beTrue()) + expect(String(libSessionVal: contact5.name)).to(beEmpty()) + expect(String(libSessionVal: contact5.nickname)).to(beEmpty()) expect(contact5.approved).to(beFalse()) expect(contact5.approved_me).to(beFalse()) expect(contact5.profile_pic).toNot(beNil()) // Creates an empty instance apparently - expect(contact5.profile_pic.url).to(beNil()) - expect(contact5.profile_pic.key).to(beNil()) - expect(contact5.profile_pic.keylen).to(equal(0)) + expect(String(libSessionVal: contact5.profile_pic.url)).to(beEmpty()) expect(contact5.blocked).to(beFalse()) // We're not setting any fields, but we should still keep a record of the session id @@ -201,24 +182,16 @@ class ConfigContactsSpec: QuickSpec { var contact6: contacts_contact = contacts_contact() let contactIterator: UnsafeMutablePointer = contacts_iterator_new(conf) while !contacts_iterator_done(contactIterator, &contact6) { - sessionIds.append( - String(cString: withUnsafeBytes(of: contact6.session_id) { [UInt8]($0) } - .map { CChar($0) } - .nullTerminated() - ) - ) - nicknames.append( - contact6.nickname.map { String(cString: $0) } ?? - "(N/A)" - ) + sessionIds.append(String(libSessionVal: contact6.session_id) ?? "(N/A)") + nicknames.append(String(libSessionVal: contact6.nickname, nullIfEmpty: true) ?? "(N/A)") contacts_iterator_advance(contactIterator) } contacts_iterator_free(contactIterator) // Need to free the iterator expect(sessionIds.count).to(equal(2)) expect(sessionIds.count).to(equal(contacts_size(conf))) - expect(sessionIds.first).to(equal(String(cString: definitelyRealId.nullTerminated()))) - expect(sessionIds.last).to(equal(String(cString: anotherId.nullTerminated()))) + expect(sessionIds.first).to(equal(definitelyRealId)) + expect(sessionIds.last).to(equal(anotherId)) expect(nicknames.first).to(equal("Joey")) expect(nicknames.last).to(equal("(N/A)")) @@ -228,24 +201,15 @@ class ConfigContactsSpec: QuickSpec { contacts_erase(conf, definitelyRealId) // Client 2 adds a new friend: - var thirdId: [CChar] = "052222222222222222222222222222222222222222222222222222222222222222" - .bytes - .map { CChar(bitPattern: $0) } - let nickname7: [CChar] = "Nickname 3" - .bytes - .map { CChar(bitPattern: $0) } - let profileUrl7: [CChar] = "http://example.com/huge.bmp" - .bytes - .map { CChar(bitPattern: $0) } - let profileKey7: [UInt8] = "qwerty".bytes + var thirdId: String = "052222222222222222222222222222222222222222222222222222222222222222" + var cThirdId: [CChar] = thirdId.cArray var contact7: contacts_contact = contacts_contact() - expect(contacts_get_or_construct(conf2, &contact7, &thirdId)).to(beTrue()) - nickname7.withUnsafeBufferPointer { contact7.nickname = $0.baseAddress } + expect(contacts_get_or_construct(conf2, &contact7, &cThirdId)).to(beTrue()) + contact7.nickname = "Nickname 3".toLibSession() contact7.approved = true contact7.approved_me = true - profileUrl7.withUnsafeBufferPointer { contact7.profile_pic.url = $0.baseAddress } - profileKey7.withUnsafeBufferPointer { contact7.profile_pic.key = $0.baseAddress } - contact7.profile_pic.keylen = 6 + contact7.profile_pic.url = "http://example.com/huge.bmp".toLibSession() + contact7.profile_pic.key = "qwerty78901234567890123456789012".data(using: .utf8)!.toLibSession() contacts_set(conf2, &contact7) expect(config_needs_push(conf)).to(beTrue()) @@ -308,23 +272,15 @@ class ConfigContactsSpec: QuickSpec { var contact8: contacts_contact = contacts_contact() let contactIterator2: UnsafeMutablePointer = contacts_iterator_new(conf) while !contacts_iterator_done(contactIterator2, &contact8) { - sessionIds2.append( - String(cString: withUnsafeBytes(of: contact8.session_id) { [UInt8]($0) } - .map { CChar($0) } - .nullTerminated() - ) - ) - nicknames2.append( - contact8.nickname.map { String(cString: $0) } ?? - "(N/A)" - ) + sessionIds2.append(String(libSessionVal: contact8.session_id) ?? "(N/A)") + nicknames2.append(String(libSessionVal: contact8.nickname, nullIfEmpty: true) ?? "(N/A)") contacts_iterator_advance(contactIterator2) } contacts_iterator_free(contactIterator2) // Need to free the iterator expect(sessionIds2.count).to(equal(2)) - expect(sessionIds2.first).to(equal(String(cString: anotherId.nullTerminated()))) - expect(sessionIds2.last).to(equal(String(cString: thirdId.nullTerminated()))) + expect(sessionIds2.first).to(equal(anotherId)) + expect(sessionIds2.last).to(equal(thirdId)) expect(nicknames2.first).to(equal("(N/A)")) expect(nicknames2.last).to(equal("Nickname 3")) } diff --git a/SessionMessagingKitTests/LibSessionUtil/ConfigConvoInfoVolatileSpec.swift b/SessionMessagingKitTests/LibSessionUtil/ConfigConvoInfoVolatileSpec.swift index da437701e..d8f706179 100644 --- a/SessionMessagingKitTests/LibSessionUtil/ConfigConvoInfoVolatileSpec.swift +++ b/SessionMessagingKitTests/LibSessionUtil/ConfigConvoInfoVolatileSpec.swift @@ -60,9 +60,9 @@ class ConfigConvoInfoVolatileSpec: QuickSpec { // The new data doesn't get stored until we call this: convo_info_volatile_set_1to1(conf, &oneToOne2) - var legacyClosed1: convo_info_volatile_legacy_closed = convo_info_volatile_legacy_closed() + var legacyGroup1: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() var oneToOne3: convo_info_volatile_1to1 = convo_info_volatile_1to1() - expect(convo_info_volatile_get_legacy_closed(conf, &legacyClosed1, &definitelyRealId)) + expect(convo_info_volatile_get_legacy_group(conf, &legacyGroup1, &definitelyRealId)) .to(beFalse()) expect(convo_info_volatile_get_1to1(conf, &oneToOne3, &definitelyRealId)).to(beTrue()) expect(oneToOne3.last_read).to(equal(nowTimestampMs)) @@ -70,40 +70,34 @@ class ConfigConvoInfoVolatileSpec: QuickSpec { expect(config_needs_push(conf)).to(beTrue()) expect(config_needs_dump(conf)).to(beTrue()) - var openGroupBaseUrl: [CChar] = "http://Example.ORG:5678" - .bytes - .map { CChar(bitPattern: $0) } + var openGroupBaseUrl: [CChar] = "http://Example.ORG:5678".cArray let openGroupBaseUrlResult: [CChar] = ("http://Example.ORG:5678" .lowercased() - .bytes - .map { CChar(bitPattern: $0) } + + .cArray + [CChar](repeating: 0, count: (268 - openGroupBaseUrl.count)) ) - var openGroupRoom: [CChar] = "SudokuRoom" - .bytes - .map { CChar(bitPattern: $0) } + var openGroupRoom: [CChar] = "SudokuRoom".cArray let openGroupRoomResult: [CChar] = ("SudokuRoom" .lowercased() - .bytes - .map { CChar(bitPattern: $0) } + + .cArray + [CChar](repeating: 0, count: (65 - openGroupRoom.count)) ) var openGroupPubkey: [UInt8] = Data(hex: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") .bytes - var openGroup1: convo_info_volatile_open = convo_info_volatile_open() - expect(convo_info_volatile_get_or_construct_open(conf, &openGroup1, &openGroupBaseUrl, &openGroupRoom, &openGroupPubkey)).to(beTrue()) - expect(withUnsafeBytes(of: openGroup1.base_url) { [UInt8]($0) } + var community1: convo_info_volatile_community = convo_info_volatile_community() + expect(convo_info_volatile_get_or_construct_community(conf, &community1, &openGroupBaseUrl, &openGroupRoom, &openGroupPubkey)).to(beTrue()) + expect(withUnsafeBytes(of: community1.base_url) { [UInt8]($0) } .map { CChar($0) } ).to(equal(openGroupBaseUrlResult)) - expect(withUnsafeBytes(of: openGroup1.room) { [UInt8]($0) } + expect(withUnsafeBytes(of: community1.room) { [UInt8]($0) } .map { CChar($0) } ).to(equal(openGroupRoomResult)) - expect(withUnsafePointer(to: openGroup1.pubkey) { Data(bytes: $0, count: 32).toHexString() }) + expect(withUnsafePointer(to: community1.pubkey) { Data(bytes: $0, count: 32).toHexString() }) .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) - openGroup1.unread = true + community1.unread = true // The new data doesn't get stored until we call this: - convo_info_volatile_set_open(conf, &openGroup1); + convo_info_volatile_set_community(conf, &community1); var toPush: UnsafeMutablePointer? = nil var toPushLen: Int = 0 @@ -143,17 +137,17 @@ class ConfigConvoInfoVolatileSpec: QuickSpec { ).to(equal(definitelyRealId.nullTerminated())) expect(oneToOne4.unread).to(beFalse()) - var openGroup2: convo_info_volatile_open = convo_info_volatile_open() - expect(convo_info_volatile_get_open(conf2, &openGroup2, &openGroupBaseUrl, &openGroupRoom, &openGroupPubkey)).to(beTrue()) - expect(withUnsafeBytes(of: openGroup2.base_url) { [UInt8]($0) } + var community2: convo_info_volatile_community = convo_info_volatile_community() + expect(convo_info_volatile_get_community(conf2, &community2, &openGroupBaseUrl, &openGroupRoom)).to(beTrue()) + expect(withUnsafeBytes(of: community2.base_url) { [UInt8]($0) } .map { CChar($0) } ).to(equal(openGroupBaseUrlResult)) - expect(withUnsafeBytes(of: openGroup2.room) { [UInt8]($0) } + expect(withUnsafeBytes(of: community2.room) { [UInt8]($0) } .map { CChar($0) } ).to(equal(openGroupRoomResult)) - expect(withUnsafePointer(to: openGroup2.pubkey) { Data(bytes: $0, count: 32).toHexString() }) + expect(withUnsafePointer(to: community2.pubkey) { Data(bytes: $0, count: 32).toHexString() }) .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) - openGroup2.unread = true + community2.unread = true var anotherId: [CChar] = "051111111111111111111111111111111111111111111111111111111111111111" .bytes @@ -165,10 +159,10 @@ class ConfigConvoInfoVolatileSpec: QuickSpec { var thirdId: [CChar] = "05cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" .bytes .map { CChar(bitPattern: $0) } - var legacyClosed2: convo_info_volatile_legacy_closed = convo_info_volatile_legacy_closed() - expect(convo_info_volatile_get_or_construct_legacy_closed(conf2, &legacyClosed2, &thirdId)).to(beTrue()) - legacyClosed2.last_read = (nowTimestampMs - 50) - convo_info_volatile_set_legacy_closed(conf2, &legacyClosed2) + var legacyGroup2: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() + expect(convo_info_volatile_get_or_construct_legacy_group(conf2, &legacyGroup2, &thirdId)).to(beTrue()) + legacyGroup2.last_read = (nowTimestampMs - 50) + convo_info_volatile_set_legacy_group(conf2, &legacyGroup2) expect(config_needs_push(conf2)).to(beTrue()) var toPush2: UnsafeMutablePointer? = nil @@ -190,12 +184,12 @@ class ConfigConvoInfoVolatileSpec: QuickSpec { var seen: [String] = [] expect(convo_info_volatile_size(conf)).to(equal(4)) expect(convo_info_volatile_size_1to1(conf)).to(equal(2)) - expect(convo_info_volatile_size_open(conf)).to(equal(1)) - expect(convo_info_volatile_size_legacy_closed(conf)).to(equal(1)) + expect(convo_info_volatile_size_communities(conf)).to(equal(1)) + expect(convo_info_volatile_size_legacy_groups(conf)).to(equal(1)) var c1: convo_info_volatile_1to1 = convo_info_volatile_1to1() - var c2: convo_info_volatile_open = convo_info_volatile_open() - var c3: convo_info_volatile_legacy_closed = convo_info_volatile_legacy_closed() + var c2: convo_info_volatile_community = convo_info_volatile_community() + var c3: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() let it: OpaquePointer = convo_info_volatile_iterator_new(targetConf) while !convo_info_volatile_iterator_done(it) { @@ -206,7 +200,7 @@ class ConfigConvoInfoVolatileSpec: QuickSpec { ) seen.append("1-to-1: \(sessionId)") } - else if convo_info_volatile_it_is_open(it, &c2) { + else if convo_info_volatile_it_is_community(it, &c2) { let baseUrl: String = String(cString: withUnsafeBytes(of: c2.base_url) { [UInt8]($0) } .map { CChar($0) } .nullTerminated() @@ -218,7 +212,7 @@ class ConfigConvoInfoVolatileSpec: QuickSpec { seen.append("og: \(baseUrl)/r/\(room)") } - else if convo_info_volatile_it_is_legacy_closed(it, &c3) { + else if convo_info_volatile_it_is_legacy_group(it, &c3) { let groupId: String = String(cString: withUnsafeBytes(of: c3.group_id) { [UInt8]($0) } .map { CChar($0) } .nullTerminated() @@ -271,11 +265,11 @@ class ConfigConvoInfoVolatileSpec: QuickSpec { ])) var seen2: [String] = [] - var c2: convo_info_volatile_open = convo_info_volatile_open() - let it2: OpaquePointer = convo_info_volatile_iterator_new_open(conf) + var c2: convo_info_volatile_community = convo_info_volatile_community() + let it2: OpaquePointer = convo_info_volatile_iterator_new_communities(conf) while !convo_info_volatile_iterator_done(it2) { - expect(convo_info_volatile_it_is_open(it2, &c2)).to(beTrue()) + expect(convo_info_volatile_it_is_community(it2, &c2)).to(beTrue()) let baseUrl: String = String(cString: withUnsafeBytes(of: c2.base_url) { [UInt8]($0) } .map { CChar($0) } .nullTerminated() @@ -291,11 +285,11 @@ class ConfigConvoInfoVolatileSpec: QuickSpec { ])) var seen3: [String] = [] - var c3: convo_info_volatile_legacy_closed = convo_info_volatile_legacy_closed() - let it3: OpaquePointer = convo_info_volatile_iterator_new_legacy_closed(conf) + var c3: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() + let it3: OpaquePointer = convo_info_volatile_iterator_new_legacy_groups(conf) while !convo_info_volatile_iterator_done(it3) { - expect(convo_info_volatile_it_is_legacy_closed(it3, &c3)).to(beTrue()) + expect(convo_info_volatile_it_is_legacy_group(it3, &c3)).to(beTrue()) let groupId: String = String(cString: withUnsafeBytes(of: c3.group_id) { [UInt8]($0) } .map { CChar($0) } .nullTerminated() diff --git a/SessionMessagingKitTests/LibSessionUtil/ConfigUserProfileSpec.swift b/SessionMessagingKitTests/LibSessionUtil/ConfigUserProfileSpec.swift index ef9324420..b36b5cbba 100644 --- a/SessionMessagingKitTests/LibSessionUtil/ConfigUserProfileSpec.swift +++ b/SessionMessagingKitTests/LibSessionUtil/ConfigUserProfileSpec.swift @@ -4,6 +4,7 @@ import Foundation import Sodium import SessionUtil import SessionUtilitiesKit +import SessionMessagingKit import Quick import Nimble @@ -68,25 +69,14 @@ class ConfigUserProfileSpec: QuickSpec { // This should also be unset: let pic: user_profile_pic = user_profile_get_pic(conf) - expect(pic.url).to(beNil()) - expect(pic.key).to(beNil()) - expect(pic.keylen).to(equal(0)) + expect(String(libSessionVal: pic.url)).to(beEmpty()) // Now let's go set a profile name and picture: expect(user_profile_set_name(conf, "Kallie")).to(equal(0)) - let profileUrl: [CChar] = "http://example.org/omg-pic-123.bmp" - .bytes - .map { CChar(bitPattern: $0) } - let profileKey: [UInt8] = "secretNOTSECRET".bytes - let p: user_profile_pic = profileUrl.withUnsafeBufferPointer { profileUrlPtr in - profileKey.withUnsafeBufferPointer { profileKeyPtr in - user_profile_pic( - url: profileUrlPtr.baseAddress, - key: profileKeyPtr.baseAddress, - keylen: 6 - ) - } - } + let p: user_profile_pic = user_profile_pic( + url: "http://example.org/omg-pic-123.bmp".toLibSession(), + key: "secret78901234567890123456789012".data(using: .utf8)!.toLibSession() + ) expect(user_profile_set_pic(conf, p)).to(equal(0)) // Retrieve them just to make sure they set properly: @@ -95,11 +85,9 @@ class ConfigUserProfileSpec: QuickSpec { expect(String(cString: namePtr2!)).to(equal("Kallie")) let pic2: user_profile_pic = user_profile_get_pic(conf); - expect(pic2.url).toNot(beNil()) - expect(pic2.key).toNot(beNil()) - expect(pic2.keylen).to(equal(6)) - expect(String(cString: pic2.url!)).to(equal("http://example.org/omg-pic-123.bmp")) - expect(String(pointer: pic2.key, length: pic2.keylen)).to(equal("secret")) + expect(String(libSessionVal: pic2.url)).to(equal("http://example.org/omg-pic-123.bmp")) + expect(Data(libSessionVal: pic2.key, count: ProfileManager.avatarAES256KeyByteLength)) + .to(equal("secret78901234567890123456789012".data(using: .utf8))) // Since we've made changes, we should need to push new config to the swarm, *and* should need // to dump the updated state: @@ -125,7 +113,7 @@ class ConfigUserProfileSpec: QuickSpec { 1:& d 1:n 6:Kallie 1:p 34:http://example.org/omg-pic-123.bmp - 1:q 6:secret + 1:q 32:secret78901234567890123456789012 e 1:< l l i0e 32: @@ -146,12 +134,13 @@ class ConfigUserProfileSpec: QuickSpec { ] .flatMap { $0 } let expPush1Encrypted: [UInt8] = Data(hex: [ - "a2952190dcb9797bc48e48f6dc7b3254d004bde9091cfc9ec3433cbc5939a3726deb04f58a546d7d79e6f8", - "0ea185d43bf93278398556304998ae882304075c77f15c67f9914c4d10005a661f29ff7a79e0a9de7f2172", - "5ba3b5a6c19eaa3797671b8fa4008d62e9af2744629cbb46664c4d8048e2867f66ed9254120371bdb24e95", - "b2d92341fa3b1f695046113a768ceb7522269f937ead5591bfa8a5eeee3010474002f2db9de043f0f0d1cf", - "b1066a03e7b5d6cfb70a8f84a20cd2df5a510cd3d175708015a52dd4a105886d916db0005dbea5706e5a5d", - "c37ffd0a0ca2824b524da2e2ad181a48bb38e21ed9abe136014a4ee1e472cb2f53102db2a46afa9d68" + "877c8e0f5d33f5fffa5a4e162785a9a89918e95de1c4b925201f1f5c29d9ee4f8c36e2b278fce1e6", + "b9d999689dd86ff8e79e0a04004fa54d24da89bc2604cb1df8c1356da8f14710543ecec44f2d57fc", + "56ea8b7e73d119c69d755f4d513d5d069f02396b8ec0cbed894169836f57ca4b782ce705895c593b", + "4230d50c175d44a08045388d3f4160bacb617b9ae8de3ebc8d9024245cd09ce102627cab2acf1b91", + "26159211359606611ca5814de320d1a7099a65c99b0eebbefb92a115f5efa6b9132809300ac010c6", + "857cfbd62af71b0fa97eccec75cb95e67edf40b35fdb9cad125a6976693ab085c6bba96a2e51826e", + "81e16b9ec1232af5680f2ced55310486" ].joined()).bytes expect(String(pointer: toPush2, length: toPush2Len, encoding: .ascii)) @@ -259,19 +248,10 @@ class ConfigUserProfileSpec: QuickSpec { user_profile_set_name(conf2, "Raz") // And, on conf2, we're also going to change the profile pic: - let profile2Url: [CChar] = "http://new.example.com/pic" - .bytes - .map { CChar(bitPattern: $0) } - let profile2Key: [UInt8] = "qwert\0yuio".bytes - let p2: user_profile_pic = profile2Url.withUnsafeBufferPointer { profile2UrlPtr in - profile2Key.withUnsafeBufferPointer { profile2KeyPtr in - user_profile_pic( - url: profile2UrlPtr.baseAddress, - key: profile2KeyPtr.baseAddress, - keylen: 10 - ) - } - } + let p2: user_profile_pic = user_profile_pic( + url: "http://new.example.com/pic".toLibSession(), + key: "qwert\0yuio1234567890123456789012".data(using: .utf8)!.toLibSession() + ) user_profile_set_pic(conf2, p2) // Both have changes, so push need a push @@ -336,14 +316,16 @@ class ConfigUserProfileSpec: QuickSpec { // Since only one of them set a profile pic there should be no conflict there: let pic3: user_profile_pic = user_profile_get_pic(conf) expect(pic3.url).toNot(beNil()) - expect(String(cString: pic3.url!)).to(equal("http://new.example.com/pic")) + expect(String(libSessionVal: pic3.url)).to(equal("http://new.example.com/pic")) expect(pic3.key).toNot(beNil()) - expect(String(pointer: pic3.key, length: pic3.keylen)).to(equal("qwert\0yuio")) + expect(Data(libSessionVal: pic3.key, count: 32).toHexString()) + .to(equal("7177657274007975696f31323334353637383930313233343536373839303132")) let pic4: user_profile_pic = user_profile_get_pic(conf2) expect(pic4.url).toNot(beNil()) - expect(String(cString: pic4.url!)).to(equal("http://new.example.com/pic")) + expect(String(libSessionVal: pic4.url)).to(equal("http://new.example.com/pic")) expect(pic4.key).toNot(beNil()) - expect(String(pointer: pic4.key, length: pic4.keylen)).to(equal("qwert\0yuio")) + expect(Data(libSessionVal: pic4.key, count: 32).toHexString()) + .to(equal("7177657274007975696f31323334353637383930313233343536373839303132")) config_confirm_pushed(conf, seqno5) config_confirm_pushed(conf2, seqno6) diff --git a/SessionMessagingKitTests/LibSessionUtil/Utilities/TypeConversionUtilitiesSpec.swift b/SessionMessagingKitTests/LibSessionUtil/Utilities/TypeConversionUtilitiesSpec.swift new file mode 100644 index 000000000..79ec212da --- /dev/null +++ b/SessionMessagingKitTests/LibSessionUtil/Utilities/TypeConversionUtilitiesSpec.swift @@ -0,0 +1,196 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import Sodium + +import Quick +import Nimble + +@testable import SessionMessagingKit + +class TypeConversionUtilitiesSpec: QuickSpec { + // MARK: - Spec + + override func spec() { + // MARK: - String + + describe("a String") { + it("can convert to a cArray") { + expect("Test123".cArray).to(equal([84, 101, 115, 116, 49, 50, 51])) + } + + context("when initialised with a pointer and length") { + it("returns null when given a null pointer") { + let test: [CChar] = [84, 101, 115, 116] + let result = test.withUnsafeBufferPointer { ptr in + String(pointer: nil, length: 5) + } + + expect(result).to(beNil()) + } + + it("returns a truncated string when given an incorrect length") { + let test: [CChar] = [84, 101, 115, 116] + let result = test.withUnsafeBufferPointer { ptr in + String(pointer: UnsafeRawPointer(ptr.baseAddress), length: 2) + } + + expect(result).to(equal("Te")) + } + + it("returns a string when valid") { + let test: [CChar] = [84, 101, 115, 116] + let result = test.withUnsafeBufferPointer { ptr in + String(pointer: UnsafeRawPointer(ptr.baseAddress), length: 5) + } + + expect(result).to(equal("Test\0")) + } + } + + context("when initialised with a libSession value") { + it("returns a string when valid and null terminated") { + let value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 115, 116, 0) + let result = String(libSessionVal: value, nullTerminated: true) + + expect(result).to(equal("Test")) + } + + it("returns a string when valid and not null terminated") { + let value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 0, 115, 116) + let result = String(libSessionVal: value, nullTerminated: false) + + expect(result).to(equal("Te\0st")) + } + + it("returns an empty string when null and not set to return null") { + let value: (CChar, CChar, CChar, CChar, CChar) = (0, 0, 0, 0, 0) + let result = String(libSessionVal: value, nullIfEmpty: false) + + expect(result).to(equal("")) + } + + it("returns null when specified and empty") { + let value: (CChar, CChar, CChar, CChar, CChar) = (0, 0, 0, 0, 0) + let result = String(libSessionVal: value, nullIfEmpty: true) + + expect(result).to(beNil()) + } + + it("defaults the null terminated flag to true") { + let value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 0, 0, 0) + let result = String(libSessionVal: value) + + expect(result).to(equal("Te")) + } + + it("defaults the null if empty flag to false") { + let value: (CChar, CChar, CChar, CChar, CChar) = (0, 0, 0, 0, 0) + let result = String(libSessionVal: value) + + expect(result).to(equal("")) + } + } + + it("can convert to a libSession value") { + let result: (CChar, CChar, CChar, CChar, CChar) = "Test".toLibSession() + expect(result.0).to(equal(84)) + expect(result.1).to(equal(101)) + expect(result.2).to(equal(115)) + expect(result.3).to(equal(116)) + expect(result.4).to(equal(0)) + } + + context("when optional") { + context("returns null when null") { + let value: String? = nil + let result: (CChar, CChar, CChar, CChar, CChar)? = value?.toLibSession() + + expect(result).to(beNil()) + } + + context("returns a libSession value when not null") { + let value: String? = "Test" + let result: (CChar, CChar, CChar, CChar, CChar)? = value?.toLibSession() + + expect(result?.0).to(equal(84)) + expect(result?.1).to(equal(101)) + expect(result?.2).to(equal(115)) + expect(result?.3).to(equal(116)) + expect(result?.4).to(equal(0)) + } + } + } + + // MARK: - Data + + describe("Data") { + it("can convert to a cArray") { + expect(Data([1, 2, 3]).cArray).to(equal([1, 2, 3])) + } + + context("when initialised with a libSession value") { + it("returns truncated data when given the wrong length") { + let value: (UInt8, UInt8, UInt8, UInt8, UInt8) = (1, 2, 3, 4, 5) + let result = Data(libSessionVal: value, count: 2) + + expect(result).to(equal(Data([1, 2]))) + } + + it("returns data when valid") { + let value: (UInt8, UInt8, UInt8, UInt8, UInt8) = (1, 2, 3, 4, 5) + let result = Data(libSessionVal: value, count: 5) + + expect(result).to(equal(Data([1, 2, 3, 4, 5]))) + } + } + + it("can convert to a libSession value") { + let result: (Int8, Int8, Int8, Int8, Int8) = Data([1, 2, 3, 4, 5]).toLibSession() + expect(result.0).to(equal(1)) + expect(result.1).to(equal(2)) + expect(result.2).to(equal(3)) + expect(result.3).to(equal(4)) + expect(result.4).to(equal(5)) + } + + context("when optional") { + context("returns null when null") { + let value: Data? = nil + let result: (Int8, Int8, Int8, Int8, Int8)? = value?.toLibSession() + + expect(result).to(beNil()) + } + + context("returns a libSession value when not null") { + let value: Data? = Data([1, 2, 3, 4, 5]) + let result: (Int8, Int8, Int8, Int8, Int8)? = value?.toLibSession() + + expect(result?.0).to(equal(1)) + expect(result?.1).to(equal(2)) + expect(result?.2).to(equal(3)) + expect(result?.3).to(equal(4)) + expect(result?.4).to(equal(5)) + } + } + } + + // MARK: - Array + + describe("an Array") { + context("when adding a null terminated character") { + it("adds a null termination character when not present") { + let value: [CChar] = [1, 2, 3, 4, 5] + + expect(value.nullTerminated()).to(equal([1, 2, 3, 4, 5, 0])) + } + + it("adds nothing when already present") { + let value: [CChar] = [1, 2, 3, 4, 0] + + expect(value.nullTerminated()).to(equal([1, 2, 3, 4, 0])) + } + } + } + } +} diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift b/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift index 384e46edc..98c0c6f5b 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift +++ b/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift @@ -186,7 +186,7 @@ class OpenGroupAPISpec: QuickSpec { it("generates the correct request") { mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI.poll( db, server: "testserver", @@ -221,7 +221,7 @@ class OpenGroupAPISpec: QuickSpec { it("retrieves recent messages if there was no last message") { mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI.poll( db, server: "testserver", @@ -250,7 +250,7 @@ class OpenGroupAPISpec: QuickSpec { } mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI.poll( db, server: "testserver", @@ -279,7 +279,7 @@ class OpenGroupAPISpec: QuickSpec { } mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI.poll( db, server: "testserver", @@ -308,7 +308,7 @@ class OpenGroupAPISpec: QuickSpec { } mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI.poll( db, server: "testserver", @@ -340,7 +340,7 @@ class OpenGroupAPISpec: QuickSpec { it("does not call the inbox and outbox endpoints") { mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI.poll( db, server: "testserver", @@ -439,7 +439,7 @@ class OpenGroupAPISpec: QuickSpec { it("includes the inbox and outbox endpoints") { mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI.poll( db, server: "testserver", @@ -466,7 +466,7 @@ class OpenGroupAPISpec: QuickSpec { it("retrieves recent inbox messages if there was no last message") { mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI.poll( db, server: "testserver", @@ -495,7 +495,7 @@ class OpenGroupAPISpec: QuickSpec { } mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI.poll( db, server: "testserver", @@ -519,7 +519,7 @@ class OpenGroupAPISpec: QuickSpec { it("retrieves recent outbox messages if there was no last message") { mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI.poll( db, server: "testserver", @@ -548,7 +548,7 @@ class OpenGroupAPISpec: QuickSpec { } mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI.poll( db, server: "testserver", @@ -609,7 +609,7 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI.poll( db, server: "testserver", @@ -639,7 +639,7 @@ class OpenGroupAPISpec: QuickSpec { it("errors when no data is returned") { mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI.poll( db, server: "testserver", @@ -668,7 +668,7 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI.poll( db, server: "testserver", @@ -697,7 +697,7 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI.poll( db, server: "testserver", @@ -726,7 +726,7 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI.poll( db, server: "testserver", @@ -787,7 +787,7 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI.poll( db, server: "testserver", @@ -825,7 +825,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Capabilities)? mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI.capabilities( db, server: "testserver", @@ -895,7 +895,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: [OpenGroupAPI.Room])? mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI.rooms( db, server: "testserver", @@ -986,7 +986,7 @@ class OpenGroupAPISpec: QuickSpec { var response: OpenGroupAPI.CapabilitiesAndRoomResponse? mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI.capabilitiesAndRoom( db, for: "testRoom", @@ -1041,7 +1041,7 @@ class OpenGroupAPISpec: QuickSpec { var response: OpenGroupAPI.CapabilitiesAndRoomResponse? mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .capabilitiesAndRoom( db, @@ -1113,7 +1113,7 @@ class OpenGroupAPISpec: QuickSpec { var response: OpenGroupAPI.CapabilitiesAndRoomResponse? mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .capabilitiesAndRoom( db, @@ -1202,7 +1202,7 @@ class OpenGroupAPISpec: QuickSpec { var response: OpenGroupAPI.CapabilitiesAndRoomResponse? mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI.capabilitiesAndRoom( db, for: "testRoom", @@ -1261,7 +1261,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .send( db, @@ -1306,7 +1306,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .send( db, @@ -1346,7 +1346,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .send( db, @@ -1381,7 +1381,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .send( db, @@ -1414,7 +1414,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .send( db, @@ -1454,7 +1454,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .send( db, @@ -1494,7 +1494,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .send( db, @@ -1529,7 +1529,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .send( db, @@ -1570,7 +1570,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .send( db, @@ -1623,7 +1623,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .message( db, @@ -1675,7 +1675,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: Data?)? mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .messageUpdate( db, @@ -1716,7 +1716,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: Data?)? mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .messageUpdate( db, @@ -1755,7 +1755,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: Data?)? mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .messageUpdate( db, @@ -1789,7 +1789,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: Data?)? mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .messageUpdate( db, @@ -1821,7 +1821,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: Data?)? mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .messageUpdate( db, @@ -1860,7 +1860,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: Data?)? mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .messageUpdate( db, @@ -1899,7 +1899,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: Data?)? mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .messageUpdate( db, @@ -1933,7 +1933,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: Data?)? mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .messageUpdate( db, @@ -1973,7 +1973,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: Data?)? mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .messageUpdate( db, @@ -2010,7 +2010,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: Data?)? mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .messageDelete( db, @@ -2054,7 +2054,7 @@ class OpenGroupAPISpec: QuickSpec { it("generates the request and handles the response correctly") { mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .messagesDeleteAll( db, @@ -2094,7 +2094,7 @@ class OpenGroupAPISpec: QuickSpec { var response: ResponseInfoType? mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .pinMessage( db, @@ -2132,7 +2132,7 @@ class OpenGroupAPISpec: QuickSpec { var response: ResponseInfoType? mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .unpinMessage( db, @@ -2170,7 +2170,7 @@ class OpenGroupAPISpec: QuickSpec { var response: ResponseInfoType? mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .unpinAll( db, @@ -2209,7 +2209,7 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .uploadFile( db, @@ -2245,7 +2245,7 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .uploadFile( db, @@ -2281,7 +2281,7 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .uploadFile( db, @@ -2319,7 +2319,7 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .downloadFile( db, @@ -2376,7 +2376,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.SendDirectMessageResponse)? mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .send( db, @@ -2425,7 +2425,7 @@ class OpenGroupAPISpec: QuickSpec { it("generates the request and handles the response correctly") { mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .userBan( db, @@ -2455,7 +2455,7 @@ class OpenGroupAPISpec: QuickSpec { it("does a global ban if no room tokens are provided") { mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .userBan( db, @@ -2487,7 +2487,7 @@ class OpenGroupAPISpec: QuickSpec { it("does room specific bans if room tokens are provided") { mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .userBan( db, @@ -2534,7 +2534,7 @@ class OpenGroupAPISpec: QuickSpec { it("generates the request and handles the response correctly") { mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .userUnban( db, @@ -2563,7 +2563,7 @@ class OpenGroupAPISpec: QuickSpec { it("does a global ban if no room tokens are provided") { mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .userUnban( db, @@ -2594,7 +2594,7 @@ class OpenGroupAPISpec: QuickSpec { it("does room specific bans if room tokens are provided") { mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .userUnban( db, @@ -2640,7 +2640,7 @@ class OpenGroupAPISpec: QuickSpec { it("generates the request and handles the response correctly") { mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .userModeratorUpdate( db, @@ -2672,7 +2672,7 @@ class OpenGroupAPISpec: QuickSpec { it("does a global update if no room tokens are provided") { mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .userModeratorUpdate( db, @@ -2706,7 +2706,7 @@ class OpenGroupAPISpec: QuickSpec { it("does room specific updates if room tokens are provided") { mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .userModeratorUpdate( db, @@ -2740,7 +2740,7 @@ class OpenGroupAPISpec: QuickSpec { it("fails if neither moderator or admin are set") { mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .userModeratorUpdate( db, @@ -2804,7 +2804,7 @@ class OpenGroupAPISpec: QuickSpec { it("generates the request and handles the response correctly") { mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .userBanAndDeleteAllMessages( db, @@ -2833,7 +2833,7 @@ class OpenGroupAPISpec: QuickSpec { it("bans the user from the specified room rather than globally") { mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .userBanAndDeleteAllMessages( db, @@ -2890,7 +2890,7 @@ class OpenGroupAPISpec: QuickSpec { } mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .rooms( db, @@ -2917,7 +2917,7 @@ class OpenGroupAPISpec: QuickSpec { } mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .rooms( db, @@ -2944,7 +2944,7 @@ class OpenGroupAPISpec: QuickSpec { } mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .rooms( db, @@ -2975,7 +2975,7 @@ class OpenGroupAPISpec: QuickSpec { it("signs correctly") { mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .rooms( db, @@ -3011,7 +3011,7 @@ class OpenGroupAPISpec: QuickSpec { mockSign.when { $0.signature(message: anyArray(), secretKey: anyArray()) }.thenReturn(nil) mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .rooms( db, @@ -3044,7 +3044,7 @@ class OpenGroupAPISpec: QuickSpec { it("signs correctly") { mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .rooms( db, @@ -3081,7 +3081,7 @@ class OpenGroupAPISpec: QuickSpec { .thenReturn(nil) mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .rooms( db, @@ -3108,7 +3108,7 @@ class OpenGroupAPISpec: QuickSpec { .thenReturn(nil) mockStorage - .readPublisherFlatMap { db in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in OpenGroupAPI .rooms( db, diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift index 07e5e81d8..cb5fa9b3a 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift +++ b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift @@ -153,7 +153,7 @@ class OpenGroupManagerSpec: QuickSpec { testGroupThread = SessionThread( id: OpenGroup.idFor(roomToken: "testRoom", server: "testServer"), - variant: .openGroup + variant: .community ) testOpenGroup = OpenGroup( server: "testServer", @@ -681,7 +681,7 @@ class OpenGroupManagerSpec: QuickSpec { mockStorage.write { db in try SessionThread( id: OpenGroup.idFor(roomToken: "testRoom", server: "http://116.203.70.33"), - variant: .openGroup, + variant: .community, creationDateTimestamp: 0, shouldBeVisible: true, isPinned: false, @@ -713,7 +713,7 @@ class OpenGroupManagerSpec: QuickSpec { mockStorage.write { db in try SessionThread( id: OpenGroup.idFor(roomToken: "testRoom", server: "http://open.getsession.org"), - variant: .openGroup, + variant: .community, creationDateTimestamp: 0, shouldBeVisible: true, isPinned: false, @@ -815,7 +815,7 @@ class OpenGroupManagerSpec: QuickSpec { var didComplete: Bool = false // Prevent multi-threading test bugs mockStorage - .writePublisherFlatMap { (db: Database) -> AnyPublisher in + .writePublisherFlatMap(receiveOn: DispatchQueue.main) { (db: Database) -> AnyPublisher in openGroupManager .add( db, @@ -845,7 +845,7 @@ class OpenGroupManagerSpec: QuickSpec { var didComplete: Bool = false // Prevent multi-threading test bugs mockStorage - .writePublisherFlatMap { (db: Database) -> AnyPublisher in + .writePublisherFlatMap(receiveOn: DispatchQueue.main) { (db: Database) -> AnyPublisher in openGroupManager .add( db, @@ -881,7 +881,7 @@ class OpenGroupManagerSpec: QuickSpec { var didComplete: Bool = false // Prevent multi-threading test bugs mockStorage - .writePublisherFlatMap { (db: Database) -> AnyPublisher in + .writePublisherFlatMap(receiveOn: DispatchQueue.main) { (db: Database) -> AnyPublisher in openGroupManager .add( db, @@ -936,7 +936,7 @@ class OpenGroupManagerSpec: QuickSpec { var error: Error? mockStorage - .writePublisherFlatMap { (db: Database) -> AnyPublisher in + .writePublisherFlatMap(receiveOn: DispatchQueue.main) { (db: Database) -> AnyPublisher in openGroupManager .add( db, @@ -3325,8 +3325,8 @@ class OpenGroupManagerSpec: QuickSpec { let publisher = Future<[OpenGroupAPI.Room], Error> { resolver in resolver(Result.success([uniqueRoomInstance])) } - .shareReplay(1) - .eraseToAnyPublisher() + .shareReplay(1) + .eraseToAnyPublisher() mockOGMCache.when { $0.defaultRoomsPublisher }.thenReturn(publisher) let publisher2 = OpenGroupManager.getDefaultRoomsIfNeeded(using: dependencies) @@ -3587,7 +3587,7 @@ class OpenGroupManagerSpec: QuickSpec { var result: Data? mockStorage - .readPublisherFlatMap { (db: Database) -> AnyPublisher in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { (db: Database) -> AnyPublisher in OpenGroupManager .roomImage( db, @@ -3606,7 +3606,7 @@ class OpenGroupManagerSpec: QuickSpec { it("does not save the fetched image to storage") { var didComplete: Bool = false mockStorage - .readPublisherFlatMap { (db: Database) -> AnyPublisher in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { (db: Database) -> AnyPublisher in OpenGroupManager .roomImage( db, @@ -3637,7 +3637,7 @@ class OpenGroupManagerSpec: QuickSpec { it("does not update the image update timestamp") { var didComplete: Bool = false mockStorage - .readPublisherFlatMap { (db: Database) -> AnyPublisher in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { (db: Database) -> AnyPublisher in OpenGroupManager .roomImage( db, @@ -3679,7 +3679,7 @@ class OpenGroupManagerSpec: QuickSpec { dependencies = dependencies.with(onionApi: TestNeverReturningApi.self) let publisher = mockStorage - .readPublisherFlatMap { (db: Database) -> AnyPublisher in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { (db: Database) -> AnyPublisher in OpenGroupManager .roomImage( db, @@ -3705,7 +3705,7 @@ class OpenGroupManagerSpec: QuickSpec { var result: Data? mockStorage - .readPublisherFlatMap { (db: Database) -> AnyPublisher in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { (db: Database) -> AnyPublisher in OpenGroupManager .roomImage( db, @@ -3725,7 +3725,7 @@ class OpenGroupManagerSpec: QuickSpec { var didComplete: Bool = false mockStorage - .readPublisherFlatMap { (db: Database) -> AnyPublisher in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { (db: Database) -> AnyPublisher in OpenGroupManager .roomImage( db, @@ -3757,7 +3757,7 @@ class OpenGroupManagerSpec: QuickSpec { var didComplete: Bool = false mockStorage - .readPublisherFlatMap { (db: Database) -> AnyPublisher in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { (db: Database) -> AnyPublisher in OpenGroupManager .roomImage( db, @@ -3805,7 +3805,7 @@ class OpenGroupManagerSpec: QuickSpec { var result: Data? mockStorage - .readPublisherFlatMap { (db: Database) -> AnyPublisher in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { (db: Database) -> AnyPublisher in OpenGroupManager .roomImage( db, @@ -3835,7 +3835,7 @@ class OpenGroupManagerSpec: QuickSpec { var result: Data? mockStorage - .readPublisherFlatMap { (db: Database) -> AnyPublisher in + .readPublisherFlatMap(receiveOn: DispatchQueue.main) { (db: Database) -> AnyPublisher in OpenGroupManager .roomImage( db, diff --git a/SessionNotificationServiceExtension/NSENotificationPresenter.swift b/SessionNotificationServiceExtension/NSENotificationPresenter.swift index 63a6916a1..9c625f9b6 100644 --- a/SessionNotificationServiceExtension/NSENotificationPresenter.swift +++ b/SessionNotificationServiceExtension/NSENotificationPresenter.swift @@ -26,7 +26,7 @@ public class NSENotificationPresenter: NSObject, NotificationsProtocol { ) var notificationTitle: String = senderName - if thread.variant == .legacyClosedGroup || thread.variant == .closedGroup || thread.variant == .openGroup { + if thread.variant == .legacyGroup || thread.variant == .group || thread.variant == .community { if thread.onlyNotifyForMentions && !interaction.hasMention { // Ignore PNs if the group is set to only notify for mentions return @@ -85,11 +85,11 @@ public class NSENotificationPresenter: NSObject, NotificationsProtocol { // Add request (try to group notifications for interactions from open groups) let identifier: String = interaction.notificationIdentifier( - shouldGroupMessagesForThread: (thread.variant == .openGroup) + shouldGroupMessagesForThread: (thread.variant == .community) ) var trigger: UNNotificationTrigger? - if thread.variant == .openGroup { + if thread.variant == .community { trigger = UNTimeIntervalNotificationTrigger( timeInterval: Notifications.delayForGroupedNotifications, repeats: false @@ -128,9 +128,9 @@ public class NSENotificationPresenter: NSObject, NotificationsProtocol { // No call notifications for muted or group threads guard Date().timeIntervalSince1970 > (thread.mutedUntilTimestamp ?? 0) else { return } guard - thread.variant != .legacyClosedGroup && - thread.variant != .closedGroup && - thread.variant != .openGroup + thread.variant != .legacyGroup && + thread.variant != .group && + thread.variant != .community else { return } guard interaction.variant == .infoCall, @@ -186,9 +186,9 @@ public class NSENotificationPresenter: NSObject, NotificationsProtocol { // No reaction notifications for muted, group threads or message requests guard Date().timeIntervalSince1970 > (thread.mutedUntilTimestamp ?? 0) else { return } guard - thread.variant != .legacyClosedGroup && - thread.variant != .closedGroup && - thread.variant != .openGroup + thread.variant != .legacyGroup && + thread.variant != .group && + thread.variant != .community else { return } guard !isMessageRequest else { return } diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index 7dd880554..fb5ef4037 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -83,7 +83,7 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension .asRequest(of: SessionThread.Variant.self) .fetchOne(db) } - let isOpenGroup: Bool = (maybeVariant == .openGroup) + let isOpenGroup: Bool = (maybeVariant == .community) switch processedMessage.messageInfo.message { case let visibleMessage as VisibleMessage: diff --git a/SessionShareExtension/ShareNavController.swift b/SessionShareExtension/ShareNavController.swift index c5eb6ce7e..87d46901b 100644 --- a/SessionShareExtension/ShareNavController.swift +++ b/SessionShareExtension/ShareNavController.swift @@ -432,163 +432,165 @@ final class ShareNavController: UINavigationController, ShareViewDelegate { } Logger.debug("matched utiType: \(srcUtiType)") - return Future { resolver in - let loadCompletion: NSItemProvider.CompletionHandler = { [weak self] value, error in - guard self != nil else { return } - if let error: Error = error { - resolver(Result.failure(error)) - return - } - - guard let value = value else { - resolver( - Result.failure(ShareViewControllerError.assertionError(description: "missing item provider")) - ) - return - } - - Logger.info("value type: \(type(of: value))") - - switch value { - case let data as Data: - let customFileName = "Contact.vcf" - - let customFileExtension = MIMETypeUtil.fileExtension(forUTIType: srcUtiType) - guard let tempFilePath = OWSFileSystem.writeData(toTemporaryFile: data, fileExtension: customFileExtension) else { - resolver( - Result.failure(ShareViewControllerError.assertionError(description: "Error writing item data: \(String(describing: error))")) - ) - return - } - let fileUrl = URL(fileURLWithPath: tempFilePath) - + return Deferred { + Future { resolver in + let loadCompletion: NSItemProvider.CompletionHandler = { [weak self] value, error in + guard self != nil else { return } + if let error: Error = error { + resolver(Result.failure(error)) + return + } + + guard let value = value else { resolver( - Result.success( - LoadedItem( - itemProvider: itemProvider, - itemUrl: fileUrl, - utiType: srcUtiType, - customFileName: customFileName, - isConvertibleToContactShare: false - ) - ) + Result.failure(ShareViewControllerError.assertionError(description: "missing item provider")) ) - - case let string as String: - Logger.debug("string provider: \(string)") - guard let data = string.filterStringForDisplay().data(using: String.Encoding.utf8) else { - resolver( - Result.failure(ShareViewControllerError.assertionError(description: "Error writing item data: \(String(describing: error))")) - ) - return - } - guard let tempFilePath = OWSFileSystem.writeData(toTemporaryFile: data, fileExtension: "txt") else { - resolver( - Result.failure(ShareViewControllerError.assertionError(description: "Error writing item data: \(String(describing: error))")) - ) - return - } - - let fileUrl = URL(fileURLWithPath: tempFilePath) - - let isConvertibleToTextMessage = !itemProvider.registeredTypeIdentifiers.contains(kUTTypeFileURL as String) - - if UTTypeConformsTo(srcUtiType as CFString, kUTTypeText) { + return + } + + Logger.info("value type: \(type(of: value))") + + switch value { + case let data as Data: + let customFileName = "Contact.vcf" + + let customFileExtension = MIMETypeUtil.fileExtension(forUTIType: srcUtiType) + guard let tempFilePath = OWSFileSystem.writeData(toTemporaryFile: data, fileExtension: customFileExtension) else { + resolver( + Result.failure(ShareViewControllerError.assertionError(description: "Error writing item data: \(String(describing: error))")) + ) + return + } + let fileUrl = URL(fileURLWithPath: tempFilePath) + resolver( Result.success( LoadedItem( itemProvider: itemProvider, itemUrl: fileUrl, utiType: srcUtiType, - isConvertibleToTextMessage: isConvertibleToTextMessage + customFileName: customFileName, + isConvertibleToContactShare: false ) ) ) - } - else { - resolver( - Result.success( - LoadedItem( - itemProvider: itemProvider, - itemUrl: fileUrl, - utiType: kUTTypeText as String, - isConvertibleToTextMessage: isConvertibleToTextMessage + + case let string as String: + Logger.debug("string provider: \(string)") + guard let data = string.filterStringForDisplay().data(using: String.Encoding.utf8) else { + resolver( + Result.failure(ShareViewControllerError.assertionError(description: "Error writing item data: \(String(describing: error))")) + ) + return + } + guard let tempFilePath = OWSFileSystem.writeData(toTemporaryFile: data, fileExtension: "txt") else { + resolver( + Result.failure(ShareViewControllerError.assertionError(description: "Error writing item data: \(String(describing: error))")) + ) + return + } + + let fileUrl = URL(fileURLWithPath: tempFilePath) + + let isConvertibleToTextMessage = !itemProvider.registeredTypeIdentifiers.contains(kUTTypeFileURL as String) + + if UTTypeConformsTo(srcUtiType as CFString, kUTTypeText) { + resolver( + Result.success( + LoadedItem( + itemProvider: itemProvider, + itemUrl: fileUrl, + utiType: srcUtiType, + isConvertibleToTextMessage: isConvertibleToTextMessage + ) ) ) - ) - } - - case let url as URL: - // If the share itself is a URL (e.g. a link from Safari), try to send this as a text message. - let isConvertibleToTextMessage = ( - itemProvider.registeredTypeIdentifiers.contains(kUTTypeURL as String) && - !itemProvider.registeredTypeIdentifiers.contains(kUTTypeFileURL as String) - ) - - if isConvertibleToTextMessage { - resolver( - Result.success( - LoadedItem( - itemProvider: itemProvider, - itemUrl: url, - utiType: kUTTypeURL as String, - isConvertibleToTextMessage: isConvertibleToTextMessage + } + else { + resolver( + Result.success( + LoadedItem( + itemProvider: itemProvider, + itemUrl: fileUrl, + utiType: kUTTypeText as String, + isConvertibleToTextMessage: isConvertibleToTextMessage + ) ) ) + } + + case let url as URL: + // If the share itself is a URL (e.g. a link from Safari), try to send this as a text message. + let isConvertibleToTextMessage = ( + itemProvider.registeredTypeIdentifiers.contains(kUTTypeURL as String) && + !itemProvider.registeredTypeIdentifiers.contains(kUTTypeFileURL as String) ) - } - else { - resolver( - Result.success( - LoadedItem( - itemProvider: itemProvider, - itemUrl: url, - utiType: srcUtiType, - isConvertibleToTextMessage: isConvertibleToTextMessage - ) - ) - ) - } - - case let image as UIImage: - if let data = image.pngData() { - let tempFilePath = OWSFileSystem.temporaryFilePath(withFileExtension: "png") - do { - let url = NSURL.fileURL(withPath: tempFilePath) - try data.write(to: url) - + + if isConvertibleToTextMessage { resolver( Result.success( LoadedItem( itemProvider: itemProvider, itemUrl: url, - utiType: srcUtiType + utiType: kUTTypeURL as String, + isConvertibleToTextMessage: isConvertibleToTextMessage ) ) ) } - catch { + else { resolver( - Result.failure(ShareViewControllerError.assertionError(description: "couldn't write UIImage: \(String(describing: error))")) + Result.success( + LoadedItem( + itemProvider: itemProvider, + itemUrl: url, + utiType: srcUtiType, + isConvertibleToTextMessage: isConvertibleToTextMessage + ) + ) ) } - } - else { + + case let image as UIImage: + if let data = image.pngData() { + let tempFilePath = OWSFileSystem.temporaryFilePath(withFileExtension: "png") + do { + let url = NSURL.fileURL(withPath: tempFilePath) + try data.write(to: url) + + resolver( + Result.success( + LoadedItem( + itemProvider: itemProvider, + itemUrl: url, + utiType: srcUtiType + ) + ) + ) + } + catch { + resolver( + Result.failure(ShareViewControllerError.assertionError(description: "couldn't write UIImage: \(String(describing: error))")) + ) + } + } + else { + resolver( + Result.failure(ShareViewControllerError.assertionError(description: "couldn't convert UIImage to PNG: \(String(describing: error))")) + ) + } + + default: + // It's unavoidable that we may sometimes receives data types that we + // don't know how to handle. resolver( - Result.failure(ShareViewControllerError.assertionError(description: "couldn't convert UIImage to PNG: \(String(describing: error))")) + Result.failure(ShareViewControllerError.assertionError(description: "unexpected value: \(String(describing: value))")) ) - } - - default: - // It's unavoidable that we may sometimes receives data types that we - // don't know how to handle. - resolver( - Result.failure(ShareViewControllerError.assertionError(description: "unexpected value: \(String(describing: value))")) - ) + } } + + itemProvider.loadItem(forTypeIdentifier: srcUtiType, options: nil, completionHandler: loadCompletion) } - - itemProvider.loadItem(forTypeIdentifier: srcUtiType, options: nil, completionHandler: loadCompletion) } .eraseToAnyPublisher() } @@ -652,11 +654,9 @@ final class ShareNavController: UINavigationController, ShareViewDelegate { private func buildAttachments() -> AnyPublisher<[SignalAttachment], Error> { return selectItemProviders() - .flatMap { [weak self] itemProviders -> AnyPublisher<[SignalAttachment], Error> in + .tryFlatMap { [weak self] itemProviders -> AnyPublisher<[SignalAttachment], Error> in guard let strongSelf = self else { - let error = ShareViewControllerError.assertionError(description: "expired") - return Fail(error: error) - .eraseToAnyPublisher() + throw ShareViewControllerError.assertionError(description: "expired") } var loadPublishers = [AnyPublisher]() @@ -676,15 +676,12 @@ final class ShareNavController: UINavigationController, ShareViewDelegate { .collect() .eraseToAnyPublisher() } - .flatMap { signalAttachments -> AnyPublisher<[SignalAttachment], Error> in + .tryMap { signalAttachments -> [SignalAttachment] in guard signalAttachments.count > 0 else { - return Fail(error: ShareViewControllerError.assertionError(description: "no valid attachments")) - .eraseToAnyPublisher() + throw ShareViewControllerError.assertionError(description: "no valid attachments") } - return Just(signalAttachments) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() + return signalAttachments } .shareReplay(1) .eraseToAnyPublisher() diff --git a/SessionShareExtension/ThreadPickerVC.swift b/SessionShareExtension/ThreadPickerVC.swift index 43906594e..52b366fcc 100644 --- a/SessionShareExtension/ThreadPickerVC.swift +++ b/SessionShareExtension/ThreadPickerVC.swift @@ -189,7 +189,7 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView NotificationCenter.default.post(name: Database.resumeNotification, object: self) Storage.shared - .writePublisher { db -> MessageSender.PreparedSendData in + .writePublisher(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db -> MessageSender.PreparedSendData in guard let thread: SessionThread = try SessionThread.fetchOne(db, id: threadId) else { throw MessageSenderError.noThread } @@ -248,7 +248,12 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView in: thread ) } - .flatMap { MessageSender.performUploadsIfNeeded(preparedSendData: $0) } + .flatMap { + MessageSender.performUploadsIfNeeded( + queue: DispatchQueue.global(qos: .userInitiated), + preparedSendData: $0 + ) + } .flatMap { MessageSender.sendImmediate(preparedSendData: $0) } .receive(on: DispatchQueue.main) .sinkUntilComplete( diff --git a/SessionSnodeKit/Models/DeleteAllBeforeResponse.swift b/SessionSnodeKit/Models/DeleteAllBeforeResponse.swift index 25e71d9ff..869a20b82 100644 --- a/SessionSnodeKit/Models/DeleteAllBeforeResponse.swift +++ b/SessionSnodeKit/Models/DeleteAllBeforeResponse.swift @@ -4,18 +4,27 @@ import Foundation import Sodium import SessionUtilitiesKit -public class DeleteAllBeforeResponse: SnodeRecursiveResponse { - // MARK: - Convenience +public class DeleteAllBeforeResponse: SnodeRecursiveResponse {} + +// MARK: - ValidatableResponse + +extension DeleteAllBeforeResponse: ValidatableResponse { + typealias ValidationData = UInt64 + typealias ValidationResponse = Bool + + /// Just one response in the swarm must be valid + internal static var requiredSuccessfulResponses: Int { 1 } internal func validResultMap( + sodium: Sodium, userX25519PublicKey: String, - beforeMs: UInt64, - sodium: Sodium - ) -> [String: Bool] { - return swarm.reduce(into: [:]) { result, next in + validationData: UInt64 + ) throws -> [String: Bool] { + let validationMap: [String: Bool] = swarm.reduce(into: [:]) { result, next in guard !next.value.failed, - let encodedSignature: Data = Data(base64Encoded: next.value.signatureBase64) + let signatureBase64: String = next.value.signatureBase64, + let encodedSignature: Data = Data(base64Encoded: signatureBase64) else { result[next.key] = false @@ -32,7 +41,7 @@ public class DeleteAllBeforeResponse: SnodeRecursiveResponse { - // MARK: - Convenience - - internal func validResultMap( - userX25519PublicKey: String, - timestampMs: UInt64, - sodium: Sodium - ) -> [String: Bool] { - return swarm.reduce(into: [:]) { result, next in - guard - !next.value.failed, - let encodedSignature: Data = Data(base64Encoded: next.value.signatureBase64) - else { - result[next.key] = false - - if let reason: String = next.value.reason, let statusCode: Int = next.value.code { - SNLog("Couldn't delete data from: \(next.key) due to error: \(reason) (\(statusCode)).") - } - else { - SNLog("Couldn't delete data from: \(next.key).") - } - return - } - - /// Signature of `( PUBKEY_HEX || TIMESTAMP || DELETEDHASH[0] || ... || DELETEDHASH[N] )` - /// signed by the node's ed25519 pubkey. When doing a multi-namespace delete the `DELETEDHASH` - /// values are totally ordered (i.e. among all the hashes deleted regardless of namespace) - let verificationBytes: [UInt8] = userX25519PublicKey.bytes - .appending(contentsOf: "\(timestampMs)".data(using: .ascii)?.bytes) - .appending(contentsOf: next.value.deleted.joined().bytes) - - result[next.key] = sodium.sign.verify( - message: verificationBytes, - publicKey: Data(hex: next.key).bytes, - signature: encodedSignature.bytes - ) - } - } -} +public class DeleteAllMessagesResponse: SnodeRecursiveResponse {} // MARK: - SwarmItem @@ -78,3 +40,52 @@ public extension DeleteAllMessagesResponse { } } } + +// MARK: - ValidatableResponse + +extension DeleteAllMessagesResponse: ValidatableResponse { + typealias ValidationData = UInt64 + typealias ValidationResponse = Bool + + /// Just one response in the swarm must be valid + internal static var requiredSuccessfulResponses: Int { 1 } + + internal func validResultMap( + sodium: Sodium, + userX25519PublicKey: String, + validationData: UInt64 + ) throws -> [String: Bool] { + let validationMap: [String: Bool] = swarm.reduce(into: [:]) { result, next in + guard + !next.value.failed, + let signatureBase64: String = next.value.signatureBase64, + let encodedSignature: Data = Data(base64Encoded: signatureBase64) + else { + result[next.key] = false + + if let reason: String = next.value.reason, let statusCode: Int = next.value.code { + SNLog("Couldn't delete data from: \(next.key) due to error: \(reason) (\(statusCode)).") + } + else { + SNLog("Couldn't delete data from: \(next.key).") + } + return + } + + /// Signature of `( PUBKEY_HEX || TIMESTAMP || DELETEDHASH[0] || ... || DELETEDHASH[N] )` + /// signed by the node's ed25519 pubkey. When doing a multi-namespace delete the `DELETEDHASH` + /// values are totally ordered (i.e. among all the hashes deleted regardless of namespace) + let verificationBytes: [UInt8] = userX25519PublicKey.bytes + .appending(contentsOf: "\(validationData)".data(using: .ascii)?.bytes) + .appending(contentsOf: next.value.deleted.joined().bytes) + + result[next.key] = sodium.sign.verify( + message: verificationBytes, + publicKey: Data(hex: next.key).bytes, + signature: encodedSignature.bytes + ) + } + + return try Self.validated(map: validationMap) + } +} diff --git a/SessionSnodeKit/Models/DeleteMessagesResponse.swift b/SessionSnodeKit/Models/DeleteMessagesResponse.swift index 50fedb56e..180d68498 100644 --- a/SessionSnodeKit/Models/DeleteMessagesResponse.swift +++ b/SessionSnodeKit/Models/DeleteMessagesResponse.swift @@ -4,43 +4,7 @@ import Foundation import Sodium import SessionUtilitiesKit -public class DeleteMessagesResponse: SnodeRecursiveResponse { - // MARK: - Convenience - - internal func validResultMap( - userX25519PublicKey: String, - serverHashes: [String], - sodium: Sodium - ) -> [String: Bool] { - return swarm.reduce(into: [:]) { result, next in - guard - !next.value.failed, - let encodedSignature: Data = Data(base64Encoded: next.value.signatureBase64) - else { - result[next.key] = false - - if let reason: String = next.value.reason, let statusCode: Int = next.value.code { - SNLog("Couldn't delete data from: \(next.key) due to error: \(reason) (\(statusCode)).") - } - else { - SNLog("Couldn't delete data from: \(next.key).") - } - return - } - - /// The signature format is `( PUBKEY_HEX || RMSG[0] || ... || RMSG[N] || DMSG[0] || ... || DMSG[M] )` - let verificationBytes: [UInt8] = userX25519PublicKey.bytes - .appending(contentsOf: serverHashes.joined().bytes) - .appending(contentsOf: next.value.deleted.joined().bytes) - - result[next.key] = sodium.sign.verify( - message: verificationBytes, - publicKey: Data(hex: next.key).bytes, - signature: encodedSignature.bytes - ) - } - } -} +public class DeleteMessagesResponse: SnodeRecursiveResponse {} // MARK: - SwarmItem @@ -57,9 +21,56 @@ public extension DeleteMessagesResponse { required init(from decoder: Decoder) throws { let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) - deleted = try container.decode([String].self, forKey: .deleted) + deleted = ((try? container.decode([String].self, forKey: .deleted)) ?? []) try super.init(from: decoder) } } } + +// MARK: - ValidatableResponse + +extension DeleteMessagesResponse: ValidatableResponse { + typealias ValidationData = [String] + typealias ValidationResponse = Bool + + /// Just one response in the swarm must be valid + internal static var requiredSuccessfulResponses: Int { 1 } + + internal func validResultMap( + sodium: Sodium, + userX25519PublicKey: String, + validationData: [String] + ) throws -> [String: Bool] { + let validationMap: [String: Bool] = swarm.reduce(into: [:]) { result, next in + guard + !next.value.failed, + let signatureBase64: String = next.value.signatureBase64, + let encodedSignature: Data = Data(base64Encoded: signatureBase64) + else { + result[next.key] = false + + if let reason: String = next.value.reason, let statusCode: Int = next.value.code { + SNLog("Couldn't delete data from: \(next.key) due to error: \(reason) (\(statusCode)).") + } + else { + SNLog("Couldn't delete data from: \(next.key).") + } + return + } + + /// The signature format is `( PUBKEY_HEX || RMSG[0] || ... || RMSG[N] || DMSG[0] || ... || DMSG[M] )` + let verificationBytes: [UInt8] = userX25519PublicKey.bytes + .appending(contentsOf: validationData.joined().bytes) + .appending(contentsOf: next.value.deleted.joined().bytes) + + result[next.key] = sodium.sign.verify( + message: verificationBytes, + publicKey: Data(hex: next.key).bytes, + signature: encodedSignature.bytes + ) + } + + return try Self.validated(map: validationMap) + } +} diff --git a/SessionSnodeKit/Models/GetMessagesRequest.swift b/SessionSnodeKit/Models/GetMessagesRequest.swift index 8f17d949d..e338874f8 100644 --- a/SessionSnodeKit/Models/GetMessagesRequest.swift +++ b/SessionSnodeKit/Models/GetMessagesRequest.swift @@ -7,10 +7,14 @@ extension SnodeAPI { enum CodingKeys: String, CodingKey { case lastHash = "last_hash" case namespace + case maxCount = "max_count" + case maxSize = "max_size" } let lastHash: String let namespace: SnodeAPI.Namespace? + let maxCount: Int64? + let maxSize: Int64? // MARK: - Init @@ -21,10 +25,14 @@ extension SnodeAPI { subkey: String?, timestampMs: UInt64, ed25519PublicKey: [UInt8], - ed25519SecretKey: [UInt8] + ed25519SecretKey: [UInt8], + maxCount: Int64? = nil, + maxSize: Int64? = nil ) { self.lastHash = lastHash self.namespace = namespace + self.maxCount = maxCount + self.maxSize = maxSize super.init( pubkey: pubkey, @@ -42,6 +50,8 @@ extension SnodeAPI { try container.encode(lastHash, forKey: .lastHash) try container.encodeIfPresent(namespace, forKey: .namespace) + try container.encodeIfPresent(maxCount, forKey: .maxCount) + try container.encodeIfPresent(maxSize, forKey: .maxSize) try super.encode(to: encoder) } diff --git a/SessionSnodeKit/Models/LegacyGetMessagesRequest.swift b/SessionSnodeKit/Models/LegacyGetMessagesRequest.swift index 644cdcafc..70dc7aa3a 100644 --- a/SessionSnodeKit/Models/LegacyGetMessagesRequest.swift +++ b/SessionSnodeKit/Models/LegacyGetMessagesRequest.swift @@ -9,11 +9,15 @@ extension SnodeAPI { case pubkey case lastHash = "last_hash" case namespace + case maxCount = "max_count" + case maxSize = "max_size" } let pubkey: String let lastHash: String let namespace: SnodeAPI.Namespace? + let maxCount: Int64? + let maxSize: Int64? // MARK: - Coding @@ -23,6 +27,8 @@ extension SnodeAPI { try container.encode(pubkey, forKey: .pubkey) try container.encode(lastHash, forKey: .lastHash) try container.encodeIfPresent(namespace, forKey: .namespace) + try container.encodeIfPresent(maxCount, forKey: .maxCount) + try container.encodeIfPresent(maxSize, forKey: .maxSize) } } } diff --git a/SessionSnodeKit/Models/RevokeSubkeyResponse.swift b/SessionSnodeKit/Models/RevokeSubkeyResponse.swift index f633fc4ed..867e4eb0e 100644 --- a/SessionSnodeKit/Models/RevokeSubkeyResponse.swift +++ b/SessionSnodeKit/Models/RevokeSubkeyResponse.swift @@ -4,24 +4,33 @@ import Foundation import Sodium import SessionUtilitiesKit -public class RevokeSubkeyResponse: SnodeRecursiveResponse { - // MARK: - Convenience +public class RevokeSubkeyResponse: SnodeRecursiveResponse {} + +// MARK: - ValidatableResponse + +extension RevokeSubkeyResponse: ValidatableResponse { + typealias ValidationData = String + typealias ValidationResponse = Bool - internal func validateResult( + /// All responses in the swarm must be valid + internal static var requiredSuccessfulResponses: Int { -1 } + + internal func validResultMap( + sodium: Sodium, userX25519PublicKey: String, - subkeyToRevoke: String, - sodium: Sodium - ) throws { - try swarm.forEach { snodePublicKey, swarmItem in + validationData: String + ) throws -> [String: Bool] { + let validationMap: [String: Bool] = try swarm.reduce(into: [:]) { result, next in guard - !swarmItem.failed, - let encodedSignature: Data = Data(base64Encoded: swarmItem.signatureBase64) + !next.value.failed, + let signatureBase64: String = next.value.signatureBase64, + let encodedSignature: Data = Data(base64Encoded: signatureBase64) else { - if let reason: String = swarmItem.reason, let statusCode: Int = swarmItem.code { - SNLog("Couldn't revoke subkey from: \(snodePublicKey) due to error: \(reason) (\(statusCode)).") + if let reason: String = next.value.reason, let statusCode: Int = next.value.code { + SNLog("Couldn't revoke subkey from: \(next.key) due to error: \(reason) (\(statusCode)).") } else { - SNLog("Couldn't revoke subkey from: \(snodePublicKey).") + SNLog("Couldn't revoke subkey from: \(next.key).") } return } @@ -29,15 +38,19 @@ public class RevokeSubkeyResponse: SnodeRecursiveResponse { /// Signature of `( PUBKEY_HEX || SUBKEY_TAG_BYTES )` where `SUBKEY_TAG_BYTES` is the /// requested subkey tag for revocation let verificationBytes: [UInt8] = userX25519PublicKey.bytes - .appending(contentsOf: subkeyToRevoke.bytes) + .appending(contentsOf: validationData.bytes) let isValid: Bool = sodium.sign.verify( message: verificationBytes, - publicKey: Data(hex: snodePublicKey).bytes, + publicKey: Data(hex: next.key).bytes, signature: encodedSignature.bytes ) // If the update signature is invalid then we want to fail here guard isValid else { throw SnodeAPIError.signatureVerificationFailed } + + result[next.key] = isValid } + + return try Self.validated(map: validationMap) } } diff --git a/SessionSnodeKit/Models/SendMessageRequest.swift b/SessionSnodeKit/Models/SendMessageRequest.swift index 91831f155..5058382df 100644 --- a/SessionSnodeKit/Models/SendMessageRequest.swift +++ b/SessionSnodeKit/Models/SendMessageRequest.swift @@ -6,7 +6,6 @@ extension SnodeAPI { public class SendMessageRequest: SnodeAuthenticatedRequestBody { enum CodingKeys: String, CodingKey { case namespace - case signatureTimestamp = "timestamp"//"sig_timestamp" // TODO: Add this back once the snodes are fixed!! } let message: SnodeMessage @@ -39,22 +38,22 @@ extension SnodeAPI { override public func encode(to encoder: Encoder) throws { var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) - try super.encode(to: encoder) - - /// **Note:** We **MUST** do the `message.encode` after we call `super.encode` because it will - /// override the `timestampMs` value with the value in the message (if we do it the other way around then - /// the the API call timestamp will be sent instead which is incorrect. For this specific request type we have a - /// separate `signatureTimestamp` value to store the timestamp used to generate the signature + /// **Note:** We **MUST** do the `message.encode` before we call `super.encode` because otherwise + /// it will override the `timestampMs` value with the value in the message which is incorrect - we actually want the + /// `timestampMs` value at the time the request was made so that older messages stuck in the job queue don't + /// end up failing due to being outside the approved timestamp window (clients use the timestamp within the message + /// data rather than this one anyway) try message.encode(to: encoder) try container.encode(namespace, forKey: .namespace) - try container.encode(timestampMs, forKey: .signatureTimestamp) + + try super.encode(to: encoder) } // MARK: - Abstract Methods override func generateSignature() throws -> [UInt8] { - /// Ed25519 signature of `("store" || namespace || sig_timestamp)`, where namespace and - /// `sig_timestamp` are the base10 expression of the namespace and `sig_timestamp` values. Must be + /// Ed25519 signature of `("store" || namespace || timestamp)`, where namespace and + /// `timestamp` are the base10 expression of the namespace and `timestamp` values. Must be /// base64 encoded for json requests; binary for OMQ requests. For non-05 type pubkeys (i.e. non /// session ids) the signature will be verified using `pubkey`. For 05 pubkeys, see the following /// option. diff --git a/SessionSnodeKit/Models/SendMessageResponse.swift b/SessionSnodeKit/Models/SendMessageResponse.swift index 052188a0e..2e21c8d23 100644 --- a/SessionSnodeKit/Models/SendMessageResponse.swift +++ b/SessionSnodeKit/Models/SendMessageResponse.swift @@ -1,6 +1,8 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import Sodium +import SessionUtilitiesKit public class SendMessagesResponse: SnodeRecursiveResponse { private enum CodingKeys: String, CodingKey { @@ -30,7 +32,7 @@ public extension SendMessagesResponse { case already } - public let hash: String + public let hash: String? /// `true` if a message with this hash was already stored /// @@ -42,10 +44,56 @@ public extension SendMessagesResponse { required init(from decoder: Decoder) throws { let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) - hash = try container.decode(String.self, forKey: .hash) + hash = try? container.decode(String.self, forKey: .hash) already = ((try? container.decode(Bool.self, forKey: .already)) ?? false) try super.init(from: decoder) } } } + +// MARK: - ValidatableResponse + +extension SendMessagesResponse: ValidatableResponse { + typealias ValidationData = Void + typealias ValidationResponse = Bool + + /// Half of the responses in the swarm must be valid + internal static var requiredSuccessfulResponses: Int { -2 } + + internal func validResultMap( + sodium: Sodium, + userX25519PublicKey: String, + validationData: Void + ) throws -> [String: Bool] { + let validationMap: [String: Bool] = swarm.reduce(into: [:]) { result, next in + guard + !next.value.failed, + let signatureBase64: String = next.value.signatureBase64, + let encodedSignature: Data = Data(base64Encoded: signatureBase64), + let hash: String = next.value.hash + else { + result[next.key] = false + + if let reason: String = next.value.reason, let statusCode: Int = next.value.code { + SNLog("Couldn't store message on: \(next.key) due to error: \(reason) (\(statusCode)).") + } + else { + SNLog("Couldn't store message on: \(next.key).") + } + return + } + + /// Signature of `hash` signed by the node's ed25519 pubkey + let verificationBytes: [UInt8] = hash.bytes + + result[next.key] = sodium.sign.verify( + message: verificationBytes, + publicKey: Data(hex: next.key).bytes, + signature: encodedSignature.bytes + ) + } + + return try Self.validated(map: validationMap) + } +} diff --git a/SessionSnodeKit/Models/SnodeSwarmItem.swift b/SessionSnodeKit/Models/SnodeSwarmItem.swift index 456b0ed86..72fc5b003 100644 --- a/SessionSnodeKit/Models/SnodeSwarmItem.swift +++ b/SessionSnodeKit/Models/SnodeSwarmItem.swift @@ -13,8 +13,9 @@ public class SnodeSwarmItem: Codable { case badPeerResponse = "bad_peer_response" case queryFailure = "query_failure" } - - public let signatureBase64: String + + /// Should be present as long as the request didn't fail + public let signatureBase64: String? /// `true` if the request failed, possibly accompanied by one of the following: `timeout`, `code`, /// `reason`, `badPeerResponse`, `queryFailure` @@ -40,7 +41,7 @@ public class SnodeSwarmItem: Codable { public required init(from decoder: Decoder) throws { let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) - signatureBase64 = try container.decode(String.self, forKey: .signatureBase64) + signatureBase64 = try? container.decode(String.self, forKey: .signatureBase64) failed = ((try? container.decode(Bool.self, forKey: .failed)) ?? false) timeout = try? container.decode(Bool.self, forKey: .timeout) code = try? container.decode(Int.self, forKey: .code) diff --git a/SessionSnodeKit/Models/UpdateExpiryAllResponse.swift b/SessionSnodeKit/Models/UpdateExpiryAllResponse.swift index 7deba77d5..389199565 100644 --- a/SessionSnodeKit/Models/UpdateExpiryAllResponse.swift +++ b/SessionSnodeKit/Models/UpdateExpiryAllResponse.swift @@ -4,50 +4,7 @@ import Foundation import Sodium import SessionUtilitiesKit -public class UpdateExpiryAllResponse: SnodeRecursiveResponse { - // MARK: - Convenience - - internal func validResultMap( - userX25519PublicKey: String, - expiryMs: UInt64, - sodium: Sodium - ) throws -> [String: [String]] { - return try swarm.reduce(into: [:]) { result, next in - guard - !next.value.failed, - let encodedSignature: Data = Data(base64Encoded: next.value.signatureBase64) - else { - result[next.key] = [] - - if let reason: String = next.value.reason, let statusCode: Int = next.value.code { - SNLog("Couldn't update expiry from: \(next.key) due to error: \(reason) (\(statusCode)).") - } - else { - SNLog("Couldn't update expiry from: \(next.key).") - } - return - } - - /// Signature of `( PUBKEY_HEX || EXPIRY || UPDATED[0] || ... || UPDATED[N] )` - /// signed by the node's ed25519 pubkey. When doing a multi-namespace delete the `UPDATED` - /// values are totally ordered (i.e. among all the hashes deleted regardless of namespace) - let verificationBytes: [UInt8] = userX25519PublicKey.bytes - .appending(contentsOf: "\(expiryMs)".data(using: .ascii)?.bytes) - .appending(contentsOf: next.value.updated.joined().bytes) - - let isValid: Bool = sodium.sign.verify( - message: verificationBytes, - publicKey: Data(hex: next.key).bytes, - signature: encodedSignature.bytes - ) - - // If the update signature is invalid then we want to fail here - guard isValid else { throw SnodeAPIError.signatureVerificationFailed } - - result[next.key] = next.value.updated - } - } -} +public class UpdateExpiryAllResponse: SnodeRecursiveResponse {} // MARK: - SwarmItem @@ -83,3 +40,57 @@ public extension UpdateExpiryAllResponse { } } } + +// MARK: - ValidatableResponse + +extension UpdateExpiryAllResponse: ValidatableResponse { + typealias ValidationData = UInt64 + typealias ValidationResponse = [String] + + /// All responses in the swarm must be valid + internal static var requiredSuccessfulResponses: Int { -1 } + + internal func validResultMap( + sodium: Sodium, + userX25519PublicKey: String, + validationData: UInt64 + ) throws -> [String: [String]] { + let validationMap: [String: [String]] = try swarm.reduce(into: [:]) { result, next in + guard + !next.value.failed, + let signatureBase64: String = next.value.signatureBase64, + let encodedSignature: Data = Data(base64Encoded: signatureBase64) + else { + result[next.key] = [] + + if let reason: String = next.value.reason, let statusCode: Int = next.value.code { + SNLog("Couldn't update expiry from: \(next.key) due to error: \(reason) (\(statusCode)).") + } + else { + SNLog("Couldn't update expiry from: \(next.key).") + } + return + } + + /// Signature of `( PUBKEY_HEX || EXPIRY || UPDATED[0] || ... || UPDATED[N] )` + /// signed by the node's ed25519 pubkey. When doing a multi-namespace delete the `UPDATED` + /// values are totally ordered (i.e. among all the hashes deleted regardless of namespace) + let verificationBytes: [UInt8] = userX25519PublicKey.bytes + .appending(contentsOf: "\(validationData)".data(using: .ascii)?.bytes) + .appending(contentsOf: next.value.updated.joined().bytes) + + let isValid: Bool = sodium.sign.verify( + message: verificationBytes, + publicKey: Data(hex: next.key).bytes, + signature: encodedSignature.bytes + ) + + // If the update signature is invalid then we want to fail here + guard isValid else { throw SnodeAPIError.signatureVerificationFailed } + + result[next.key] = next.value.updated + } + + return try Self.validated(map: validationMap, totalResponseCount: swarm.count) + } +} diff --git a/SessionSnodeKit/Models/UpdateExpiryRequest.swift b/SessionSnodeKit/Models/UpdateExpiryRequest.swift index 4add9bd5d..388b7a964 100644 --- a/SessionSnodeKit/Models/UpdateExpiryRequest.swift +++ b/SessionSnodeKit/Models/UpdateExpiryRequest.swift @@ -7,16 +7,37 @@ extension SnodeAPI { enum CodingKeys: String, CodingKey { case messageHashes = "messages" case expiryMs = "expiry" + case shorten + case extend } + /// Array of message hash strings (as provided by the storage server) to update. Messages can be from any namespace(s) let messageHashes: [String] + + /// The new expiry timestamp (milliseconds since unix epoch). Must be >= 60s ago. The new expiry can be anywhere from + /// current time up to the maximum TTL (30 days) from now; specifying a later timestamp will be truncated to the maximum let expiryMs: UInt64 + /// If provided and set to true then the expiry is only shortened, but not extended. If the expiry is already at or before the given + /// `expiry` timestamp then expiry will not be changed + /// + /// **Note:** This option is only supported starting at network version 19.3). This option is not permitted when using + /// subkey authentication + let shorten: Bool? + + /// If provided and set to true then the expiry is only extended, but not shortened. If the expiry is already at or beyond + /// the given `expiry` timestamp then expiry will not be changed + /// + /// **Note:** This option is only supported starting at network version 19.3. This option is mutually exclusive of "shorten" + let extend: Bool? + // MARK: - Init public init( messageHashes: [String], expiryMs: UInt64, + shorten: Bool? = nil, + extend: Bool? = nil, pubkey: String, ed25519PublicKey: [UInt8], ed25519SecretKey: [UInt8], @@ -24,6 +45,8 @@ extension SnodeAPI { ) { self.messageHashes = messageHashes self.expiryMs = expiryMs + self.shorten = shorten + self.extend = extend super.init( pubkey: pubkey, @@ -40,6 +63,8 @@ extension SnodeAPI { try container.encode(messageHashes, forKey: .messageHashes) try container.encode(expiryMs, forKey: .expiryMs) + try container.encodeIfPresent(shorten, forKey: .shorten) + try container.encodeIfPresent(extend, forKey: .extend) try super.encode(to: encoder) } diff --git a/SessionSnodeKit/Models/UpdateExpiryResponse.swift b/SessionSnodeKit/Models/UpdateExpiryResponse.swift index 41f7a5ede..523a842bf 100644 --- a/SessionSnodeKit/Models/UpdateExpiryResponse.swift +++ b/SessionSnodeKit/Models/UpdateExpiryResponse.swift @@ -4,18 +4,52 @@ import Foundation import Sodium import SessionUtilitiesKit -public class UpdateExpiryResponse: SnodeRecursiveResponse { - // MARK: - Convenience +public class UpdateExpiryResponse: SnodeRecursiveResponse {} + +// MARK: - SwarmItem + +public extension UpdateExpiryResponse { + class SwarmItem: SnodeSwarmItem { + private enum CodingKeys: String, CodingKey { + case updated + case expiry + } + + public let updated: [String] + public let expiry: UInt64? + + // MARK: - Initialization + + required init(from decoder: Decoder) throws { + let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) + + updated = ((try? container.decode([String].self, forKey: .updated)) ?? []) + expiry = try? container.decode(UInt64.self, forKey: .expiry) + + try super.init(from: decoder) + } + } +} + +// MARK: - ValidatableResponse + +extension UpdateExpiryResponse: ValidatableResponse { + typealias ValidationData = [String] + typealias ValidationResponse = (hashes: [String], expiry: UInt64) + + /// All responses in the swarm must be valid + internal static var requiredSuccessfulResponses: Int { -1 } internal func validResultMap( + sodium: Sodium, userX25519PublicKey: String, - messageHashes: [String], - sodium: Sodium + validationData: [String] ) throws -> [String: (hashes: [String], expiry: UInt64)] { - return try swarm.reduce(into: [:]) { result, next in + let validationMap: [String: (hashes: [String], expiry: UInt64)] = try swarm.reduce(into: [:]) { result, next in guard !next.value.failed, - let encodedSignature: Data = Data(base64Encoded: next.value.signatureBase64) + let signatureBase64: String = next.value.signatureBase64, + let encodedSignature: Data = Data(base64Encoded: signatureBase64) else { result[next.key] = ([], 0) @@ -33,8 +67,8 @@ public class UpdateExpiryResponse: SnodeRecursiveResponse = try decoder.container(keyedBy: CodingKeys.self) - - updated = ((try? container.decode([String].self, forKey: .updated)) ?? []) - expiry = try container.decode(UInt64.self, forKey: .expiry) - - try super.init(from: decoder) + // If we didn't get an `expiry` value from the snode then don't bother adding it to the result + // as it's not valid data + guard let expiry: UInt64 = next.value.expiry else { return } + + result[next.key] = (hashes: next.value.updated, expiry: expiry) } + + return try Self.validated(map: validationMap, totalResponseCount: swarm.count) } } diff --git a/SessionSnodeKit/Networking/OnionRequestAPI+Encryption.swift b/SessionSnodeKit/Networking/OnionRequestAPI+Encryption.swift index 86f25c0fd..36aa1c995 100644 --- a/SessionSnodeKit/Networking/OnionRequestAPI+Encryption.swift +++ b/SessionSnodeKit/Networking/OnionRequestAPI+Encryption.swift @@ -33,16 +33,8 @@ internal extension OnionRequestAPI { case .snode(let snode): // Need to wrap the payload for snode requests return encode(ciphertext: payload, json: [ "headers" : "" ]) - .flatMap { data -> AnyPublisher in - do { - return Just(try AES.GCM.encrypt(data, for: snode.x25519PublicKey)) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - } - catch { - return Fail(error: error) - .eraseToAnyPublisher() - } + .tryMap { data -> AES.GCM.EncryptionResult in + try AES.GCM.encrypt(data, for: snode.x25519PublicKey) } .eraseToAnyPublisher() @@ -89,17 +81,7 @@ internal extension OnionRequestAPI { }() return encode(ciphertext: previousEncryptionResult.ciphertext, json: parameters) - .flatMap { data -> AnyPublisher in - do { - return Just(try AES.GCM.encrypt(data, for: x25519PublicKey)) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - } - catch (let error) { - return Fail(error: error) - .eraseToAnyPublisher() - } - } + .tryMap { data -> AES.GCM.EncryptionResult in try AES.GCM.encrypt(data, for: x25519PublicKey) } .eraseToAnyPublisher() } } diff --git a/SessionSnodeKit/Networking/OnionRequestAPI.swift b/SessionSnodeKit/Networking/OnionRequestAPI.swift index 34ba3dab6..538733eea 100644 --- a/SessionSnodeKit/Networking/OnionRequestAPI.swift +++ b/SessionSnodeKit/Networking/OnionRequestAPI.swift @@ -72,25 +72,20 @@ public enum OnionRequestAPI: OnionRequestAPIType { return HTTP.execute(.get, url, timeout: timeout) .subscribe(on: DispatchQueue.global(qos: .userInitiated)) - .flatMap { responseData -> AnyPublisher in + .tryMap { responseData -> Void in // TODO: Remove JSON usage guard let responseJson: JSON = try? JSONSerialization.jsonObject(with: responseData, options: [ .fragmentsAllowed ]) as? JSON else { - return Fail(error: HTTPError.invalidJSON) - .eraseToAnyPublisher() + throw HTTPError.invalidJSON } guard let version = responseJson["version"] as? String else { - return Fail(error: OnionRequestAPIError.missingSnodeVersion) - .eraseToAnyPublisher() + throw OnionRequestAPIError.missingSnodeVersion } guard version >= "2.0.7" else { SNLog("Unsupported snode version: \(version).") - return Fail(error: OnionRequestAPIError.unsupportedSnodeVersion(version)) - .eraseToAnyPublisher() + throw OnionRequestAPIError.unsupportedSnodeVersion(version) } - return Just(()) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() + return () } .eraseToAnyPublisher() } diff --git a/SessionSnodeKit/Networking/SnodeAPI.swift b/SessionSnodeKit/Networking/SnodeAPI.swift index b78546548..9ba2c851d 100644 --- a/SessionSnodeKit/Networking/SnodeAPI.swift +++ b/SessionSnodeKit/Networking/SnodeAPI.swift @@ -73,19 +73,16 @@ public final class SnodeAPI { hasLoadedSnodePool.mutate { $0 = true } } - private static func setSnodePool(to newValue: Set, db: Database? = nil) { + private static func setSnodePool(_ db: Database? = nil, to newValue: Set) { + guard let db: Database = db else { + Storage.shared.write { db in setSnodePool(db, to: newValue) } + return + } + snodePool.mutate { $0 = newValue } - if let db: Database = db { - _ = try? Snode.deleteAll(db) - newValue.forEach { try? $0.save(db) } - } - else { - Storage.shared.write { db in - _ = try? Snode.deleteAll(db) - newValue.forEach { try? $0.save(db) } - } - } + _ = try? Snode.deleteAll(db) + newValue.forEach { try? $0.save(db) } } private static func dropSnodeFromSnodePool(_ snode: Snode) { @@ -172,16 +169,13 @@ public final class SnodeAPI { getSnodePoolPublisher.mutate { $0 = publisher } return publisher - .flatMap { snodePool -> AnyPublisher, Error> in - guard !snodePool.isEmpty else { - return Fail(error: SnodeAPIError.snodePoolUpdatingFailed) - .eraseToAnyPublisher() - } + .tryFlatMap { snodePool -> AnyPublisher, Error> in + guard !snodePool.isEmpty else { throw SnodeAPIError.snodePoolUpdatingFailed } return Storage.shared - .writePublisher { db in + .writePublisher(receiveOn: Threading.workQueue) { db in db[.lastSnodePoolRefreshDate] = now - setSnodePool(to: snodePool, db: db) + setSnodePool(db, to: snodePool) return snodePool } @@ -233,22 +227,12 @@ public final class SnodeAPI { associatedWith: nil ) .decoded(as: ONSResolveResponse.self) - .flatMap { _, response -> AnyPublisher in - do { - let result: String = try response.sessionId( - sodium: sodium.wrappedValue, - nameBytes: nameAsData, - nameHashBytes: nameHash - ) - - return Just(result) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - } - catch { - return Fail(error: error) - .eraseToAnyPublisher() - } + .tryMap { _, response -> String in + try response.sessionId( + sodium: sodium.wrappedValue, + nameBytes: nameAsData, + nameHashBytes: nameHash + ) } .retry(4) .eraseToAnyPublisher() @@ -257,15 +241,12 @@ public final class SnodeAPI { ) .subscribe(on: Threading.workQueue) .collect() - .flatMap { results -> AnyPublisher in + .tryMap { results -> String in guard results.count == validationCount, Set(results).count == 1 else { - return Fail(error: SnodeAPIError.validationFailed) - .eraseToAnyPublisher() + throw SnodeAPIError.validationFailed } - return Just(results[0]) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() + return results[0] } .eraseToAnyPublisher() } @@ -311,99 +292,124 @@ public final class SnodeAPI { // MARK: - Retrieve - public static func getMessages( - in namespaces: [SnodeAPI.Namespace], + public static func poll( + namespaces: [SnodeAPI.Namespace], + refreshingConfigHashes: [String] = [], from snode: Snode, associatedWith publicKey: String, using dependencies: SSKDependencies = SSKDependencies() ) -> AnyPublisher<[SnodeAPI.Namespace: (info: ResponseInfoType, data: (messages: [SnodeReceivedMessage], lastHash: String?)?)], Error> { + guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else { + return Fail(error: SnodeAPIError.noKeyPair) + .eraseToAnyPublisher() + } + + let userX25519PublicKey: String = getUserHexEncodedPublicKey() let targetPublicKey: String = (Features.useTestnet ? publicKey.removingIdPrefixIfNeeded() : publicKey ) - var userED25519KeyPair: Box.KeyPair? return Just(()) .setFailureType(to: Error.self) - .flatMap { _ -> Future<[SnodeAPI.Namespace: String], Error> in - Future<[SnodeAPI.Namespace: String], Error> { resolver in - let namespaceLastHash: [SnodeAPI.Namespace: String] = namespaces - .reduce(into: [:]) { result, namespace in - // Prune expired message hashes for this namespace on this service node - SnodeReceivedMessageInfo.pruneExpiredMessageHashInfo( + .map { _ -> [SnodeAPI.Namespace: String] in + namespaces + .reduce(into: [:]) { result, namespace in + // Prune expired message hashes for this namespace on this service node + SnodeReceivedMessageInfo.pruneExpiredMessageHashInfo( + for: snode, + namespace: namespace, + associatedWith: publicKey + ) + + let maybeLastHash: String? = SnodeReceivedMessageInfo + .fetchLastNotExpired( for: snode, namespace: namespace, associatedWith: publicKey - ) - - let maybeLastHash: String? = SnodeReceivedMessageInfo - .fetchLastNotExpired( - for: snode, - namespace: namespace, - associatedWith: publicKey - )? - .hash - - guard let lastHash: String = maybeLastHash else { return } - - result[namespace] = lastHash - } - - resolver(Result.success(namespaceLastHash)) - } + )? + .hash + + guard let lastHash: String = maybeLastHash else { return } + + result[namespace] = lastHash + } } .flatMap { namespaceLastHash -> AnyPublisher<[SnodeAPI.Namespace: (info: ResponseInfoType, data: (messages: [SnodeReceivedMessage], lastHash: String?)?)], Error> in - let requests: [SnodeAPI.BatchRequest.Info] - - do { - requests = try namespaces - .map { namespace -> SnodeAPI.BatchRequest.Info in - // Check if this namespace requires authentication - guard namespace.requiresReadAuthentication else { - return BatchRequest.Info( - request: SnodeRequest( - endpoint: .getMessages, - body: LegacyGetMessagesRequest( - pubkey: targetPublicKey, - lastHash: (namespaceLastHash[namespace] ?? ""), - namespace: namespace - ) + var requests: [SnodeAPI.BatchRequest.Info] = [] + + // If we have any config hashes to refresh TTLs then add those requests first + if !refreshingConfigHashes.isEmpty { + requests.append( + BatchRequest.Info( + request: SnodeRequest( + endpoint: .expire, + body: UpdateExpiryRequest( + messageHashes: refreshingConfigHashes, + expiryMs: UInt64( + SnodeAPI.currentOffsetTimestampMs() + + (30 * 24 * 60 * 60 * 1000) // 30 days ), - responseType: GetMessagesResponse.self + extend: true, + pubkey: userX25519PublicKey, + ed25519PublicKey: userED25519KeyPair.publicKey, + ed25519SecretKey: userED25519KeyPair.secretKey, + subkey: nil // TODO: Need to get this ) - } - - // Generate the signature - guard let keyPair: Box.KeyPair = (userED25519KeyPair ?? Storage.shared.read { db in Identity.fetchUserEd25519KeyPair(db) }) else { - throw SnodeAPIError.signingFailed - } - - userED25519KeyPair = keyPair - + ), + responseType: UpdateExpiryResponse.self + ) + ) + } + + // Determine the maxSize each namespace in the request should take up + let namespaceMaxSizeMap: [SnodeAPI.Namespace: Int64] = SnodeAPI.Namespace.maxSizeMap(for: namespaces) + let fallbackSize: Int64 = (namespaceMaxSizeMap.values.min() ?? 1) + + // Add the various 'getMessages' requests + requests.append( + contentsOf: namespaces.map { namespace -> SnodeAPI.BatchRequest.Info in + // Check if this namespace requires authentication + guard namespace.requiresReadAuthentication else { return BatchRequest.Info( request: SnodeRequest( endpoint: .getMessages, - body: GetMessagesRequest( + body: LegacyGetMessagesRequest( + pubkey: targetPublicKey, lastHash: (namespaceLastHash[namespace] ?? ""), namespace: namespace, - pubkey: targetPublicKey, - subkey: nil, - timestampMs: UInt64(SnodeAPI.currentOffsetTimestampMs()), - ed25519PublicKey: keyPair.publicKey, - ed25519SecretKey: keyPair.secretKey + maxCount: nil, + maxSize: namespaceMaxSizeMap[namespace] + .defaulting(to: fallbackSize) ) ), responseType: GetMessagesResponse.self ) } - } - catch { - return Fail(error: error) - .eraseToAnyPublisher() - } - + + return BatchRequest.Info( + request: SnodeRequest( + endpoint: .getMessages, + body: GetMessagesRequest( + lastHash: (namespaceLastHash[namespace] ?? ""), + namespace: namespace, + pubkey: targetPublicKey, + subkey: nil, // TODO: Need to get this + timestampMs: UInt64(SnodeAPI.currentOffsetTimestampMs()), + ed25519PublicKey: userED25519KeyPair.publicKey, + ed25519SecretKey: userED25519KeyPair.secretKey, + maxSize: namespaceMaxSizeMap[namespace] + .defaulting(to: fallbackSize) + ) + ), + responseType: GetMessagesResponse.self + ) + } + ) + + // Actually send the request let responseTypes = requests.map { $0.responseType } - + return SnodeAPI .send( request: SnodeRequest( @@ -416,19 +422,17 @@ public final class SnodeAPI { ) .decoded(as: responseTypes, using: dependencies) .map { batchResponse -> [SnodeAPI.Namespace: (info: ResponseInfoType, data: (messages: [SnodeReceivedMessage], lastHash: String?)?)] in - zip(namespaces, batchResponse.responses) + let messageResponses: [HTTP.BatchSubResponse] = batchResponse.responses + .compactMap { $0 as? HTTP.BatchSubResponse } + + return zip(namespaces, messageResponses) .reduce(into: [:]) { result, next in - guard - let subResponse: HTTP.BatchSubResponse = (next.1 as? HTTP.BatchSubResponse), - let messageResponse: GetMessagesResponse = subResponse.body - else { - return - } + guard let messageResponse: GetMessagesResponse = next.1.body else { return } let namespace: SnodeAPI.Namespace = next.0 result[namespace] = ( - info: subResponse.responseInfo, + info: next.1.responseInfo, data: ( messages: messageResponse.messages .compactMap { rawMessage -> SnodeReceivedMessage? in @@ -449,6 +453,112 @@ public final class SnodeAPI { .eraseToAnyPublisher() } + /// **Note:** This is the direct request to retrieve messages so should be retrieved automatically from the `poll()` method, in order to call + /// this directly remove the `@available` line + @available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead") + public static func getMessages( + in namespace: SnodeAPI.Namespace, + from snode: Snode, + associatedWith publicKey: String, + using dependencies: SSKDependencies = SSKDependencies() + ) -> AnyPublisher<(info: ResponseInfoType, data: (messages: [SnodeReceivedMessage], lastHash: String?)?), Error> { + let targetPublicKey: String = (Features.useTestnet ? + publicKey.removingIdPrefixIfNeeded() : + publicKey + ) + + return Deferred { + Future { resolver in + // Prune expired message hashes for this namespace on this service node + SnodeReceivedMessageInfo.pruneExpiredMessageHashInfo( + for: snode, + namespace: namespace, + associatedWith: publicKey + ) + + let maybeLastHash: String? = SnodeReceivedMessageInfo + .fetchLastNotExpired( + for: snode, + namespace: namespace, + associatedWith: publicKey + )? + .hash + + resolver(Result.success(maybeLastHash)) + } + } + .tryFlatMap { lastHash -> AnyPublisher<(info: ResponseInfoType, data: GetMessagesResponse?, lastHash: String?), Error> in + + guard namespace.requiresWriteAuthentication else { + return SnodeAPI + .send( + request: SnodeRequest( + endpoint: .getMessages, + body: LegacyGetMessagesRequest( + pubkey: targetPublicKey, + lastHash: (lastHash ?? ""), + namespace: namespace, + maxCount: nil, + maxSize: nil + ) + ), + to: snode, + associatedWith: publicKey, + using: dependencies + ) + .decoded(as: GetMessagesResponse.self, using: dependencies) + .map { info, data in (info, data, lastHash) } + .eraseToAnyPublisher() + } + + guard let userED25519KeyPair: Box.KeyPair = Storage.shared.read({ db in Identity.fetchUserEd25519KeyPair(db) }) else { + throw SnodeAPIError.noKeyPair + } + + return SnodeAPI + .send( + request: SnodeRequest( + endpoint: .getMessages, + body: GetMessagesRequest( + lastHash: (lastHash ?? ""), + namespace: namespace, + pubkey: targetPublicKey, + subkey: nil, + timestampMs: UInt64(SnodeAPI.currentOffsetTimestampMs()), + ed25519PublicKey: userED25519KeyPair.publicKey, + ed25519SecretKey: userED25519KeyPair.secretKey + ) + ), + to: snode, + associatedWith: publicKey, + using: dependencies + ) + .decoded(as: GetMessagesResponse.self, using: dependencies) + .map { info, data in (info, data, lastHash) } + .eraseToAnyPublisher() + } + .map { info, data, lastHash -> (info: ResponseInfoType, data: (messages: [SnodeReceivedMessage], lastHash: String?)?) in + return ( + info: info, + data: data.map { messageResponse -> (messages: [SnodeReceivedMessage], lastHash: String?) in + return ( + messages: messageResponse.messages + .compactMap { rawMessage -> SnodeReceivedMessage? in + SnodeReceivedMessage( + snode: snode, + publicKey: publicKey, + namespace: namespace, + rawMessage: rawMessage + ) + }, + lastHash: lastHash + ) + } + ) + } + .eraseToAnyPublisher() + } + // MARK: - Store public static func sendMessage( @@ -460,9 +570,11 @@ public final class SnodeAPI { message.recipient.removingIdPrefixIfNeeded() : message.recipient ) + let userX25519PublicKey: String = getUserHexEncodedPublicKey() + let sendTimestamp: UInt64 = UInt64(SnodeAPI.currentOffsetTimestampMs()) // Create a convenience method to send a message to an individual Snode - func sendMessage(to snode: Snode) -> AnyPublisher<(any ResponseInfoType, SendMessagesResponse), Error> { + func sendMessage(to snode: Snode) throws -> AnyPublisher<(any ResponseInfoType, SendMessagesResponse), Error> { guard namespace.requiresWriteAuthentication else { return SnodeAPI .send( @@ -482,8 +594,7 @@ public final class SnodeAPI { } guard let userED25519KeyPair: Box.KeyPair = Storage.shared.read({ db in Identity.fetchUserEd25519KeyPair(db) }) else { - return Fail(error: SnodeAPIError.noKeyPair) - .eraseToAnyPublisher() + throw SnodeAPIError.noKeyPair } return SnodeAPI @@ -494,7 +605,7 @@ public final class SnodeAPI { message: message, namespace: namespace, subkey: nil, - timestampMs: UInt64(SnodeAPI.currentOffsetTimestampMs()), + timestampMs: sendTimestamp, ed25519PublicKey: userED25519KeyPair.publicKey, ed25519SecretKey: userED25519KeyPair.secretKey ) @@ -508,14 +619,16 @@ public final class SnodeAPI { } return getSwarm(for: publicKey) - .subscribe(on: Threading.workQueue) - .flatMap { swarm -> AnyPublisher<(ResponseInfoType, SendMessagesResponse), Error> in - guard let snode: Snode = swarm.randomElement() else { - return Fail(error: SnodeAPIError.generic) - .eraseToAnyPublisher() - } + .tryFlatMap { swarm -> AnyPublisher<(ResponseInfoType, SendMessagesResponse), Error> in + guard let snode: Snode = swarm.randomElement() else { throw SnodeAPIError.generic } - return sendMessage(to: snode) + return try sendMessage(to: snode) + .tryMap { info, response -> (ResponseInfoType, SendMessagesResponse) in + try response.validateResultMap( + sodium: sodium.wrappedValue, + userX25519PublicKey: userX25519PublicKey + ) + } .retry(maxRetryCount) .eraseToAnyPublisher() } @@ -600,12 +713,8 @@ public final class SnodeAPI { let responseTypes = requests.map { $0.responseType } return getSwarm(for: publicKey) - .subscribe(on: Threading.workQueue) - .flatMap { swarm -> AnyPublisher in - guard let snode: Snode = swarm.randomElement() else { - return Fail(error: SnodeAPIError.generic) - .eraseToAnyPublisher() - } + .tryFlatMap { swarm -> AnyPublisher in + guard let snode: Snode = swarm.randomElement() else { throw SnodeAPIError.generic } return SnodeAPI .send( @@ -645,11 +754,8 @@ public final class SnodeAPI { return getSwarm(for: publicKey) .subscribe(on: Threading.workQueue) - .flatMap { swarm -> AnyPublisher<[String: (hashes: [String], expiry: UInt64)], Error> in - guard let snode: Snode = swarm.randomElement() else { - return Fail(error: SnodeAPIError.generic) - .eraseToAnyPublisher() - } + .tryFlatMap { swarm -> AnyPublisher<[String: (hashes: [String], expiry: UInt64)], Error> in + guard let snode: Snode = swarm.randomElement() else { throw SnodeAPIError.generic } return SnodeAPI .send( @@ -669,22 +775,12 @@ public final class SnodeAPI { using: dependencies ) .decoded(as: UpdateExpiryResponse.self, using: dependencies) - .flatMap { _, response -> AnyPublisher<[String: (hashes: [String], expiry: UInt64)], Error> in - do { - let result: [String: (hashes: [String], expiry: UInt64)] = try response.validResultMap( - userX25519PublicKey: getUserHexEncodedPublicKey(), - messageHashes: serverHashes, - sodium: sodium.wrappedValue - ) - - return Just(result) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - } - catch { - return Fail(error: error) - .eraseToAnyPublisher() - } + .tryMap { _, response -> [String: (hashes: [String], expiry: UInt64)] in + try response.validResultMap( + sodium: sodium.wrappedValue, + userX25519PublicKey: getUserHexEncodedPublicKey(), + validationData: serverHashes + ) } .retry(maxRetryCount) .eraseToAnyPublisher() @@ -710,11 +806,8 @@ public final class SnodeAPI { return getSwarm(for: publicKey) .subscribe(on: Threading.workQueue) - .flatMap { swarm -> AnyPublisher in - guard let snode: Snode = swarm.randomElement() else { - return Fail(error: SnodeAPIError.generic) - .eraseToAnyPublisher() - } + .tryFlatMap { swarm -> AnyPublisher in + guard let snode: Snode = swarm.randomElement() else { throw SnodeAPIError.generic } return SnodeAPI .send( @@ -732,22 +825,14 @@ public final class SnodeAPI { using: dependencies ) .decoded(as: RevokeSubkeyResponse.self, using: dependencies) - .flatMap { _, response -> AnyPublisher in - do { - try response.validateResult( - userX25519PublicKey: getUserHexEncodedPublicKey(), - subkeyToRevoke: subkeyToRevoke, - sodium: sodium.wrappedValue - ) - } - catch { - return Fail(error: error) - .eraseToAnyPublisher() - } + .tryMap { _, response -> Void in + try response.validateResultMap( + sodium: sodium.wrappedValue, + userX25519PublicKey: getUserHexEncodedPublicKey(), + validationData: subkeyToRevoke + ) - return Just(()) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() + return () } .retry(maxRetryCount) .eraseToAnyPublisher() @@ -779,15 +864,10 @@ public final class SnodeAPI { .flatMap { swarm -> AnyPublisher<[String: Bool], Error> in Just(()) .setFailureType(to: Error.self) - .flatMap { _ -> AnyPublisher in - guard let snode: Snode = swarm.randomElement() else { - return Fail(error: SnodeAPIError.generic) - .eraseToAnyPublisher() - } + .tryMap { _ -> Snode in + guard let snode: Snode = swarm.randomElement() else { throw SnodeAPIError.generic } - return Just(snode) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() + return snode } .flatMap { snode -> AnyPublisher<[String: Bool], Error> in SnodeAPI @@ -809,24 +889,22 @@ public final class SnodeAPI { .subscribe(on: Threading.workQueue) .eraseToAnyPublisher() .decoded(as: DeleteMessagesResponse.self, using: dependencies) - .map { _, response -> [String: Bool] in - let validResultMap: [String: Bool] = response.validResultMap( + .tryMap { _, response -> [String: Bool] in + let validResultMap: [String: Bool] = try response.validResultMap( + sodium: sodium.wrappedValue, userX25519PublicKey: userX25519PublicKey, - serverHashes: serverHashes, - sodium: sodium.wrappedValue + validationData: serverHashes ) - // If at least one service node deleted successfully then we should - // mark the hash as invalid so we don't try to fetch updates using - // that hash going forward (if we do we would end up re-fetching - // all old messages) - if validResultMap.values.contains(true) { - Storage.shared.writeAsync { db in - try? SnodeReceivedMessageInfo.handlePotentialDeletedOrInvalidHash( - db, - potentiallyInvalidHashes: serverHashes - ) - } + // If `validResultMap` didn't throw then at least one service node + // deleted successfully so we should mark the hash as invalid so we + // don't try to fetch updates using that hash going forward (if we + // do we would end up re-fetching all old messages) + Storage.shared.writeAsync { db in + try? SnodeReceivedMessageInfo.handlePotentialDeletedOrInvalidHash( + db, + potentiallyInvalidHashes: serverHashes + ) } return validResultMap @@ -854,11 +932,8 @@ public final class SnodeAPI { return getSwarm(for: userX25519PublicKey) .subscribe(on: Threading.workQueue) - .flatMap { swarm -> AnyPublisher<[String: Bool], Error> in - guard let snode: Snode = swarm.randomElement() else { - return Fail(error: SnodeAPIError.generic) - .eraseToAnyPublisher() - } + .tryFlatMap { swarm -> AnyPublisher<[String: Bool], Error> in + guard let snode: Snode = swarm.randomElement() else { throw SnodeAPIError.generic } return getNetworkTime(from: snode) .flatMap { timestampMs -> AnyPublisher<[String: Bool], Error> in @@ -879,14 +954,12 @@ public final class SnodeAPI { using: dependencies ) .decoded(as: DeleteAllMessagesResponse.self, using: dependencies) - .map { _, response in - let validResultMap: [String: Bool] = response.validResultMap( + .tryMap { _, response -> [String: Bool] in + try response.validResultMap( + sodium: sodium.wrappedValue, userX25519PublicKey: userX25519PublicKey, - timestampMs: timestampMs, - sodium: sodium.wrappedValue + validationData: timestampMs ) - - return validResultMap } .eraseToAnyPublisher() } @@ -912,11 +985,8 @@ public final class SnodeAPI { return getSwarm(for: userX25519PublicKey) .subscribe(on: Threading.workQueue) - .flatMap { swarm -> AnyPublisher<[String: Bool], Error> in - guard let snode: Snode = swarm.randomElement() else { - return Fail(error: SnodeAPIError.generic) - .eraseToAnyPublisher() - } + .tryFlatMap { swarm -> AnyPublisher<[String: Bool], Error> in + guard let snode: Snode = swarm.randomElement() else { throw SnodeAPIError.generic } return getNetworkTime(from: snode) .flatMap { timestampMs -> AnyPublisher<[String: Bool], Error> in @@ -938,14 +1008,12 @@ public final class SnodeAPI { using: dependencies ) .decoded(as: DeleteAllBeforeResponse.self, using: dependencies) - .map { _, response in - let validResultMap: [String: Bool] = response.validResultMap( + .tryMap { _, response -> [String: Bool] in + try response.validResultMap( + sodium: sodium.wrappedValue, userX25519PublicKey: userX25519PublicKey, - beforeMs: beforeMs, - sodium: sodium.wrappedValue + validationData: beforeMs ) - - return validResultMap } .eraseToAnyPublisher() } @@ -1104,20 +1172,15 @@ public final class SnodeAPI { } ) .collect() - .flatMap { results -> AnyPublisher, Error> in + .tryMap { results -> Set in let result: Set = results.reduce(Set()) { prev, next in prev.intersection(next) } // We want the snodes to agree on at least this many snodes - guard result.count > 24 else { - return Fail(error: SnodeAPIError.inconsistentSnodePools) - .eraseToAnyPublisher() - } + guard result.count > 24 else { throw SnodeAPIError.inconsistentSnodePools } // Limit the snode pool size to 256 so that we don't go too long without // refreshing it - return Just(Set(result.prefix(256))) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() + return Set(result.prefix(256)) } .eraseToAnyPublisher() } diff --git a/SessionSnodeKit/Types/SnodeAPIError.swift b/SessionSnodeKit/Types/SnodeAPIError.swift index 07e3c9366..a7ec7050f 100644 --- a/SessionSnodeKit/Types/SnodeAPIError.swift +++ b/SessionSnodeKit/Types/SnodeAPIError.swift @@ -12,6 +12,7 @@ public enum SnodeAPIError: LocalizedError { case signatureVerificationFailed case invalidIP case emptySnodePool + case responseFailedValidation // ONS case decryptionFailed @@ -29,6 +30,7 @@ public enum SnodeAPIError: LocalizedError { case .signatureVerificationFailed: return "Failed to verify the signature." case .invalidIP: return "Invalid IP." case .emptySnodePool: return "Service Node pool is empty." + case .responseFailedValidation: return "Response failed validation." // ONS case .decryptionFailed: return "Couldn't decrypt ONS name." diff --git a/SessionSnodeKit/Types/SnodeAPINamespace.swift b/SessionSnodeKit/Types/SnodeAPINamespace.swift index d5f559217..84ee98a3e 100644 --- a/SessionSnodeKit/Types/SnodeAPINamespace.swift +++ b/SessionSnodeKit/Types/SnodeAPINamespace.swift @@ -9,7 +9,7 @@ public extension SnodeAPI { case configUserProfile = 2 case configContacts = 3 case configConvoInfoVolatile = 4 - case configGroups = 5 + case configUserGroups = 5 case configClosedGroupInfo = 11 case legacyClosedGroup = -10 @@ -37,5 +37,52 @@ public extension SnodeAPI { default: return "\(self.rawValue)" } } + + /// When performing a batch request we want to try to use the amount of data available in the response as effectively as possible + /// this priority allows us to split the response effectively between the number of namespaces we are requesting from where + /// namespaces with the same priority will be given the same response size divider, for example: + /// ``` + /// default = 1 + /// config1, config2 = 2 + /// config3, config4 = 3 + /// + /// Response data split: + /// _____________________________ + /// | | + /// | default | + /// |_____________________________| + /// | | | config3 | + /// | config1 | config2 | config4 | + /// |_________|_________|_________| + /// + var batchRequestSizePriority: Int64 { + switch self { + case .`default`, .legacyClosedGroup: return 10 + + case .configUserProfile, .configContacts, + .configConvoInfoVolatile, .configUserGroups, + .configClosedGroupInfo: + return 1 + } + } + + static func maxSizeMap(for namespaces: [Namespace]) -> [Namespace: Int64] { + var lastSplit: Int64 = 1 + let namespacePriorityGroups: [Int64: [Namespace]] = namespaces + .grouped { $0.batchRequestSizePriority } + let lowestPriority: Int64 = (namespacePriorityGroups.keys.min() ?? 1) + + return namespacePriorityGroups + .map { $0 } + .sorted(by: { lhs, rhs -> Bool in lhs.key > rhs.key }) + .flatMap { priority, namespaces -> [(namespace: Namespace, maxSize: Int64)] in + lastSplit *= Int64(namespaces.count + (priority == lowestPriority ? 0 : 1)) + + return namespaces.map { ($0, lastSplit) } + } + .reduce(into: [:]) { result, next in + result[next.namespace] = -next.maxSize + } + } } } diff --git a/SessionSnodeKit/Types/ValidatableResponse.swift b/SessionSnodeKit/Types/ValidatableResponse.swift new file mode 100644 index 000000000..67e573a37 --- /dev/null +++ b/SessionSnodeKit/Types/ValidatableResponse.swift @@ -0,0 +1,86 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import Sodium + +internal protocol ValidatableResponse { + associatedtype ValidationData + associatedtype ValidationResponse + + /// This valid controls the number of successful responses for a response to be considered "valid", a + /// positive number indicates an exact number of responses required whereas a negative number indicates + /// a dividing factor, eg. + /// 2 = Two nodes need to have returned success responses + /// -2 = 50% of the nodes need to have returned success responses + /// -4 = 25% of the nodes need to have returned success responses + static var requiredSuccessfulResponses: Int { get } + + static func validated( + map validResultMap: [String: ValidationResponse], + totalResponseCount: Int + ) throws -> [String: ValidationResponse] + + func validResultMap( + sodium: Sodium, + userX25519PublicKey: String, + validationData: ValidationData + ) throws -> [String: ValidationResponse] + + func validateResultMap(sodium: Sodium, userX25519PublicKey: String, validationData: ValidationData) throws +} + +// MARK: - Convenience + +internal extension ValidatableResponse { + func validateResultMap(sodium: Sodium, userX25519PublicKey: String, validationData: ValidationData) throws { + _ = try validResultMap( + sodium: sodium, + userX25519PublicKey: userX25519PublicKey, + validationData: validationData + ) + } + + static func validated( + map validResultMap: [String: ValidationResponse], + totalResponseCount: Int + ) throws -> [String: ValidationResponse] { + let numSuccessResponses: Int = validResultMap.count + let successPercentage: CGFloat = (CGFloat(numSuccessResponses) / CGFloat(totalResponseCount)) + + guard + ( // Positive value is an exact number comparison + Self.requiredSuccessfulResponses >= 0 && + numSuccessResponses >= Self.requiredSuccessfulResponses + ) || ( + // Negative value is a "divisor" for a percentage comparison + Self.requiredSuccessfulResponses < 0 && + successPercentage >= abs(1 / CGFloat(Self.requiredSuccessfulResponses)) + ) + else { throw SnodeAPIError.responseFailedValidation } + + return validResultMap + } +} + +internal extension ValidatableResponse where ValidationData == Void { + func validResultMap(sodium: Sodium, userX25519PublicKey: String) throws -> [String: ValidationResponse] { + return try validResultMap(sodium: sodium, userX25519PublicKey: userX25519PublicKey, validationData: ()) + } + + func validateResultMap(sodium: Sodium, userX25519PublicKey: String) throws { + _ = try validResultMap( + sodium: sodium, + userX25519PublicKey: userX25519PublicKey, + validationData: () + ) + } +} + +internal extension ValidatableResponse where ValidationResponse == Bool { + static func validated(map validResultMap: [String: Bool]) throws -> [String: Bool] { + return try validated( + map: validResultMap.filter { $0.value }, + totalResponseCount: validResultMap.count + ) + } +} diff --git a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift index e88705002..da4ca0e8f 100644 --- a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift +++ b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift @@ -429,14 +429,14 @@ class ThreadSettingsViewModelSpec: QuickSpec { try SessionThread( id: "TestId", - variant: .legacyClosedGroup + variant: .legacyGroup ).insert(db) } viewModel = ThreadSettingsViewModel( dependencies: dependencies, threadId: "TestId", - threadVariant: .legacyClosedGroup, + threadVariant: .legacyGroup, didTriggerSearch: { didTriggerSearchCallbackTriggered = true } @@ -471,14 +471,14 @@ class ThreadSettingsViewModelSpec: QuickSpec { try SessionThread( id: "TestId", - variant: .openGroup + variant: .community ).insert(db) } viewModel = ThreadSettingsViewModel( dependencies: dependencies, threadId: "TestId", - threadVariant: .openGroup, + threadVariant: .community, didTriggerSearch: { didTriggerSearchCallbackTriggered = true } diff --git a/SessionUIKit/Utilities/UIContextualAction+Theming.swift b/SessionUIKit/Utilities/UIContextualAction+Theming.swift index 408ce8d36..c9f2bd151 100644 --- a/SessionUIKit/Utilities/UIContextualAction+Theming.swift +++ b/SessionUIKit/Utilities/UIContextualAction+Theming.swift @@ -87,6 +87,11 @@ public extension UIContextualAction { label.numberOfLines = (title.components(separatedBy: " ").count > 1 ? 2 : 1) label.frame = CGRect( origin: .zero, + // Note: It looks like there is a semi-max width of 68px for images in the swipe actions + // if the image ends up larger then there an odd behaviour can occur where 8/10 times the + // image is scaled down to fit, but ocassionally (primarily if you hide the action and + // immediately swipe to show it again once the cell hits the edge of the screen) the image + // won't be scaled down but will be full size - appearing as if two different images are used size: label.sizeThatFits(CGSize(width: 68, height: 999)) ) label.set(.width, to: label.frame.width) diff --git a/SessionUtilitiesKit/Combine/Publisher+Utilities.swift b/SessionUtilitiesKit/Combine/Publisher+Utilities.swift index 9aef2ea5c..a49341ae4 100644 --- a/SessionUtilitiesKit/Combine/Publisher+Utilities.swift +++ b/SessionUtilitiesKit/Combine/Publisher+Utilities.swift @@ -74,6 +74,25 @@ public extension Publisher { } .eraseToAnyPublisher() } + + func tryFlatMap( + maxPublishers: Subscribers.Demand = .unlimited, + _ transform: @escaping (Self.Output) throws -> P + ) -> AnyPublisher where T == P.Output, P : Publisher, P.Failure == Error { + return self + .mapError { $0 } + .flatMap(maxPublishers: maxPublishers) { output -> AnyPublisher in + do { + return try transform(output) + .eraseToAnyPublisher() + } + catch { + return Fail(error: error) + .eraseToAnyPublisher() + } + } + .eraseToAnyPublisher() + } } // MARK: - Convenience @@ -124,17 +143,7 @@ public extension AnyPublisher where Output == Data, Failure == Error { using dependencies: Dependencies = Dependencies() ) -> AnyPublisher { self - .flatMap { data -> AnyPublisher in - do { - return Just(try data.decoded(as: type, using: dependencies)) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - } - catch { - return Fail(error: error) - .eraseToAnyPublisher() - } - } + .tryMap { data -> R in try data.decoded(as: type, using: dependencies) } .eraseToAnyPublisher() } } @@ -145,21 +154,10 @@ public extension AnyPublisher where Output == (ResponseInfoType, Data?), Failure using dependencies: Dependencies = Dependencies() ) -> AnyPublisher<(ResponseInfoType, R), Error> { self - .flatMap { responseInfo, maybeData -> AnyPublisher<(ResponseInfoType, R), Error> in - guard let data: Data = maybeData else { - return Fail(error: HTTPError.parsingFailed) - .eraseToAnyPublisher() - } + .tryMap { responseInfo, maybeData -> (ResponseInfoType, R) in + guard let data: Data = maybeData else { throw HTTPError.parsingFailed } - do { - return Just((responseInfo, try data.decoded(as: type, using: dependencies))) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - } - catch { - return Fail(error: HTTPError.parsingFailed) - .eraseToAnyPublisher() - } + return (responseInfo, try data.decoded(as: type, using: dependencies)) } .eraseToAnyPublisher() } diff --git a/SessionUtilitiesKit/Database/Storage.swift b/SessionUtilitiesKit/Database/Storage.swift index 1b3e15373..32583c068 100644 --- a/SessionUtilitiesKit/Database/Storage.swift +++ b/SessionUtilitiesKit/Database/Storage.swift @@ -336,23 +336,29 @@ open class Storage { ) } - open func writePublisher(updates: @escaping (Database) throws -> T) -> AnyPublisher { + open func writePublisher( + receiveOn scheduler: S, + updates: @escaping (Database) throws -> T + ) -> AnyPublisher where S: Scheduler { guard isValid, let dbWriter: DatabaseWriter = dbWriter else { return Fail(error: StorageError.databaseInvalid) .eraseToAnyPublisher() } - return dbWriter.writePublisher(updates: updates) + return dbWriter.writePublisher(receiveOn: scheduler, updates: updates) .eraseToAnyPublisher() } - open func readPublisher(value: @escaping (Database) throws -> T) -> AnyPublisher { + open func readPublisher( + receiveOn scheduler: S, + value: @escaping (Database) throws -> T + ) -> AnyPublisher where S: Scheduler { guard isValid, let dbWriter: DatabaseWriter = dbWriter else { return Fail(error: StorageError.databaseInvalid) .eraseToAnyPublisher() } - return dbWriter.readPublisher(value: value) + return dbWriter.readPublisher(receiveOn: scheduler, value: value) .eraseToAnyPublisher() } @@ -417,14 +423,20 @@ open class Storage { // MARK: - Combine Extensions public extension Storage { - func readPublisherFlatMap(value: @escaping (Database) throws -> AnyPublisher) -> AnyPublisher { - return readPublisher(value: value) + func readPublisherFlatMap( + receiveOn scheduler: S, + value: @escaping (Database) throws -> AnyPublisher + ) -> AnyPublisher where S: Scheduler { + return readPublisher(receiveOn: scheduler, value: value) .flatMap { resultPublisher -> AnyPublisher in resultPublisher } .eraseToAnyPublisher() } - func writePublisherFlatMap(updates: @escaping (Database) throws -> AnyPublisher) -> AnyPublisher { - return writePublisher(updates: updates) + func writePublisherFlatMap( + receiveOn scheduler: S, + updates: @escaping (Database) throws -> AnyPublisher + ) -> AnyPublisher where S: Scheduler { + return writePublisher(receiveOn: scheduler, updates: updates) .flatMap { resultPublisher -> AnyPublisher in resultPublisher } .eraseToAnyPublisher() } diff --git a/SessionUtilitiesKit/General/Array+Utilities.swift b/SessionUtilitiesKit/General/Array+Utilities.swift index 85e9cd6d6..cf350e86d 100644 --- a/SessionUtilitiesKit/General/Array+Utilities.swift +++ b/SessionUtilitiesKit/General/Array+Utilities.swift @@ -63,11 +63,3 @@ public extension Array where Element == String { return self.reversed() } } - -public extension Array where Element == CChar { - func nullTerminated() -> [Element] { - guard self.last != CChar(0) else { return self } - - return self.appending(CChar(0)) - } -} diff --git a/SessionUtilitiesKit/General/String+Utilities.swift b/SessionUtilitiesKit/General/String+Utilities.swift index 4294375db..864ddffd5 100644 --- a/SessionUtilitiesKit/General/String+Utilities.swift +++ b/SessionUtilitiesKit/General/String+Utilities.swift @@ -75,20 +75,6 @@ public extension String { } } -// MARK: - Convenience - -public extension String { - /// Initialize with an optional pointer and a spoecific length - init?(pointer: UnsafeRawPointer?, length: Int, encoding: String.Encoding = .utf8) { - guard - let pointer: UnsafeRawPointer = pointer, - let result: String = String(data: Data(bytes: pointer, count: length), encoding: encoding) - else { return nil } - - self = result - } -} - // MARK: - Formatting public extension String { diff --git a/SessionUtilitiesKit/JobRunner/JobRunner.swift b/SessionUtilitiesKit/JobRunner/JobRunner.swift index 7296a0779..ed058a229 100644 --- a/SessionUtilitiesKit/JobRunner/JobRunner.swift +++ b/SessionUtilitiesKit/JobRunner/JobRunner.swift @@ -844,9 +844,27 @@ private final class JobQueue { detailsForCurrentlyRunningJobs.mutate { $0 = $0.setting(nextJob.id, nextJob.details) } SNLog("[JobRunner] \(queueContext) started \(nextJob.variant) job (\(executionType == .concurrent ? "\(numJobsRunning) currently running, " : "")\(numJobsRemaining) remaining)") + /// As it turns out Combine doesn't plat too nicely with concurrent Dispatch Queues, in Combine events are dispatched asynchronously to + /// the queue which means an odd situation can occasionally occur where the `finished` event can actually run before the `output` + /// event - this can result in unexpected behaviours (for more information see https://github.com/groue/GRDB.swift/issues/1334) + /// + /// Due to this if a job is meant to run on a concurrent queue then we actually want to create a temporary serial queue just for the execution + /// of that job + let targetQueue: DispatchQueue = { + guard executionType == .concurrent else { return internalQueue } + + return DispatchQueue( + label: "\(self.queueContext)-serial", + qos: self.qosClass, + attributes: [], + autoreleaseFrequency: .inherit, + target: nil + ) + }() + jobExecutor.run( nextJob, - queue: internalQueue, + queue: targetQueue, success: handleJobSucceeded, failure: handleJobFailed, deferred: handleJobDeferred diff --git a/SessionUtilitiesKit/Networking/BatchResponse.swift b/SessionUtilitiesKit/Networking/BatchResponse.swift index 05c8a77fe..78575a775 100644 --- a/SessionUtilitiesKit/Networking/BatchResponse.swift +++ b/SessionUtilitiesKit/Networking/BatchResponse.swift @@ -85,15 +85,11 @@ public extension AnyPublisher where Output == (ResponseInfoType, Data?), Failure using dependencies: Dependencies = Dependencies() ) -> AnyPublisher { self - .flatMap { responseInfo, maybeData -> AnyPublisher in + .tryMap { responseInfo, maybeData -> HTTP.BatchResponse in // Need to split the data into an array of data so each item can be Decoded correctly - guard let data: Data = maybeData else { - return Fail(error: HTTPError.parsingFailed) - .eraseToAnyPublisher() - } + guard let data: Data = maybeData else { throw HTTPError.parsingFailed } guard let jsonObject: Any = try? JSONSerialization.jsonObject(with: data, options: [.fragmentsAllowed]) else { - return Fail(error: HTTPError.parsingFailed) - .eraseToAnyPublisher() + throw HTTPError.parsingFailed } let dataArray: [Data] @@ -103,8 +99,7 @@ public extension AnyPublisher where Output == (ResponseInfoType, Data?), Failure dataArray = anyArray.compactMap { try? JSONSerialization.data(withJSONObject: $0) } guard !requireAllResults || dataArray.count == types.count else { - return Fail(error: HTTPError.parsingFailed) - .eraseToAnyPublisher() + throw HTTPError.parsingFailed } case let anyDict as [String: Any]: @@ -115,34 +110,19 @@ public extension AnyPublisher where Output == (ResponseInfoType, Data?), Failure !requireAllResults || resultsArray.count == types.count ) - else { - return Fail(error: HTTPError.parsingFailed) - .eraseToAnyPublisher() - } + else { throw HTTPError.parsingFailed } dataArray = resultsArray - default: - return Fail(error: HTTPError.parsingFailed) - .eraseToAnyPublisher() + default: throw HTTPError.parsingFailed } - do { - // TODO: Remove the 'Swift.' - return Just( - HTTP.BatchResponse( - info: responseInfo, - responses: try Swift.zip(dataArray, types) - .map { data, type in try type.decoded(from: data, using: dependencies) } - ) - ) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - } - catch { - return Fail(error: HTTPError.parsingFailed) - .eraseToAnyPublisher() - } + // TODO: Remove the 'Swift.' + return HTTP.BatchResponse( + info: responseInfo, + responses: try Swift.zip(dataArray, types) + .map { data, type in try type.decoded(from: data, using: dependencies) } + ) } .eraseToAnyPublisher() } diff --git a/SessionUtilitiesKit/Networking/ProxiedContentDownloader.swift b/SessionUtilitiesKit/Networking/ProxiedContentDownloader.swift index 50ffffaef..186262e49 100644 --- a/SessionUtilitiesKit/Networking/ProxiedContentDownloader.swift +++ b/SessionUtilitiesKit/Networking/ProxiedContentDownloader.swift @@ -513,22 +513,24 @@ open class ProxiedContentDownloader: NSObject, URLSessionTaskDelegate, URLSessio // Cache miss. // // Asset requests are done queued and performed asynchronously. - return Future { [weak self] resolver in - let assetRequest = ProxiedContentAssetRequest( - assetDescription: assetDescription, - priority: priority, - success: { request, asset in resolver(Result.success((asset, request))) }, - failure: { request in - resolver(Result.failure(HTTPError.generic)) - } - ) - assetRequest.shouldIgnoreSignalProxy = shouldIgnoreSignalProxy - self?.assetRequestQueue.append(assetRequest) - // Process the queue (which may start this request) - // asynchronously so that the caller has time to store - // a reference to the asset request returned by this - // method before its success/failure handler is called. - self?.processRequestQueueAsync() + return Deferred { + Future { [weak self] resolver in + let assetRequest = ProxiedContentAssetRequest( + assetDescription: assetDescription, + priority: priority, + success: { request, asset in resolver(Result.success((asset, request))) }, + failure: { request in + resolver(Result.failure(HTTPError.generic)) + } + ) + assetRequest.shouldIgnoreSignalProxy = shouldIgnoreSignalProxy + self?.assetRequestQueue.append(assetRequest) + // Process the queue (which may start this request) + // asynchronously so that the caller has time to store + // a reference to the asset request returned by this + // method before its success/failure handler is called. + self?.processRequestQueueAsync() + } }.eraseToAnyPublisher() } diff --git a/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift b/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift index e4250cb4d..56f3b9fc4 100644 --- a/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift +++ b/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift @@ -201,7 +201,7 @@ public final class ProfilePictureView: UIView { // Otherwise there are conversation-type-specific behaviours switch threadVariant { - case .openGroup: + case .community: switch self.size { case Values.smallProfilePictureSize..(updates: @escaping (Database) throws -> T) -> AnyPublisher { + override func readPublisher( + receiveOn scheduler: S, + value: @escaping (Database) throws -> T + ) -> AnyPublisher where S: Scheduler { + guard let result: T = super.read(value) else { + return Fail(error: StorageError.generic) + .eraseToAnyPublisher() + } + + return Just(result) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + + override func writePublisher( + receiveOn scheduler: S, + updates: @escaping (Database) throws -> T + ) -> AnyPublisher where S: Scheduler { guard let result: T = super.write(updates: updates) else { return Fail(error: StorageError.generic) .eraseToAnyPublisher() From c8a199a8babd0fe25b072cc606711943a03e10bd Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 21 Feb 2023 09:55:21 +1100 Subject: [PATCH 031/135] Fixed the remaining broken tests --- .../Utilities/TypeConversion+Utilities.swift | 21 ++- .../LibSessionUtil/ConfigContactsSpec.swift | 10 +- .../ConfigConvoInfoVolatileSpec.swift | 123 ++++++-------- .../TypeConversionUtilitiesSpec.swift | 158 +++++++++++------- 4 files changed, 172 insertions(+), 140 deletions(-) diff --git a/SessionMessagingKit/LibSessionUtil/Utilities/TypeConversion+Utilities.swift b/SessionMessagingKit/LibSessionUtil/Utilities/TypeConversion+Utilities.swift index f418ecae2..e28cc01d4 100644 --- a/SessionMessagingKit/LibSessionUtil/Utilities/TypeConversion+Utilities.swift +++ b/SessionMessagingKit/LibSessionUtil/Utilities/TypeConversion+Utilities.swift @@ -19,16 +19,21 @@ public extension String { init?( libSessionVal: T, - nullTerminated: Bool = true, + fixedLength: Int? = .none, nullIfEmpty: Bool = false ) { let result: String = { - guard !nullTerminated else { - return String(cString: withUnsafeBytes(of: libSessionVal) { [UInt8]($0) }) + guard let fixedLength: Int = fixedLength else { + // Note: The `String(cString:)` function requires that the value is null-terminated + // so add a null-termination character if needed + return String( + cString: withUnsafeBytes(of: libSessionVal) { [UInt8]($0) } + .nullTerminated() + ) } return String( - data: Data(libSessionVal: libSessionVal, count: MemoryLayout.size), + data: Data(libSessionVal: libSessionVal, count: fixedLength), encoding: .utf8 ) .defaulting(to: "") @@ -102,3 +107,11 @@ public extension Array where Element == CChar { return self.appending(CChar(0)) } } + +public extension Array where Element == UInt8 { + func nullTerminated() -> [Element] { + guard self.last != UInt8(0) else { return self } + + return self.appending(UInt8(0)) + } +} diff --git a/SessionMessagingKitTests/LibSessionUtil/ConfigContactsSpec.swift b/SessionMessagingKitTests/LibSessionUtil/ConfigContactsSpec.swift index e9aeb70f5..e48b72099 100644 --- a/SessionMessagingKitTests/LibSessionUtil/ConfigContactsSpec.swift +++ b/SessionMessagingKitTests/LibSessionUtil/ConfigContactsSpec.swift @@ -32,7 +32,7 @@ class ConfigContactsSpec: QuickSpec { error?.deallocate() // Empty contacts shouldn't have an existing contact - var definitelyRealId: String = "050000000000000000000000000000000000000000000000000000000000000000" + let definitelyRealId: String = "050000000000000000000000000000000000000000000000000000000000000000" var cDefinitelyRealId: [CChar] = definitelyRealId.cArray let contactPtr: UnsafeMutablePointer? = nil expect(contacts_get(conf, contactPtr, &cDefinitelyRealId)).to(beFalse()) @@ -75,7 +75,7 @@ class ConfigContactsSpec: QuickSpec { // Ensure the contact details were updated var contact3: contacts_contact = contacts_contact() - expect(contacts_get(conf, &contact3, &definitelyRealId)).to(beTrue()) + expect(contacts_get(conf, &contact3, &cDefinitelyRealId)).to(beTrue()) expect(String(libSessionVal: contact3.name)).to(equal("Joe")) expect(String(libSessionVal: contact3.nickname)).to(equal("Joey")) expect(contact3.approved).to(beTrue()) @@ -129,7 +129,7 @@ class ConfigContactsSpec: QuickSpec { // Ensure the contact details were updated var contact4: contacts_contact = contacts_contact() - expect(contacts_get(conf2, &contact4, &definitelyRealId)).to(beTrue()) + expect(contacts_get(conf2, &contact4, &cDefinitelyRealId)).to(beTrue()) expect(String(libSessionVal: contact4.name)).to(equal("Joe")) expect(String(libSessionVal: contact4.nickname)).to(equal("Joey")) expect(contact4.approved).to(beTrue()) @@ -138,7 +138,7 @@ class ConfigContactsSpec: QuickSpec { expect(String(libSessionVal: contact4.profile_pic.url)).to(beEmpty()) expect(contact4.blocked).to(beFalse()) - var anotherId: String = "051111111111111111111111111111111111111111111111111111111111111111" + let anotherId: String = "051111111111111111111111111111111111111111111111111111111111111111" var cAnotherId: [CChar] = anotherId.cArray var contact5: contacts_contact = contacts_contact() expect(contacts_get_or_construct(conf2, &contact5, &cAnotherId)).to(beTrue()) @@ -201,7 +201,7 @@ class ConfigContactsSpec: QuickSpec { contacts_erase(conf, definitelyRealId) // Client 2 adds a new friend: - var thirdId: String = "052222222222222222222222222222222222222222222222222222222222222222" + let thirdId: String = "052222222222222222222222222222222222222222222222222222222222222222" var cThirdId: [CChar] = thirdId.cArray var contact7: contacts_contact = contacts_contact() expect(contacts_get_or_construct(conf2, &contact7, &cThirdId)).to(beTrue()) diff --git a/SessionMessagingKitTests/LibSessionUtil/ConfigConvoInfoVolatileSpec.swift b/SessionMessagingKitTests/LibSessionUtil/ConfigConvoInfoVolatileSpec.swift index d8f706179..31a9c0a2c 100644 --- a/SessionMessagingKitTests/LibSessionUtil/ConfigConvoInfoVolatileSpec.swift +++ b/SessionMessagingKitTests/LibSessionUtil/ConfigConvoInfoVolatileSpec.swift @@ -32,20 +32,16 @@ class ConfigConvoInfoVolatileSpec: QuickSpec { error?.deallocate() // Empty contacts shouldn't have an existing contact - var definitelyRealId: [CChar] = "055000000000000000000000000000000000000000000000000000000000000000" - .bytes - .map { CChar(bitPattern: $0) } + let definitelyRealId: String = "055000000000000000000000000000000000000000000000000000000000000000" + var cDefinitelyRealId: [CChar] = definitelyRealId.cArray var oneToOne1: convo_info_volatile_1to1 = convo_info_volatile_1to1() - expect(convo_info_volatile_get_1to1(conf, &oneToOne1, &definitelyRealId)).to(beFalse()) + expect(convo_info_volatile_get_1to1(conf, &oneToOne1, &cDefinitelyRealId)).to(beFalse()) expect(convo_info_volatile_size(conf)).to(equal(0)) var oneToOne2: convo_info_volatile_1to1 = convo_info_volatile_1to1() - expect(convo_info_volatile_get_or_construct_1to1(conf, &oneToOne2, definitelyRealId)) + expect(convo_info_volatile_get_or_construct_1to1(conf, &oneToOne2, &cDefinitelyRealId)) .to(beTrue()) - - let oneToOne2SessionId: [CChar] = withUnsafeBytes(of: oneToOne2.session_id) { [UInt8]($0) } - .map { CChar($0) } - expect(oneToOne2SessionId).to(equal(definitelyRealId.nullTerminated())) + expect(String(libSessionVal: oneToOne2.session_id)).to(equal(definitelyRealId)) expect(oneToOne2.last_read).to(equal(0)) expect(oneToOne2.unread).to(beFalse()) @@ -62,37 +58,37 @@ class ConfigConvoInfoVolatileSpec: QuickSpec { var legacyGroup1: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() var oneToOne3: convo_info_volatile_1to1 = convo_info_volatile_1to1() - expect(convo_info_volatile_get_legacy_group(conf, &legacyGroup1, &definitelyRealId)) + expect(convo_info_volatile_get_legacy_group(conf, &legacyGroup1, &cDefinitelyRealId)) .to(beFalse()) - expect(convo_info_volatile_get_1to1(conf, &oneToOne3, &definitelyRealId)).to(beTrue()) + expect(convo_info_volatile_get_1to1(conf, &oneToOne3, &cDefinitelyRealId)).to(beTrue()) expect(oneToOne3.last_read).to(equal(nowTimestampMs)) expect(config_needs_push(conf)).to(beTrue()) expect(config_needs_dump(conf)).to(beTrue()) - var openGroupBaseUrl: [CChar] = "http://Example.ORG:5678".cArray - let openGroupBaseUrlResult: [CChar] = ("http://Example.ORG:5678" - .lowercased() - .cArray + - [CChar](repeating: 0, count: (268 - openGroupBaseUrl.count)) - ) - var openGroupRoom: [CChar] = "SudokuRoom".cArray - let openGroupRoomResult: [CChar] = ("SudokuRoom" - .lowercased() - .cArray + - [CChar](repeating: 0, count: (65 - openGroupRoom.count)) - ) - var openGroupPubkey: [UInt8] = Data(hex: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") + let openGroupBaseUrl: String = "http://Example.ORG:5678" + var cOpenGroupBaseUrl: [CChar] = openGroupBaseUrl.cArray + let openGroupBaseUrlResult: String = openGroupBaseUrl.lowercased() +// ("http://Example.ORG:5678" +// .lowercased() +// .cArray + +// [CChar](repeating: 0, count: (268 - openGroupBaseUrl.count)) +// ) + let openGroupRoom: String = "SudokuRoom" + var cOpenGroupRoom: [CChar] = openGroupRoom.cArray + let openGroupRoomResult: String = openGroupRoom.lowercased() +// ("SudokuRoom" +// .lowercased() +// .cArray + +// [CChar](repeating: 0, count: (65 - openGroupRoom.count)) +// ) + var cOpenGroupPubkey: [UInt8] = Data(hex: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") .bytes var community1: convo_info_volatile_community = convo_info_volatile_community() - expect(convo_info_volatile_get_or_construct_community(conf, &community1, &openGroupBaseUrl, &openGroupRoom, &openGroupPubkey)).to(beTrue()) - expect(withUnsafeBytes(of: community1.base_url) { [UInt8]($0) } - .map { CChar($0) } - ).to(equal(openGroupBaseUrlResult)) - expect(withUnsafeBytes(of: community1.room) { [UInt8]($0) } - .map { CChar($0) } - ).to(equal(openGroupRoomResult)) - expect(withUnsafePointer(to: community1.pubkey) { Data(bytes: $0, count: 32).toHexString() }) + expect(convo_info_volatile_get_or_construct_community(conf, &community1, &cOpenGroupBaseUrl, &cOpenGroupRoom, &cOpenGroupPubkey)).to(beTrue()) + expect(String(libSessionVal: community1.base_url)).to(equal(openGroupBaseUrlResult)) + expect(String(libSessionVal: community1.room)).to(equal(openGroupRoomResult)) + expect(Data(libSessionVal: community1.pubkey, count: 32).toHexString()) .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) community1.unread = true @@ -106,7 +102,6 @@ class ConfigConvoInfoVolatileSpec: QuickSpec { let seqno: Int64 = config_push(conf, &toPush, &toPushLen) expect(toPush).toNot(beNil()) expect(seqno).to(equal(1)) - expect(toPushLen).to(equal(512)) toPush?.deallocate() // Pretend we uploaded it @@ -128,39 +123,30 @@ class ConfigConvoInfoVolatileSpec: QuickSpec { expect(config_needs_push(conf2)).to(beFalse()) var oneToOne4: convo_info_volatile_1to1 = convo_info_volatile_1to1() - expect(convo_info_volatile_get_1to1(conf2, &oneToOne4, &definitelyRealId)).to(equal(true)) + expect(convo_info_volatile_get_1to1(conf2, &oneToOne4, &cDefinitelyRealId)).to(equal(true)) expect(oneToOne4.last_read).to(equal(nowTimestampMs)) - expect( - withUnsafeBytes(of: oneToOne4.session_id) { [UInt8]($0) } - .map { CChar($0) } - .nullTerminated() - ).to(equal(definitelyRealId.nullTerminated())) + expect(String(libSessionVal: oneToOne4.session_id)).to(equal(definitelyRealId)) expect(oneToOne4.unread).to(beFalse()) var community2: convo_info_volatile_community = convo_info_volatile_community() - expect(convo_info_volatile_get_community(conf2, &community2, &openGroupBaseUrl, &openGroupRoom)).to(beTrue()) - expect(withUnsafeBytes(of: community2.base_url) { [UInt8]($0) } - .map { CChar($0) } - ).to(equal(openGroupBaseUrlResult)) - expect(withUnsafeBytes(of: community2.room) { [UInt8]($0) } - .map { CChar($0) } - ).to(equal(openGroupRoomResult)) - expect(withUnsafePointer(to: community2.pubkey) { Data(bytes: $0, count: 32).toHexString() }) + expect(convo_info_volatile_get_community(conf2, &community2, &cOpenGroupBaseUrl, &cOpenGroupRoom)).to(beTrue()) + expect(String(libSessionVal: community2.base_url)).to(equal(openGroupBaseUrlResult)) + expect(String(libSessionVal: community2.room)).to(equal(openGroupRoomResult)) + expect(Data(libSessionVal: community2.pubkey, count: 32).toHexString()) .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) community2.unread = true - var anotherId: [CChar] = "051111111111111111111111111111111111111111111111111111111111111111" - .bytes - .map { CChar(bitPattern: $0) } + let anotherId: String = "051111111111111111111111111111111111111111111111111111111111111111" + var cAnotherId: [CChar] = anotherId.cArray var oneToOne5: convo_info_volatile_1to1 = convo_info_volatile_1to1() - expect(convo_info_volatile_get_or_construct_1to1(conf2, &oneToOne5, &anotherId)).to(beTrue()) + expect(convo_info_volatile_get_or_construct_1to1(conf2, &oneToOne5, &cAnotherId)).to(beTrue()) + oneToOne5.unread = true convo_info_volatile_set_1to1(conf2, &oneToOne5) - var thirdId: [CChar] = "05cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" - .bytes - .map { CChar(bitPattern: $0) } + let thirdId: String = "05cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + var cThirdId: [CChar] = thirdId.cArray var legacyGroup2: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() - expect(convo_info_volatile_get_or_construct_legacy_group(conf2, &legacyGroup2, &thirdId)).to(beTrue()) + expect(convo_info_volatile_get_or_construct_legacy_group(conf2, &legacyGroup2, &cThirdId)).to(beTrue()) legacyGroup2.last_read = (nowTimestampMs - 50) convo_info_volatile_set_legacy_group(conf2, &legacyGroup2) expect(config_needs_push(conf2)).to(beTrue()) @@ -239,23 +225,20 @@ class ConfigConvoInfoVolatileSpec: QuickSpec { expect(config_needs_push(conf)).to(beFalse()) convo_info_volatile_erase_1to1(conf, &fourthId) expect(config_needs_push(conf)).to(beFalse()) - convo_info_volatile_erase_1to1(conf, &definitelyRealId) + convo_info_volatile_erase_1to1(conf, &cDefinitelyRealId) expect(config_needs_push(conf)).to(beTrue()) expect(convo_info_volatile_size(conf)).to(equal(3)) expect(convo_info_volatile_size_1to1(conf)).to(equal(1)) // Check the single-type iterators: - var seen1: [String] = [] + var seen1: [String?] = [] var c1: convo_info_volatile_1to1 = convo_info_volatile_1to1() let it1: OpaquePointer = convo_info_volatile_iterator_new_1to1(conf) while !convo_info_volatile_iterator_done(it1) { expect(convo_info_volatile_it_is_1to1(it1, &c1)).to(beTrue()) - let sessionId: String = String(cString: withUnsafeBytes(of: c1.session_id) { [UInt8]($0) } - .map { CChar($0) } - .nullTerminated() - ) - seen1.append(sessionId) + + seen1.append(String(libSessionVal: c1.session_id)) convo_info_volatile_iterator_advance(it1) } @@ -264,18 +247,14 @@ class ConfigConvoInfoVolatileSpec: QuickSpec { "051111111111111111111111111111111111111111111111111111111111111111" ])) - var seen2: [String] = [] + var seen2: [String?] = [] var c2: convo_info_volatile_community = convo_info_volatile_community() let it2: OpaquePointer = convo_info_volatile_iterator_new_communities(conf) while !convo_info_volatile_iterator_done(it2) { expect(convo_info_volatile_it_is_community(it2, &c2)).to(beTrue()) - let baseUrl: String = String(cString: withUnsafeBytes(of: c2.base_url) { [UInt8]($0) } - .map { CChar($0) } - .nullTerminated() - ) - seen2.append(baseUrl) + seen2.append(String(libSessionVal: c2.base_url)) convo_info_volatile_iterator_advance(it2) } @@ -284,18 +263,14 @@ class ConfigConvoInfoVolatileSpec: QuickSpec { "http://example.org:5678" ])) - var seen3: [String] = [] + var seen3: [String?] = [] var c3: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() let it3: OpaquePointer = convo_info_volatile_iterator_new_legacy_groups(conf) while !convo_info_volatile_iterator_done(it3) { expect(convo_info_volatile_it_is_legacy_group(it3, &c3)).to(beTrue()) - let groupId: String = String(cString: withUnsafeBytes(of: c3.group_id) { [UInt8]($0) } - .map { CChar($0) } - .nullTerminated() - ) - seen3.append(groupId) + seen3.append(String(libSessionVal: c3.group_id)) convo_info_volatile_iterator_advance(it3) } diff --git a/SessionMessagingKitTests/LibSessionUtil/Utilities/TypeConversionUtilitiesSpec.swift b/SessionMessagingKitTests/LibSessionUtil/Utilities/TypeConversionUtilitiesSpec.swift index 79ec212da..2ca104bc9 100644 --- a/SessionMessagingKitTests/LibSessionUtil/Utilities/TypeConversionUtilitiesSpec.swift +++ b/SessionMessagingKitTests/LibSessionUtil/Utilities/TypeConversionUtilitiesSpec.swift @@ -19,6 +19,14 @@ class TypeConversionUtilitiesSpec: QuickSpec { expect("Test123".cArray).to(equal([84, 101, 115, 116, 49, 50, 51])) } + it("can contain emoji") { + let original: String = "Hi 👋" + let libSessionVal: (CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar) = original.toLibSession() + let result: String? = String(libSessionVal: libSessionVal) + + expect(result).to(equal(original)) + } + context("when initialised with a pointer and length") { it("returns null when given a null pointer") { let test: [CChar] = [84, 101, 115, 116] @@ -49,20 +57,41 @@ class TypeConversionUtilitiesSpec: QuickSpec { } context("when initialised with a libSession value") { - it("returns a string when valid and null terminated") { + it("returns a string when valid and has no fixed length") { let value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 115, 116, 0) - let result = String(libSessionVal: value, nullTerminated: true) + let result = String(libSessionVal: value, fixedLength: .none) expect(result).to(equal("Test")) } - it("returns a string when valid and not null terminated") { + it("returns a string when valid and has a fixed length") { let value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 0, 115, 116) - let result = String(libSessionVal: value, nullTerminated: false) + let result = String(libSessionVal: value, fixedLength: 5) expect(result).to(equal("Te\0st")) } + it("truncates at the first null termination character when fixed length is none") { + let value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 0, 115, 116) + let result = String(libSessionVal: value, fixedLength: .none) + + expect(result).to(equal("Te")) + } + + it("parses successfully if there is no null termination character and there is no fixed length") { + let value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 115, 116, 84) + let result = String(libSessionVal: value, fixedLength: .none) + + expect(result).to(equal("TestT")) + } + + it("defaults the fixed length value to none") { + let value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 0, 0, 0) + let result = String(libSessionVal: value) + + expect(result).to(equal("Te")) + } + it("returns an empty string when null and not set to return null") { let value: (CChar, CChar, CChar, CChar, CChar) = (0, 0, 0, 0, 0) let result = String(libSessionVal: value, nullIfEmpty: false) @@ -77,13 +106,6 @@ class TypeConversionUtilitiesSpec: QuickSpec { expect(result).to(beNil()) } - it("defaults the null terminated flag to true") { - let value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 0, 0, 0) - let result = String(libSessionVal: value) - - expect(result).to(equal("Te")) - } - it("defaults the null if empty flag to false") { let value: (CChar, CChar, CChar, CChar, CChar) = (0, 0, 0, 0, 0) let result = String(libSessionVal: value) @@ -92,32 +114,43 @@ class TypeConversionUtilitiesSpec: QuickSpec { } } - it("can convert to a libSession value") { - let result: (CChar, CChar, CChar, CChar, CChar) = "Test".toLibSession() - expect(result.0).to(equal(84)) - expect(result.1).to(equal(101)) - expect(result.2).to(equal(115)) - expect(result.3).to(equal(116)) - expect(result.4).to(equal(0)) - } - - context("when optional") { - context("returns null when null") { - let value: String? = nil - let result: (CChar, CChar, CChar, CChar, CChar)? = value?.toLibSession() - - expect(result).to(beNil()) + context("when converting to a libSession value") { + it("succeeeds with a valid value") { + let result: (CChar, CChar, CChar, CChar, CChar) = "Test".toLibSession() + expect(result.0).to(equal(84)) + expect(result.1).to(equal(101)) + expect(result.2).to(equal(115)) + expect(result.3).to(equal(116)) + expect(result.4).to(equal(0)) } - context("returns a libSession value when not null") { - let value: String? = "Test" - let result: (CChar, CChar, CChar, CChar, CChar)? = value?.toLibSession() + it("truncates when too long") { + let result: (CChar, CChar, CChar, CChar, CChar) = "TestTest".toLibSession() + expect(result.0).to(equal(84)) + expect(result.1).to(equal(101)) + expect(result.2).to(equal(115)) + expect(result.3).to(equal(116)) + expect(result.4).to(equal(84)) + } + + context("when optional") { + context("returns null when null") { + let value: String? = nil + let result: (CChar, CChar, CChar, CChar, CChar)? = value?.toLibSession() + + expect(result).to(beNil()) + } - expect(result?.0).to(equal(84)) - expect(result?.1).to(equal(101)) - expect(result?.2).to(equal(115)) - expect(result?.3).to(equal(116)) - expect(result?.4).to(equal(0)) + context("returns a libSession value when not null") { + let value: String? = "Test" + let result: (CChar, CChar, CChar, CChar, CChar)? = value?.toLibSession() + + expect(result?.0).to(equal(84)) + expect(result?.1).to(equal(101)) + expect(result?.2).to(equal(115)) + expect(result?.3).to(equal(116)) + expect(result?.4).to(equal(0)) + } } } } @@ -145,32 +178,43 @@ class TypeConversionUtilitiesSpec: QuickSpec { } } - it("can convert to a libSession value") { - let result: (Int8, Int8, Int8, Int8, Int8) = Data([1, 2, 3, 4, 5]).toLibSession() - expect(result.0).to(equal(1)) - expect(result.1).to(equal(2)) - expect(result.2).to(equal(3)) - expect(result.3).to(equal(4)) - expect(result.4).to(equal(5)) - } - - context("when optional") { - context("returns null when null") { - let value: Data? = nil - let result: (Int8, Int8, Int8, Int8, Int8)? = value?.toLibSession() - - expect(result).to(beNil()) + context("when converting to a libSession value") { + it("succeeeds with a valid value") { + let result: (Int8, Int8, Int8, Int8, Int8) = Data([1, 2, 3, 4, 5]).toLibSession() + expect(result.0).to(equal(1)) + expect(result.1).to(equal(2)) + expect(result.2).to(equal(3)) + expect(result.3).to(equal(4)) + expect(result.4).to(equal(5)) } - context("returns a libSession value when not null") { - let value: Data? = Data([1, 2, 3, 4, 5]) - let result: (Int8, Int8, Int8, Int8, Int8)? = value?.toLibSession() + it("truncates when too long") { + let result: (Int8, Int8, Int8, Int8, Int8) = Data([1, 2, 3, 4, 1, 2, 3, 4]).toLibSession() + expect(result.0).to(equal(1)) + expect(result.1).to(equal(2)) + expect(result.2).to(equal(3)) + expect(result.3).to(equal(4)) + expect(result.4).to(equal(1)) + } + + context("when optional") { + context("returns null when null") { + let value: Data? = nil + let result: (Int8, Int8, Int8, Int8, Int8)? = value?.toLibSession() + + expect(result).to(beNil()) + } - expect(result?.0).to(equal(1)) - expect(result?.1).to(equal(2)) - expect(result?.2).to(equal(3)) - expect(result?.3).to(equal(4)) - expect(result?.4).to(equal(5)) + context("returns a libSession value when not null") { + let value: Data? = Data([1, 2, 3, 4, 5]) + let result: (Int8, Int8, Int8, Int8, Int8)? = value?.toLibSession() + + expect(result?.0).to(equal(1)) + expect(result?.1).to(equal(2)) + expect(result?.2).to(equal(3)) + expect(result?.3).to(equal(4)) + expect(result?.4).to(equal(5)) + } } } } From ff36b3eeaba5e35e54cb7ae0b351f32b26d8e140 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 21 Feb 2023 17:10:02 +1100 Subject: [PATCH 032/135] Resolved a number of TODOs and a couple of crashes Updated the logic to always fetch all config messages (and not de-dupe them) Updated the onboarding to fallback to fetch the legacy config if the there is no updated user config Added sorting to config message processing to reduce the turn-around time for certain edge-cases to resolve Removed some redundant code Fixed a database re-entrancy crash Fixed a crash when merging config messages Fixed an issue which could occur by registering too many 'afterTransaction' closures --- Session/Notifications/SyncPushTokensJob.swift | 7 +- Session/Onboarding/Onboarding.swift | 76 +++++++-- Session/Onboarding/PNModeVC.swift | 2 +- Session/Utilities/BackgroundPoller.swift | 6 +- .../Models/ControlMessageProcessRecord.swift | 8 +- .../Database/Models/SharedConfigDump.swift | 15 +- .../LibSessionUtil/SessionUtil.swift | 14 +- .../libsession-util.xcframework/Info.plist | 24 +-- .../ios-arm64/libsession-util.a | Bin 2034440 -> 2034896 bytes .../libsession-util.a | Bin 4484472 -> 4485400 bytes .../session/config/contacts.h | 3 + .../session/config/profile_pic.h | 3 + .../session/config/user_groups.h | 19 ++- SessionMessagingKit/Messages/Message.swift | 4 + .../Sending & Receiving/MessageSender.swift | 24 ++- .../Sending & Receiving/Pollers/Poller.swift | 154 +++++++++--------- .../Utilities/ProfileManager.swift | 6 +- SessionSnodeKit/Models/SnodeMessage.swift | 5 +- .../Models/SnodeReceivedMessage.swift | 2 + SessionSnodeKit/Networking/SnodeAPI.swift | 49 +----- SessionSnodeKit/Types/SnodeAPINamespace.swift | 18 ++ SessionUtilitiesKit/JobRunner/JobRunner.swift | 4 +- 22 files changed, 269 insertions(+), 174 deletions(-) diff --git a/Session/Notifications/SyncPushTokensJob.swift b/Session/Notifications/SyncPushTokensJob.swift index 5ca477831..b9d3cdf3a 100644 --- a/Session/Notifications/SyncPushTokensJob.swift +++ b/Session/Notifications/SyncPushTokensJob.swift @@ -21,8 +21,11 @@ public enum SyncPushTokensJob: JobExecutor { failure: @escaping (Job, Error?, Bool) -> (), deferred: @escaping (Job) -> () ) { - // Don't run when inactive or not in main app - guard (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) else { + // Don't run when inactive or not in main app or if the user doesn't exist yet + guard + (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false), + Identity.userExists() + else { deferred(job) // Don't need to do anything if it's not the main app return } diff --git a/Session/Onboarding/Onboarding.swift b/Session/Onboarding/Onboarding.swift index acdf75b68..9d4913b2f 100644 --- a/Session/Onboarding/Onboarding.swift +++ b/Session/Onboarding/Onboarding.swift @@ -26,16 +26,72 @@ enum Onboarding { .tryFlatMap { swarm -> AnyPublisher in guard let snode = swarm.randomElement() else { throw SnodeAPIError.generic } - return CurrentUserPoller.poll( - namespaces: [.configUserProfile], - from: snode, - for: userPublicKey, - on: DispatchQueue.global(qos: .userInitiated), - // Note: These values mean the received messages will be - // processed immediately rather than async as part of a Job - calledFromBackgroundPoller: true, - isBackgroundPollValid: { true } - ) + return CurrentUserPoller + .poll( + namespaces: [.configUserProfile], + from: snode, + for: userPublicKey, + on: DispatchQueue.global(qos: .userInitiated), + // Note: These values mean the received messages will be + // processed immediately rather than async as part of a Job + calledFromBackgroundPoller: true, + isBackgroundPollValid: { true } + ) + .tryFlatMap { receivedMessageTypes -> AnyPublisher in + // FIXME: Remove this entire 'tryFlatMap' once the updated user config has been released for long enough + guard !receivedMessageTypes.isEmpty else { + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + + SNLog("Onboarding failed to retrieve user config, checking for legacy config") + + return CurrentUserPoller + .poll( + namespaces: [.default], + from: snode, + for: userPublicKey, + on: DispatchQueue.global(qos: .userInitiated), + // Note: These values mean the received messages will be + // processed immediately rather than async as part of a Job + calledFromBackgroundPoller: true, + isBackgroundPollValid: { true } + ) + .tryMap { receivedMessageTypes -> Void in + guard + let message: ConfigurationMessage = receivedMessageTypes + .last(where: { $0 is ConfigurationMessage }) + .asType(ConfigurationMessage.self), + let displayName: String = message.displayName + else { return () } + + // Handle user profile changes + Storage.shared.write { db in + try ProfileManager.updateProfileIfNeeded( + db, + publicKey: userPublicKey, + name: displayName, + avatarUpdate: { + guard + let profilePictureUrl: String = message.profilePictureUrl, + let profileKey: Data = message.profileKey + else { return .none } + + return .updateTo( + url: profilePictureUrl, + key: profileKey, + fileName: nil + ) + }(), + sentTimestamp: TimeInterval((message.sentTimestamp ?? 0) / 1000), + calledFromConfigHandling: false + ) + } + return () + } + .eraseToAnyPublisher() + } } .flatMap { _ -> AnyPublisher in Storage.shared.readPublisher(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db in diff --git a/Session/Onboarding/PNModeVC.swift b/Session/Onboarding/PNModeVC.swift index 8906f75cb..6bc15d8d2 100644 --- a/Session/Onboarding/PNModeVC.swift +++ b/Session/Onboarding/PNModeVC.swift @@ -176,7 +176,7 @@ final class PNModeVC: BaseVC, OptionViewDelegate { // If we don't have one then show a loading indicator and try to retrieve the existing name ModalActivityIndicatorViewController.present(fromViewController: self) { viewController in Onboarding.profileNamePublisher - .timeout(.seconds(10), scheduler: DispatchQueue.main, customError: { HTTPError.timeout }) + .timeout(.seconds(15), scheduler: DispatchQueue.main, customError: { HTTPError.timeout }) .catch { _ -> AnyPublisher in SNLog("Onboarding failed to retrieve existing profile information") return Just(nil) diff --git a/Session/Utilities/BackgroundPoller.swift b/Session/Utilities/BackgroundPoller.swift index b5391a23a..d468e8c0c 100644 --- a/Session/Utilities/BackgroundPoller.swift +++ b/Session/Utilities/BackgroundPoller.swift @@ -67,7 +67,7 @@ public final class BackgroundPoller { return SnodeAPI.getSwarm(for: userPublicKey) .subscribeOnMain(immediately: true) .receiveOnMain(immediately: true) - .tryFlatMap { swarm -> AnyPublisher in + .tryFlatMap { swarm -> AnyPublisher<[Message], Error> in guard let snode = swarm.randomElement() else { throw SnodeAPIError.generic } return CurrentUserPoller.poll( @@ -79,6 +79,7 @@ public final class BackgroundPoller { isBackgroundPollValid: { BackgroundPoller.isValid } ) } + .map { _ in () } .eraseToAnyPublisher() } @@ -101,7 +102,7 @@ public final class BackgroundPoller { SnodeAPI.getSwarm(for: groupPublicKey) .subscribeOnMain(immediately: true) .receiveOnMain(immediately: true) - .tryFlatMap { swarm -> AnyPublisher in + .tryFlatMap { swarm -> AnyPublisher<[Message], Error> in guard let snode: Snode = swarm.randomElement() else { throw OnionRequestAPIError.insufficientSnodes } @@ -115,6 +116,7 @@ public final class BackgroundPoller { isBackgroundPollValid: { BackgroundPoller.isValid } ) } + .map { _ in () } .eraseToAnyPublisher() } } diff --git a/SessionMessagingKit/Database/Models/ControlMessageProcessRecord.swift b/SessionMessagingKit/Database/Models/ControlMessageProcessRecord.swift index 481a5f59e..3890df232 100644 --- a/SessionMessagingKit/Database/Models/ControlMessageProcessRecord.swift +++ b/SessionMessagingKit/Database/Models/ControlMessageProcessRecord.swift @@ -83,6 +83,12 @@ public struct ControlMessageProcessRecord: Codable, FetchableRecord, Persistable // message handling to make sure the messages are for the same ongoing call if message is CallMessage { return nil } + // We don't want to do any de-duping for SharedConfigMessages as libSession will handle + // the deduping for us, it also gives libSession more options to potentially recover from + // invalid data, conflicts or even process new changes which weren't supported from older + // versions of the library as it will always re-process messages + if message is SharedConfigMessage { return nil } + // Allow '.new' and 'encryptionKeyPair' closed group control message duplicates to avoid // the following situation: // • The app performed a background poll or received a push notification @@ -103,7 +109,7 @@ public struct ControlMessageProcessRecord: Codable, FetchableRecord, Persistable case is ClosedGroupControlMessage: return .closedGroupControlMessage case is DataExtractionNotification: return .dataExtractionNotification case is ExpirationTimerUpdate: return .expirationTimerUpdate - case is ConfigurationMessage, is SharedConfigMessage: return .configurationMessage // TODO: Confirm this is desired + case is ConfigurationMessage, is SharedConfigMessage: return .configurationMessage case is UnsendRequest: return .unsendRequest case is MessageRequestResponse: return .messageRequestResponse case is CallMessage: return .call diff --git a/SessionMessagingKit/Database/Models/SharedConfigDump.swift b/SessionMessagingKit/Database/Models/SharedConfigDump.swift index db2fae388..8468dcf74 100644 --- a/SessionMessagingKit/Database/Models/SharedConfigDump.swift +++ b/SessionMessagingKit/Database/Models/SharedConfigDump.swift @@ -66,7 +66,9 @@ public extension ConfigDump { } public extension ConfigDump.Variant { - static let userVariants: [ConfigDump.Variant] = [ .userProfile, .contacts, .convoInfoVolatile, .userGroups ] + static let userVariants: [ConfigDump.Variant] = [ + .userProfile, .contacts, .convoInfoVolatile, .userGroups + ] var configMessageKind: SharedConfigMessage.Kind { switch self { @@ -85,4 +87,15 @@ public extension ConfigDump.Variant { case .userGroups: return SnodeAPI.Namespace.configUserGroups } } + + /// This value defines the order that the SharedConfigMessages should be processed in, while we re-process config + /// messages every time we poll this will prevent an edge-case where we have `convoInfoVolatile` data related + /// to a new conversation which hasn't been created yet because it's associated `contacts`/`userGroups` message + /// hasn't yet been processed (without this we would have to wait until the next poll for it to be processed correctly) + var processingOrder: Int { + switch self { + case .userProfile, .contacts, .userGroups: return 0 + case .convoInfoVolatile: return 1 + } + } } diff --git a/SessionMessagingKit/LibSessionUtil/SessionUtil.swift b/SessionMessagingKit/LibSessionUtil/SessionUtil.swift index b16038aa2..dedc595f8 100644 --- a/SessionMessagingKit/LibSessionUtil/SessionUtil.swift +++ b/SessionMessagingKit/LibSessionUtil/SessionUtil.swift @@ -317,13 +317,13 @@ public enum SessionUtil { guard !messages.isEmpty else { return } guard !publicKey.isEmpty else { throw MessageReceiverError.noThread } - let groupedMessages: [SharedConfigMessage.Kind: [SharedConfigMessage]] = messages - .grouped(by: \.kind) - + let groupedMessages: [ConfigDump.Variant: [SharedConfigMessage]] = messages + .grouped(by: \.kind.configDumpVariant) // Merge the config messages into the current state let mergeResults: [ConfigDump.Variant: IncomingConfResult] = groupedMessages + .sorted { lhs, rhs in lhs.key.processingOrder < rhs.key.processingOrder } .reduce(into: [:]) { result, next in - let key: ConfigKey = ConfigKey(variant: next.key.configDumpVariant, publicKey: publicKey) + let key: ConfigKey = ConfigKey(variant: next.key, publicKey: publicKey) let atomicConf: Atomic?> = ( SessionUtil.configStore.wrappedValue[key] ?? Atomic(nil) @@ -340,8 +340,8 @@ public enum SessionUtil { var mergeData: [UnsafePointer?] = next.value .map { message -> [UInt8] in message.data.bytes } .unsafeCopy() - var mergeSize: [Int] = messages.map { $0.data.count } - config_merge(conf, &mergeData, &mergeSize, messages.count) + var mergeSize: [Int] = next.value.map { $0.data.count } + config_merge(conf, &mergeData, &mergeSize, next.value.count) mergeData.forEach { $0?.deallocate() } // Get the state of this variant @@ -350,7 +350,7 @@ public enum SessionUtil { } // Return the current state of the config - result[next.key.configDumpVariant] = IncomingConfResult( + result[next.key] = IncomingConfResult( needsPush: needsPush, needsDump: needsDump, messageHashes: messageHashes, diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/Info.plist b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/Info.plist index c9a2d9b3e..97310ed4d 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/Info.plist +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/Info.plist @@ -4,18 +4,6 @@ AvailableLibraries - - LibraryIdentifier - ios-arm64 - LibraryPath - libsession-util.a - SupportedArchitectures - - arm64 - - SupportedPlatform - ios - LibraryIdentifier ios-arm64_x86_64-simulator @@ -31,6 +19,18 @@ SupportedPlatformVariant simulator + + LibraryIdentifier + ios-arm64 + LibraryPath + libsession-util.a + SupportedArchitectures + + arm64 + + SupportedPlatform + ios + CFBundlePackageType XFWK diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64/libsession-util.a b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64/libsession-util.a index 3f7bee4866f4335c016eb5b69d3b158242fccb27..2ad2f5c1107e9947f969ef59dc701bf6f2855c93 100644 GIT binary patch delta 47938 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 diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64_x86_64-simulator/libsession-util.a b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64_x86_64-simulator/libsession-util.a index de85c6780954b6555140a492a4b4815bc1a7b18a..496ccbf153497ad3e0ce4c8d472808ff29df5a92 100644 GIT binary patch delta 98919 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

    vj#QTxZbtawY2_< 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 z�~#yLZ6hyto|Cmm(N;CLw)x~qCC9+de(;m&!OTb& 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 z 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�bM5N_4%;91UY}TfbRRW3At8 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 y^zQ~2~ohU`SYt+TtA`nCukhyaDmw}L1N}% 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

    47Y4MJ^aa9+g;gYivjn1#0~ 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 V#e!4AGprvi-1h5Njo5e7{{RJ%f3yGq delta 98855 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 I +// Maximum length of the profile pic URL (not including the null terminator) +extern const size_t PROFILE_PIC_MAX_URL_LENGTH; + typedef struct user_profile_pic { // Null-terminated C string containing the uploaded URL of the pic. Will be length 0 if there // is no profile pic. diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.h index a4897da6f..232e67977 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.h +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.h @@ -7,15 +7,26 @@ extern "C" { #include "base.h" #include "util.h" +// Maximum length of a group name, in bytes +extern const size_t GROUP_NAME_MAX_LENGTH; + +// Maximum length of a community full URL +extern const size_t COMMUNITY_URL_MAX_LENGTH; + +// Maximum length of a community room token +extern const size_t COMMUNITY_ROOM_MAX_LENGTH; + typedef struct ugroups_legacy_group_info { char session_id[67]; // in hex; 66 hex chars + null terminator. - char name[101]; // Null-terminated C string (human-readable). Max length is 511. Will always - // be set (even if an empty string). + char name[101]; // Null-terminated C string (human-readable). Max length is 100 (plus 1 for + // null). Will always be set (even if an empty string). bool have_enc_keys; // Will be true if we have an encryption keypair, false if not. - unsigned char enc_pubkey[32]; // If `have_enc_keys`, this is the 32-byte pubkey - unsigned char enc_seckey[32]; // If `have_enc_keys`, this is the 32-byte secret key + unsigned char enc_pubkey[32]; // If `have_enc_keys`, this is the 32-byte pubkey (no NULL + // terminator). + unsigned char enc_seckey[32]; // If `have_enc_keys`, this is the 32-byte secret key (no NULL + // terminator). int64_t disappearing_timer; // Minutes. 0 == disabled. bool hidden; // true if hidden from the convo list diff --git a/SessionMessagingKit/Messages/Message.swift b/SessionMessagingKit/Messages/Message.swift index 2f2767632..4c6333c45 100644 --- a/SessionMessagingKit/Messages/Message.swift +++ b/SessionMessagingKit/Messages/Message.swift @@ -209,6 +209,10 @@ public extension Message { handleClosedGroupKeyUpdateMessages: true ) + // Ensure we actually want to de-dupe messages for this namespace, otherwise just + // succeed early + guard rawMessage.namespace.shouldDedupeMessages else { return processedMessage } + // Retrieve the number of entries we have for the hash of this message let numExistingHashes: Int = (try? SnodeReceivedMessageInfo .filter(SnodeReceivedMessageInfo.Columns.hash == rawMessage.info.hash) diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index 22143d133..6b409b121 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -1088,21 +1088,19 @@ public final class MessageSender { if let message = message as? VisibleMessage { message.syncTarget = publicKey } if let message = message as? ExpirationTimerUpdate { message.syncTarget = publicKey } - Storage.shared.write { db in - JobRunner.add( - db, - job: Job( - variant: .messageSend, - threadId: threadId, - interactionId: interactionId, - details: MessageSendJob.Details( - destination: .contact(publicKey: currentUserPublicKey), - message: message, - isSyncMessage: true - ) + JobRunner.add( + db, + job: Job( + variant: .messageSend, + threadId: threadId, + interactionId: interactionId, + details: MessageSendJob.Details( + destination: .contact(publicKey: currentUserPublicKey), + message: message, + isSyncMessage: true ) ) - } + ) } } } diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift index 230218237..799c9f0a5 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift @@ -88,7 +88,7 @@ public class Poller { getSnodeForPolling(for: publicKey) .subscribe(on: Threading.pollerQueue) - .flatMap { snode -> AnyPublisher in + .flatMap { snode -> AnyPublisher<[Message], Error> in Poller.poll( namespaces: namespaces, from: snode, @@ -126,7 +126,7 @@ public class Poller { self?.getSnodeForPolling(for: publicKey) .subscribe(on: Threading.pollerQueue) - .flatMap { snode -> AnyPublisher in + .flatMap { snode -> AnyPublisher<[Message], Error> in Poller.poll( namespaces: namespaces, from: snode, @@ -177,6 +177,11 @@ public class Poller { } } + /// Polls the specified namespaces and processes any messages, returning an array of messages that were + /// successfully processed + /// + /// **Note:** The returned messages will have already been processed by the `Poller`, they are only returned + /// for cases where we need explicit/custom behaviours to occur (eg. Onboarding) public static func poll( namespaces: [SnodeAPI.Namespace], from snode: Snode, @@ -185,13 +190,13 @@ public class Poller { calledFromBackgroundPoller: Bool = false, isBackgroundPollValid: @escaping (() -> Bool) = { true }, poller: Poller? = nil - ) -> AnyPublisher { + ) -> AnyPublisher<[Message], Error> { // If the polling has been cancelled then don't continue guard (calledFromBackgroundPoller && isBackgroundPollValid()) || poller?.isPolling.wrappedValue[publicKey] == true else { - return Just(()) + return Just([]) .setFailureType(to: Error.self) .eraseToAnyPublisher() } @@ -210,26 +215,25 @@ public class Poller { from: snode, associatedWith: publicKey ) - .flatMap { namespacedResults -> AnyPublisher in + .flatMap { namespacedResults -> AnyPublisher<[Message], Error> in guard (calledFromBackgroundPoller && isBackgroundPollValid()) || poller?.isPolling.wrappedValue[publicKey] == true else { - return Just(()) + return Just([]) .setFailureType(to: Error.self) .eraseToAnyPublisher() } - let allMessagesCount: Int = namespacedResults - .map { $0.value.data?.messages.count ?? 0 } - .reduce(0, +) + let allMessages: [SnodeReceivedMessage] = namespacedResults + .compactMap { _, result -> [SnodeReceivedMessage]? in result.data?.messages } + .flatMap { $0 } // No need to do anything if there are no messages - guard allMessagesCount > 0 else { - if !calledFromBackgroundPoller { - SNLog("Received no new messages in \(pollerName)") - } - return Just(()) + guard !allMessages.isEmpty else { + if !calledFromBackgroundPoller { SNLog("Received no new messages in \(pollerName)") } + + return Just([]) .setFailureType(to: Error.self) .eraseToAnyPublisher() } @@ -237,90 +241,92 @@ public class Poller { // Otherwise process the messages and add them to the queue for handling let lastHashes: [String] = namespacedResults .compactMap { $0.value.data?.lastHash } + let otherKnownHashes: [String] = namespacedResults + .filter { $0.key.shouldDedupeMessages } + .compactMap { $0.value.data?.messages.map { $0.info.hash } } + .reduce([], +) var messageCount: Int = 0 + var processedMessages: [Message] = [] var hadValidHashUpdate: Bool = false var jobsToRun: [Job] = [] Storage.shared.write { db in - namespacedResults.forEach { namespace, result in - result.data?.messages - .compactMap { message -> ProcessedMessage? in - do { - return try Message.processRawReceivedMessage(db, rawMessage: message) - } - catch { - switch error { - // Ignore duplicate & selfSend message errors (and don't bother logging - // them as there will be a lot since we each service node duplicates messages) - case DatabaseError.SQLITE_CONSTRAINT_UNIQUE, - MessageReceiverError.duplicateMessage, - MessageReceiverError.duplicateControlMessage, - MessageReceiverError.selfSend: - break - - case MessageReceiverError.duplicateMessageNewSnode: - hadValidHashUpdate = true - break - - case DatabaseError.SQLITE_ABORT: - // In the background ignore 'SQLITE_ABORT' (it generally means - // the BackgroundPoller has timed out - if !calledFromBackgroundPoller { - SNLog("Failed to the database being suspended (running in background with no background task).") - } - break - - default: SNLog("Failed to deserialize envelope due to error: \(error).") - } - - return nil - } + allMessages + .compactMap { message -> ProcessedMessage? in + do { + return try Message.processRawReceivedMessage(db, rawMessage: message) } - .grouped { threadId, _, _ in (threadId ?? Message.nonThreadMessageId) } - .forEach { threadId, threadMessages in - messageCount += threadMessages.count + catch { + switch error { + // Ignore duplicate & selfSend message errors (and don't bother logging + // them as there will be a lot since we each service node duplicates messages) + case DatabaseError.SQLITE_CONSTRAINT_UNIQUE, + MessageReceiverError.duplicateMessage, + MessageReceiverError.duplicateControlMessage, + MessageReceiverError.selfSend: + break + + case MessageReceiverError.duplicateMessageNewSnode: + hadValidHashUpdate = true + break + + case DatabaseError.SQLITE_ABORT: + // In the background ignore 'SQLITE_ABORT' (it generally means + // the BackgroundPoller has timed out + if !calledFromBackgroundPoller { + SNLog("Failed to the database being suspended (running in background with no background task).") + } + break + + default: SNLog("Failed to deserialize envelope due to error: \(error).") + } - let jobToRun: Job? = Job( - variant: .messageReceive, - behaviour: .runOnce, - threadId: threadId, - details: MessageReceiveJob.Details( - messages: threadMessages.map { $0.messageInfo }, - calledFromBackgroundPoller: calledFromBackgroundPoller - ) + return nil + } + } + .grouped { threadId, _, _ in (threadId ?? Message.nonThreadMessageId) } + .forEach { threadId, threadMessages in + messageCount += threadMessages.count + processedMessages += threadMessages.map { $0.messageInfo.message } + + let jobToRun: Job? = Job( + variant: .messageReceive, + behaviour: .runOnce, + threadId: threadId, + details: MessageReceiveJob.Details( + messages: threadMessages.map { $0.messageInfo }, + calledFromBackgroundPoller: calledFromBackgroundPoller ) - jobsToRun = jobsToRun.appending(jobToRun) - - // If we are force-polling then add to the JobRunner so they are - // persistent and will retry on the next app run if they fail but - // don't let them auto-start - JobRunner.add(db, job: jobToRun, canStartJob: !calledFromBackgroundPoller) - } - } + ) + jobsToRun = jobsToRun.appending(jobToRun) + + // If we are force-polling then add to the JobRunner so they are + // persistent and will retry on the next app run if they fail but + // don't let them auto-start + JobRunner.add(db, job: jobToRun, canStartJob: !calledFromBackgroundPoller) + } // Clean up message hashes and add some logs about the poll results - if allMessagesCount == 0 && !hadValidHashUpdate { + if allMessages.isEmpty && !hadValidHashUpdate { if !calledFromBackgroundPoller { - SNLog("Received \(allMessagesCount) new message\(allMessagesCount == 1 ? "" : "s"), all duplicates - marking the hash we polled with as invalid") + SNLog("Received \(allMessages.count) new message\(allMessages.count == 1 ? "" : "s"), all duplicates - marking the hash we polled with as invalid") } // Update the cached validity of the messages try SnodeReceivedMessageInfo.handlePotentialDeletedOrInvalidHash( db, potentiallyInvalidHashes: lastHashes, - otherKnownValidHashes: namespacedResults - .compactMap { $0.value.data?.messages.map { $0.info.hash } } - .reduce([], +) + otherKnownValidHashes: otherKnownHashes ) } else if !calledFromBackgroundPoller { - SNLog("Received \(messageCount) new message\(messageCount == 1 ? "" : "s") in \(pollerName) (duplicates: \(allMessagesCount - messageCount))") + SNLog("Received \(messageCount) new message\(messageCount == 1 ? "" : "s") in \(pollerName) (duplicates: \(allMessages.count - messageCount))") } } // If we aren't runing in a background poller then just finish immediately guard calledFromBackgroundPoller else { - return Just(()) + return Just(processedMessages) .setFailureType(to: Error.self) .eraseToAnyPublisher() } @@ -345,7 +351,7 @@ public class Poller { } ) .collect() - .map { _ in () } + .map { _ in processedMessages } .eraseToAnyPublisher() } .eraseToAnyPublisher() diff --git a/SessionMessagingKit/Utilities/ProfileManager.swift b/SessionMessagingKit/Utilities/ProfileManager.swift index def1a651a..d2b7b368e 100644 --- a/SessionMessagingKit/Utilities/ProfileManager.swift +++ b/SessionMessagingKit/Utilities/ProfileManager.swift @@ -533,6 +533,7 @@ public struct ProfileManager { // Profile picture & profile key var avatarNeedsDownload: Bool = false + var targetAvatarUrl: String? = nil let shouldUpdateAvatar: Bool = { guard isCurrentUser else { return true } @@ -568,6 +569,7 @@ public struct ProfileManager { profileChanges.append(Profile.Columns.profilePictureUrl.set(to: url)) profileChanges.append(Profile.Columns.profileEncryptionKey.set(to: key)) avatarNeedsDownload = true + targetAvatarUrl = url } // Profile filename (this isn't synchronized between devices) @@ -618,7 +620,9 @@ public struct ProfileManager { // Download the profile picture if needed guard avatarNeedsDownload else { return } - db.afterNextTransactionNested { db in + let dedupeIdentifier: String = "AvatarDownload-\(publicKey)-\(targetAvatarUrl ?? "remove")" + + db.afterNextTransactionNestedOnce(dedupeIdentifier: dedupeIdentifier) { db in // Need to refetch to ensure the db changes have occurred ProfileManager.downloadAvatar(for: Profile.fetchOrCreate(db, id: publicKey)) } diff --git a/SessionSnodeKit/Models/SnodeMessage.swift b/SessionSnodeKit/Models/SnodeMessage.swift index fea1d5bf7..69cff264e 100644 --- a/SessionSnodeKit/Models/SnodeMessage.swift +++ b/SessionSnodeKit/Models/SnodeMessage.swift @@ -53,10 +53,7 @@ extension SnodeMessage { public func encode(to encoder: Encoder) throws { var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) - try container.encode( - (Features.useTestnet ? recipient.removingIdPrefixIfNeeded() : recipient), - forKey: .recipient - ) + try container.encode(recipient, forKey: .recipient) try container.encode(data, forKey: .data) try container.encode(ttl, forKey: .ttl) try container.encode(timestampMs, forKey: .timestampMs) diff --git a/SessionSnodeKit/Models/SnodeReceivedMessage.swift b/SessionSnodeKit/Models/SnodeReceivedMessage.swift index a4659cb78..6e20921d1 100644 --- a/SessionSnodeKit/Models/SnodeReceivedMessage.swift +++ b/SessionSnodeKit/Models/SnodeReceivedMessage.swift @@ -9,6 +9,7 @@ public struct SnodeReceivedMessage: CustomDebugStringConvertible { public static let defaultExpirationSeconds: Int64 = ((15 * 24 * 60 * 60) * 1000) public let info: SnodeReceivedMessageInfo + public let namespace: SnodeAPI.Namespace public let data: Data init?( @@ -29,6 +30,7 @@ public struct SnodeReceivedMessage: CustomDebugStringConvertible { hash: rawMessage.hash, expirationDateMs: (rawMessage.expiration ?? SnodeReceivedMessage.defaultExpirationSeconds) ) + self.namespace = namespace self.data = data } diff --git a/SessionSnodeKit/Networking/SnodeAPI.swift b/SessionSnodeKit/Networking/SnodeAPI.swift index 7489e4011..2f5e1d289 100644 --- a/SessionSnodeKit/Networking/SnodeAPI.swift +++ b/SessionSnodeKit/Networking/SnodeAPI.swift @@ -264,17 +264,13 @@ public final class SnodeAPI { } SNLog("Getting swarm for: \((publicKey == getUserHexEncodedPublicKey()) ? "self" : publicKey).") - let targetPublicKey: String = (Features.useTestnet ? - publicKey.removingIdPrefixIfNeeded() : - publicKey - ) return getRandomSnode() .flatMap { snode in SnodeAPI.send( request: SnodeRequest( endpoint: .getSwarm, - body: GetSwarmRequest(pubkey: targetPublicKey) + body: GetSwarmRequest(pubkey: publicKey) ), to: snode, associatedWith: publicKey, @@ -305,16 +301,14 @@ public final class SnodeAPI { } let userX25519PublicKey: String = getUserHexEncodedPublicKey() - let targetPublicKey: String = (Features.useTestnet ? - publicKey.removingIdPrefixIfNeeded() : - publicKey - ) return Just(()) .setFailureType(to: Error.self) .map { _ -> [SnodeAPI.Namespace: String] in namespaces .reduce(into: [:]) { result, namespace in + guard namespace.shouldDedupeMessages else { return } + // Prune expired message hashes for this namespace on this service node SnodeReceivedMessageInfo.pruneExpiredMessageHashInfo( for: snode, @@ -375,7 +369,7 @@ public final class SnodeAPI { request: SnodeRequest( endpoint: .getMessages, body: LegacyGetMessagesRequest( - pubkey: targetPublicKey, + pubkey: publicKey, lastHash: (namespaceLastHash[namespace] ?? ""), namespace: namespace, maxCount: nil, @@ -393,7 +387,7 @@ public final class SnodeAPI { body: GetMessagesRequest( lastHash: (namespaceLastHash[namespace] ?? ""), namespace: namespace, - pubkey: targetPublicKey, + pubkey: publicKey, subkey: nil, // TODO: Need to get this timestampMs: UInt64(SnodeAPI.currentOffsetTimestampMs()), ed25519PublicKey: userED25519KeyPair.publicKey, @@ -462,11 +456,6 @@ public final class SnodeAPI { associatedWith publicKey: String, using dependencies: SSKDependencies = SSKDependencies() ) -> AnyPublisher<(info: ResponseInfoType, data: (messages: [SnodeReceivedMessage], lastHash: String?)?), Error> { - let targetPublicKey: String = (Features.useTestnet ? - publicKey.removingIdPrefixIfNeeded() : - publicKey - ) - return Deferred { Future { resolver in // Prune expired message hashes for this namespace on this service node @@ -495,7 +484,7 @@ public final class SnodeAPI { request: SnodeRequest( endpoint: .getMessages, body: LegacyGetMessagesRequest( - pubkey: targetPublicKey, + pubkey: publicKey, lastHash: (lastHash ?? ""), namespace: namespace, maxCount: nil, @@ -522,7 +511,7 @@ public final class SnodeAPI { body: GetMessagesRequest( lastHash: (lastHash ?? ""), namespace: namespace, - pubkey: targetPublicKey, + pubkey: publicKey, subkey: nil, timestampMs: UInt64(SnodeAPI.currentOffsetTimestampMs()), ed25519PublicKey: userED25519KeyPair.publicKey, @@ -566,10 +555,7 @@ public final class SnodeAPI { in namespace: Namespace, using dependencies: SSKDependencies = SSKDependencies() ) -> AnyPublisher<(ResponseInfoType, SendMessagesResponse), Error> { - let publicKey: String = (Features.useTestnet ? - message.recipient.removingIdPrefixIfNeeded() : - message.recipient - ) + let publicKey: String = message.recipient let userX25519PublicKey: String = getUserHexEncodedPublicKey() let sendTimestamp: UInt64 = UInt64(SnodeAPI.currentOffsetTimestampMs()) @@ -657,10 +643,7 @@ public final class SnodeAPI { } let userX25519PublicKey: String = getUserHexEncodedPublicKey() - let publicKey: String = (Features.useTestnet ? - recipient.removingIdPrefixIfNeeded() : - recipient - ) + let publicKey: String = recipient var requests: [SnodeAPI.BatchRequest.Info] = targetedMessages .map { message, namespace in // Check if this namespace requires authentication @@ -749,11 +732,6 @@ public final class SnodeAPI { .eraseToAnyPublisher() } - let publicKey: String = (Features.useTestnet ? - publicKey.removingIdPrefixIfNeeded() : - publicKey - ) - return getSwarm(for: publicKey) .subscribe(on: Threading.workQueue) .tryFlatMap { swarm -> AnyPublisher<[String: (hashes: [String], expiry: UInt64)], Error> in @@ -801,11 +779,6 @@ public final class SnodeAPI { .eraseToAnyPublisher() } - let publicKey: String = (Features.useTestnet ? - publicKey.removingIdPrefixIfNeeded() : - publicKey - ) - return getSwarm(for: publicKey) .subscribe(on: Threading.workQueue) .tryFlatMap { swarm -> AnyPublisher in @@ -855,10 +828,6 @@ public final class SnodeAPI { .eraseToAnyPublisher() } - let publicKey: String = (Features.useTestnet ? - publicKey.removingIdPrefixIfNeeded() : - publicKey - ) let userX25519PublicKey: String = getUserHexEncodedPublicKey() return getSwarm(for: publicKey) diff --git a/SessionSnodeKit/Types/SnodeAPINamespace.swift b/SessionSnodeKit/Types/SnodeAPINamespace.swift index 84ee98a3e..4d75897f7 100644 --- a/SessionSnodeKit/Types/SnodeAPINamespace.swift +++ b/SessionSnodeKit/Types/SnodeAPINamespace.swift @@ -31,6 +31,24 @@ public extension SnodeAPI { } } + /// This flag indicates whether we should dedupe messages from the specified namespace, when `true` we will + /// store a `SnodeReceivedMessageInfo` record for the message and check for a matching record whenever + /// we receive a message from this namespace + /// + /// **Note:** An additional side-effect of this flag is that when we poll for messages from the specified namespace + /// we will always retrieve **all** messages from the namespace (instead of just new messages since the last one + /// we have seen) + public var shouldDedupeMessages: Bool { + switch self { + case .`default`, .legacyClosedGroup: return true + + case .configUserProfile, .configContacts, + .configConvoInfoVolatile, .configUserGroups, + .configClosedGroupInfo: + return false + } + } + var verificationString: String { switch self { case .`default`: return "" diff --git a/SessionUtilitiesKit/JobRunner/JobRunner.swift b/SessionUtilitiesKit/JobRunner/JobRunner.swift index ed058a229..c6928b245 100644 --- a/SessionUtilitiesKit/JobRunner/JobRunner.swift +++ b/SessionUtilitiesKit/JobRunner/JobRunner.swift @@ -143,7 +143,7 @@ public final class JobRunner { guard canStartJob else { return } // Start the job runner if needed - db.afterNextTransactionNested { _ in + db.afterNextTransactionNestedOnce(dedupeIdentifier: "JobRunner-Start: \(updatedJob.variant)") { _ in queues.wrappedValue[updatedJob.variant]?.start() } } @@ -166,7 +166,7 @@ public final class JobRunner { guard canStartJob else { return } // Start the job runner if needed - db.afterNextTransactionNested { _ in + db.afterNextTransactionNestedOnce(dedupeIdentifier: "JobRunner-Start: \(job.variant)") { _ in queues.wrappedValue[job.variant]?.start() } } From 8eed08b5b4a840fbb12cd40aeb1c5c75d3cca1e6 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 28 Feb 2023 17:23:56 +1100 Subject: [PATCH 033/135] Updated to the latest lib, started handling UserGroups Added unit tests for the UserGroup config type Updated the logic to use the 'pinnedPriority' and deprecated 'isPinned' (not sorting yet but using the value) Updated the code to use the libSession community url parsing instead of custom parsing Fixed an issue where initialising Data with a libSession value wasn't returning null when the data had no actual value Fixed an issue where the OpenGroupPoller could user an incorrect failure could when handling poll responses Fixed the UpdateExpiryRequest signature --- Session.xcodeproj/project.pbxproj | 50 +- Session/Closed Groups/NewClosedGroupVC.swift | 3 +- .../ConversationVC+Interaction.swift | 7 +- .../Settings/ThreadSettingsViewModel.swift | 18 +- Session/Home/HomeVC.swift | 23 +- Session/Home/HomeViewModel.swift | 13 +- .../MessageRequestsViewController.swift | 3 +- .../MessageRequestsViewModel.swift | 5 +- Session/Meta/AppDelegate.swift | 6 +- Session/Open Groups/JoinOpenGroupVC.swift | 7 +- Session/Shared/FullConversationCell.swift | 2 +- SessionMessagingKit/Calls/WebRTCSession.swift | 8 + .../Migrations/_012_SharedUtilChanges.swift | 153 +++- .../Database/Models/ClosedGroup.swift | 55 +- .../Database/Models/OpenGroup.swift | 9 +- .../Database/Models/SessionThread.swift | 59 +- .../Database/Models/SharedConfigDump.swift | 33 +- .../Jobs/Types/ConfigurationSyncJob.swift | 230 ++----- .../Jobs/Types/GarbageCollectionJob.swift | 2 + .../Jobs/Types/MessageSendJob.swift | 1 + .../Jobs/Types/SendReadReceiptsJob.swift | 1 + .../SessionUtil+Contacts.swift | 360 +++++----- .../SessionUtil+ConvoInfoVolatile.swift | 479 +++++++------ .../Config Handling/SessionUtil+Groups.swift | 19 - .../Config Handling/SessionUtil+Shared.swift | 195 ++++++ .../SessionUtil+UserGroups.swift | 651 ++++++++++++++++++ .../SessionUtil+UserProfile.swift | 113 +-- .../QueryInterfaceRequest+Utilities.swift | 108 ++- .../LibSessionUtil/SessionUtil.swift | 462 ++++++------- .../Utilities/TypeConversion+Utilities.swift | 124 +++- .../ios-arm64/libsession-util.a | Bin 2034896 -> 2080640 bytes .../libsession-util.a | Bin 4485400 -> 4583632 bytes .../module.modulemap | 1 + .../session/config.hpp | 31 +- .../session/config/base.h | 59 +- .../session/config/base.hpp | 70 +- .../session/config/community.h | 44 ++ .../session/config/community.hpp | 17 +- .../session/config/contacts.h | 2 +- .../session/config/contacts.hpp | 6 +- .../session/config/convo_info_volatile.hpp | 9 +- .../session/config/user_groups.h | 99 ++- .../session/config/user_groups.hpp | 20 +- .../session/config/user_profile.h | 6 + .../session/config/user_profile.hpp | 8 + .../ConfigurationMessage+Convenience.swift | 4 +- .../Messages/Message+Destination.swift | 8 + .../Open Groups/OpenGroupManager.swift | 168 ++--- .../MessageReceiver+Calls.swift | 1 + ...essageReceiver+ConfigurationMessages.swift | 2 +- .../MessageSender+ClosedGroups.swift | 7 +- .../MessageSender+Convenience.swift | 9 +- .../Sending & Receiving/MessageSender.swift | 63 +- .../Pollers/OpenGroupPoller.swift | 61 +- .../SessionThreadViewModel.swift | 24 +- .../Utilities/ProfileManager.swift | 4 +- .../{ => Configs}/ConfigContactsSpec.swift | 169 ++--- .../ConfigConvoInfoVolatileSpec.swift | 106 ++- .../Configs/ConfigUserGroupsSpec.swift | 576 ++++++++++++++++ .../{ => Configs}/ConfigUserProfileSpec.swift | 189 +++-- .../LibSessionUtil/LibSessionSpec.swift | 20 + .../LibSessionUtil/SessionUtilSpec.swift | 212 ++++++ ...bSessionTypeConversionUtilitiesSpec.swift} | 139 +++- .../Open Groups/Models/OpenGroupSpec.swift | 12 - .../Open Groups/OpenGroupManagerSpec.swift | 163 +---- .../NotificationServiceExtension.swift | 4 +- .../ShareNavController.swift | 4 +- .../Models/UpdateExpiryRequest.swift | 9 +- SessionSnodeKit/Networking/SnodeAPI.swift | 24 +- .../Database/StorageError.swift | 1 + .../Utilities/Database+Utilities.swift | 33 +- SessionUtilitiesKit/General/Atomic.swift | 69 +- .../General/Collection+Utilities.swift | 1 - SessionUtilitiesKit/JobRunner/JobRunner.swift | 4 +- 74 files changed, 3923 insertions(+), 1734 deletions(-) delete mode 100644 SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Groups.swift create mode 100644 SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Shared.swift create mode 100644 SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/community.h rename SessionMessagingKitTests/LibSessionUtil/{ => Configs}/ConfigContactsSpec.swift (67%) rename SessionMessagingKitTests/LibSessionUtil/{ => Configs}/ConfigConvoInfoVolatileSpec.swift (82%) create mode 100644 SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserGroupsSpec.swift rename SessionMessagingKitTests/LibSessionUtil/{ => Configs}/ConfigUserProfileSpec.swift (67%) create mode 100644 SessionMessagingKitTests/LibSessionUtil/LibSessionSpec.swift create mode 100644 SessionMessagingKitTests/LibSessionUtil/SessionUtilSpec.swift rename SessionMessagingKitTests/LibSessionUtil/Utilities/{TypeConversionUtilitiesSpec.swift => LibSessionTypeConversionUtilitiesSpec.swift} (62%) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 4b9ce0c4b..5c13a263f 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -635,7 +635,7 @@ FD432432299C6933008A0213 /* _011_AddPendingReadReceipts.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD432431299C6933008A0213 /* _011_AddPendingReadReceipts.swift */; }; FD432434299C6985008A0213 /* PendingReadReceipt.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD432433299C6985008A0213 /* PendingReadReceipt.swift */; }; FD432437299DEA38008A0213 /* TypeConversion+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD432436299DEA38008A0213 /* TypeConversion+Utilities.swift */; }; - FD43EE9D297A5190009C87C5 /* SessionUtil+Groups.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD43EE9C297A5190009C87C5 /* SessionUtil+Groups.swift */; }; + FD43EE9D297A5190009C87C5 /* SessionUtil+UserGroups.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD43EE9C297A5190009C87C5 /* SessionUtil+UserGroups.swift */; }; FD43EE9F297E2EE0009C87C5 /* SessionUtil+ConvoInfoVolatile.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD43EE9E297E2EE0009C87C5 /* SessionUtil+ConvoInfoVolatile.swift */; }; FD4B200E283492210034334B /* InsetLockableTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD4B200D283492210034334B /* InsetLockableTableView.swift */; }; FD52090028AF6153006098F6 /* OWSBackgroundTask.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDC1B255A581F00E217F9 /* OWSBackgroundTask.m */; }; @@ -749,6 +749,10 @@ FD9004152818B46300ABAAF6 /* JobRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B7432804EF1B004C14C5 /* JobRunner.swift */; }; FD9004162818B46700ABAAF6 /* JobRunnerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE77F68280F9EDA002CFC5D /* JobRunnerError.swift */; }; FD9B30F3293EA0BF008DEE3E /* BatchResponseSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9B30F2293EA0BF008DEE3E /* BatchResponseSpec.swift */; }; + FDA1E83629A5748F00C5C3BD /* ConfigUserGroupsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA1E83529A5748F00C5C3BD /* ConfigUserGroupsSpec.swift */; }; + FDA1E83929A5771A00C5C3BD /* LibSessionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA1E83829A5771A00C5C3BD /* LibSessionSpec.swift */; }; + FDA1E83B29A5F2D500C5C3BD /* SessionUtil+Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA1E83A29A5F2D500C5C3BD /* SessionUtil+Shared.swift */; }; + FDA1E83D29AC71A800C5C3BD /* SessionUtilSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA1E83C29AC71A800C5C3BD /* SessionUtilSpec.swift */; }; FDA8EAFE280E8B78002B68E5 /* FailedMessageSendsJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA8EAFD280E8B78002B68E5 /* FailedMessageSendsJob.swift */; }; FDA8EB00280E8D58002B68E5 /* FailedAttachmentDownloadsJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA8EAFF280E8D58002B68E5 /* FailedAttachmentDownloadsJob.swift */; }; FDA8EB10280F8238002B68E5 /* Codable+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA8EB0F280F8238002B68E5 /* Codable+Utilities.swift */; }; @@ -810,7 +814,7 @@ FDD2506E283711D600198BDA /* DifferenceKit+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD2506D283711D600198BDA /* DifferenceKit+Utilities.swift */; }; FDD250702837199200198BDA /* GarbageCollectionJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD2506F2837199200198BDA /* GarbageCollectionJob.swift */; }; FDD250722837234B00198BDA /* MediaGalleryNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD250712837234B00198BDA /* MediaGalleryNavigationController.swift */; }; - FDDC08F229A300E800BF9681 /* TypeConversionUtilitiesSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDDC08F129A300E800BF9681 /* TypeConversionUtilitiesSpec.swift */; }; + FDDC08F229A300E800BF9681 /* LibSessionTypeConversionUtilitiesSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDDC08F129A300E800BF9681 /* LibSessionTypeConversionUtilitiesSpec.swift */; }; FDE658A129418C7900A33BC1 /* CryptoKit+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE658A029418C7900A33BC1 /* CryptoKit+Utilities.swift */; }; FDE658A329418E2F00A33BC1 /* KeyPair.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE658A229418E2F00A33BC1 /* KeyPair.swift */; }; FDE77F6B280FEB28002CFC5D /* ControlMessageProcessRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE77F6A280FEB28002CFC5D /* ControlMessageProcessRecord.swift */; }; @@ -1765,7 +1769,7 @@ FD432431299C6933008A0213 /* _011_AddPendingReadReceipts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _011_AddPendingReadReceipts.swift; sourceTree = ""; }; FD432433299C6985008A0213 /* PendingReadReceipt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PendingReadReceipt.swift; sourceTree = ""; }; FD432436299DEA38008A0213 /* TypeConversion+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TypeConversion+Utilities.swift"; sourceTree = ""; }; - FD43EE9C297A5190009C87C5 /* SessionUtil+Groups.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionUtil+Groups.swift"; sourceTree = ""; }; + FD43EE9C297A5190009C87C5 /* SessionUtil+UserGroups.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionUtil+UserGroups.swift"; sourceTree = ""; }; FD43EE9E297E2EE0009C87C5 /* SessionUtil+ConvoInfoVolatile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionUtil+ConvoInfoVolatile.swift"; sourceTree = ""; }; FD4B200D283492210034334B /* InsetLockableTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsetLockableTableView.swift; sourceTree = ""; }; FD52090228B4680F006098F6 /* RadioButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButton.swift; sourceTree = ""; }; @@ -1871,6 +1875,10 @@ FD8ECF93293856AF00C0D1BB /* Randomness.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Randomness.swift; sourceTree = ""; }; FD9004132818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_SetupStandardJobs.swift; sourceTree = ""; }; FD9B30F2293EA0BF008DEE3E /* BatchResponseSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchResponseSpec.swift; sourceTree = ""; }; + FDA1E83529A5748F00C5C3BD /* ConfigUserGroupsSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigUserGroupsSpec.swift; sourceTree = ""; }; + FDA1E83829A5771A00C5C3BD /* LibSessionSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibSessionSpec.swift; sourceTree = ""; }; + FDA1E83A29A5F2D500C5C3BD /* SessionUtil+Shared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionUtil+Shared.swift"; sourceTree = ""; }; + FDA1E83C29AC71A800C5C3BD /* SessionUtilSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionUtilSpec.swift; sourceTree = ""; }; FDA8EAFD280E8B78002B68E5 /* FailedMessageSendsJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailedMessageSendsJob.swift; sourceTree = ""; }; FDA8EAFF280E8D58002B68E5 /* FailedAttachmentDownloadsJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailedAttachmentDownloadsJob.swift; sourceTree = ""; }; FDA8EB0F280F8238002B68E5 /* Codable+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Codable+Utilities.swift"; sourceTree = ""; }; @@ -1931,7 +1939,7 @@ FDD2506D283711D600198BDA /* DifferenceKit+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DifferenceKit+Utilities.swift"; sourceTree = ""; }; FDD2506F2837199200198BDA /* GarbageCollectionJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GarbageCollectionJob.swift; sourceTree = ""; }; FDD250712837234B00198BDA /* MediaGalleryNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaGalleryNavigationController.swift; sourceTree = ""; }; - FDDC08F129A300E800BF9681 /* TypeConversionUtilitiesSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypeConversionUtilitiesSpec.swift; sourceTree = ""; }; + FDDC08F129A300E800BF9681 /* LibSessionTypeConversionUtilitiesSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibSessionTypeConversionUtilitiesSpec.swift; sourceTree = ""; }; FDE658A029418C7900A33BC1 /* CryptoKit+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CryptoKit+Utilities.swift"; sourceTree = ""; }; FDE658A229418E2F00A33BC1 /* KeyPair.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyPair.swift; sourceTree = ""; }; FDE7214F287E50D50093DF33 /* ProtoWrappers.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = ProtoWrappers.py; sourceTree = ""; }; @@ -4061,10 +4069,10 @@ FD8ECF802934385900C0D1BB /* LibSessionUtil */ = { isa = PBXGroup; children = ( + FDA1E83729A5770C00C5C3BD /* Configs */, FDDC08F029A300D500BF9681 /* Utilities */, - FD2B4AFA29429D1000AB4848 /* ConfigContactsSpec.swift */, - FD8ECF812934387A00C0D1BB /* ConfigUserProfileSpec.swift */, - FDBB25E02983909300F1508E /* ConfigConvoInfoVolatileSpec.swift */, + FDA1E83829A5771A00C5C3BD /* LibSessionSpec.swift */, + FDA1E83C29AC71A800C5C3BD /* SessionUtilSpec.swift */, ); path = LibSessionUtil; sourceTree = ""; @@ -4075,7 +4083,8 @@ FD8ECF8F29381FC200C0D1BB /* SessionUtil+UserProfile.swift */, FD2B4AFC294688D000AB4848 /* SessionUtil+Contacts.swift */, FD43EE9E297E2EE0009C87C5 /* SessionUtil+ConvoInfoVolatile.swift */, - FD43EE9C297A5190009C87C5 /* SessionUtil+Groups.swift */, + FD43EE9C297A5190009C87C5 /* SessionUtil+UserGroups.swift */, + FDA1E83A29A5F2D500C5C3BD /* SessionUtil+Shared.swift */, ); path = "Config Handling"; sourceTree = ""; @@ -4097,6 +4106,17 @@ path = Networking; sourceTree = ""; }; + FDA1E83729A5770C00C5C3BD /* Configs */ = { + isa = PBXGroup; + children = ( + FD2B4AFA29429D1000AB4848 /* ConfigContactsSpec.swift */, + FD8ECF812934387A00C0D1BB /* ConfigUserProfileSpec.swift */, + FDBB25E02983909300F1508E /* ConfigConvoInfoVolatileSpec.swift */, + FDA1E83529A5748F00C5C3BD /* ConfigUserGroupsSpec.swift */, + ); + path = Configs; + sourceTree = ""; + }; FDC2909227D710A9005DAE71 /* Types */ = { isa = PBXGroup; children = ( @@ -4228,7 +4248,7 @@ FDDC08F029A300D500BF9681 /* Utilities */ = { isa = PBXGroup; children = ( - FDDC08F129A300E800BF9681 /* TypeConversionUtilitiesSpec.swift */, + FDDC08F129A300E800BF9681 /* LibSessionTypeConversionUtilitiesSpec.swift */, ); path = Utilities; sourceTree = ""; @@ -5738,7 +5758,7 @@ FD245C692850666800B966DD /* ExpirationTimerUpdate.swift in Sources */, FD42F9A8285064B800A0C77D /* PushNotificationAPI.swift in Sources */, FD245C6C2850669200B966DD /* MessageReceiveJob.swift in Sources */, - FD43EE9D297A5190009C87C5 /* SessionUtil+Groups.swift in Sources */, + FD43EE9D297A5190009C87C5 /* SessionUtil+UserGroups.swift in Sources */, FD83B9CC27D179BC005E1583 /* FSEndpoint.swift in Sources */, FD7115F228C6CB3900B47552 /* _010_AddThreadIdToFTS.swift in Sources */, FD716E6428502DDD00C96BF4 /* CallManagerProtocol.swift in Sources */, @@ -5774,6 +5794,7 @@ C32C598A256D0664003C73A2 /* SNProtoEnvelope+Conversion.swift in Sources */, FDC438CB27BB7DB100C60D73 /* UpdateMessageRequest.swift in Sources */, FD8ECF7F2934298100C0D1BB /* SharedConfigDump.swift in Sources */, + FDA1E83B29A5F2D500C5C3BD /* SessionUtil+Shared.swift in Sources */, C352A2FF25574B6300338F3E /* MessageSendJob.swift in Sources */, FDC438C327BB512200C60D73 /* SodiumProtocols.swift in Sources */, B8856D11256F112A001CE70E /* OWSAudioSession.swift in Sources */, @@ -6058,7 +6079,7 @@ buildActionMask = 2147483647; files = ( FD3C905C27E3FBEF00CD579F /* BatchRequestInfoSpec.swift in Sources */, - FDDC08F229A300E800BF9681 /* TypeConversionUtilitiesSpec.swift in Sources */, + FDDC08F229A300E800BF9681 /* LibSessionTypeConversionUtilitiesSpec.swift in Sources */, FD859EFA27C2F5C500510D0C /* MockGenericHash.swift in Sources */, FDC2909427D710B4005DAE71 /* SOGSEndpointSpec.swift in Sources */, FDC290B327DFF9F5005DAE71 /* TestOnionRequestAPI.swift in Sources */, @@ -6080,9 +6101,11 @@ FD859EF427C2F49200510D0C /* MockSodium.swift in Sources */, FD078E4D27E17156000769AF /* MockOGMCache.swift in Sources */, FD078E5227E1760A000769AF /* OGMDependencyExtensions.swift in Sources */, + FDA1E83629A5748F00C5C3BD /* ConfigUserGroupsSpec.swift in Sources */, FD859EFC27C2F60700510D0C /* MockEd25519.swift in Sources */, FDC290A627D860CE005DAE71 /* Mock.swift in Sources */, FD2B4AFB29429D1000AB4848 /* ConfigContactsSpec.swift in Sources */, + FDA1E83D29AC71A800C5C3BD /* SessionUtilSpec.swift in Sources */, FD83B9C027CF2294005E1583 /* TestConstants.swift in Sources */, FD3C906F27E43E8700CD579F /* MockBox.swift in Sources */, FDC4389A27BA002500C60D73 /* OpenGroupAPISpec.swift in Sources */, @@ -6093,6 +6116,7 @@ FDC290A827D9B46D005DAE71 /* NimbleExtensions.swift in Sources */, FDC2908F27D70938005DAE71 /* SendDirectMessageRequestSpec.swift in Sources */, FDC438BD27BB2AB400C60D73 /* Mockable.swift in Sources */, + FDA1E83929A5771A00C5C3BD /* LibSessionSpec.swift in Sources */, FD859EF627C2F52C00510D0C /* MockSign.swift in Sources */, FDC2908927D70656005DAE71 /* RoomPollInfoSpec.swift in Sources */, FDFD645D27F273F300808CA1 /* MockGeneralCache.swift in Sources */, @@ -7577,7 +7601,7 @@ SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Session.app/Session"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Session.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Session"; }; name = Debug; }; @@ -7642,7 +7666,7 @@ SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Session.app/Session"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Session.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Session"; VALIDATE_PRODUCT = YES; }; name = "App Store Release"; diff --git a/Session/Closed Groups/NewClosedGroupVC.swift b/Session/Closed Groups/NewClosedGroupVC.swift index 887fa9b08..c123794a5 100644 --- a/Session/Closed Groups/NewClosedGroupVC.swift +++ b/Session/Closed Groups/NewClosedGroupVC.swift @@ -334,7 +334,7 @@ final class NewClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegate ModalActivityIndicatorViewController.present(fromViewController: navigationController!, message: message) { [weak self] _ in Storage.shared .writePublisherFlatMap(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db in - MessageSender.createClosedGroup(db, name: name, members: selectedContacts) + try MessageSender.createClosedGroup(db, name: name, members: selectedContacts) } .receive(on: DispatchQueue.main) .sinkUntilComplete( @@ -357,7 +357,6 @@ final class NewClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegate } }, receiveValue: { thread in - ConfigurationSyncJob.enqueue() self?.presentingViewController?.dismiss(animated: true, completion: nil) SessionApp.presentConversation(for: thread.id, action: .compose, animated: false) } diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index ece275e22..9bdd8c86e 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -1375,6 +1375,7 @@ extension ConversationVC: ) ), to: try Message.Destination.from(db, thread: thread), + namespace: try Message.Destination.from(db, thread: thread).defaultNamespace, interactionId: cellViewModel.id ) @@ -1571,7 +1572,8 @@ extension ConversationVC: guard let presentingViewController: UIViewController = modal.presentingViewController else { return } - guard let (room, server, publicKey) = OpenGroupManager.parseOpenGroup(from: url) else { + + guard let (room, server, publicKey) = SessionUtil.parseCommunity(url: url) else { let errorModal: ConfirmationModal = ConfirmationModal( info: ConfirmationModal.Info( title: "COMMUNITY_ERROR_GENERIC".localized(), @@ -1589,7 +1591,8 @@ extension ConversationVC: db, roomToken: room, server: server, - publicKey: publicKey + publicKey: publicKey, + calledFromConfigHandling: false ) } .receive(on: DispatchQueue.main) diff --git a/Session/Conversations/Settings/ThreadSettingsViewModel.swift b/Session/Conversations/Settings/ThreadSettingsViewModel.swift index ace9bf25d..24ccbfdf9 100644 --- a/Session/Conversations/Settings/ThreadSettingsViewModel.swift +++ b/Session/Conversations/Settings/ThreadSettingsViewModel.swift @@ -334,7 +334,7 @@ class ThreadSettingsViewModel: SessionTableViewModel 0 ? "UNPIN_BUTTON_TEXT".localized() : "PIN_BUTTON_TEXT".localized() ), - icon: (threadViewModel.threadIsPinned ? + icon: (threadViewModel.threadPinnedPriority > 0 ? UIImage(systemName: "pin.slash") : UIImage(systemName: "pin") ), @@ -763,16 +763,27 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, SeedRemi tableView: tableView ) { _, _, completionHandler in (tableView.cellForRow(at: indexPath) as? FullConversationCell)?.optimisticUpdate( - isPinned: !threadViewModel.threadIsPinned + isPinned: !(threadViewModel.threadPinnedPriority > 0) ) completionHandler(true) // Delay the change to give the cell "unswipe" animation some time to complete DispatchQueue.global(qos: .default).asyncAfter(deadline: .now() + unswipeAnimationDelay) { Storage.shared.writeAsync { db in - try SessionThread - .filter(id: threadViewModel.threadId) - .updateAll(db, SessionThread.Columns.isPinned.set(to: !threadViewModel.threadIsPinned)) + // If we are unpinning then just clear the value + guard threadViewModel.threadPinnedPriority == 0 else { + try SessionThread + .filter(id: threadViewModel.threadId) + .updateAllAndConfig( + db, + SessionThread.Columns.pinnedPriority.set(to: 0) + ) + return + } + + // Otherwise we want to reset the priority values for all of the currently + // pinned threads (adding the newly pinned one at the end) + try SessionThread.refreshPinnedPriorities(db, adding: threadViewModel.threadId) } } } diff --git a/Session/Home/HomeViewModel.swift b/Session/Home/HomeViewModel.swift index 5317bdb13..8350635d3 100644 --- a/Session/Home/HomeViewModel.swift +++ b/Session/Home/HomeViewModel.swift @@ -62,7 +62,7 @@ public class HomeViewModel { columns: [ .id, .shouldBeVisible, - .isPinned, + .pinnedPriority, .mutedUntilTimestamp, .onlyNotifyForMentions, .markedAsUnread @@ -326,8 +326,9 @@ public class HomeViewModel { elements: data .filter { $0.id != SessionThreadViewModel.invalidId } .sorted { lhs, rhs -> Bool in - if lhs.threadIsPinned && !rhs.threadIsPinned { return true } - if !lhs.threadIsPinned && rhs.threadIsPinned { return false } + guard lhs.threadPinnedPriority == rhs.threadPinnedPriority else { + return lhs.threadPinnedPriority > rhs.threadPinnedPriority + } return lhs.lastInteractionDate > rhs.lastInteractionDate } @@ -373,7 +374,11 @@ public class HomeViewModel { .sinkUntilComplete() case .community: - OpenGroupManager.shared.delete(db, openGroupId: threadId) + OpenGroupManager.shared.delete( + db, + openGroupId: threadId, + calledFromConfigHandling: false + ) default: break } diff --git a/Session/Home/Message Requests/MessageRequestsViewController.swift b/Session/Home/Message Requests/MessageRequestsViewController.swift index 98b424f4e..579f109da 100644 --- a/Session/Home/Message Requests/MessageRequestsViewController.swift +++ b/Session/Home/Message Requests/MessageRequestsViewController.swift @@ -490,7 +490,8 @@ class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDat try ClosedGroup.removeKeysAndUnsubscribe( db, threadIds: closedGroupThreadIds, - removeGroupData: true + removeGroupData: true, + calledFromConfigHandling: false ) } }) diff --git a/Session/Home/Message Requests/MessageRequestsViewModel.swift b/Session/Home/Message Requests/MessageRequestsViewModel.swift index 058c24486..85c31dbda 100644 --- a/Session/Home/Message Requests/MessageRequestsViewModel.swift +++ b/Session/Home/Message Requests/MessageRequestsViewModel.swift @@ -195,11 +195,12 @@ public class MessageRequestsViewModel { try ClosedGroup.removeKeysAndUnsubscribe( db, threadId: threadId, - removeGroupData: true + removeGroupData: true, + calledFromConfigHandling: false ) // Trigger a config sync - ConfigurationSyncJob.enqueue(db) + ConfigurationSyncJob.enqueue(db, publicKey: getUserHexEncodedPublicKey(db)) } } diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 23e9894bb..1a0683458 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -295,7 +295,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD appVersion.lastAppVersion != appVersion.currentAppVersion ) { - ConfigurationSyncJob.enqueue(db) + ConfigurationSyncJob.enqueue(db, publicKey: getUserHexEncodedPublicKey(db)) } } } @@ -666,7 +666,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD Storage.shared .writeAsync( - updates: { db in ConfigurationSyncJob.enqueue(db) }, + updates: { db in + ConfigurationSyncJob.enqueue(db, publicKey: getUserHexEncodedPublicKey(db)) + }, completion: { _, result in switch result { case .failure: break diff --git a/Session/Open Groups/JoinOpenGroupVC.swift b/Session/Open Groups/JoinOpenGroupVC.swift index e204a2005..f762e2930 100644 --- a/Session/Open Groups/JoinOpenGroupVC.swift +++ b/Session/Open Groups/JoinOpenGroupVC.swift @@ -151,7 +151,7 @@ final class JoinOpenGroupVC: BaseVC, UIPageViewControllerDataSource, UIPageViewC fileprivate func joinOpenGroup(with urlString: String) { // A V2 open group URL will look like: + + + + // The host doesn't parse if no explicit scheme is provided - guard let (room, server, publicKey) = OpenGroupManager.parseOpenGroup(from: urlString) else { + guard let (room, server, publicKey) = SessionUtil.parseCommunity(url: urlString) else { showError( title: "invalid_url".localized(), message: "COMMUNITY_ERROR_INVALID_URL".localized() @@ -169,12 +169,13 @@ final class JoinOpenGroupVC: BaseVC, UIPageViewControllerDataSource, UIPageViewC ModalActivityIndicatorViewController.present(fromViewController: navigationController, canCancel: false) { [weak self] _ in Storage.shared - .writePublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .writePublisherFlatMap(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db in OpenGroupManager.shared.add( db, roomToken: roomToken, server: server, - publicKey: publicKey + publicKey: publicKey, + calledFromConfigHandling: false ) } .receive(on: DispatchQueue.main) diff --git a/Session/Shared/FullConversationCell.swift b/Session/Shared/FullConversationCell.swift index 975893f5f..a70273c33 100644 --- a/Session/Shared/FullConversationCell.swift +++ b/Session/Shared/FullConversationCell.swift @@ -397,7 +397,7 @@ public final class FullConversationCell: UITableViewCell { accentLineView.alpha = (unreadCount > 0 ? 1 : 0.0001) // Setting the alpha to exactly 0 causes an issue on iOS 12 } - isPinnedIcon.isHidden = !cellViewModel.threadIsPinned + isPinnedIcon.isHidden = (cellViewModel.threadPinnedPriority == 0) unreadCountView.isHidden = (unreadCount <= 0) unreadImageView.isHidden = (!unreadCountView.isHidden || !threadIsUnread) unreadCountLabel.text = (unreadCount <= 0 ? diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index 4b32ea07e..09ea50b33 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -135,6 +135,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { db, message: message, to: try Message.Destination.from(db, thread: thread), + namespace: try Message.Destination.from(db, thread: thread).defaultNamespace, interactionId: interactionId ) ) @@ -194,6 +195,8 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { sentTimestampMs: UInt64(SnodeAPI.currentOffsetTimestampMs()) ), to: try Message.Destination.from(db, thread: thread), + namespace: try Message.Destination.from(db, thread: thread) + .defaultNamespace, interactionId: nil ) } @@ -257,6 +260,8 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { sdps: [ sdp.sdp ] ), to: try Message.Destination.from(db, thread: thread), + namespace: try Message.Destination.from(db, thread: thread) + .defaultNamespace, interactionId: nil ) } @@ -314,6 +319,8 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { sdps: candidates.map { $0.sdp } ), to: try Message.Destination.from(db, thread: thread), + namespace: try Message.Destination.from(db, thread: thread) + .defaultNamespace, interactionId: nil ) ) @@ -338,6 +345,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { sdps: [] ), to: try Message.Destination.from(db, thread: thread), + namespace: try Message.Destination.from(db, thread: thread).defaultNamespace, interactionId: nil ) diff --git a/SessionMessagingKit/Database/Migrations/_012_SharedUtilChanges.swift b/SessionMessagingKit/Database/Migrations/_012_SharedUtilChanges.swift index 7b215c5a6..612fcbc59 100644 --- a/SessionMessagingKit/Database/Migrations/_012_SharedUtilChanges.swift +++ b/SessionMessagingKit/Database/Migrations/_012_SharedUtilChanges.swift @@ -17,8 +17,15 @@ enum _012_SharedUtilChanges: Migration { // Add `markedAsUnread` to the thread table try db.alter(table: SessionThread.self) { t in t.add(.markedAsUnread, .boolean) + t.add(.pinnedPriority, .integer) } + // Add an index for the 'ClosedGroupKeyPair' so we can lookup existing keys + try db.createIndex( + on: ClosedGroupKeyPair.self, + columns: [.threadId, .publicKey, .secretKey] + ) + // New table for storing the latest config dump for each type try db.create(table: ConfigDump.self) { t in t.column(.variant, .text) @@ -28,11 +35,18 @@ enum _012_SharedUtilChanges: Migration { .indexed() t.column(.data, .blob) .notNull() - t.column(.combinedMessageHashes, .text) t.primaryKey([.variant, .publicKey]) } + // Migrate the 'isPinned' value to 'pinnedPriority' + try SessionThread + .filter(SessionThread.Columns.isPinned == true) + .updateAll( + db, + SessionThread.Columns.pinnedPriority.set(to: 1) + ) + // If we don't have an ed25519 key then no need to create cached dump data let userPublicKey: String = getUserHexEncodedPublicKey(db) @@ -41,7 +55,17 @@ enum _012_SharedUtilChanges: Migration { return } - // Create a dump for the user profile data + // MARK: - Shared Data + + let pinnedThreadIds: [String] = try SessionThread + .select(SessionThread.Columns.id) + .filter(SessionThread.Columns.isPinned) + .order(Column.rowID) + .asRequest(of: String.self) + .fetchAll(db) + + // MARK: - UserProfile Config Dump + let userProfileConf: UnsafeMutablePointer? = try SessionUtil.loadState( for: .userProfile, secretKey: secretKey, @@ -49,7 +73,7 @@ enum _012_SharedUtilChanges: Migration { ) let userProfileConfResult: SessionUtil.ConfResult = try SessionUtil.update( profile: Profile.fetchOrCreateCurrentUser(db), - in: Atomic(userProfileConf) + in: userProfileConf ) if userProfileConfResult.needsDump { @@ -57,23 +81,13 @@ enum _012_SharedUtilChanges: Migration { .createDump( conf: userProfileConf, for: .userProfile, - publicKey: userPublicKey, - messageHashes: nil + publicKey: userPublicKey )? .save(db) } - // Create a dump for the contacts data - struct ContactInfo: FetchableRecord, Decodable, ColumnExpressible { - typealias Columns = CodingKeys - enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable { - case contact - case profile - } - - let contact: Contact - let profile: Profile? - } + // MARK: - Contact Config Dump + let contactsData: [ContactInfo] = try Contact .including(optional: Contact.profile) .asRequest(of: ContactInfo.self) @@ -85,8 +99,17 @@ enum _012_SharedUtilChanges: Migration { cachedData: nil ) let contactsConfResult: SessionUtil.ConfResult = try SessionUtil.upsert( - contactData: contactsData.map { ($0.contact.id, $0.contact, $0.profile) }, - in: Atomic(contactsConf) + contactData: contactsData + .map { data in + ( + data.contact.id, + data.contact, + data.profile, + Int32(pinnedThreadIds.firstIndex(of: data.contact.id) ?? 0), + false + ) + }, + in: contactsConf ) if contactsConfResult.needsDump { @@ -94,13 +117,13 @@ enum _012_SharedUtilChanges: Migration { .createDump( conf: contactsConf, for: .contacts, - publicKey: userPublicKey, - messageHashes: nil + publicKey: userPublicKey )? .save(db) } - // Create a dump for the convoInfoVolatile data + // MARK: - ConvoInfoVolatile Config Dump + let volatileThreadInfo: [SessionUtil.VolatileThreadInfo] = SessionUtil.VolatileThreadInfo.fetchAll(db) let convoInfoVolatileConf: UnsafeMutablePointer? = try SessionUtil.loadState( for: .convoInfoVolatile, @@ -109,7 +132,7 @@ enum _012_SharedUtilChanges: Migration { ) let convoInfoVolatileConfResult: SessionUtil.ConfResult = try SessionUtil.upsert( convoInfoVolatileChanges: volatileThreadInfo, - in: Atomic(convoInfoVolatileConf) + in: convoInfoVolatileConf ) if convoInfoVolatileConfResult.needsDump { @@ -117,11 +140,93 @@ enum _012_SharedUtilChanges: Migration { .createDump( conf: contactsConf, for: .convoInfoVolatile, - publicKey: userPublicKey, - messageHashes: nil + publicKey: userPublicKey )? .save(db) } + + // MARK: - UserGroups Config Dump + + let legacyGroupData: [SessionUtil.LegacyGroupInfo] = try SessionUtil.LegacyGroupInfo.fetchAll(db) + let communityData: [SessionUtil.OpenGroupUrlInfo] = try SessionUtil.OpenGroupUrlInfo.fetchAll(db) + + let userGroupsConf: UnsafeMutablePointer? = try SessionUtil.loadState( + for: .userGroups, + secretKey: secretKey, + cachedData: nil + ) + let userGroupConfResult1: SessionUtil.ConfResult = try SessionUtil.upsert( + legacyGroups: legacyGroupData, + in: userGroupsConf + ) + let userGroupConfResult2: SessionUtil.ConfResult = try SessionUtil.upsert( + communities: communityData.map { ($0, nil) }, + in: userGroupsConf + ) + + if userGroupConfResult1.needsDump || userGroupConfResult2.needsDump { + try SessionUtil + .createDump( + conf: userGroupsConf, + for: .userGroups, + publicKey: userPublicKey + )? + .save(db) + } + + // MARK: - Pinned thread priorities + + struct PinnedTeadInfo: Decodable, FetchableRecord { + let id: String + let creationDateTimestamp: TimeInterval + let maxInteractionTimestampMs: Int64? + + var targetTimestamp: Int64 { + (maxInteractionTimestampMs ?? Int64(creationDateTimestamp * 1000)) + } + } + + // At the time of writing the thread sorting was 'pinned (flag), most recent interaction + // timestamp, thread creation timestamp) + let thread: TypedTableAlias = TypedTableAlias() + let interaction: TypedTableAlias = TypedTableAlias() + let pinnedThreads: [PinnedTeadInfo] = try SessionThread + .select(.id, .creationDateTimestamp) + .filter(SessionThread.Columns.isPinned == true) + .annotated(with: SessionThread.interactions.max(Interaction.Columns.timestampMs)) + .asRequest(of: PinnedTeadInfo.self) + .fetchAll(db) + .sorted { lhs, rhs in lhs.targetTimestamp > rhs.targetTimestamp } + + // Update the pinned thread priorities + try SessionUtil + .updateThreadPrioritiesIfNeeded(db, [SessionThread.Columns.pinnedPriority.set(to: 0)], []) Storage.update(progress: 1, for: self, in: target) // In case this is the last migration } + + // MARK: Fetchable Types + + struct ContactInfo: FetchableRecord, Decodable, ColumnExpressible { + typealias Columns = CodingKeys + enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable { + case contact + case profile + } + + let contact: Contact + let profile: Profile? + } + + struct GroupInfo: FetchableRecord, Decodable, ColumnExpressible { + typealias Columns = CodingKeys + enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable { + case closedGroup + case disappearingMessagesConfiguration + case groupMembers + } + + let closedGroup: ClosedGroup + let disappearingMessagesConfiguration: DisappearingMessagesConfiguration? + let groupMembers: [GroupMember] + } } diff --git a/SessionMessagingKit/Database/Models/ClosedGroup.swift b/SessionMessagingKit/Database/Models/ClosedGroup.swift index 085744805..e24dff75b 100644 --- a/SessionMessagingKit/Database/Models/ClosedGroup.swift +++ b/SessionMessagingKit/Database/Models/ClosedGroup.swift @@ -8,7 +8,7 @@ import SessionUtilitiesKit public struct ClosedGroup: Codable, Identifiable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { public static var databaseTableName: String { "closedGroup" } internal static let threadForeignKey = ForeignKey([Columns.threadId], to: [SessionThread.Columns.id]) - private static let thread = belongsTo(SessionThread.self, using: threadForeignKey) + public static let thread = belongsTo(SessionThread.self, using: threadForeignKey) internal static let keyPairs = hasMany( ClosedGroupKeyPair.self, using: ClosedGroupKeyPair.closedGroupForeignKey @@ -22,6 +22,12 @@ public struct ClosedGroup: Codable, Identifiable, FetchableRecord, PersistableRe case formationTimestamp } + /// The Group public key takes up 32 bytes + static let pubkeyByteLength: Int = 32 + + /// The Group secret key takes up 32 bytes + static let secretKeyByteLength: Int = 32 + public var id: String { threadId } // Identifiable public var publicKey: String { threadId } @@ -95,22 +101,32 @@ public extension ClosedGroup { static func removeKeysAndUnsubscribe( _ db: Database? = nil, threadId: String, - removeGroupData: Bool = false + removeGroupData: Bool, + calledFromConfigHandling: Bool ) throws { - try removeKeysAndUnsubscribe(db, threadIds: [threadId], removeGroupData: removeGroupData) + try removeKeysAndUnsubscribe( + db, + threadIds: [threadId], + removeGroupData: removeGroupData, + calledFromConfigHandling: calledFromConfigHandling + ) } static func removeKeysAndUnsubscribe( _ db: Database? = nil, threadIds: [String], - removeGroupData: Bool = false + removeGroupData: Bool, + calledFromConfigHandling: Bool ) throws { + guard !threadIds.isEmpty else { return } guard let db: Database = db else { Storage.shared.write { db in try ClosedGroup.removeKeysAndUnsubscribe( db, threadIds: threadIds, - removeGroupData: removeGroupData) + removeGroupData: removeGroupData, + calledFromConfigHandling: calledFromConfigHandling + ) } return } @@ -135,6 +151,17 @@ public extension ClosedGroup { .filter(threadIds.contains(ClosedGroupKeyPair.Columns.threadId)) .deleteAll(db) + struct ThreadIdVariant: Decodable, FetchableRecord { + let id: String + let variant: SessionThread.Variant + } + + let threadVariants: [ThreadIdVariant] = try SessionThread + .select(.id, .variant) + .filter(ids: threadIds) + .asRequest(of: ThreadIdVariant.self) + .fetchAll(db) + // Remove the remaining group data if desired if removeGroupData { try SessionThread @@ -149,5 +176,23 @@ public extension ClosedGroup { .filter(threadIds.contains(GroupMember.Columns.groupId)) .deleteAll(db) } + + // If we weren't called from config handling then we need to remove the group + // data from the config + if !calledFromConfigHandling { + try SessionUtil.remove( + db, + legacyGroupIds: threadVariants + .filter { $0.variant == .legacyGroup } + .map { $0.id } + ) + + try SessionUtil.remove( + db, + groupIds: threadVariants + .filter { $0.variant == .group } + .map { $0.id } + ) + } } } diff --git a/SessionMessagingKit/Database/Models/OpenGroup.swift b/SessionMessagingKit/Database/Models/OpenGroup.swift index 8f91813ec..55da87c1d 100644 --- a/SessionMessagingKit/Database/Models/OpenGroup.swift +++ b/SessionMessagingKit/Database/Models/OpenGroup.swift @@ -8,7 +8,7 @@ public struct OpenGroup: Codable, Identifiable, FetchableRecord, PersistableReco public static var databaseTableName: String { "openGroup" } internal static let threadForeignKey = ForeignKey([Columns.threadId], to: [SessionThread.Columns.id]) private static let thread = belongsTo(SessionThread.self, using: threadForeignKey) - private static let members = hasMany(GroupMember.self, using: GroupMember.openGroupForeignKey) + public static let members = hasMany(GroupMember.self, using: GroupMember.openGroupForeignKey) public typealias Columns = CodingKeys public enum CodingKeys: String, CodingKey, ColumnExpression { @@ -61,6 +61,9 @@ public struct OpenGroup: Codable, Identifiable, FetchableRecord, PersistableReco static let all: Permissions = [ .read, .write, .upload ] } + /// The Community public key takes up 32 bytes + static let pubkeyByteLength: Int = 32 + public var id: String { threadId } // Identifiable /// The id for the thread this open group belongs to @@ -219,10 +222,6 @@ public extension OpenGroup { // Always force the server to lowercase return "\(server.lowercased()).\(roomToken)" } - - static func urlFor(server: String, roomToken: String, publicKey: String) -> String { - return "\(server)/\(roomToken)?public_key=\(publicKey)" - } } extension OpenGroup: CustomStringConvertible, CustomDebugStringConvertible { diff --git a/SessionMessagingKit/Database/Models/SessionThread.swift b/SessionMessagingKit/Database/Models/SessionThread.swift index 75e4db415..15ff5d1ee 100644 --- a/SessionMessagingKit/Database/Models/SessionThread.swift +++ b/SessionMessagingKit/Database/Models/SessionThread.swift @@ -11,7 +11,7 @@ public struct SessionThread: Codable, Identifiable, Equatable, FetchableRecord, public static let contact = hasOne(Contact.self, using: Contact.threadForeignKey) public static let closedGroup = hasOne(ClosedGroup.self, using: ClosedGroup.threadForeignKey) public static let openGroup = hasOne(OpenGroup.self, using: OpenGroup.threadForeignKey) - private static let disappearingMessagesConfiguration = hasOne( + public static let disappearingMessagesConfiguration = hasOne( DisappearingMessagesConfiguration.self, using: DisappearingMessagesConfiguration.threadForeignKey ) @@ -33,6 +33,7 @@ public struct SessionThread: Codable, Identifiable, Equatable, FetchableRecord, case mutedUntilTimestamp case onlyNotifyForMentions case markedAsUnread + case pinnedPriority } public enum Variant: Int, Codable, Hashable, DatabaseValueConvertible { @@ -60,7 +61,8 @@ public struct SessionThread: Codable, Identifiable, Equatable, FetchableRecord, public let shouldBeVisible: Bool /// A flag indicating whether the thread is pinned - public let isPinned: Bool + @available(*, unavailable, message: "use 'pinnedPriority' instead") + public let isPinned: Bool = false /// The value the user started entering into the input field before they left the conversation screen public let messageDraft: String? @@ -79,6 +81,9 @@ public struct SessionThread: Codable, Identifiable, Equatable, FetchableRecord, /// A flag indicating whether this thread has been manually marked as unread by the user public let markedAsUnread: Bool? + /// A value indicating the priority of this conversation within the pinned conversations + public let pinnedPriority: Int32? + // MARK: - Relationships public var contact: QueryInterfaceRequest { @@ -117,18 +122,21 @@ public struct SessionThread: Codable, Identifiable, Equatable, FetchableRecord, notificationSound: Preferences.Sound? = nil, mutedUntilTimestamp: TimeInterval? = nil, onlyNotifyForMentions: Bool = false, - markedAsUnread: Bool? = false + markedAsUnread: Bool? = false, + pinnedPriority: Int32? = nil ) { self.id = id self.variant = variant self.creationDateTimestamp = creationDateTimestamp self.shouldBeVisible = shouldBeVisible - self.isPinned = isPinned self.messageDraft = messageDraft self.notificationSound = notificationSound self.mutedUntilTimestamp = mutedUntilTimestamp self.onlyNotifyForMentions = onlyNotifyForMentions self.markedAsUnread = markedAsUnread + self.pinnedPriority = ((pinnedPriority ?? 0) > 0 ? pinnedPriority : + (isPinned ? 1 : 0) + ) } // MARK: - Custom Database Interaction @@ -143,19 +151,19 @@ public struct SessionThread: Codable, Identifiable, Equatable, FetchableRecord, public extension SessionThread { func with( shouldBeVisible: Bool? = nil, - isPinned: Bool? = nil + pinnedPriority: Int32? = nil ) -> SessionThread { return SessionThread( id: id, variant: variant, creationDateTimestamp: creationDateTimestamp, shouldBeVisible: (shouldBeVisible ?? self.shouldBeVisible), - isPinned: (isPinned ?? self.isPinned), messageDraft: messageDraft, notificationSound: notificationSound, mutedUntilTimestamp: mutedUntilTimestamp, onlyNotifyForMentions: onlyNotifyForMentions, - markedAsUnread: markedAsUnread + markedAsUnread: markedAsUnread, + pinnedPriority: (pinnedPriority ?? self.pinnedPriority) ) } } @@ -190,6 +198,43 @@ public extension SessionThread { .defaulting(to: false) == false ) } + + static func refreshPinnedPriorities(_ db: Database, adding threadId: String) throws { + struct PinnedPriority: TableRecord, ColumnExpressible { + public typealias Columns = CodingKeys + public enum CodingKeys: String, CodingKey, ColumnExpression { + case id + case rowIndex + } + } + + let thread: TypedTableAlias = TypedTableAlias() + let pinnedPriority: TypedTableAlias = TypedTableAlias() + let rowIndexLiteral: SQL = SQL(stringLiteral: PinnedPriority.Columns.rowIndex.name) + let pinnedPriorityLiteral: SQL = SQL(stringLiteral: SessionThread.Columns.pinnedPriority.name) + + try db.execute(literal: """ + WITH \(PinnedPriority.self) AS ( + SELECT + \(thread[.id]), + ROW_NUMBER() OVER ( + ORDER BY \(SQL("\(thread[.id]) != \(threadId)")), + \(thread[.pinnedPriority]) ASC + ) AS \(rowIndexLiteral) + FROM \(SessionThread.self) + WHERE + \(thread[.pinnedPriority]) > 0 OR + \(SQL("\(thread[.id]) = \(threadId)")) + ) + + UPDATE \(SessionThread.self) + SET \(pinnedPriorityLiteral) = ( + SELECT \(pinnedPriority[.rowIndex]) + FROM \(PinnedPriority.self) + WHERE \(pinnedPriority[.id]) = \(thread[.id]) + ) + """) + } } // MARK: - Convenience diff --git a/SessionMessagingKit/Database/Models/SharedConfigDump.swift b/SessionMessagingKit/Database/Models/SharedConfigDump.swift index 8468dcf74..cca17d29f 100644 --- a/SessionMessagingKit/Database/Models/SharedConfigDump.swift +++ b/SessionMessagingKit/Database/Models/SharedConfigDump.swift @@ -13,7 +13,6 @@ public struct ConfigDump: Codable, Equatable, Hashable, FetchableRecord, Persist case variant case publicKey case data - case combinedMessageHashes } public enum Variant: String, Codable, DatabaseValueConvertible { @@ -34,37 +33,19 @@ public struct ConfigDump: Codable, Equatable, Hashable, FetchableRecord, Persist /// The data for this dump public let data: Data - /// A comma delimited array of message hashes for previously stored messages on the server - private let combinedMessageHashes: String? - - /// An array of message hashes for previously stored messages on the server - var messageHashes: [String]? { ConfigDump.messageHashes(from: combinedMessageHashes) } - internal init( variant: Variant, publicKey: String, - data: Data, - messageHashes: [String]? + data: Data ) { self.variant = variant self.publicKey = publicKey self.data = data - self.combinedMessageHashes = ConfigDump.combinedMessageHashes(from: messageHashes) } } // MARK: - Convenience -public extension ConfigDump { - static func combinedMessageHashes(from messageHashes: [String]?) -> String? { - return messageHashes?.joined(separator: ",") - } - - static func messageHashes(from combinedMessageHashes: String?) -> [String]? { - return combinedMessageHashes?.components(separatedBy: ",") - } -} - public extension ConfigDump.Variant { static let userVariants: [ConfigDump.Variant] = [ .userProfile, .contacts, .convoInfoVolatile, .userGroups @@ -89,13 +70,15 @@ public extension ConfigDump.Variant { } /// This value defines the order that the SharedConfigMessages should be processed in, while we re-process config - /// messages every time we poll this will prevent an edge-case where we have `convoInfoVolatile` data related - /// to a new conversation which hasn't been created yet because it's associated `contacts`/`userGroups` message - /// hasn't yet been processed (without this we would have to wait until the next poll for it to be processed correctly) + /// messages every time we poll this will prevent an edge-case where data/logic between different config messages + /// could be dependant on each other (eg. there could be `convoInfoVolatile` data related to a new conversation + /// which hasn't been created yet because it's associated `contacts`/`userGroups` message hasn't yet been + /// processed (without this we would have to wait until the next poll for it to be processed correctly) var processingOrder: Int { switch self { - case .userProfile, .contacts, .userGroups: return 0 - case .convoInfoVolatile: return 1 + case .userProfile, .contacts: return 0 + case .userGroups: return 1 + case .convoInfoVolatile: return 2 } } } diff --git a/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift b/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift index d5ba2baad..277e85294 100644 --- a/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift +++ b/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift @@ -38,19 +38,9 @@ public enum ConfigurationSyncJob: JobExecutor { // as the user doesn't exist yet (this will get triggered on the first launch of a // fresh install due to the migrations getting run) guard - let pendingSwarmConfigChanges: [SingleDestinationChanges] = Storage.shared - .read({ db in try SessionUtil.pendingChanges(db) })? - .grouped(by: { $0.destination }) - .map({ (destination: Message.Destination, value: [SessionUtil.OutgoingConfResult]) -> SingleDestinationChanges in - SingleDestinationChanges( - destination: destination, - messages: value, - allOldHashes: value - .map { ($0.oldMessageHashes ?? []) } - .reduce([], +) - .asSet() - ) - }) + let publicKey: String = job.threadId, + let pendingConfigChanges: [SessionUtil.OutgoingConfResult] = Storage.shared + .read({ db in try SessionUtil.pendingChanges(db, publicKey: publicKey) }) else { failure(job, StorageError.generic, false) return @@ -58,137 +48,69 @@ public enum ConfigurationSyncJob: JobExecutor { // If there are no pending changes then the job can just complete (next time something // is updated we want to try and run immediately so don't scuedule another run in this case) - guard !pendingSwarmConfigChanges.isEmpty else { + guard !pendingConfigChanges.isEmpty else { success(job, true) return } + // Identify the destination and merge all obsolete hashes into a single set + let destination: Message.Destination = (publicKey == getUserHexEncodedPublicKey() ? + Message.Destination.contact(publicKey: publicKey) : + Message.Destination.closedGroup(groupPublicKey: publicKey) + ) + let allObsoleteHashes: Set = pendingConfigChanges + .map { $0.obsoleteHashes } + .reduce([], +) + .asSet() + Storage.shared .readPublisher(receiveOn: queue) { db in - try pendingSwarmConfigChanges - .map { (change: SingleDestinationChanges) -> (messages: [TargetedMessage], allOldHashes: Set) in - ( - messages: try change.messages - .map { (outgoingConf: SessionUtil.OutgoingConfResult) -> TargetedMessage in - TargetedMessage( - sendData: try MessageSender.preparedSendData( - db, - message: outgoingConf.message, - to: change.destination, - interactionId: nil - ), - namespace: outgoingConf.namespace, - oldHashes: (outgoingConf.oldMessageHashes ?? []) - ) - }, - allOldHashes: change.allOldHashes - ) - } - } - .flatMap { (pendingSwarmChange: [(messages: [TargetedMessage], allOldHashes: Set)]) -> AnyPublisher<[HTTP.BatchResponse], Error> in - Publishers - .MergeMany( - pendingSwarmChange - .map { (messages: [TargetedMessage], oldHashes: Set) in - // Note: We do custom sending logic here because we want to batch the - // sending and deletion of messages within the same swarm - SnodeAPI - .sendConfigMessages( - messages - .compactMap { targetedMessage -> SnodeAPI.TargetedMessage? in - targetedMessage.sendData.snodeMessage - .map { ($0, targetedMessage.namespace) } - }, - oldHashes: Array(oldHashes) - ) - } + try pendingConfigChanges.map { change -> MessageSender.PreparedSendData in + try MessageSender.preparedSendData( + db, + message: change.message, + to: destination, + namespace: change.namespace, + interactionId: nil + ) + } + } + .flatMap { (changes: [MessageSender.PreparedSendData]) -> AnyPublisher in + SnodeAPI + .sendConfigMessages( + changes.compactMap { change in + guard + let namespace: SnodeAPI.Namespace = change.namespace, + let snodeMessage: SnodeMessage = change.snodeMessage + else { return nil } + + return (snodeMessage, namespace) + }, + allObsoleteHashes: Array(allObsoleteHashes) ) - .collect() - .eraseToAnyPublisher() } .receive(on: queue) - .tryMap { (responses: [HTTP.BatchResponse]) -> [SuccessfulChange] in - // We make a sequence call for this so it's possible to get fewer responses than - // expected so if that happens fail and re-run later - guard responses.count == pendingSwarmConfigChanges.count else { - throw HTTPError.invalidResponse - } - - // Process the response data into an easy to understand for (this isn't strictly - // needed but the code gets convoluted without this) - return zip(responses, pendingSwarmConfigChanges) - .compactMap { (batchResponse: HTTP.BatchResponse, pendingSwarmChange: SingleDestinationChanges) -> [SuccessfulChange]? in - let maybePublicKey: String? = { - switch pendingSwarmChange.destination { - case .contact(let publicKey), .closedGroup(let publicKey): - return publicKey - - default: return nil - } - }() + .map { (response: HTTP.BatchResponse) -> [ConfigDump] in + /// The number of responses returned might not match the number of changes sent but they will be returned + /// in the same order, this means we can just `zip` the two arrays as it will take the smaller of the two and + /// correctly align the response to the change + zip(response.responses, pendingConfigChanges) + .compactMap { (subResponse: Codable, change: SessionUtil.OutgoingConfResult) in + /// If the request wasn't successful then just ignore it (the next time we sync this config we will try + /// to send the changes again) + guard + let typedResponse: HTTP.BatchSubResponse = (subResponse as? HTTP.BatchSubResponse), + 200...299 ~= typedResponse.code, + !typedResponse.failedToParseBody, + let sendMessageResponse: SendMessagesResponse = typedResponse.body + else { return nil } - // If we don't have a publicKey then this is an invalid config - guard let publicKey: String = maybePublicKey else { return nil } - - // Need to know if we successfully deleted old messages (if we didn't then - // we want to keep the old hashes so we can delete them the next time) - let didDeleteOldConfigMessages: Bool = { - guard - let subResponse: HTTP.BatchSubResponse = (batchResponse.responses.last as? HTTP.BatchSubResponse), - 200...299 ~= subResponse.code - else { return false } - - return true - }() - - return zip(batchResponse.responses, pendingSwarmChange.messages) - .reduce(into: []) { (result: inout [SuccessfulChange], next: ResponseChange) in - // If the request wasn't successful then just ignore it (the next - // config sync will try make the changes again - guard - let subResponse: HTTP.BatchSubResponse = (next.response as? HTTP.BatchSubResponse), - 200...299 ~= subResponse.code, - !subResponse.failedToParseBody, - let sendMessageResponse: SendMessagesResponse = subResponse.body - else { return } - - result.append( - SuccessfulChange( - message: next.change.message, - publicKey: publicKey, - updatedHashes: (didDeleteOldConfigMessages ? - [sendMessageResponse.hash] : - (next.change.oldMessageHashes ?? []) - .appending(sendMessageResponse.hash) - ) - ) - ) - } - } - .flatMap { $0 } - } - .map { (successfulChanges: [SuccessfulChange]) -> [ConfigDump] in - // Now that we have the successful changes, we need to mark them as pushed and - // generate any config dumps which need to be stored - successfulChanges - .compactMap { successfulChange -> ConfigDump? in - // Updating the pushed state returns a flag indicating whether the config - // needs to be dumped - guard SessionUtil.markAsPushed(message: successfulChange.message, publicKey: successfulChange.publicKey) else { - return nil - } - - let variant: ConfigDump.Variant = successfulChange.message.kind.configDumpVariant - let atomicConf: Atomic?> = SessionUtil.config( - for: variant, - publicKey: successfulChange.publicKey - ) - - return try? SessionUtil.createDump( - conf: atomicConf.wrappedValue, - for: variant, - publicKey: successfulChange.publicKey, - messageHashes: successfulChange.updatedHashes + /// Since this change was successful we need to mark it as pushed and generate any config dumps + /// which need to be stored + return SessionUtil.markingAsPushed( + message: change.message, + serverHash: sendMessageResponse.hash, + publicKey: publicKey ) } } @@ -219,6 +141,7 @@ public enum ConfigurationSyncJob: JobExecutor { let existingJob: Job = try? Job .filter(Job.Columns.id != job.id) .filter(Job.Columns.variant == Job.Variant.configurationSync) + .filter(Job.Columns.threadId == publicKey) .fetchOne(db) { // If the next job isn't currently running then delay it's start time @@ -245,39 +168,11 @@ public enum ConfigurationSyncJob: JobExecutor { } } -// MARK: - Convenience Types - -public extension ConfigurationSyncJob { - fileprivate struct SingleDestinationChanges { - let destination: Message.Destination - let messages: [SessionUtil.OutgoingConfResult] - let allOldHashes: Set - } - - fileprivate struct TargetedMessage { - let sendData: MessageSender.PreparedSendData - let namespace: SnodeAPI.Namespace - let oldHashes: [String] - } - - typealias ResponseChange = (response: Codable, change: SessionUtil.OutgoingConfResult) - - fileprivate struct SuccessfulChange { - let message: SharedConfigMessage - let publicKey: String - let updatedHashes: [String] - } -} - // MARK: - Convenience public extension ConfigurationSyncJob { - static func enqueue(_ db: Database? = nil) { - guard let db: Database = db else { - Storage.shared.writeAsync { ConfigurationSyncJob.enqueue($0) } - return - } + static func enqueue(_ db: Database, publicKey: String) { // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent guard Features.useSharedUtilForUserConfig else { // If we don't have a userKeyPair (or name) yet then there is no need to sync the @@ -310,15 +205,16 @@ public extension ConfigurationSyncJob { // to add another one) JobRunner.upsert( db, - job: ConfigurationSyncJob.createOrUpdateIfNeeded(db) + job: ConfigurationSyncJob.createOrUpdateIfNeeded(db, publicKey: publicKey) ) } - @discardableResult static func createOrUpdateIfNeeded(_ db: Database) -> Job { + @discardableResult static func createOrUpdateIfNeeded(_ db: Database, publicKey: String) -> Job { // Try to get an existing job (if there is one that's not running) if let existingJobs: [Job] = try? Job .filter(Job.Columns.variant == Job.Variant.configurationSync) + .filter(Job.Columns.threadId == publicKey) .fetchAll(db), let existingJob: Job = existingJobs.first(where: { !JobRunner.isCurrentlyRunning($0) }) { @@ -328,7 +224,8 @@ public extension ConfigurationSyncJob { // Otherwise create a new job return Job( variant: .configurationSync, - behaviour: .recurring + behaviour: .recurring, + threadId: publicKey ) } @@ -348,6 +245,7 @@ public extension ConfigurationSyncJob { db, message: try ConfigurationMessage.getCurrent(db), to: Message.Destination.contact(publicKey: publicKey), + namespace: .default, interactionId: nil ) } diff --git a/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift b/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift index d583b6c69..3f01b4011 100644 --- a/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift +++ b/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift @@ -116,6 +116,8 @@ public enum GarbageCollectionJob: JobExecutor { LEFT JOIN \(SessionThread.self) ON \(thread[.id]) = \(job[.threadId]) LEFT JOIN \(Interaction.self) ON \(interaction[.id]) = \(job[.interactionId]) WHERE ( + -- Never delete config sync jobs, even if their threads were deleted + \(SQL("\(job[.variant]) != \(Job.Variant.configurationSync)")) AND ( \(job[.threadId]) IS NOT NULL AND \(thread[.id]) IS NULL diff --git a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift index 0fef91609..0438f1314 100644 --- a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift +++ b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift @@ -170,6 +170,7 @@ public enum MessageSendJob: JobExecutor { db, message: details.message, to: details.destination, + namespace: details.destination.defaultNamespace, interactionId: job.interactionId ) } diff --git a/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift b/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift index 2e266baf0..c492ceb47 100644 --- a/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift +++ b/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift @@ -43,6 +43,7 @@ public enum SendReadReceiptsJob: JobExecutor { timestamps: details.timestampMsValues.map { UInt64($0) } ), to: details.destination, + namespace: details.destination.defaultNamespace, interactionId: nil, isSyncMessage: false ) diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift index 09f85da72..89e64d3c1 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift @@ -10,9 +10,9 @@ internal extension SessionUtil { static func handleContactsUpdate( _ db: Database, - in atomicConf: Atomic?>, - mergeResult: ConfResult - ) throws -> ConfResult { + in conf: UnsafeMutablePointer?, + mergeNeedsDump: Bool + ) throws { typealias ContactData = [ String: ( contact: Contact, @@ -21,60 +21,53 @@ internal extension SessionUtil { ) ] - guard mergeResult.needsDump else { return mergeResult } - guard atomicConf.wrappedValue != nil else { throw SessionUtilError.nilConfigObject } + guard mergeNeedsDump else { return } + guard conf != nil else { throw SessionUtilError.nilConfigObject } - // Since we are doing direct memory manipulation we are using an `Atomic` type which has - // blocking access in it's `mutate` closure - let contactData: ContactData = atomicConf.mutate { conf -> ContactData in - var contactData: ContactData = [:] - var contact: contacts_contact = contacts_contact() - let contactIterator: UnsafeMutablePointer = contacts_iterator_new(conf) - - while !contacts_iterator_done(contactIterator, &contact) { - let contactId: String = String(cString: withUnsafeBytes(of: contact.session_id) { [UInt8]($0) } - .map { CChar($0) } - .nullTerminated() - ) - let contactResult: Contact = Contact( - id: contactId, - isApproved: contact.approved, - isBlocked: contact.blocked, - didApproveMe: contact.approved_me - ) - let profilePictureUrl: String? = String(libSessionVal: contact.profile_pic.url, nullIfEmpty: true) - let profileResult: Profile = Profile( - id: contactId, - name: (String(libSessionVal: contact.name) ?? ""), - nickname: String(libSessionVal: contact.nickname, nullIfEmpty: true), - profilePictureUrl: profilePictureUrl, - profileEncryptionKey: (profilePictureUrl == nil ? nil : - Data( - libSessionVal: contact.profile_pic.key, - count: ProfileManager.avatarAES256KeyByteLength - ) + var contactData: ContactData = [:] + var contact: contacts_contact = contacts_contact() + let contactIterator: UnsafeMutablePointer = contacts_iterator_new(conf) + + while !contacts_iterator_done(contactIterator, &contact) { + let contactId: String = String(cString: withUnsafeBytes(of: contact.session_id) { [UInt8]($0) } + .map { CChar($0) } + .nullTerminated() + ) + let contactResult: Contact = Contact( + id: contactId, + isApproved: contact.approved, + isBlocked: contact.blocked, + didApproveMe: contact.approved_me + ) + let profilePictureUrl: String? = String(libSessionVal: contact.profile_pic.url, nullIfEmpty: true) + let profileResult: Profile = Profile( + id: contactId, + name: String(libSessionVal: contact.name), + nickname: String(libSessionVal: contact.nickname, nullIfEmpty: true), + profilePictureUrl: profilePictureUrl, + profileEncryptionKey: (profilePictureUrl == nil ? nil : + Data( + libSessionVal: contact.profile_pic.key, + count: ProfileManager.avatarAES256KeyByteLength ) ) - - contactData[contactId] = ( - contactResult, - profileResult, - false - ) - contacts_iterator_advance(contactIterator) - } - contacts_iterator_free(contactIterator) // Need to free the iterator + ) - return contactData + contactData[contactId] = ( + contactResult, + profileResult, + ) + contacts_iterator_advance(contactIterator) } + contacts_iterator_free(contactIterator) // Need to free the iterator // The current users contact data is handled separately so exclude it if it's present (as that's // actually a bug) - let userPublicKey: String = getUserHexEncodedPublicKey() + let userPublicKey: String = getUserHexEncodedPublicKey(db) let targetContactData: ContactData = contactData.filter { $0.key != userPublicKey } // If we only updated the current user contact then no need to continue - guard !targetContactData.isEmpty else { return mergeResult } + guard !targetContactData.isEmpty else { return } // Since we don't sync 100% of the data stored against the contact and profile objects we // need to only update the data we do have to ensure we don't overwrite anything that doesn't @@ -141,79 +134,100 @@ internal extension SessionUtil { ].compactMap { $0 } ) } + + /// If the contact's `hidden` flag doesn't match the visibility of their conversation then create/delete the + /// associated contact conversation accordingly + let threadExists: Bool = try SessionThread.exists(db, id: contact.id) + + if data.isHiddenConversation && threadExists { + try SessionThread + .deleteOne(db, id: contact.id) + } + else if !data.isHiddenConversation && !threadExists { + try SessionThread(id: contact.id, variant: .contact) + .save(db) + } } - return mergeResult } // MARK: - Outgoing Changes + typealias ContactData = ( + id: String, + contact: Contact?, + profile: Profile?, + priority: Int32?, + hidden: Bool? + ) + static func upsert( - contactData: [(id: String, contact: Contact?, profile: Profile?)], - in atomicConf: Atomic?> + contactData: [ContactData], + in conf: UnsafeMutablePointer? ) throws -> ConfResult { - guard atomicConf.wrappedValue != nil else { throw SessionUtilError.nilConfigObject } + guard conf != nil else { throw SessionUtilError.nilConfigObject } // The current users contact data doesn't need to sync so exclude it let userPublicKey: String = getUserHexEncodedPublicKey() - let targetContacts: [(id: String, contact: Contact?, profile: Profile?)] = contactData .filter { $0.id != userPublicKey } + let targetContacts: [(id: String, contact: Contact?, profile: Profile?, priority: Int32?, hidden: Bool?)] = contactData // If we only updated the current user contact then no need to continue guard !targetContacts.isEmpty else { return ConfResult(needsPush: false, needsDump: false) } - // Since we are doing direct memory manipulation we are using an `Atomic` type which has - // blocking access in it's `mutate` closure - return atomicConf.mutate { conf in - // Update the name - targetContacts - .forEach { (id, maybeContact, maybeProfile) in - var sessionId: [CChar] = id.cArray - var contact: contacts_contact = contacts_contact() - guard contacts_get_or_construct(conf, &contact, &sessionId) else { - SNLog("Unable to upsert contact from Config Message") - return - } - - // Assign all properties to match the updated contact (if there is one) - if let updatedContact: Contact = maybeContact { - contact.approved = updatedContact.isApproved - contact.approved_me = updatedContact.didApproveMe - contact.blocked = updatedContact.isBlocked - - // Store the updated contact (needs to happen before variables go out of scope) - contacts_set(conf, &contact) - } - - // Update the profile data (if there is one - users we have sent a message request to may - // not have profile info in certain situations) - if let updatedProfile: Profile = maybeProfile { - let oldAvatarUrl: String? = String(libSessionVal: contact.profile_pic.url) - let oldAvatarKey: Data? = Data( - libSessionVal: contact.profile_pic.key, - count: ProfileManager.avatarAES256KeyByteLength - ) - - contact.name = updatedProfile.name.toLibSession() - contact.nickname = updatedProfile.nickname.toLibSession() - contact.profile_pic.url = updatedProfile.profilePictureUrl.toLibSession() - contact.profile_pic.key = updatedProfile.profileEncryptionKey.toLibSession() - - // Download the profile picture if needed - if oldAvatarUrl != updatedProfile.profilePictureUrl || oldAvatarKey != updatedProfile.profileEncryptionKey { - ProfileManager.downloadAvatar(for: updatedProfile) - } - - // Store the updated contact (needs to happen before variables go out of scope) - contacts_set(conf, &contact) - } + // Update the name + targetContacts + .forEach { (id, maybeContact, maybeProfile, priority, hidden) in + var sessionId: [CChar] = id.cArray + var contact: contacts_contact = contacts_contact() + guard contacts_get_or_construct(conf, &contact, &sessionId) else { + SNLog("Unable to upsert contact from Config Message") + return } - - return ConfResult( - needsPush: config_needs_push(conf), - needsDump: config_needs_dump(conf) - ) - } + + // Assign all properties to match the updated contact (if there is one) + if let updatedContact: Contact = maybeContact { + contact.approved = updatedContact.isApproved + contact.approved_me = updatedContact.didApproveMe + contact.blocked = updatedContact.isBlocked + + // Store the updated contact (needs to happen before variables go out of scope) + contacts_set(conf, &contact) + } + + // Update the profile data (if there is one - users we have sent a message request to may + // not have profile info in certain situations) + if let updatedProfile: Profile = maybeProfile { + let oldAvatarUrl: String? = String(libSessionVal: contact.profile_pic.url) + let oldAvatarKey: Data? = Data( + libSessionVal: contact.profile_pic.key, + count: ProfileManager.avatarAES256KeyByteLength + ) + + contact.name = updatedProfile.name.toLibSession() + contact.nickname = updatedProfile.nickname.toLibSession() + contact.profile_pic.url = updatedProfile.profilePictureUrl.toLibSession() + contact.profile_pic.key = updatedProfile.profileEncryptionKey.toLibSession() + + // Download the profile picture if needed + if oldAvatarUrl != updatedProfile.profilePictureUrl || oldAvatarKey != updatedProfile.profileEncryptionKey { + ProfileManager.downloadAvatar(for: updatedProfile) + } + + // Store the updated contact (needs to happen before variables go out of scope) + contacts_set(conf, &contact) + } + + // Store the updated contact (can't be sure if we made any changes above) + contact.hidden = (hidden ?? contact.hidden) + contact.priority = (priority ?? contact.priority) + contacts_set(conf, &contact) + } + + return ConfResult( + needsPush: config_needs_push(conf), + needsDump: config_needs_dump(conf) + ) } } @@ -230,38 +244,34 @@ internal extension SessionUtil { // If we only updated the current user contact then no need to continue guard !targetContacts.isEmpty else { return updated } - db.afterNextTransactionNested { db in - do { - let atomicConf: Atomic?> = SessionUtil.config( - for: .contacts, - publicKey: userPublicKey - ) + do { + let atomicConf: Atomic?> = SessionUtil.config( + for: .contacts, + publicKey: userPublicKey + ) + + // Since we are doing direct memory manipulation we are using an `Atomic` type which has + // blocking access in it's `mutate` closure + try atomicConf.mutate { conf in let result: ConfResult = try SessionUtil .upsert( - contactData: targetContacts.map { (id: $0.id, contact: $0, profile: nil) }, - in: atomicConf + contactData: targetContacts.map { ($0.id, $0, nil, nil, nil) }, + in: conf ) // If we don't need to dump the data the we can finish early guard result.needsDump else { return } - try SessionUtil.saveState( - db, - keepingExistingMessageHashes: true, - configDump: try atomicConf.mutate { conf in - try SessionUtil.createDump( - conf: conf, - for: .contacts, - publicKey: userPublicKey, - messageHashes: nil - ) - } - ) - } - catch { - SNLog("[libSession-util] Failed to dump updated data") + try SessionUtil.createDump( + conf: conf, + for: .contacts, + publicKey: userPublicKey + )?.save(db) } } + catch { + SNLog("[libSession-util] Failed to dump updated data") + } return updated } @@ -285,68 +295,64 @@ internal extension SessionUtil { // Get the user public key (updating their profile is handled separately let userPublicKey: String = getUserHexEncodedPublicKey(db) + let targetProfiles: [Profile] = updatedProfiles + .filter { + $0.id != userPublicKey && + existingContactIds.contains($0.id) + } - db.afterNextTransactionNested { db in - do { - // Update the user profile first (if needed) - if let updatedUserProfile: Profile = updatedProfiles.first(where: { $0.id == userPublicKey }) { - let atomicConf: Atomic?> = SessionUtil.config( + do { + // Update the user profile first (if needed) + if let updatedUserProfile: Profile = updatedProfiles.first(where: { $0.id == userPublicKey }) { + try SessionUtil + .config( for: .userProfile, publicKey: userPublicKey ) - let result: ConfResult = try SessionUtil.update( - profile: updatedUserProfile, - in: atomicConf - ) - - if result.needsDump { - try SessionUtil.saveState( - db, - keepingExistingMessageHashes: true, - configDump: try atomicConf.mutate { conf in - try SessionUtil.createDump( - conf: conf, - for: .userProfile, - publicKey: userPublicKey, - messageHashes: nil - ) - } + .mutate { conf in + let result: ConfResult = try SessionUtil.update( + profile: updatedUserProfile, + in: conf ) + + // If we don't need to dump the data the we can finish early + guard result.needsDump else { return } + + try SessionUtil.createDump( + conf: conf, + for: .userProfile, + publicKey: userPublicKey + )?.save(db) } - } - - // Then update other contacts - let atomicConf: Atomic?> = SessionUtil.config( + } + + // Since we are doing direct memory manipulation we are using an `Atomic` type which has + // blocking access in it's `mutate` closure + try SessionUtil + .config( for: .contacts, publicKey: userPublicKey ) - let result: ConfResult = try SessionUtil - .upsert( - contactData: updatedProfiles - .filter { $0.id != userPublicKey } - .map { (id: $0.id, contact: nil, profile: $0) }, - in: atomicConf - ) - - // If we don't need to dump the data the we can finish early - guard result.needsDump else { return } - - try SessionUtil.saveState( - db, - keepingExistingMessageHashes: true, - configDump: try atomicConf.mutate { conf in - try SessionUtil.createDump( - conf: conf, - for: .contacts, - publicKey: userPublicKey, - messageHashes: nil + .mutate { conf in + let result: ConfResult = try SessionUtil + .upsert( + contactData: targetProfiles + .map { ($0.id, nil, $0, nil, nil) }, + in: conf ) - } - ) - } - catch { - SNLog("[libSession-util] Failed to dump updated data") - } + + // If we don't need to dump the data the we can finish early + guard result.needsDump else { return } + + try SessionUtil.createDump( + conf: conf, + for: .contacts, + publicKey: userPublicKey + )?.save(db) + } + } + catch { + SNLog("[libSession-util] Failed to dump updated data") } return updated diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift index 7c6d9facd..f48abe88d 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift @@ -6,103 +6,88 @@ import SessionUtil import SessionUtilitiesKit internal extension SessionUtil { + static let columnsRelatedToConvoInfoVolatile: [ColumnExpression] = [ + // Note: We intentionally exclude 'Interaction.Columns.wasRead' from here as we want to + // manually manage triggering config updates from marking as read + SessionThread.Columns.markedAsUnread + ] + // MARK: - Incoming Changes static func handleConvoInfoVolatileUpdate( _ db: Database, - in atomicConf: Atomic?>, - mergeResult: ConfResult - ) throws -> ConfResult { - guard mergeResult.needsDump else { return mergeResult } - guard atomicConf.wrappedValue != nil else { throw SessionUtilError.nilConfigObject } + in conf: UnsafeMutablePointer?, + mergeNeedsDump: Bool + ) throws { + guard mergeNeedsDump else { return } + guard conf != nil else { throw SessionUtilError.nilConfigObject } - // Since we are doing direct memory manipulation we are using an `Atomic` type which has - // blocking access in it's `mutate` closure - let volatileThreadInfo: [VolatileThreadInfo] = atomicConf.mutate { conf -> [VolatileThreadInfo] in - var volatileThreadInfo: [VolatileThreadInfo] = [] - var oneToOne: convo_info_volatile_1to1 = convo_info_volatile_1to1() - var community: convo_info_volatile_community = convo_info_volatile_community() - var legacyGroup: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() - let convoIterator: OpaquePointer = convo_info_volatile_iterator_new(conf) + var volatileThreadInfo: [VolatileThreadInfo] = [] + var oneToOne: convo_info_volatile_1to1 = convo_info_volatile_1to1() + var community: convo_info_volatile_community = convo_info_volatile_community() + var legacyGroup: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() + let convoIterator: OpaquePointer = convo_info_volatile_iterator_new(conf) - while !convo_info_volatile_iterator_done(convoIterator) { - if convo_info_volatile_it_is_1to1(convoIterator, &oneToOne) { - let sessionId: String = String(cString: withUnsafeBytes(of: oneToOne.session_id) { [UInt8]($0) } - .map { CChar($0) } - .nullTerminated() + while !convo_info_volatile_iterator_done(convoIterator) { + if convo_info_volatile_it_is_1to1(convoIterator, &oneToOne) { + volatileThreadInfo.append( + VolatileThreadInfo( + threadId: String(libSessionVal: oneToOne.session_id), + variant: .contact, + changes: [ + .markedAsUnread(oneToOne.unread), + .lastReadTimestampMs(oneToOne.last_read) + ] ) - - volatileThreadInfo.append( - VolatileThreadInfo( - threadId: sessionId, - variant: .contact, - changes: [ - .markedAsUnread(oneToOne.unread), - .lastReadTimestampMs(oneToOne.last_read) - ] - ) - ) - } - else if convo_info_volatile_it_is_community(convoIterator, &community) { - let server: String = String(cString: withUnsafeBytes(of: community.base_url) { [UInt8]($0) } - .map { CChar($0) } - .nullTerminated() - ) - let roomToken: String = String(cString: withUnsafeBytes(of: community.room) { [UInt8]($0) } - .map { CChar($0) } - .nullTerminated() - ) - let publicKey: String = withUnsafePointer(to: community.pubkey, { pubkeyBytes in - Data(bytes: pubkeyBytes, count: 32).toHexString() - }) - - volatileThreadInfo.append( - VolatileThreadInfo( - threadId: OpenGroup.idFor(roomToken: roomToken, server: server), - variant: .community, - openGroupUrlInfo: VolatileThreadInfo.OpenGroupUrlInfo( - threadId: OpenGroup.idFor(roomToken: roomToken, server: server), - server: server, - roomToken: roomToken, - publicKey: publicKey - ), - changes: [ - .markedAsUnread(community.unread), - .lastReadTimestampMs(community.last_read) - ] - ) - ) - } - else if convo_info_volatile_it_is_legacy_group(convoIterator, &legacyGroup) { - let groupId: String = String(cString: withUnsafeBytes(of: legacyGroup.group_id) { [UInt8]($0) } - .map { CChar($0) } - .nullTerminated() - ) - - volatileThreadInfo.append( - VolatileThreadInfo( - threadId: groupId, - variant: .legacyGroup, - changes: [ - .markedAsUnread(legacyGroup.unread), - .lastReadTimestampMs(legacyGroup.last_read) - ] - ) - ) - } - else { - SNLog("Ignoring unknown conversation type when iterating through volatile conversation info update") - } - - convo_info_volatile_iterator_advance(convoIterator) + ) + } + else if convo_info_volatile_it_is_community(convoIterator, &community) { + let server: String = String(libSessionVal: community.base_url) + let roomToken: String = String(libSessionVal: community.room) + let publicKey: String = Data( + libSessionVal: community.pubkey, + count: OpenGroup.pubkeyByteLength + ).toHexString() + + volatileThreadInfo.append( + VolatileThreadInfo( + threadId: OpenGroup.idFor(roomToken: roomToken, server: server), + variant: .community, + openGroupUrlInfo: OpenGroupUrlInfo( + threadId: OpenGroup.idFor(roomToken: roomToken, server: server), + server: server, + roomToken: roomToken, + publicKey: publicKey + ), + changes: [ + .markedAsUnread(community.unread), + .lastReadTimestampMs(community.last_read) + ] + ) + ) + } + else if convo_info_volatile_it_is_legacy_group(convoIterator, &legacyGroup) { + volatileThreadInfo.append( + VolatileThreadInfo( + threadId: String(libSessionVal: legacyGroup.group_id), + variant: .legacyGroup, + changes: [ + .markedAsUnread(legacyGroup.unread), + .lastReadTimestampMs(legacyGroup.last_read) + ] + ) + ) + } + else { + SNLog("Ignoring unknown conversation type when iterating through volatile conversation info update") } - convo_info_volatile_iterator_free(convoIterator) // Need to free the iterator - return volatileThreadInfo + convo_info_volatile_iterator_advance(convoIterator) } + convo_info_volatile_iterator_free(convoIterator) // Need to free the iterator // If we don't have any conversations then no need to continue - guard !volatileThreadInfo.isEmpty else { return mergeResult } + guard !volatileThreadInfo.isEmpty else { return } // Get the local volatile thread info from all conversations let localVolatileThreadInfo: [String: VolatileThreadInfo] = VolatileThreadInfo.fetchAll(db) @@ -171,109 +156,105 @@ internal extension SessionUtil { } // If there are no newer local last read timestamps then just return the mergeResult - guard !newerLocalChanges.isEmpty else { return mergeResult } + guard !newerLocalChanges.isEmpty else { return } - return try upsert( + try upsert( convoInfoVolatileChanges: newerLocalChanges, - in: atomicConf + in: conf ) } - static func upsert( + @discardableResult static func upsert( convoInfoVolatileChanges: [VolatileThreadInfo], - in atomicConf: Atomic?> + in conf: UnsafeMutablePointer? ) throws -> ConfResult { - guard atomicConf.wrappedValue != nil else { throw SessionUtilError.nilConfigObject } + guard conf != nil else { throw SessionUtilError.nilConfigObject } - // Since we are doing direct memory manipulation we are using an `Atomic` type which has - // blocking access in it's `mutate` closure - return atomicConf.mutate { conf in - convoInfoVolatileChanges.forEach { threadInfo in - var cThreadId: [CChar] = threadInfo.threadId.cArray - - switch threadInfo.variant { - case .contact: - var oneToOne: convo_info_volatile_1to1 = convo_info_volatile_1to1() - - guard convo_info_volatile_get_or_construct_1to1(conf, &oneToOne, &cThreadId) else { - SNLog("Unable to create contact conversation when updating last read timestamp") - return - } - - threadInfo.changes.forEach { change in - switch change { - case .lastReadTimestampMs(let lastReadMs): - oneToOne.last_read = lastReadMs - - case .markedAsUnread(let unread): - oneToOne.unread = unread - } - } - convo_info_volatile_set_1to1(conf, &oneToOne) - - case .legacyGroup: - var legacyGroup: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() - - guard convo_info_volatile_get_or_construct_legacy_group(conf, &legacyGroup, &cThreadId) else { - SNLog("Unable to create legacy group conversation when updating last read timestamp") - return - } - - threadInfo.changes.forEach { change in - switch change { - case .lastReadTimestampMs(let lastReadMs): - legacyGroup.last_read = lastReadMs - - case .markedAsUnread(let unread): - legacyGroup.unread = unread - } - } - convo_info_volatile_set_legacy_group(conf, &legacyGroup) - - case .community: - guard - var cBaseUrl: [CChar] = threadInfo.openGroupUrlInfo?.server.cArray, - var cRoomToken: [CChar] = threadInfo.openGroupUrlInfo?.roomToken.cArray, - var cPubkey: [UInt8] = threadInfo.openGroupUrlInfo?.publicKey.bytes - else { - SNLog("Unable to create community conversation when updating last read timestamp due to missing URL info") - return - } - - var community: convo_info_volatile_community = convo_info_volatile_community() - - guard convo_info_volatile_get_or_construct_community(conf, &community, &cBaseUrl, &cRoomToken, &cPubkey) else { - SNLog("Unable to create legacy group conversation when updating last read timestamp") - return - } - - threadInfo.changes.forEach { change in - switch change { - case .lastReadTimestampMs(let lastReadMs): - community.last_read = lastReadMs - - case .markedAsUnread(let unread): - community.unread = unread - } - } - convo_info_volatile_set_community(conf, &community) - - case .group: return // TODO: Need to add when the type is added to the lib - } - } + convoInfoVolatileChanges.forEach { threadInfo in + var cThreadId: [CChar] = threadInfo.threadId.cArray - return ConfResult( - needsPush: config_needs_push(conf), - needsDump: config_needs_dump(conf) - ) + switch threadInfo.variant { + case .contact: + var oneToOne: convo_info_volatile_1to1 = convo_info_volatile_1to1() + + guard convo_info_volatile_get_or_construct_1to1(conf, &oneToOne, &cThreadId) else { + SNLog("Unable to create contact conversation when updating last read timestamp") + return + } + + threadInfo.changes.forEach { change in + switch change { + case .lastReadTimestampMs(let lastReadMs): + oneToOne.last_read = lastReadMs + + case .markedAsUnread(let unread): + oneToOne.unread = unread + } + } + convo_info_volatile_set_1to1(conf, &oneToOne) + + case .legacyGroup: + var legacyGroup: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() + + guard convo_info_volatile_get_or_construct_legacy_group(conf, &legacyGroup, &cThreadId) else { + SNLog("Unable to create legacy group conversation when updating last read timestamp") + return + } + + threadInfo.changes.forEach { change in + switch change { + case .lastReadTimestampMs(let lastReadMs): + legacyGroup.last_read = lastReadMs + + case .markedAsUnread(let unread): + legacyGroup.unread = unread + } + } + convo_info_volatile_set_legacy_group(conf, &legacyGroup) + + case .community: + guard + var cBaseUrl: [CChar] = threadInfo.openGroupUrlInfo?.server.cArray, + var cRoomToken: [CChar] = threadInfo.openGroupUrlInfo?.roomToken.cArray, + var cPubkey: [UInt8] = threadInfo.openGroupUrlInfo?.publicKey.bytes + else { + SNLog("Unable to create community conversation when updating last read timestamp due to missing URL info") + return + } + + var community: convo_info_volatile_community = convo_info_volatile_community() + + guard convo_info_volatile_get_or_construct_community(conf, &community, &cBaseUrl, &cRoomToken, &cPubkey) else { + SNLog("Unable to create legacy group conversation when updating last read timestamp") + return + } + + threadInfo.changes.forEach { change in + switch change { + case .lastReadTimestampMs(let lastReadMs): + community.last_read = lastReadMs + + case .markedAsUnread(let unread): + community.unread = unread + } + } + convo_info_volatile_set_community(conf, &community) + + case .group: return // TODO: Need to add when the type is added to the lib. + } } + + return ConfResult( + needsPush: config_needs_push(conf), + needsDump: config_needs_dump(conf) + ) } } // MARK: - Convenience internal extension SessionUtil { - static func updatingThreads(_ db: Database, _ updated: [T]) throws -> [T] { + @discardableResult static func updatingThreadsConvoInfoVolatile(_ db: Database, _ updated: [T]) throws -> [T] { guard let updatedThreads: [SessionThread] = updated as? [SessionThread] else { throw StorageError.generic } @@ -287,42 +268,38 @@ internal extension SessionUtil { threadId: thread.id, variant: thread.variant, openGroupUrlInfo: (thread.variant != .community ? nil : - try VolatileThreadInfo.OpenGroupUrlInfo.fetchOne(db, id: thread.id) + try OpenGroupUrlInfo.fetchOne(db, id: thread.id) ), changes: [.markedAsUnread(thread.markedAsUnread ?? false)] ) } - db.afterNextTransactionNested { db in - do { - let atomicConf: Atomic?> = SessionUtil.config( + do { + try SessionUtil + .config( for: .convoInfoVolatile, publicKey: userPublicKey ) - let result: ConfResult = try upsert( - convoInfoVolatileChanges: changes, - in: atomicConf - ) - - // If we don't need to dump the data the we can finish early - guard result.needsDump else { return } - - try SessionUtil.saveState( - db, - keepingExistingMessageHashes: true, - configDump: try atomicConf.mutate { conf in - try SessionUtil.createDump( - conf: conf, - for: .convoInfoVolatile, - publicKey: userPublicKey, - messageHashes: nil - ) - } - ) - } - catch { - SNLog("[libSession-util] Failed to dump updated data") - } + .mutate { conf in + guard conf != nil else { throw SessionUtilError.nilConfigObject } + + let result: ConfResult = try upsert( + convoInfoVolatileChanges: changes, + in: conf + ) + + // If we don't need to dump the data the we can finish early + guard result.needsDump else { return } + + try SessionUtil.createDump( + conf: conf, + for: .convoInfoVolatile, + publicKey: userPublicKey + )?.save(db) + } + } + catch { + SNLog("[libSession-util] Failed to dump updated data") } return updated @@ -335,44 +312,45 @@ internal extension SessionUtil { lastReadTimestampMs: Int64 ) throws { let userPublicKey: String = getUserHexEncodedPublicKey(db) - let atomicConf: Atomic?> = SessionUtil.config( - for: .convoInfoVolatile, - publicKey: userPublicKey - ) let change: VolatileThreadInfo = VolatileThreadInfo( threadId: threadId, variant: threadVariant, openGroupUrlInfo: (threadVariant != .community ? nil : - try VolatileThreadInfo.OpenGroupUrlInfo.fetchOne(db, id: threadId) + try OpenGroupUrlInfo.fetchOne(db, id: threadId) ), changes: [.lastReadTimestampMs(lastReadTimestampMs)] ) - // Update the conf - let result: ConfResult = try upsert( - convoInfoVolatileChanges: [change], - in: atomicConf - ) - - // If we need to dump then do so here - if result.needsDump { - try SessionUtil.saveState( - db, - keepingExistingMessageHashes: true, - configDump: try atomicConf.mutate { conf in - try SessionUtil.createDump( - conf: conf, - for: .contacts, - publicKey: userPublicKey, - messageHashes: nil - ) - } + let needsPush: Bool = try SessionUtil + .config( + for: .convoInfoVolatile, + publicKey: userPublicKey ) - } + .mutate { conf in + guard conf != nil else { throw SessionUtilError.nilConfigObject } + + let result: ConfResult = try upsert( + convoInfoVolatileChanges: [change], + in: conf + ) + + // If we don't need to dump the data the we can finish early + guard result.needsDump else { return result.needsPush } + + try SessionUtil.createDump( + conf: conf, + for: .contacts, + publicKey: userPublicKey + )?.save(db) + + return result.needsPush + } // If we need to push then enqueue a 'ConfigurationSyncJob' - if result.needsPush { - ConfigurationSyncJob.enqueue(db) + guard needsPush else { return } + + db.afterNextTransactionNestedOnce(dedupeId: SessionUtil.syncDedupeId(userPublicKey)) { db in + ConfigurationSyncJob.enqueue(db, publicKey: userPublicKey) } } @@ -434,27 +412,42 @@ internal extension SessionUtil { // MARK: - VolatileThreadInfo public extension SessionUtil { + internal struct OpenGroupUrlInfo: FetchableRecord, Codable, Hashable { + let threadId: String + let server: String + let roomToken: String + let publicKey: String + + static func fetchOne(_ db: Database, id: String) throws -> OpenGroupUrlInfo? { + return try OpenGroup + .filter(id: id) + .select(.threadId, .server, .roomToken, .publicKey) + .asRequest(of: OpenGroupUrlInfo.self) + .fetchOne(db) + } + + static func fetchAll(_ db: Database, ids: [String]) throws -> [OpenGroupUrlInfo] { + return try OpenGroup + .filter(ids: ids) + .select(.threadId, .server, .roomToken, .publicKey) + .asRequest(of: OpenGroupUrlInfo.self) + .fetchAll(db) + } + + static func fetchAll(_ db: Database) throws -> [OpenGroupUrlInfo] { + return try OpenGroup + .select(.threadId, .server, .roomToken, .publicKey) + .asRequest(of: OpenGroupUrlInfo.self) + .fetchAll(db) + } + } + struct VolatileThreadInfo { enum Change { case markedAsUnread(Bool) case lastReadTimestampMs(Int64) } - fileprivate struct OpenGroupUrlInfo: FetchableRecord, Codable, Hashable { - let threadId: String - let server: String - let roomToken: String - let publicKey: String - - static func fetchOne(_ db: Database, id: String) throws -> OpenGroupUrlInfo? { - return try OpenGroup - .filter(id: id) - .select(.threadId, .server, .roomToken, .publicKey) - .asRequest(of: OpenGroupUrlInfo.self) - .fetchOne(db) - } - } - let threadId: String let variant: SessionThread.Variant fileprivate let openGroupUrlInfo: OpenGroupUrlInfo? @@ -550,7 +543,7 @@ public extension SessionUtil { let publicKey: String = threadInfo.publicKey else { return nil } - return VolatileThreadInfo.OpenGroupUrlInfo( + return OpenGroupUrlInfo( threadId: threadInfo.id, server: server, roomToken: roomToken, diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Groups.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Groups.swift deleted file mode 100644 index 76cbbcd6f..000000000 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Groups.swift +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. - -import Foundation -import GRDB -import SessionUtil -import SessionUtilitiesKit - -internal extension SessionUtil { - // MARK: - Incoming Changes - - static func handleGroupsUpdate( - _ db: Database, - in atomicConf: Atomic?>, - mergeResult: ConfResult - ) throws -> ConfResult { - // TODO: This - return mergeResult - } -} diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Shared.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Shared.swift new file mode 100644 index 000000000..e60f9db4f --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Shared.swift @@ -0,0 +1,195 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import GRDB +import SessionUtil +import SessionUtilitiesKit + +// MARK: - Convenience + +internal extension SessionUtil { + static func assignmentsRequireConfigUpdate(_ assignments: [ConfigColumnAssignment]) -> Bool { + let targetColumns: Set = Set(assignments.map { ColumnKey($0.column) }) + let allColumnsThatTriggerConfigUpdate: Set = [] + .appending(contentsOf: columnsRelatedToUserProfile) + .appending(contentsOf: columnsRelatedToContacts) + .appending(contentsOf: columnsRelatedToConvoInfoVolatile) + .map { ColumnKey($0) } + .asSet() + + return !allColumnsThatTriggerConfigUpdate.isDisjoint(with: targetColumns) + } + /// This function assumes that the `pinnedPriority` values get set correctly elsewhere rather than trying to enforce + /// uniqueness in here (this means if we eventually allow for "priority grouping" this logic wouldn't change - just where the + /// priorities get updated in the HomeVC + static func updateThreadPrioritiesIfNeeded( + _ db: Database, + _ assignments: [ConfigColumnAssignment], + _ updated: [T] + ) throws { + // Note: This logic assumes that the 'pinnedPriority' values get set correctly elsewhere + // rather than trying to enforce uniqueness in here (this means if we eventually allow for + // "priority grouping" this logic wouldn't change - just where the priorities get updated + // in the HomeVC + let userPublicKey: String = getUserHexEncodedPublicKey(db) + let pinnedThreadInfo: [PriorityInfo] = try SessionThread + .select(.id, .variant, .pinnedPriority) + .asRequest(of: PriorityInfo.self) + .fetchAll(db) + let groupedPriorityInfo: [SessionThread.Variant: [PriorityInfo]] = pinnedThreadInfo + .grouped(by: \.variant) + let pinnedCommunities: [String: OpenGroupUrlInfo] = try OpenGroupUrlInfo + .fetchAll(db, ids: pinnedThreadInfo.map { $0.id }) + .reduce(into: [:]) { result, next in result[next.threadId] = next } + + do { + try groupedPriorityInfo.forEach { variant, priorityInfo in + switch variant { + case .contact: + // If the 'Note to Self' conversation is pinned then we need to custom handle it + // first as it's part of the UserProfile config + if let noteToSelfPriority: PriorityInfo = priorityInfo.first(where: { $0.id == userPublicKey }) { + let atomicConf: Atomic?> = SessionUtil.config( + for: .userProfile, + publicKey: userPublicKey + ) + + try SessionUtil.updateNoteToSelfPriority( + db, + priority: Int32(noteToSelfPriority.pinnedPriority ?? 0), + in: atomicConf + ) + } + + // Remove the 'Note to Self' convo from the list for updating contact priorities + let targetPriorities: [PriorityInfo] = priorityInfo.filter { $0.id != userPublicKey } + + guard !targetPriorities.isEmpty else { return } + + // Since we are doing direct memory manipulation we are using an `Atomic` + // type which has blocking access in it's `mutate` closure + try SessionUtil + .config( + for: .contacts, + publicKey: userPublicKey + ) + .mutate { conf in + let result: ConfResult = try SessionUtil.upsert( + contactData: targetPriorities + .map { ($0.id, nil, nil, Int32($0.pinnedPriority ?? 0), nil) }, + in: conf + ) + + // If we don't need to dump the data the we can finish early + guard result.needsDump else { return } + + try SessionUtil.createDump( + conf: conf, + for: .contacts, + publicKey: userPublicKey + )?.save(db) + } + + case .community: + // Since we are doing direct memory manipulation we are using an `Atomic` + // type which has blocking access in it's `mutate` closure + try SessionUtil + .config( + for: .userGroups, + publicKey: userPublicKey + ) + .mutate { conf in + let result: ConfResult = try SessionUtil.upsert( + communities: priorityInfo + .compactMap { info in + guard let communityInfo: OpenGroupUrlInfo = pinnedCommunities[info.id] else { + return nil + } + + return (communityInfo, info.pinnedPriority) + }, + in: conf + ) + + // If we don't need to dump the data the we can finish early + guard result.needsDump else { return } + + try SessionUtil.createDump( + conf: conf, + for: .userGroups, + publicKey: userPublicKey + )?.save(db) + } + + case .legacyGroup: + // Since we are doing direct memory manipulation we are using an `Atomic` + // type which has blocking access in it's `mutate` closure + try SessionUtil + .config( + for: .userGroups, + publicKey: userPublicKey + ) + .mutate { conf in + let result: ConfResult = try SessionUtil.upsert( + legacyGroups: priorityInfo + .map { LegacyGroupInfo(id: $0.id, priority: $0.pinnedPriority) }, + in: conf + ) + + // If we don't need to dump the data the we can finish early + guard result.needsDump else { return } + + try SessionUtil.createDump( + conf: conf, + for: .userGroups, + publicKey: userPublicKey + )?.save(db) + } + + case .group: + // TODO: Add this + break + } + } + } + catch { + SNLog("[libSession-util] Failed to dump updated data") + } + } +} + +// MARK: - ColumnKey + +internal extension SessionUtil { + struct ColumnKey: Equatable, Hashable { + let sourceType: Any.Type + let columnName: String + + init(_ column: ColumnExpression) { + self.sourceType = type(of: column) + self.columnName = column.name + } + + func hash(into hasher: inout Hasher) { + ObjectIdentifier(sourceType).hash(into: &hasher) + columnName.hash(into: &hasher) + } + + static func == (lhs: ColumnKey, rhs: ColumnKey) -> Bool { + return ( + lhs.sourceType == rhs.sourceType && + lhs.columnName == rhs.columnName + ) + } + } +} + +// MARK: - Pinned Priority + +extension SessionUtil { + struct PriorityInfo: Codable, FetchableRecord, Identifiable { + let id: String + let variant: SessionThread.Variant + let pinnedPriority: Int32? + } +} diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift new file mode 100644 index 000000000..084e2b9fe --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift @@ -0,0 +1,651 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import GRDB +import Sodium +import SessionUtil +import SessionUtilitiesKit +import SessionSnodeKit +// TODO: Expose 'GROUP_NAME_MAX_LENGTH', 'COMMUNITY_URL_MAX_LENGTH' & 'COMMUNITY_ROOM_MAX_LENGTH' +internal extension SessionUtil { + // MARK: - Incoming Changes + + static func handleGroupsUpdate( + _ db: Database, + in conf: UnsafeMutablePointer?, + mergeNeedsDump: Bool, + latestConfigUpdateSentTimestamp: TimeInterval + ) throws { + guard mergeNeedsDump else { return } + guard conf != nil else { throw SessionUtilError.nilConfigObject } + + var communities: [PrioritisedData] = [] + var legacyGroups: [PrioritisedData] = [] + var groups: [PrioritisedData] = [] + var community: ugroups_community_info = ugroups_community_info() + var legacyGroup: ugroups_legacy_group_info = ugroups_legacy_group_info() + let groupsIterator: OpaquePointer = user_groups_iterator_new(conf) + + while !user_groups_iterator_done(groupsIterator) { + if user_groups_it_is_community(groupsIterator, &community) { + let server: String = String(libSessionVal: community.base_url) + let roomToken: String = String(libSessionVal: community.room) + + communities.append( + PrioritisedData( + data: OpenGroupUrlInfo( + threadId: OpenGroup.idFor(roomToken: roomToken, server: server), + server: server, + roomToken: roomToken, + publicKey: Data( + libSessionVal: community.pubkey, + count: OpenGroup.pubkeyByteLength + ).toHexString() + ), + priority: community.priority + ) + ) + } + else if user_groups_it_is_legacy_group(groupsIterator, &legacyGroup) { + let groupId: String = String(libSessionVal: legacyGroup.session_id) + + legacyGroups.append( + PrioritisedData( + data: LegacyGroupInfo( + id: groupId, + name: String(libSessionVal: legacyGroup.name), + lastKeyPair: ClosedGroupKeyPair( + threadId: groupId, + publicKey: Data( + libSessionVal: legacyGroup.enc_pubkey, + count: ClosedGroup.pubkeyByteLength + ), + secretKey: Data( + libSessionVal: legacyGroup.enc_seckey, + count: ClosedGroup.secretKeyByteLength + ), + receivedTimestamp: (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) + ), + disappearingConfig: DisappearingMessagesConfiguration + .defaultWith(groupId) + .with( + // TODO: double check the 'isEnabled' flag + isEnabled: (legacyGroup.disappearing_timer > 0), + durationSeconds: (legacyGroup.disappearing_timer == 0 ? nil : + TimeInterval(legacyGroup.disappearing_timer) + ) + ), + groupMembers: [], //[GroupMember] // TODO: This + hidden: legacyGroup.hidden + ), + priority: legacyGroup.priority + ) + ) + } + else { + SNLog("Ignoring unknown conversation type when iterating through volatile conversation info update") + } + + user_groups_iterator_advance(groupsIterator) + } + user_groups_iterator_free(groupsIterator) // Need to free the iterator + + // If we don't have any conversations then no need to continue + guard !communities.isEmpty || !legacyGroups.isEmpty || !groups.isEmpty else { return } + + // Extract all community/legacyGroup/group thread priorities + let existingThreadPriorities: [String: PriorityInfo] = (try? SessionThread + .select(.id, .variant, .pinnedPriority) + .filter( + [ + SessionThread.Variant.community, + SessionThread.Variant.legacyGroup, + SessionThread.Variant.group + ].contains(SessionThread.Columns.variant) + ) + .asRequest(of: PriorityInfo.self) + .fetchAll(db)) + .defaulting(to: []) + .reduce(into: [:]) { result, next in result[next.id] = next } + + // MARK: -- Handle Community Changes + + // Add any new communities (via the OpenGroupManager) + communities.forEach { community in + OpenGroupManager.shared + .add( + db, + roomToken: community.data.roomToken, + server: community.data.server, + publicKey: community.data.publicKey, + calledFromConfigHandling: true + ) + .sinkUntilComplete() + + // Set the priority if it's changed (new communities will have already been inserted at + // this stage) + if existingThreadPriorities[community.data.threadId]?.pinnedPriority != community.priority { + _ = try? SessionThread + .filter(id: community.data.threadId) + .updateAll( // Handling a config update so don't use `updateAllAndConfig` + db, + SessionThread.Columns.pinnedPriority.set(to: community.priority) + ) + } + } + + // Remove any communities which are no longer in the config + let communityIdsToRemove: Set = Set(existingThreadPriorities + .filter { $0.value.variant == .community } + .keys) + .subtracting(communities.map { $0.data.threadId }) + + communityIdsToRemove.forEach { threadId in + OpenGroupManager.shared.delete( + db, + openGroupId: threadId, + calledFromConfigHandling: true + ) + } + + // MARK: -- Handle Legacy Group Changes + + let existingLegacyGroupIds: Set = Set(existingThreadPriorities + .filter { $0.value.variant == .legacyGroup } + .keys) + + try legacyGroups.forEach { group in + guard + let name: String = group.data.name, + let lastKeyPair: ClosedGroupKeyPair = group.data.lastKeyPair, + let members: [GroupMember] = group.data.groupMembers + else { return } + + if !existingLegacyGroupIds.contains(group.data.id) { + // Add a new group if it doesn't already exist + try MessageReceiver.handleNewClosedGroup( + db, + groupPublicKey: group.data.id, + name: name, + encryptionKeyPair: Box.KeyPair( + publicKey: lastKeyPair.publicKey.bytes, + secretKey: lastKeyPair.secretKey.bytes + ), + members: members + .filter { $0.role == .standard } + .map { $0.profileId }, + admins: members + .filter { $0.role == .admin } + .map { $0.profileId }, + expirationTimer: UInt32(group.data.disappearingConfig?.durationSeconds ?? 0), + messageSentTimestamp: UInt64(latestConfigUpdateSentTimestamp * 1000) + ) + } + else { + // Otherwise update the existing group + _ = try? ClosedGroup + .filter(id: group.data.id) + .updateAll( // Handling a config update so don't use `updateAllAndConfig` + db, + ClosedGroup.Columns.name.set(to: name) + ) + + // Update the lastKey + let keyPairExists: Bool = ClosedGroupKeyPair + .filter( + ClosedGroupKeyPair.Columns.threadId == lastKeyPair.threadId && + ClosedGroupKeyPair.Columns.publicKey == lastKeyPair.publicKey && + ClosedGroupKeyPair.Columns.secretKey == lastKeyPair.secretKey + ) + .isNotEmpty(db) + + if !keyPairExists { + try lastKeyPair.insert(db) + } + + // Update the disappearing messages timer + _ = try DisappearingMessagesConfiguration + .fetchOne(db, id: group.data.id) + .defaulting(to: DisappearingMessagesConfiguration.defaultWith(group.data.id)) + .with( + // TODO: double check the 'isEnabled' flag + isEnabled: (group.data.disappearingConfig?.isEnabled == true), + durationSeconds: group.data.disappearingConfig?.durationSeconds + ) + .saved(db) + + // Update the members + // TODO: This + // TODO: Going to need to decide whether we want to update the 'GroupMember' records in the database based on this config message changing +// let members: [String] +// let admins: [String] + } + + // TODO: 'hidden' flag - just toggle the 'shouldBeVisible' flag? Delete messages as well??? + + + // Set the priority if it's changed + if existingThreadPriorities[group.data.id]?.pinnedPriority != group.priority { + _ = try? SessionThread + .filter(id: group.data.id) + .updateAll( // Handling a config update so don't use `updateAllAndConfig` + db, + SessionThread.Columns.pinnedPriority.set(to: group.priority) + ) + } + } + + // Remove any legacy groups which are no longer in the config + let legacyGroupIdsToRemove: Set = existingLegacyGroupIds + .subtracting(legacyGroups.map { $0.data.id }) + + if !legacyGroupIdsToRemove.isEmpty { + try ClosedGroup.removeKeysAndUnsubscribe( + db, + threadIds: Array(legacyGroupIdsToRemove), + removeGroupData: true, + calledFromConfigHandling: true + ) + } + + // MARK: -- Handle Group Changes + // TODO: Add this + } + + // MARK: - Outgoing Changes + + static func upsert( + legacyGroups: [LegacyGroupInfo], + in conf: UnsafeMutablePointer? + ) throws -> ConfResult { + guard conf != nil else { throw SessionUtilError.nilConfigObject } + guard !legacyGroups.isEmpty else { return ConfResult(needsPush: false, needsDump: false) } + + // Since we are doing direct memory manipulation we are using an `Atomic` type which has + // blocking access in it's `mutate` closure + legacyGroups + .forEach { legacyGroup in + var cGroupId: [CChar] = legacyGroup.id.cArray + let userGroup: UnsafeMutablePointer = user_groups_get_or_construct_legacy_group(conf, &cGroupId) + + + // Assign all properties to match the updated group (if there is one) + if let updatedName: String = legacyGroup.name { + userGroup.pointee.name = updatedName.toLibSession() + + // Store the updated group (needs to happen before variables go out of scope) + user_groups_set_legacy_group(conf, &userGroup) + } + + if let lastKeyPair: ClosedGroupKeyPair = legacyGroup.lastKeyPair { + userGroup.pointee.enc_pubkey = lastKeyPair.publicKey.toLibSession() + userGroup.pointee.enc_seckey = lastKeyPair.secretKey.toLibSession() + + // Store the updated group (needs to happen before variables go out of scope) + user_groups_set_legacy_group(conf, &userGroup) + } + + // Assign all properties to match the updated disappearing messages config (if there is one) + if let updatedConfig: DisappearingMessagesConfiguration = legacyGroup.disappearingConfig { + // TODO: double check the 'isEnabled' flag + userGroup.pointee.disappearing_timer = (!updatedConfig.isEnabled ? 0 : + Int64(floor(updatedConfig.durationSeconds)) + ) + } + + // TODO: Need to add members/admins + + // Store the updated group (can't be sure if we made any changes above) + userGroup.pointee.hidden = (legacyGroup.hidden ?? userGroup.pointee.hidden) + userGroup.pointee.priority = (legacyGroup.priority ?? userGroup.pointee.priority) + + // Note: Need to free the legacy group pointer + user_groups_set_free_legacy_group(conf, userGroup) + } + + return ConfResult( + needsPush: config_needs_push(conf), + needsDump: config_needs_dump(conf) + ) + } + + static func upsert( + communities: [(info: OpenGroupUrlInfo, priority: Int32?)], + in conf: UnsafeMutablePointer? + ) throws -> ConfResult { + guard conf != nil else { throw SessionUtilError.nilConfigObject } + guard !communities.isEmpty else { return ConfResult.init(needsPush: false, needsDump: false) } + + communities + .forEach { info, priority in + var cBaseUrl: [CChar] = info.server.cArray + var cRoom: [CChar] = info.roomToken.cArray + var cPubkey: [UInt8] = Data(hex: info.publicKey).cArray + var userCommunity: ugroups_community_info = ugroups_community_info() + + guard user_groups_get_or_construct_community(conf, &userCommunity, &cBaseUrl, &cRoom, &cPubkey) else { + SNLog("Unable to upsert community conversation to Config Message") + return + } + + userCommunity.priority = (priority ?? userCommunity.priority) + user_groups_set_community(conf, &userCommunity) + } + + return ConfResult( + needsPush: config_needs_push(conf), + needsDump: config_needs_dump(conf) + ) + } + + // MARK: -- Communities + + static func add( + _ db: Database, + server: String, + rootToken: String, + publicKey: String + ) throws { + let userPublicKey: String = getUserHexEncodedPublicKey(db) + + // Since we are doing direct memory manipulation we are using an `Atomic` type which has + // blocking access in it's `mutate` closure + let needsPush: Bool = try SessionUtil + .config( + for: .userGroups, + publicKey: userPublicKey + ) + .mutate { conf in + guard conf != nil else { throw SessionUtilError.nilConfigObject } + + let result: ConfResult = try SessionUtil.upsert( + communities: [ + ( + OpenGroupUrlInfo( + threadId: OpenGroup.idFor(roomToken: rootToken, server: server), + server: server, + roomToken: rootToken, + publicKey: publicKey + ), + nil + ) + ], + in: conf + ) + + // If we don't need to dump the data the we can finish early + guard result.needsDump else { return result.needsPush } + + try SessionUtil.createDump( + conf: conf, + for: .userGroups, + publicKey: userPublicKey + )?.save(db) + + return result.needsPush + } + + // Make sure we need a push before scheduling one + guard needsPush else { return } + + db.afterNextTransactionNestedOnce(dedupeId: SessionUtil.syncDedupeId(userPublicKey)) { db in + ConfigurationSyncJob.enqueue(db, publicKey: userPublicKey) + } + } + + static func remove(_ db: Database, server: String, roomToken: String) throws { + let userPublicKey: String = getUserHexEncodedPublicKey(db) + + // Since we are doing direct memory manipulation we are using an `Atomic` type which has + // blocking access in it's `mutate` closure + let needsPush: Bool = try SessionUtil + .config( + for: .userGroups, + publicKey: userPublicKey + ) + .mutate { conf in + guard conf != nil else { throw SessionUtilError.nilConfigObject } + + var cBaseUrl: [CChar] = server.cArray + var cRoom: [CChar] = roomToken.cArray + + // Don't care if the community doesn't exist + user_groups_erase_community(conf, &cBaseUrl, &cRoom) + + let needsPush: Bool = config_needs_push(conf) + + // If we don't need to dump the data the we can finish early + guard config_needs_dump(conf) else { return needsPush } + + try SessionUtil.createDump( + conf: conf, + for: .userGroups, + publicKey: userPublicKey + )?.save(db) + + return needsPush + } + + // Make sure we need a push before scheduling one + guard needsPush else { return } + + db.afterNextTransactionNestedOnce(dedupeId: SessionUtil.syncDedupeId(userPublicKey)) { db in + ConfigurationSyncJob.enqueue(db, publicKey: userPublicKey) + } + } + + // MARK: -- Legacy Group Changes + + static func add( + _ db: Database, + groupPublicKey: String, + name: String, + latestKeyPairPublicKey: Data, + latestKeyPairSecretKey: Data, + latestKeyPairReceivedTimestamp: TimeInterval, + members: Set, + admins: Set + ) throws { + let userPublicKey: String = getUserHexEncodedPublicKey(db) + + // Since we are doing direct memory manipulation we are using an `Atomic` type which has + // blocking access in it's `mutate` closure + let needsPush: Bool = try SessionUtil + .config( + for: .userGroups, + publicKey: userPublicKey + ) + .mutate { conf in + guard conf != nil else { throw SessionUtilError.nilConfigObject } + + let result: ConfResult = try SessionUtil.upsert( + legacyGroups: [ + LegacyGroupInfo( + id: groupPublicKey, + name: name, + lastKeyPair: ClosedGroupKeyPair( + threadId: groupPublicKey, + publicKey: latestKeyPairPublicKey, + secretKey: latestKeyPairSecretKey, + receivedTimestamp: latestKeyPairReceivedTimestamp + ), + groupMembers: members + .map { memberId in + GroupMember( + groupId: groupPublicKey, + profileId: memberId, + role: .standard, + isHidden: false + ) + } + .appending( + contentsOf: admins + .map { memberId in + GroupMember( + groupId: groupPublicKey, + profileId: memberId, + role: .admin, + isHidden: false + ) + } + ) + ) + ], + in: conf + ) + + // If we don't need to dump the data the we can finish early + guard result.needsDump else { return result.needsPush } + + try SessionUtil.createDump( + conf: conf, + for: .userGroups, + publicKey: userPublicKey + )?.save(db) + + return result.needsPush + } + + // Make sure we need a push before scheduling one + guard needsPush else { return } + + db.afterNextTransactionNestedOnce(dedupeId: SessionUtil.syncDedupeId(userPublicKey)) { db in + ConfigurationSyncJob.enqueue(db, publicKey: userPublicKey) + } + } + + static func hide(_ db: Database, legacyGroupIds: [String]) throws { + } + + static func remove(_ db: Database, legacyGroupIds: [String]) throws { + let userPublicKey: String = getUserHexEncodedPublicKey(db) + + // Since we are doing direct memory manipulation we are using an `Atomic` type which has + // blocking access in it's `mutate` closure + let needsPush: Bool = try SessionUtil + .config( + for: .userGroups, + publicKey: userPublicKey + ) + .mutate { conf in + guard conf != nil else { throw SessionUtilError.nilConfigObject } + + legacyGroupIds.forEach { threadId in + var cGroupId: [CChar] = threadId.cArray + + // Don't care if the group doesn't exist + user_groups_erase_legacy_group(conf, &cGroupId) + } + + let needsPush: Bool = config_needs_push(conf) + + // If we don't need to dump the data the we can finish early + guard config_needs_dump(conf) else { return needsPush } + + try SessionUtil.createDump( + conf: conf, + for: .userGroups, + publicKey: userPublicKey + )?.save(db) + + return needsPush + } + + // Make sure we need a push before scheduling one + guard needsPush else { return } + + db.afterNextTransactionNestedOnce(dedupeId: SessionUtil.syncDedupeId(userPublicKey)) { db in + ConfigurationSyncJob.enqueue(db, publicKey: userPublicKey) + } + } + + // MARK: -- Group Changes + + static func hide(_ db: Database, groupIds: [String]) throws { + } + + static func remove(_ db: Database, groupIds: [String]) throws { + } +} + + } + + return updated + } +} + +// MARK: - LegacyGroupInfo + +extension SessionUtil { + struct LegacyGroupInfo: Decodable, FetchableRecord, ColumnExpressible { + typealias Columns = CodingKeys + enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable { + case threadId + case name + case lastKeyPair + case disappearingConfig + case groupMembers + case hidden + case priority + } + + var id: String { threadId } + + let threadId: String + let name: String? + let lastKeyPair: ClosedGroupKeyPair? + let disappearingConfig: DisappearingMessagesConfiguration? + let groupMembers: [GroupMember]? + let hidden: Bool? + let priority: Int32? + + init( + id: String, + name: String? = nil, + lastKeyPair: ClosedGroupKeyPair? = nil, + disappearingConfig: DisappearingMessagesConfiguration? = nil, + groupMembers: [GroupMember]? = nil, + hidden: Bool? = nil, + priority: Int32? = nil + ) { + self.threadId = id + self.name = name + self.lastKeyPair = lastKeyPair + self.disappearingConfig = disappearingConfig + self.groupMembers = groupMembers + self.hidden = hidden + self.priority = priority + } + + static func fetchAll(_ db: Database) throws -> [LegacyGroupInfo] { + return try ClosedGroup + .filter(ClosedGroup.Columns.threadId.like("\(SessionId.Prefix.standard.rawValue)%")) + .including( + required: ClosedGroup.keyPairs + .order(ClosedGroupKeyPair.Columns.receivedTimestamp.desc) + .forKey(Columns.lastKeyPair.name) + ) + .including(all: ClosedGroup.members) + .joining( + optional: ClosedGroup.thread + .including( + optional: SessionThread.disappearingMessagesConfiguration + .forKey(Columns.disappearingConfig.name) + ) + ) + .asRequest(of: LegacyGroupInfo.self) + .fetchAll(db) + } + } + + fileprivate struct GroupThreadData { + let communities: [PrioritisedData] + let legacyGroups: [PrioritisedData] + let groups: [PrioritisedData] + } + + fileprivate struct PrioritisedData { + let data: T + let priority: Int32 + } +} diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift index f6c1cb8d6..de9926f91 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift @@ -6,63 +6,47 @@ import SessionUtil import SessionUtilitiesKit internal extension SessionUtil { + static let columnsRelatedToUserProfile: [Profile.Columns] = [ + Profile.Columns.name, + Profile.Columns.profilePictureUrl, + Profile.Columns.profileEncryptionKey + ] + // MARK: - Incoming Changes static func handleUserProfileUpdate( _ db: Database, - in atomicConf: Atomic?>, - mergeResult: ConfResult, + in conf: UnsafeMutablePointer?, + mergeNeedsDump: Bool, latestConfigUpdateSentTimestamp: TimeInterval - ) throws -> ConfResult { + ) throws { typealias ProfileData = (profileName: String, profilePictureUrl: String?, profilePictureKey: Data?) - guard mergeResult.needsDump else { return mergeResult } - guard atomicConf.wrappedValue != nil else { throw SessionUtilError.nilConfigObject } + guard mergeNeedsDump else { return } + guard conf != nil else { throw SessionUtilError.nilConfigObject } + + // A profile must have a name so if this is null then it's invalid and can be ignored + guard let profileNamePtr: UnsafePointer = user_profile_get_name(conf) else { return } let userPublicKey: String = getUserHexEncodedPublicKey(db) - - // Since we are doing direct memory manipulation we are using an `Atomic` type which has - // blocking access in it's `mutate` closure - let maybeProfileData: ProfileData? = atomicConf.mutate { conf -> ProfileData? in - // A profile must have a name so if this is null then it's invalid and can be ignored - guard let profileNamePtr: UnsafePointer = user_profile_get_name(conf) else { - return nil - } - - let profileName: String = String(cString: profileNamePtr) - let profilePic: user_profile_pic = user_profile_get_pic(conf) - let profilePictureUrl: String? = String(libSessionVal: profilePic.url, nullIfEmpty: true) - - // Make sure the url and key exists before reading the memory - return ( - profileName: profileName, - profilePictureUrl: profilePictureUrl, - profilePictureKey: (profilePictureUrl == nil ? nil : - Data( - libSessionVal: profilePic.url, - count: ProfileManager.avatarAES256KeyByteLength - ) - ) - ) - } - - // Only save the data in the database if it's valid - guard let profileData: ProfileData = maybeProfileData else { return mergeResult } + let profileName: String = String(cString: profileNamePtr) + let profilePic: user_profile_pic = user_profile_get_pic(conf) + let profilePictureUrl: String? = String(libSessionVal: profilePic.url, nullIfEmpty: true) // Handle user profile changes try ProfileManager.updateProfileIfNeeded( db, publicKey: userPublicKey, - name: profileData.profileName, + name: profileName, avatarUpdate: { - guard - let profilePictureUrl: String = profileData.profilePictureUrl, - let profileKey: Data = profileData.profilePictureKey - else { return .remove } + guard let profilePictureUrl: String = profilePictureUrl else { return .remove } return .updateTo( url: profilePictureUrl, - key: profileKey, + key: Data( + libSessionVal: profilePic.url, + count: ProfileManager.avatarAES256KeyByteLength + ), fileName: nil ) }(), @@ -85,35 +69,54 @@ internal extension SessionUtil { Contact.Columns.didApproveMe.set(to: true) ) } - - return mergeResult } // MARK: - Outgoing Changes static func update( profile: Profile, - in atomicConf: Atomic?> + in conf: UnsafeMutablePointer? ) throws -> ConfResult { + guard conf != nil else { throw SessionUtilError.nilConfigObject } + + // Update the name + var updatedName: [CChar] = profile.name.cArray + user_profile_set_name(conf, &updatedName) + + // Either assign the updated profile pic, or sent a blank profile pic (to remove the current one) + var profilePic: user_profile_pic = user_profile_pic() + profilePic.url = profile.profilePictureUrl.toLibSession() + profilePic.key = profile.profileEncryptionKey.toLibSession() + user_profile_set_pic(conf, profilePic) + + return ConfResult( + needsPush: config_needs_push(conf), + needsDump: config_needs_dump(conf) + ) + } + + static func updateNoteToSelfPriority( + _ db: Database, + priority: Int32, + in atomicConf: Atomic?> + ) throws { guard atomicConf.wrappedValue != nil else { throw SessionUtilError.nilConfigObject } + let userPublicKey: String = getUserHexEncodedPublicKey(db) + // Since we are doing direct memory manipulation we are using an `Atomic` type which has // blocking access in it's `mutate` closure - return atomicConf.mutate { conf in - // Update the name - var updatedName: [CChar] = profile.name.cArray - user_profile_set_name(conf, &updatedName) + try atomicConf.mutate { conf in + user_profile_set_nts_priority(conf, priority) - // Either assign the updated profile pic, or sent a blank profile pic (to remove the current one) - var profilePic: user_profile_pic = user_profile_pic() - profilePic.url = profile.profilePictureUrl.toLibSession() - profilePic.key = profile.profileEncryptionKey.toLibSession() - user_profile_set_pic(conf, profilePic) + // If we don't need to dump the data the we can finish early + guard config_needs_dump(conf) else { return } - return ConfResult( - needsPush: config_needs_push(conf), - needsDump: config_needs_dump(conf) - ) + try SessionUtil.createDump( + conf: conf, + for: .userProfile, + publicKey: userPublicKey + )?.save(db) } } } diff --git a/SessionMessagingKit/LibSessionUtil/Database/QueryInterfaceRequest+Utilities.swift b/SessionMessagingKit/LibSessionUtil/Database/QueryInterfaceRequest+Utilities.swift index 30ce830e0..bded18a07 100644 --- a/SessionMessagingKit/LibSessionUtil/Database/QueryInterfaceRequest+Utilities.swift +++ b/SessionMessagingKit/LibSessionUtil/Database/QueryInterfaceRequest+Utilities.swift @@ -4,13 +4,52 @@ import Foundation import GRDB import SessionUtilitiesKit -// MARK: - GRDB +// MARK: - ConfigColumnAssignment + +public struct ConfigColumnAssignment { + var column: ColumnExpression + var assignment: ColumnAssignment + + init( + column: ColumnExpression, + assignment: ColumnAssignment + ) { + self.column = column + self.assignment = assignment + } +} + +// MARK: - ColumnExpression + +extension ColumnExpression { + public func set(to value: (any SQLExpressible)?) -> ConfigColumnAssignment { + ConfigColumnAssignment(column: self, assignment: self.set(to: value)) + } +} + +// MARK: - QueryInterfaceRequest public extension QueryInterfaceRequest { + @discardableResult + func updateAll( + _ db: Database, + _ assignments: ConfigColumnAssignment... + ) throws -> Int { + return try updateAll(db, assignments) + } + + @discardableResult + func updateAll( + _ db: Database, + _ assignments: [ConfigColumnAssignment] + ) throws -> Int { + return try self.updateAll(db, assignments.map { $0.assignment }) + } + @discardableResult func updateAllAndConfig( _ db: Database, - _ assignments: ColumnAssignment... + _ assignments: ConfigColumnAssignment... ) throws -> Int { return try updateAllAndConfig(db, assignments) } @@ -18,8 +57,15 @@ public extension QueryInterfaceRequest { @discardableResult func updateAllAndConfig( _ db: Database, - _ assignments: [ColumnAssignment] + _ assignments: [ConfigColumnAssignment] ) throws -> Int { + let targetAssignments: [ColumnAssignment] = assignments.map { $0.assignment } + + // Before we do anything make sure the changes actually do need to be sunced + guard SessionUtil.assignmentsRequireConfigUpdate(assignments) else { + return try self.updateAll(db, targetAssignments) + } + switch self { case let contactRequest as QueryInterfaceRequest: return try contactRequest.updateAndFetchAllAndUpdateConfig(db, assignments).count @@ -29,9 +75,11 @@ public extension QueryInterfaceRequest { case let threadRequest as QueryInterfaceRequest: return try threadRequest.updateAndFetchAllAndUpdateConfig(db, assignments).count + + case let threadRequest as QueryInterfaceRequest: + return try threadRequest.updateAndFetchAllAndUpdateConfig(db, assignments).count - - default: return try self.updateAll(db, assignments) + default: return try self.updateAll(db, targetAssignments) } } } @@ -40,7 +88,7 @@ public extension QueryInterfaceRequest where RowDecoder: FetchableRecord & Table @discardableResult func updateAndFetchAllAndUpdateConfig( _ db: Database, - _ assignments: ColumnAssignment... + _ assignments: ConfigColumnAssignment... ) throws -> [RowDecoder] { return try updateAndFetchAllAndUpdateConfig(db, assignments) } @@ -48,41 +96,45 @@ public extension QueryInterfaceRequest where RowDecoder: FetchableRecord & Table @discardableResult func updateAndFetchAllAndUpdateConfig( _ db: Database, - _ assignments: [ColumnAssignment] + _ assignments: [ConfigColumnAssignment] ) throws -> [RowDecoder] { + // First perform the actual updates + let updatedData: [RowDecoder] = try self.updateAndFetchAll(db, assignments.map { $0.assignment }) + + // Then check if any of the changes could affect the config + guard + // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent + Features.useSharedUtilForUserConfig, + SessionUtil.assignmentsRequireConfigUpdate(assignments) + else { return updatedData } + defer { - // If we change one of these types then we may as well automatically enqueue - // a new config sync job once the transaction completes (but only enqueue it - // once per transaction - doing it more than once is pointless) - if - self is QueryInterfaceRequest || - self is QueryInterfaceRequest || - self is QueryInterfaceRequest || - self is QueryInterfaceRequest - { - db.afterNextTransactionNestedOnce(dedupeIdentifier: "EnqueueConfigurationSyncJob") { db in - ConfigurationSyncJob.enqueue(db) - } + // If we changed a column that requires a config update then we may as well automatically + // enqueue a new config sync job once the transaction completes (but only enqueue it once + // per transaction - doing it more than once is pointless) + let userPublicKey: String = getUserHexEncodedPublicKey(db) + + db.afterNextTransactionNestedOnce(dedupeId: SessionUtil.syncDedupeId(userPublicKey)) { db in + ConfigurationSyncJob.enqueue(db, publicKey: userPublicKey) } } - // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard Features.useSharedUtilForUserConfig else { - return try self.updateAndFetchAll(db, assignments) - } - // Update the config dump state where needed + + try SessionUtil.updateThreadPrioritiesIfNeeded(db, assignments, updatedData) + switch self { case is QueryInterfaceRequest: - return try SessionUtil.updatingContacts(db, try updateAndFetchAll(db, assignments)) + return try SessionUtil.updatingContacts(db, updatedData) case is QueryInterfaceRequest: - return try SessionUtil.updatingProfiles(db, try updateAndFetchAll(db, assignments)) + return try SessionUtil.updatingProfiles(db, updatedData) case is QueryInterfaceRequest: - return try SessionUtil.updatingThreads(db, try updateAndFetchAll(db, assignments)) + return updatedData - default: return try self.updateAndFetchAll(db, assignments) + + default: return updatedData } } } diff --git a/SessionMessagingKit/LibSessionUtil/SessionUtil.swift b/SessionMessagingKit/LibSessionUtil/SessionUtil.swift index dedc595f8..59624ec88 100644 --- a/SessionMessagingKit/LibSessionUtil/SessionUtil.swift +++ b/SessionMessagingKit/LibSessionUtil/SessionUtil.swift @@ -24,8 +24,7 @@ public enum SessionUtil { public struct OutgoingConfResult { let message: SharedConfigMessage let namespace: SnodeAPI.Namespace - let destination: Message.Destination - let oldMessageHashes: [String]? + let obsoleteHashes: [String] } // MARK: - Configs @@ -43,6 +42,10 @@ public enum SessionUtil { // MARK: - Variables + internal static func syncDedupeId(_ publicKey: String) -> String { + return "EnqueueConfigurationSyncJob-\(publicKey)" + } + /// Returns `true` if there is a config which needs to be pushed, but returns `false` if the configs are all up to date or haven't been /// loaded yet (eg. fresh install) public static var needsSync: Bool { @@ -150,44 +153,10 @@ public enum SessionUtil { return conf } - internal static func saveState( - _ db: Database, - keepingExistingMessageHashes: Bool, - configDump: ConfigDump? - ) throws { - guard let configDump: ConfigDump = configDump else { return } - - // If we want to keep the existing message hashes then we need - // to fetch them from the db and create a new 'ConfigDump' instance - let targetDump: ConfigDump = try { - guard keepingExistingMessageHashes else { return configDump } - - let existingCombinedMessageHashes: String? = try ConfigDump - .filter( - ConfigDump.Columns.variant == configDump.variant && - ConfigDump.Columns.publicKey == configDump.publicKey - ) - .select(.combinedMessageHashes) - .asRequest(of: String.self) - .fetchOne(db) - - return ConfigDump( - variant: configDump.variant, - publicKey: configDump.publicKey, - data: configDump.data, - messageHashes: ConfigDump.messageHashes(from: existingCombinedMessageHashes) - ) - }() - - // Actually save the dump - try targetDump.save(db) - } - internal static func createDump( conf: UnsafeMutablePointer?, for variant: ConfigDump.Variant, - publicKey: String, - messageHashes: [String]? + publicKey: String ) throws -> ConfigDump? { guard conf != nil else { throw SessionUtilError.nilConfigObject } @@ -206,103 +175,131 @@ public enum SessionUtil { return ConfigDump( variant: variant, publicKey: publicKey, - data: dumpData, - messageHashes: messageHashes + data: dumpData ) } // MARK: - Pushes - public static func pendingChanges(_ db: Database) throws -> [OutgoingConfResult] { + public static func pendingChanges( + _ db: Database, + publicKey: String + ) throws -> [OutgoingConfResult] { guard Identity.userExists(db) else { throw SessionUtilError.userDoesNotExist } let userPublicKey: String = getUserHexEncodedPublicKey(db) - let existingDumpInfo: Set = try ConfigDump - .select(.variant, .publicKey, .combinedMessageHashes) - .asRequest(of: DumpInfo.self) + var existingDumpVariants: Set = try ConfigDump + .select(.variant) + .filter(ConfigDump.Columns.publicKey == publicKey) + .asRequest(of: ConfigDump.Variant.self) .fetchSet(db) // Ensure we always check the required user config types for changes even if there is no dump // data yet (to deal with first launch cases) - return existingDumpInfo - .inserting( - contentsOf: DumpInfo.requiredUserConfigDumpInfo(userPublicKey: userPublicKey) - .filter { requiredInfo -> Bool in - !existingDumpInfo.contains(where: { - $0.variant == requiredInfo.variant && - $0.publicKey == requiredInfo.publicKey - }) + if publicKey == userPublicKey { + ConfigDump.Variant.userVariants.forEach { existingDumpVariants.insert($0) } + } + + // Ensure we always check the required user config types for changes even if there is no dump + // data yet (to deal with first launch cases) + return existingDumpVariants + .compactMap { variant -> OutgoingConfResult? in + SessionUtil + .config(for: variant, publicKey: publicKey) + .mutate { conf in + // Check if the config needs to be pushed + guard conf != nil && config_needs_push(conf) else { return nil } + + let cPushData: UnsafeMutablePointer = config_push(conf) + let pushData: Data = Data( + bytes: cPushData.pointee.config, + count: cPushData.pointee.config_len + ) + let hashesToRemove: [String] = [String]( + pointer: cPushData.pointee.obsolete, + count: cPushData.pointee.obsolete_len, + defaultValue: [] + ) + let seqNo: Int64 = cPushData.pointee.seqno + cPushData.deallocate() + + return OutgoingConfResult( + message: SharedConfigMessage( + kind: variant.configMessageKind, + seqNo: seqNo, + data: pushData + ), + namespace: variant.namespace, + obsoleteHashes: hashesToRemove + ) } - ) - .compactMap { dumpInfo -> OutgoingConfResult? in - let key: ConfigKey = ConfigKey(variant: dumpInfo.variant, publicKey: dumpInfo.publicKey) - let atomicConf: Atomic?> = ( - SessionUtil.configStore.wrappedValue[key] ?? - Atomic(nil) - ) - - // Check if the config needs to be pushed - guard - atomicConf.wrappedValue != nil && - config_needs_push(atomicConf.wrappedValue) - else { return nil } - - var toPush: UnsafeMutablePointer? = nil - var toPushLen: Int = 0 - let seqNo: Int64 = atomicConf.mutate { config_push($0, &toPush, &toPushLen) } - - guard let toPush: UnsafeMutablePointer = toPush else { return nil } - - let pushData: Data = Data(bytes: toPush, count: toPushLen) - toPush.deallocate() - - return OutgoingConfResult( - message: SharedConfigMessage( - kind: dumpInfo.variant.configMessageKind, - seqNo: seqNo, - data: pushData - ), - namespace: dumpInfo.variant.namespace, - destination: (dumpInfo.publicKey == userPublicKey ? - Message.Destination.contact(publicKey: userPublicKey) : - Message.Destination.closedGroup(groupPublicKey: dumpInfo.publicKey) - ), - oldMessageHashes: dumpInfo.messageHashes - ) } } - public static func markAsPushed( + public static func markingAsPushed( message: SharedConfigMessage, + serverHash: String, publicKey: String - ) -> Bool { - let key: ConfigKey = ConfigKey(variant: message.kind.configDumpVariant, publicKey: publicKey) - let atomicConf: Atomic?> = ( - SessionUtil.configStore.wrappedValue[key] ?? - Atomic(nil) - ) - - guard atomicConf.wrappedValue != nil else { return false } - - // Mark the config as pushed - config_confirm_pushed(atomicConf.wrappedValue, message.seqNo) - - // Update the result to indicate whether the config needs to be dumped - return config_needs_dump(atomicConf.wrappedValue) + ) -> ConfigDump? { + return SessionUtil + .config( + for: message.kind.configDumpVariant, + publicKey: publicKey + ) + .mutate { conf in + guard conf != nil else { return nil } + + // Mark the config as pushed + var cHash: [CChar] = serverHash.cArray + config_confirm_pushed(conf, message.seqNo, &cHash) + + // Update the result to indicate whether the config needs to be dumped + guard config_needs_dump(conf) else { return nil } + + return try? SessionUtil.createDump( + conf: conf, + for: message.kind.configDumpVariant, + publicKey: publicKey + ) + } } public static func configHashes(for publicKey: String) -> [String] { return Storage.shared - .read { db in - try ConfigDump + .read { db -> [String] in + guard Identity.userExists(db) else { return [] } + + let existingDumpVariants: Set = (try? ConfigDump + .select(.variant) .filter(ConfigDump.Columns.publicKey == publicKey) - .select(.combinedMessageHashes) - .asRequest(of: String.self) - .fetchAll(db) + .asRequest(of: ConfigDump.Variant.self) + .fetchSet(db)) + .defaulting(to: []) + + /// Extract all existing hashes for any dumps associated with the given `publicKey` + return existingDumpVariants + .map { variant -> [String] in + guard + let conf = SessionUtil + .config(for: variant, publicKey: publicKey) + .wrappedValue, + let hashList: UnsafeMutablePointer = config_current_hashes(conf) + else { + return [] + } + + let result: [String] = [String]( + pointer: hashList.pointee.value, + count: hashList.pointee.len, + defaultValue: [] + ) + hashList.deallocate() + + return result + } + .reduce([], +) } .defaulting(to: []) - .compactMap { ConfigDump.messageHashes(from: $0) } - .flatMap { $0 } } // MARK: - Receiving @@ -319,143 +316,87 @@ public enum SessionUtil { let groupedMessages: [ConfigDump.Variant: [SharedConfigMessage]] = messages .grouped(by: \.kind.configDumpVariant) - // Merge the config messages into the current state - let mergeResults: [ConfigDump.Variant: IncomingConfResult] = groupedMessages + + let needsPush: Bool = try groupedMessages .sorted { lhs, rhs in lhs.key.processingOrder < rhs.key.processingOrder } - .reduce(into: [:]) { result, next in - let key: ConfigKey = ConfigKey(variant: next.key, publicKey: publicKey) - let atomicConf: Atomic?> = ( - SessionUtil.configStore.wrappedValue[key] ?? - Atomic(nil) - ) - var needsPush: Bool = false - var needsDump: Bool = false - let messageHashes: [String] = next.value.compactMap { $0.serverHash } + .reduce(false) { prevNeedsPush, next -> Bool in let messageSentTimestamp: TimeInterval = TimeInterval( (next.value.compactMap { $0.sentTimestamp }.max() ?? 0) / 1000 ) + let needsPush: Bool = try SessionUtil + .config(for: next.key, publicKey: publicKey) + .mutate { conf in + // Merge the messages + var mergeHashes: [UnsafePointer?] = next.value + .map { message in + (message.serverHash ?? "").cArray + .nullTerminated() + } + .unsafeCopy() + var mergeData: [UnsafePointer?] = next.value + .map { message -> [UInt8] in message.data.bytes } + .unsafeCopy() + var mergeSize: [Int] = next.value.map { $0.data.count } + config_merge(conf, &mergeHashes, &mergeData, &mergeSize, next.value.count) + mergeHashes.forEach { $0?.deallocate() } + mergeData.forEach { $0?.deallocate() } + + // Apply the updated states to the database + switch next.key { + case .userProfile: + try SessionUtil.handleUserProfileUpdate( + db, + in: conf, + mergeNeedsDump: config_needs_dump(conf), + latestConfigUpdateSentTimestamp: messageSentTimestamp + ) + + case .contacts: + try SessionUtil.handleContactsUpdate( + db, + in: conf, + mergeNeedsDump: config_needs_dump(conf) + ) + + case .convoInfoVolatile: + try SessionUtil.handleConvoInfoVolatileUpdate( + db, + in: conf, + mergeNeedsDump: config_needs_dump(conf) + ) + + case .userGroups: + try SessionUtil.handleGroupsUpdate( + db, + in: conf, + mergeNeedsDump: config_needs_dump(conf), + latestConfigUpdateSentTimestamp: messageSentTimestamp + ) + } + + // Need to check if the config needs to be dumped (this might have changed + // after handling the merge changes) + guard config_needs_dump(conf) else { return config_needs_push(conf) } + + try SessionUtil.createDump( + conf: conf, + for: next.key, + publicKey: publicKey + )?.save(db) - // Block the config while we are merging - atomicConf.mutate { conf in - var mergeData: [UnsafePointer?] = next.value - .map { message -> [UInt8] in message.data.bytes } - .unsafeCopy() - var mergeSize: [Int] = next.value.map { $0.data.count } - config_merge(conf, &mergeData, &mergeSize, next.value.count) - mergeData.forEach { $0?.deallocate() } - - // Get the state of this variant - needsPush = config_needs_push(conf) - needsDump = config_needs_dump(conf) - } + return config_needs_push(conf) + } - // Return the current state of the config - result[next.key] = IncomingConfResult( - needsPush: needsPush, - needsDump: needsDump, - messageHashes: messageHashes, - latestSentTimestamp: messageSentTimestamp - ) + // Update the 'needsPush' state as needed + return (prevNeedsPush || needsPush) } - // Process the results from the merging - let finalResults: [ConfResult] = try mergeResults.map { variant, mergeResult in - let key: ConfigKey = ConfigKey(variant: variant, publicKey: publicKey) - let atomicConf: Atomic?> = ( - SessionUtil.configStore.wrappedValue[key] ?? - Atomic(nil) - ) - - // Apply the updated states to the database - let postHandlingResult: ConfResult = try { - switch variant { - case .userProfile: - return try SessionUtil.handleUserProfileUpdate( - db, - in: atomicConf, - mergeResult: mergeResult.result, - latestConfigUpdateSentTimestamp: mergeResult.latestSentTimestamp - ) - - case .contacts: - return try SessionUtil.handleContactsUpdate( - db, - in: atomicConf, - mergeResult: mergeResult.result - ) - - case .convoInfoVolatile: - return try SessionUtil.handleConvoInfoVolatileUpdate( - db, - in: atomicConf, - mergeResult: mergeResult.result - ) - - case .userGroups: - return try SessionUtil.handleGroupsUpdate( - db, - in: atomicConf, - mergeResult: mergeResult.result - ) - } - }() - - // We need to get the existing message hashes and combine them with the latest from the - // service node to ensure the next push will properly clean up old messages - let oldMessageHashes: Set = try ConfigDump - .filter( - ConfigDump.Columns.variant == variant && - ConfigDump.Columns.publicKey == publicKey - ) - .select(.combinedMessageHashes) - .asRequest(of: String.self) - .fetchOne(db) - .map { ConfigDump.messageHashes(from: $0) } - .defaulting(to: []) - .asSet() - let allMessageHashes: [String] = Array(oldMessageHashes - .inserting(contentsOf: mergeResult.messageHashes.asSet())) - let messageHashesChanged: Bool = (oldMessageHashes != mergeResult.messageHashes.asSet()) - - // Now that the changes are applied, update the cached dumps - switch (postHandlingResult.needsDump, messageHashesChanged) { - case (true, _): - // The config data had changes so regenerate the dump and save it - try atomicConf - .mutate { conf -> ConfigDump? in - try SessionUtil.createDump( - conf: conf, - for: variant, - publicKey: publicKey, - messageHashes: allMessageHashes - ) - }? - .save(db) - - case (false, true): - // The config data didn't change but there were different messages on the service node - // so just update the message hashes so the next sync can properly remove any old ones - try ConfigDump - .filter( - ConfigDump.Columns.variant == variant && - ConfigDump.Columns.publicKey == publicKey - ) - .updateAll( - db, - ConfigDump.Columns.combinedMessageHashes - .set(to: ConfigDump.combinedMessageHashes(from: allMessageHashes)) - ) - - default: break - } - - return postHandlingResult - } + // Now that the local state has been updated, schedule a config sync if needed (this will + // push any pending updates and properly update the state) + guard needsPush else { return } - // Now that the local state has been updated, trigger a config sync (this will push any - // pending updates and properly update the state) - if finalResults.contains(where: { $0.needsPush }) { - ConfigurationSyncJob.enqueue(db) + db.afterNextTransactionNestedOnce(dedupeId: SessionUtil.syncDedupeId(publicKey)) { db in + ConfigurationSyncJob.enqueue(db, publicKey: publicKey) } } } @@ -467,20 +408,41 @@ fileprivate extension SessionUtil { let variant: ConfigDump.Variant let publicKey: String } +} + +// MARK: - Convenience + +public extension SessionUtil { + static func parseCommunity(url: String) -> (room: String, server: String, publicKey: String)? { + var cFullUrl: [CChar] = url.cArray + var cBaseUrl: [CChar] = [CChar](repeating: 0, count: COMMUNITY_BASE_URL_MAX_LENGTH) + var cRoom: [CChar] = [CChar](repeating: 0, count: COMMUNITY_ROOM_MAX_LENGTH) + var cPubkey: [UInt8] = [UInt8](repeating: 0, count: OpenGroup.pubkeyByteLength) + + guard + community_parse_full_url(&cFullUrl, &cBaseUrl, &cRoom, &cPubkey) && + !String(cString: cRoom).isEmpty && + !String(cString: cBaseUrl).isEmpty && + cPubkey.contains(where: { $0 != 0 }) + else { return nil } + + // Note: Need to store them in variables instead of returning directly to ensure they + // don't get freed from memory early (was seeing this happen intermittently during + // unit tests...) + let room: String = String(cString: cRoom) + let baseUrl: String = String(cString: cBaseUrl) + let pubkeyHex: String = Data(cPubkey).toHexString() + + return (room, baseUrl, pubkeyHex) + } - struct DumpInfo: FetchableRecord, Decodable, Hashable { - let variant: ConfigDump.Variant - let publicKey: String - private let combinedMessageHashes: String? + static func communityUrlFor(server: String, roomToken: String, publicKey: String) -> String { + var cBaseUrl: [CChar] = server.cArray + var cRoom: [CChar] = roomToken.cArray + var cPubkey: [UInt8] = Data(hex: publicKey).cArray + var cFullUrl: [CChar] = [CChar](repeating: 0, count: COMMUNITY_FULL_URL_MAX_LENGTH) + community_make_full_url(&cBaseUrl, &cRoom, &cPubkey, &cFullUrl) - var messageHashes: [String]? { ConfigDump.messageHashes(from: combinedMessageHashes) } - - // MARK: - Convenience - - static func requiredUserConfigDumpInfo(userPublicKey: String) -> Set { - return ConfigDump.Variant.userVariants - .map { DumpInfo(variant: $0, publicKey: userPublicKey, combinedMessageHashes: nil) } - .asSet() - } + return String(cString: cFullUrl) } } diff --git a/SessionMessagingKit/LibSessionUtil/Utilities/TypeConversion+Utilities.swift b/SessionMessagingKit/LibSessionUtil/Utilities/TypeConversion+Utilities.swift index e28cc01d4..edee7ba7a 100644 --- a/SessionMessagingKit/LibSessionUtil/Utilities/TypeConversion+Utilities.swift +++ b/SessionMessagingKit/LibSessionUtil/Utilities/TypeConversion+Utilities.swift @@ -17,27 +17,41 @@ public extension String { self = result } + init( + libSessionVal: T, + fixedLength: Int? = .none + ) { + guard let fixedLength: Int = fixedLength else { + // Note: The `String(cString:)` function requires that the value is null-terminated + // so add a null-termination character if needed + self = String( + cString: withUnsafeBytes(of: libSessionVal) { [UInt8]($0) } + .nullTerminated() + ) + return + } + + guard + let fixedLengthData: Data = Data( + libSessionVal: libSessionVal, + count: fixedLength, + nullIfEmpty: true + ), + let result: String = String(data: fixedLengthData, encoding: .utf8) + else { + self = "" + return + } + + self = result + } + init?( libSessionVal: T, fixedLength: Int? = .none, - nullIfEmpty: Bool = false + nullIfEmpty: Bool ) { - let result: String = { - guard let fixedLength: Int = fixedLength else { - // Note: The `String(cString:)` function requires that the value is null-terminated - // so add a null-termination character if needed - return String( - cString: withUnsafeBytes(of: libSessionVal) { [UInt8]($0) } - .nullTerminated() - ) - } - - return String( - data: Data(libSessionVal: libSessionVal, count: fixedLength), - encoding: .utf8 - ) - .defaulting(to: "") - }() + let result = String(libSessionVal: libSessionVal, fixedLength: fixedLength) guard !nullIfEmpty || !result.isEmpty else { return nil } @@ -46,13 +60,15 @@ public extension String { func toLibSession() -> T { let targetSize: Int = MemoryLayout.stride - let result: UnsafeMutableRawPointer = UnsafeMutableRawPointer.allocate( - byteCount: targetSize, - alignment: MemoryLayout.alignment + var dataMatchingDestinationSize: [CChar] = [CChar](repeating: 0, count: targetSize) + dataMatchingDestinationSize.replaceSubrange( + 0..(libSessionVal: T, count: Int) { - self = Data( - bytes: Swift.withUnsafeBytes(of: libSessionVal) { [UInt8]($0) }, - count: count - ) + let result: Data = Swift.withUnsafePointer(to: libSessionVal) { + Data(bytes: $0, count: count) + } + + self = result + } + + init?(libSessionVal: T, count: Int, nullIfEmpty: Bool) { + let result: Data = Data(libSessionVal: libSessionVal, count: count) + + // If all of the values are 0 then return the data as null + guard !nullIfEmpty || result.contains(where: { $0 != 0 }) else { return nil } + + self = result } func toLibSession() -> T { let targetSize: Int = MemoryLayout.stride - let result: UnsafeMutableRawPointer = UnsafeMutableRawPointer.allocate( - byteCount: targetSize, - alignment: MemoryLayout.alignment + var dataMatchingDestinationSize: Data = Data(count: targetSize) + dataMatchingDestinationSize.replaceSubrange( + 0.. { // MARK: - Array +public extension Array where Element == String { + init?( + pointer: UnsafeMutablePointer?>?, + count: Int? + ) { + guard + let pointee: UnsafeMutablePointer = pointer?.pointee, + let count: Int = count + else { return nil } + + self = (0..?>?, + count: Int?, + defaultValue: [String] + ) { + self = ([String](pointer: pointer, count: count) ?? defaultValue) + } +} + public extension Array where Element == CChar { func nullTerminated() -> [Element] { guard self.last != CChar(0) else { return self } diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64/libsession-util.a b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64/libsession-util.a index 2ad2f5c1107e9947f969ef59dc701bf6f2855c93..f040566d8919b24ba06c7fd8db1e74b1bc872b80 100644 GIT binary patch delta 215174 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�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Ȳ^aRBz_H}_ZqQ#nSP%Uy$>euH&XY*%Kb)_=5oet4pwu<94+UJ3N7S}A`R>? zyulqfNbWFFG`_<~(C`i;O7lC60`>1S0z2Wt4#P+5JB$X?6FZG0C=@VWUKY1tU~|iK3A#!d%gqrDbo? zsPLfjxKVu^&i~$6_&pj9K4FBOfc__pz!OMMJz=C77oRXnH2;KApqVF(Eb|*r7)`r; z$q1B?zVL+MD^#Et;lr2k;dFrNK`k*C=|7&)d_o-(RWA#OZnG->TA zqy7}qqfZ;Lr&;c4BS&YRHZnBvw2^$8=RIvy88@CbnzZ(`QGeRN{V7y2!W9;*7)ctd z81V{D$US4sK7+XQj8UeAXN=-AxY9Du8rf%!+_NZ>*==NZBdNaIXzVtcJS_alDE`SP z{Rz2&KO4b6BPstEqwp7__!k^DUND+381pY6H}zK|{a3?_lmBKk{$@1)hP0WNjLb{0 z)G*2o#xEP0mtpdsM(Uri{7<7oeXkh)S76~4qe!bwW3CA^uNv7`VdYh$N&~MM!Pj8& z-$v@+u<#$F_#asNk5Q-j1+P(9z(IY%Xi(o6X?B#m9|j9-oPna$43X2c)PZ*~@VklXB>-HeR6&CVL*`OVG+ z+Su%DZjM|OG!O8WBhE?$N%e@cLFXdQS_G$LqRwm-mbN&{TaaGc;;hr^7U$d+r2DUR z2ChXMz1A6{p=+JtYmpv}Ib$)zshBfO6ESBphIHR`PXBczyTL%w6xS(aQDCD%0oU&RQJteB8M}8*yhdj`Zve&fE=%i#Iq+G=GD$a0Alo zH#i%NeK$J&H^TWFoC}O+ZgggDM1J)q=iE&&e6urpvlEv}>wO*(V5dJ8^uP*L+1=RB?7;%wZ4^yID1)UB{_m$P{noSSjhX5h@-&dl9#_Lt85 zFJbyu&Y54q&^^xZJusGY#_@eM+|G93vw|5ro#CA@^?)<|0Gye1W@hPcoSEOi;)Bl8 zgD{@QEgI$@auyzfa}POdv{Z1G3ydFi)*gki$DHxUVEHj;g_a(7mLG?KCvYc*wI`f) zFApluIIGXV)U(d?voO8eIkOuhoZaor)9h|%Za32Ne|8rB>@;1)|2RwkahCss!|;ML zy5Nj0;4u1zGxmlv{ss;+Z#uJYI&*L0u=b|2{-(3R!_-^O^jo+JahUeGW_+%U&xKn> zah&ASB#c6xXN@c;Hm}Se89Cp8v(DY$%E8ouJmOt1DCA}hfbQf4C&F! zU9rny=yF$>`Yw0*=}gd-3BqE~RigeYU4bic{(R7dLnmEedj3jR;Yt|Y=!$KGp^dIE z^=)+dX~w(JmE}QUqpL_~H@fmPaJ4IVHH=;Diqr7bt|%>pUBxi0hFx>C9ClS`bF*uH zGvlASDnEtApSnsk7j?}>VJYe=(?Zl$q>ZSnNxg|{T*+(D^XN4$96D+28f4_RxC&cf zZi{P{rnk6ewzzP!kGYyL*L(~~b3b#{eukvz^{&|UuK4vxO8(rH`Z)~W;ELV=OEaStr*RCe5d4KJy^T3~V1+p-jb;W2X>k8A# zy{_uLuyL=eNo)7I>NI$tD|8=>-{(ru=zXpj&ED_I-4BcRyGk^FzpFs~J6wSsFuKDP zqoEzHF!j#taAkOq-{C6I+z!_)^*`zgJPN~)yP}W7(&Mf&O+4XBJ^=$ISFi+Uo^)lN zgw-cqb2L+SWy`Qsc9qMxbZcc-omR`PIi^>hbyc5*jb~j==ylefb=8?L_nfQt916}q z=USkR=Uh!1ueuUd7_GWuv{H3dt0-Trx=PglyesfLFTLF^9OBa3?ONE)2A+2{pNF;Q zU3FT1-c_NQx+`0U{(rav|8U{{Kl2Y)<{vEh4_BSWUUbD@gq0UvRhoOrHTx1wG+fCB zOug(%zYJ&p>B|3;`Ayes6ZrDST6Q`~-)3mwH zoTs&my=MJl6Hil@n1xHs;w4B6_|2f-#D}`yjMK2+jQWwD^_w}yMZa01dB0g;`hwr| zT?&(znkhQJ-dtGE@*B+D2AB_;g&?e4X;!aJV=*rdYB94OLxF{u>AMa#W9EE}1+Ft=*TK-w%<#`(@p`j# zJ&ecAL>%R2;%0`X;$}L|{J2?SJQp`>v=TR~O!xiV^#2@|Z!jx2z~&9+{0+zt+-L^9 zH{!r|qv^j98I^=tO(1S0%qFcR%z6Up!JEv`O^D++nF$)b$&B5ERjc1*Hf}PTJZ#)- zHg7iPZ^mIXX~vRfJc$#tNi#>wNwbo~iS?w}pmRyH#`N%3uNmEngV|fn{H?Hht2uWo z3N&vu=V|>`v%&OG$_%FvCsJmT#!_ZHh4fs?oMl`}nPpl?nMI~oZZoU5A#U7eHfilP zvwj=WXSSJ{ZHV*R%mU4Mx0$oskTJK-tTADJo4G(6+sx)R3?`d4b7^xnjlncjNXm( z%-v=dvDaC++bl9;_HHxJjN091o$?m>R>9|oeS)|9ZW;|0?uE^)InVUqeP-xB#PR#g1dZNj#_mJ@YJsyf{ehFw@K^?l4QtnA>62Xk~|4WqNI=S>K6xVW;VP z05*4;^E;6~H*3~r5zo(>3$!t7HfNEZdeBTi2m^UDn1?eDnVE->pMS_K(A-1j>_a^N zcV^A|I~)WbHbW01BmS_NpwWlT*uzL~K5Wi24iwB_0s0E2zku{)!A#NFf|+M}xnNdk zv0#>%-Yl5&j02CD!AE%BBc}fmr28K=1CJt(K5E8j=uy)feiRvj$IRekNQgaV#%cI5 zGx`|QA2)-KGyQQhPQ#Cz(Z`XV{Joj_J)Hl&xj+j~n#CtsewP{Ch5eb|Wfpds#a&3s z{K3rr0ZECnnJk;BG7cBYrtc}!{}giL-lxpOQ>c07DKkS;Pnl_Eq@Olto`#`k%GUQkUa!_VSkez6Sr&GW1XU^Ev)0W6$f$_(#koNZ$F@TRS*+y5!Gj`JwON-}U+W410(MH|U58{E-Tb!2NkS;a7h| zj6*;E#fz^~o|p0mT_E{E{^$kDPipyhua@#tVJZL884{1;A0GMYs!DnP=kf3Qe4Wh^ zqJpSa@bc3Vr!`J+M99x+yoen}Jg4zRI;9Pr!FxFX4Cp!?(Am#R{2PrAJqQ;>( zmKpIn-lqva)ean{J-lA0a9rc}=n|PJJ}miv0ZSHPMtu@rv}Z`P_d+OeJ5vTH!ux<%HAOAtx>?t?-k$LReu{E4*r%#QgJNdWgyLRBTX(|u!)&XoLfbjH@}o_djgh?fnR{Anb-9=Z>)iC;;ZkoeheNd=8pr2-VU z_ds6v!09@|5`H$Ful;oZ*K7Pw-WM>y_xxD$AKfkSH#C2vw<-yjYeMIqO{X3Dwl2X6 zt>E-WQ66;_0(N_j>=z}DeNYK1rS>j=h7g!(4_kT(9-*^l$uOhihypDT0V#8tf z4M~AbybKXnwSpzPrGja#;KG{ZC$&8P%f&1|r14HJ0UAtb`CXs4^BoHp%8YHmuQad& zak~Rp&{Orpq5N+tun#+c3JO}moqINo&d`Exy5!Ybq0Y$MY8k+DI%3l!@w+wd()eXA z5$Ydc%nfz5_Bf~oe#=dT0ws-~=N>@Z)OdjnV#zMj>wdFd*E=-s(mP@6m!$(Qam{gl zx5jSm@G8V?-?8&MD1fq=YZ&2|>CcmRR^wk!NLK#4Uylk(rMcQ}Vr#tqD{?{0IAbGpV)aY-I-fXSd{cX#OP_81rXQ4`1tXM`Y~n-6{#+tjY;JdcrHuO5CsUa2H}M$&fC| zM|6h9G(UTtR5*Z{;Oo_EB_7iF2&_FDT&?jpqjv0djB7&tr&3_8#!undjVJgtzI-=g zj5wtYe!MDiS!0)WsHX9WERUiVlDq6mPdWJY5XY+$jd41(+bvW1?^e^ z|0~lx;ShbqI{tZyAJz(v;fx@Ek>)?CBRp8+3-o#7Y>ofQe6%;F?H%+CKmU=C(u6HK z!dLW!<8;k(n!i&!*r^@-TL&t{rYx?L5g(`>yy!xSKdCdYm&X6p{L?fZdjadu4wSTk zj5g4#4VXH@yq=KJ5ib3{H1J2Ap#hC+902MY_^RY5w7!0=uT7WqQ7wP2SEp!H6W+xU zV@l&1uj2?%VOA?#u4_K{HEG~KcOoB$EARyiUn}pBcu3<5wn@BN<88M|Jg%|#@3%_A zT1_|%_XAd#)%a+AAQ{&NFVZFYz?5`gyN-BT^SgD*kJ0=g9Y9F)pVTEdQ}Vrzy>vtg zE#S}sKi3KlV~iP?)%+K#h%rMs%|B$fLOC>IAyuU8#K|hii`ZYf*TJSZd z#tH5r45)1k`DowqXEe+LS89PXSOE%jvjASt=>x}L!wwy<>W9xN&3{a<>!lY-`P+5V z4e6#k7b9hTBU=6f?Z6abw(r<}5DH+#J{|E-4wN{g@hy654QT}z`jL;e=KdueTB#i_ zXnx-%lD~Rh@@KWf70rKro#Z#>_4EJNK1o>66W*;;-p~`aKaYHjp!MrA!smBOoY458 zs>E51&(IN8G`{#EsXxu%6~Jp^TH@JOuO!^8J#77k#E)}HxC9y>p;Nd@<5$>0EZNt8 zEamIEiC1g>HQM26jicJZq{dy^!Ms-!&eJKbX}rKSlV`I#$`kD~d* zrTj}*NL?>?R>8z_7OQ@T&HYS3p|b+5Wf@)>6c>H?M93dRyBX?2N9z~ zdF{}dFWMEF9r{hCPw5Oawfx?j?0iS2SK52-S=Q&pFC6LjY(90h6bK$E1@_Y^>ec*p zS4n=y(UN~5E=P>mbZdiO#r4k)4r}?i-Z`fUI6$qa?q6vgAL=2Huum((>QX@)v7|vf6>i-^b5?oICLuso>as zrNT*#&&E&4Gk>kdpL|5(sKzUC1u#FU@rT|maZ2NTPnS5Y@%x7)?r}?dZ|uj>)Oegzh}fs`7rE;ZM>Srrx86P9n*Ay_F*?|<_m$m`OFXC@eyNBU z1MFRcO7QY~cOwD4n$!XV=i>L&jhrLZE(K=BB+eZmvGHYzOYfHW$LB~~ZlpJE(e`KTN&+7;WpOg4c+QC)WynJ1(ag+II-?3V!sP+J}@r0s1Y~qH( z*DYo3!26`Zt-TVj_^8C+)3~!k;>CJ?=M{4PbDH0STP|PMo+#%p4NCit@-A)QFsWe6 zA0$p5BJn3Qj=f9b*SjTu2)9(e-iP}Jr+AgdTXADy%-?qA>z7(SuLHb#spOA^`FDHq zI$Tef)C$i3mJ}Fy)XsMNm-*R0(Er0*v&|702)3~PM6cDVC6 zS&{pnkbJ*gA2!u%?UE41X5;JcI^{Fkp-Xhen>wN+_G0;*!$iY;?X3;OQ4e2V*EoTC z__DOV`IjZWQD=yMwc6g)-_QbcTHsABz&~fr*WQ{R)+zqJ=JPL#@%6`68Ne83lCSG@ ziW@S;j_>V~{3#vKewtsnS~vA!ZBn3Tu~hIcox<4*B!2UL$e67TvJzgugOX71+ka+Fw5(l#q_n16 zi5pdk^QTJO{FB7{x+O06NCQV4CvmAy;*VMq=ei~S$UPFTXv+ByebSD-j?Pyk;Zp@E z(6L!!hjz%+5nrlPHu!0&@H72VK2(?TAN`obt1&XZ`1g11HOEZw_0wNT9MJP$z`zeS zyg5fu8{nTTx7Y9mIicmQd6Jb$mmt*=Y@wc91Gv`GH?jKtBuNPhZZ ziMw=9?ae#2*T^|WaCz{xL^oLu74!98j(Cyp|DKRO!cB!Z;FAV^o|D+2@wE>!#uJxU z^1p@W2$t{F{H?r|BOcWFr5(8bnb4sLAJhtxTEX3q$q9Y>#PtI6O`l`p+tR>WtPt_o zgv8VA5aI>R|DIhQU#)BW33dqiCuw=#1GxT~uuKzX(IdvY^n_P=S(*54evQ|#L&!hy zJZW%;-Qk>LR0mM9N0@WOH9jkc@{924s^9cFO$V6M0q(!kD+NaOd}X8+^lE$!JA?{+ zT0x#2T7>TpXa#$9BgPW0J=I~q4nr(mlX&07{~iAe=e|AvIR1M0+Y`35{&#%#JL01w zJHFj^uy+)f@!L=P%ijMT_q`*2KkmqHFMrG7{~foyqrUmi{C9rgzw7flmhSc61rGf0 z0*-$z`R{zsI~u&=9pgUt9TWH2cXarIFTTA#d=AxdvAlgmpN{-IYktgoIWDTVC*OO0~CBah!}e(dy~chr6yKJeb&I{%i}+vAn*nC883(|mjWtC7?1GY>vw z(L0Kr`Hp7(vHj}%%)J&Z>HFLl2G04^7d~~)DW`n$!Zne`n~UBbIqhGI_S*D=FYLc* z^rizh^?s>k`(D2>r_KLS^r*=19x@;N9~m8+FJHXRrfZ(xYg6u9ABz0@cmK1{`y+1} zi}%`e_AlPEDgA{dn|jYUgpEbe*t<66&VJXX+kSC?W-QvY_>2SIp4qiMS1>PZ`5!I6 zfBP4IZ=US@pE)~9X59E+Ig#V`Ui^XWKig$~ZQsbYj+P~nU;0{7kvpGm-EY(A*plsg z)y2=mJGTF(t!3_icCh7Z6Q`WECQ@9|^2q<{|6)7)3!mL|b@&IH`om{M z7Vpz?ROHk5wOzFRl)YPCUDMKs0&BLP;B7hE*#3=mEi2yhziQcjU$Eti=;fySA317M z=e7HyzvpahY5cFEn|jx@M4lR3e8f+KEnEJ-1^PF(;7><9V5a{+C60(ZyRGGBH1pm~ zEj^oZ-+15K;=gNhWayiVGjjZi?FVgcd1O)K6W6t57WFyKIp>SRk(Zw^mq*%v(Q-*7 z{j(M`viEf@`)pshw&jyXjXV~;*6a^amVUwmJFKe;ZuUGhni-!JmV zvz;gG*QQ^qGu?@-2ELi_w?&a7zUVyafIWG=`kcMuIVsbBx)C{P=)ip=p)Wc+-`8K0 z8gqhU{O^(9-PLl!`y79jgwtBY?t0|Nqn!sudcWlC-1oE>CGqw5h~Ix1Mvh#x&-){z z#l6gk4}}gK{I_KKaMR^$@(uhMTR4bs0j*&GfzDtI{lKh73YnYY%2E&otf}a#Y+K|@t66F8+@aRXG@3Oh0x2IP- zdlpAlUAeg3+jJRQUvW9Mwzl{Gs?~6;So=Q5ueu$+&yVjkj(y`tpSY!Y+DW&(cGd&Q zoa>xj;|J|HHsF|F*?M6APXhRXk5}J%>;2;g^}jy;=36u0+v&J^^n=FLyIuQkMPuPT zW$$-7j=lfOj${AEvitYHez4&<)_aiS^}ir~>j5M3g-70fU?jf(fvu6@545&>Pr75> zmapCs*i!wF<7!sZMAxPk^}jx1IA$EKZ~n|N(z(RZ*Ly^(Iwb=3a zvBuKN@ldg}3;%!JwRG$C&W^u-4SAQnyS(Mt$2+b)*V+I2;XnAn{ADi3%+@dWAN!dD z9sA&6<^0qU{kL3r$K_ihcWmBroYC)neT&mEbMU(yFFW7k7(M_MUxp<(gpoP&HiyU(%a&o9181J$A;GTk?VSolE=9vPN|u zdoNiL&IiCYCP#3yCagNoTIneBcq)d_FydvGPM@p z_=nbFuz~D->z_-L%5*WtBbl}g%5?7sGR@mjmZ93N9vLnw=U0@8S^?)Dw_-+JD50=A zdSCa*sSj_G#yx&2Ss`7?b`8fqxtA&)L$`_0HT-l9j^=i8Ruh!2bGfA|q#LEe=F$ra zt#Dt5NA=CHRG+k29bIb-T`LvGpG@vLf$dBhQhIA*zlrF-w>Z&31}zA%ePrZj?msCO zXPS-QO zyhZJ4s-n)Lu1^g;sbPvWc~FKa(V=Ewmtc%bu4GO1$8VXAL>BpfUAVz6G-7xqx7_vUOkan}0z(oa8pCg(o?=BQ_M zcO6|fl9^MlPT%*c=qz2%W?We9u{U?FRk42LZ1r5yEiLjD6v$JxuZ-kZ{MzYZC;#sG z`Hmm{@Jx=cHhSB6`fVnW&SB{(Wtu3}d2cSG-X-0s$ciqw`A%L9^W=5kk7AwlieogX zC{XtsJ&-;4v(lIhWOJX|yeWdQzwoFPJN&3>Ok}CP50km(y=byx9+`J|M!smjH+#dF z`!Y8dW~jz|Yjk%989v}0`Qk+UC*nV;FZ${t%kH)rm`?s({e0OMI?v3qGQ0DpIh#28 zZ`^Hg{6Ul+s&p7e@K-0kogQFkJuh(%2%jUxxM% zJoUh)%pY5^1lk|7)zB|7i_QAz%NAB*L$xiq1yqa~f5 zttD?is$TYQQ!mH8{vETdk=<+Pb#EWnO(M2%-aBT;Ebqwf0y@N>h&!?4%wLNl$>fao z`zsStl?i%UD#62NlWnPC^@q-Vy}q%Ty^LoSOeyg!=dxFP`FUjJKmFIDrASa~E=cGk zTCcOM*LTx`5#-9Q(KYrxOtK<@KL@Z&hGogHlXe!0e@!yk#!A4Ad!9BEze7yMqPcVy zH+S4|#t~=dmG-6oI{*KA);Bp?cK>aR0u9;HzdW;EOJ;;$PI~<>+xXdx0P@?ic1(ix zLSM%pRogqPANt8EhZY;fZ!nEQQvSC1tsNDMBcKjt z{UgJD@!7t{PHcP}(@lXj>$UaIi#6nQ_s}`-B!jyAQg?uV@S54i?$>L(zg)#SZd8r& z->GQdSSl~#8P)9IEjv}kx;x2@iP=(;s^n<8i#e_8FSLP=53&d#ZS0Ku+F zUN_q{$&*Yw%UD*pW;sE{{9)DDc~x#KP658gyeFN}ZOq4fjmJ=0p4--XUrr{YIkDtV zRbSavVKlV6`>#=b%@b9{XsY(a^XZKf$En5>IjZkG)%r`SH4|l1&zzz(O1gx1E z3Y%)}^yckQRHwA_81I;R(e`gV$5BWRD`S~-$L#Q?)!x=6ai!VDSHzBb>~8NEW|FPp zz85@WT%}UO(+!LeOBp%38BbqiJe_4_b(b+rMQv-0YdAI$GTkA>-z{sK)DV)-5K=O& zeiPrz%@C5+ZA-fj8bTOEgPq6I+$y~)UA`7iHC+MU#ZbkTWR;kDj_3bQPbRPH$)x|9 z@O6FB47GCaTYA8h#PPLhlu{dGC(jmD$QaddBk$cxc;8s(+UI<;qvIvo7`O28=p5J1 zPChx`wX+k1l`=xf%C>p7=VeJ|(@A39V8VYYoY+Cr&t2(B_!qVp=l82vzEXX)w}ks@ zW#o8W^|g2_65X_5gsHb|<&BBRN|iW~pHciQ=@*eMc~(6gwH2*3Cfs-Id2izzw2ZWv z6tF{POA5%AgTIx|vzJAplqDcJcRES)7%N?6Bx$r;Vw&$>gT2(5G7pLX4Yg_q&q zxk1dkJEd; z+#YIAuMKsKkdbd}EO}h@bv#OUdrb9VaD7oJ7l-IrYV9jF{c@?--dxA;gJz$yo($e3 zgD1!!%6wP8Nge)E2Ebo{5z z9slJUJ=aVTiYX#aPmwlGb+As{VgusGTOJ|XW|ZH|EO2veDAiLeI^#GxW9M4uH~;0k zY^IEX7f(MT`z#$J&acRd=bIjS%`I*ZEq!W(lkr>M_0jITyp6hRjxYwM{;6BC&THG@ zy)6;HdTgxjD`C3hP}3bpU{@-U+@PKw=#8^I|3|%1EUd#yL}$kH_Z9Pr)R}ek8tI7H-@@%C7fRgOjLe$u0Ysh%cj%igDwzo~na$Y5tgPrT6cq-UmjTuP?& zPP#BXII5ab!aF~~5{kazmnkPh_1UG^EGt!62`H;Q)R|Ko8h9R78#?5vy=}mqRU4Ac zW@@Zqnbjx?hv%q@#Er;%q`kP1Q-l{dO*qMdrkgQo;-zZD<7_~8jVIyBkCev5BP&g{ z$|wQ&vY7OGA&9^P)ReEFvRI zA6cMWu=DcL*gu7{7>l#PwMcV=2=K6cki`B);o_xfMKkB7tLe5kT%5nlTDgr)hDz7r zai){byD-LK)W+KN&gKr^rO^}gwt`PGcE8PH@Hzs%TpNln$7<>J$z&jwBtaP&w0KAF zNg2sDM!M9H{SmYjZQ`PAT4g8pYTCzQk4h>qhbD z&{z^Vk$<7M+rv*qbr=5@1LO(ndZLdL3wEZl@z=+m^^Qu2K_wp>9<$Z`x5XW)k?fev z$S3MiV(G*p&aZaG9{XxcPA_g`Ly@GfA6uK&_~`R8FX-9z9aC~OL|y+sn_1T|+ZHkq zcVB&LY~Ikm@I&*+FrR_4^rP6pmVBU+q@lpEN{|OLKip1XK9KoY{FarPfbyQ1ocz00EGI`N zwh}PgQ!JbD;Z$i3N=Tf1gm(@{Aj0zz{+w+3=sF)JIPzX{R}z^b>jY!69F>uAaVO6* z!K4n27y->JvzE4+bzzq2Wac<#mhEFu-;OWY!<-bG%u@^fBw+db(UHbt4n zK2$;cFDWNJ6dqxh@g zzLV@2%Pczf>GFZ)Q4$$4Gel1ZoajrwQDX-!kT4{73YQ(G#Bo|*^5sUKW@MJ(K4mdj z!}R#kz3#}n!NYSt^OBh2*5``rgtMNL$!^9-(sW*@E$S@Alf7(e?vl72GMC+KERHmV zq&@N20(WO5`?*dIR12)yl2D<{O}|rplSIHQogfwQICaBm_Db#ml) zjPd9V?ZaJxkzU8^n)Dj{X>Qs20NP> zH<=s}i*H~{Por%Z5q`RkMIarAy%tiDG7Yv{+x6FtgLm8QB9%H2V34UFev` zpYAR|{Q=R4knnRk93pZ*(Z75_Y>_w5T{?Z%`rl_H*6I9s)yHL2Fk&&Jb5hEOmHO(Y(ZI}>b<^m5DA|OW zmU;7D+&ZGJ`_U_+s$whSKo!fLxWmpoP3>Bf6Ra=fw1rO6mfDGniJfDGeU?MQ$k5V6 z>=Aub_Bf;BBBng+sSRvG9~jt#zKz8+r>nj=RyJxP$F6?W*6#U`YnCXbrsKRk(;EHY z$?-InR6k}a$3Y$MatcK=%;Ma>4h3iP%Sp!Qqa1xo+z9>UMCNOoX8vW==DEb5c<QEu*BfD5*@(@f_855B=sBw4>}&??%QLWXMHjQ zaW65*M4Byi;&Gm)Oe3xMB^ifDPjxm)GkZ@*lNCJ}T^)UN2bQa>3w_kjRp=~p6`Do0 zEweHo6hSf}%lW%Jin7#z1Z72LRce~T7{EVU$S`q$*Tpveb z^j~+($3r4ZpglbNbPHE(od^=EpEX?HUl;DRiVKNnKV18CU5;IE`NMP;)s)Kk7^A41 z=84~V&L=-3i=xMz-`wQSx3)PSd&M{UI(*MRrrZXKZ~ZUbV2_itG}!KNUzIF*|5Jx| zKF`t$w{vRV(A>Pm^sqWl4#${tUOnNxev-2MS)8Lca*p2U)K+;fjiP9TNu5%zJjMuD zMU$>#7?#u7+KUP)@d~|adLd&%MBhXerwXz0Y;xDr*oze2=IpSARky6JJGd`s5j!#q z0~t0_Co0lm*k!9G$@J@F+T5SqB}Z?)2&v z%F0`w%Xm(0eo`BzI!|JT>_^@7#IcjfnW->In_A(zewXeoj<`oQNAjLDWU;+LTbvY9{G}k{DWFi?iJWUxUm{=a@|aSm z#*%kPz;Uaa=v_m1edi20Gt_a6m$daS7sGOY*RGPH11aNXRAjF)J&6Gq4qeU;Ajg^UT(%8WZmhq!O zX(dg(KRM&5s4Y6jmP&)DP+7*T)#YI+|vzZlM_!*%(wx!ylWxz zcdF)EsEyQVsSJy$7Ef7M2iB1?M_1{g)GYPy+IGv33nh4Zh-zDA1dAf=tYlWCGGbt} zhbLx4jQhic|4^x!IhML!=Knt>lkUzgBBptrHqBpTsu^yR(45f^Y~SeAvemO`f^rhO z+dnyR7__D9un}Z-Y_o|Y2jNl!$6nV{=U&$Qvbt65@T3N$CM5h%>1r3(67KW$hSza^ z>3U4VgHvJmSw?{t--W82gn!DQB&sl=Raon_iN%TbL?Id&Rp22DBa z%RVsW)QbCe%GEt}r5JxKoX>Qc!|6tjds;6&N;V@~xd`oKx(@2;?MlgK(DYW;pPVVy zQOk57(^FKC2HA7&+hg)7R~1WyD&ib-et(x|ytCr9tTd;6f;X*TWOF#in@R3 zIB9I*TKyb(w|qw>;?K6rh2PB1wR=mNIj@M1COxm1T}c_=K3*Ej|GYXK#|ZZDu8YXB z*$(CBtJBZ1Cga#Fm_J)RbtC6>_H5NSD*EQ?hf-$M;VJ$3s>`id70Ra)%_fmw+4Uzq zc749e?0)0gx$iJ;SL8?4=||a31gO-N2t7e%oaBVRn_WtTSCtA!sv^#4Pj6!l|F!!S zha*xjeIx!htB}viT8xc_Q?bN*J;mIv)x5{ZY)66P*FYJmHc)hj*On zuBA#brwv4Ul{M1VkEp)7Nt`~AK(;OqUddkj-iZm>smO9yt}3=(tDHsi(nmU5?i*oq zNugIyvP*yXJ>H$9SP`}aWsHro?+^o$bwJ?6Hy=8I3YlT<$-Mz5IeEmG1^HH>JA9VA z0N;IQI%$9*r|)~nuZGlT`oj-(dxq4%}KTbU*m7Q-5 z=|0A5V6sH(CSK<7Flhtz_v3a>E%8hplX`tc)>2}_XQ}-S6n2DD`haKbM_O2_iFt{4 zjto+mTOs$uGM_b53ejA`Zs*06c!qQ3Vc*PYIL~J2F-oGl)7miJ$|H&NJQ=4Yr>~9B zi}1)O=dVk%X5?1k{@N&=PO_C5N3w9TKk6NGEpMwb4y|0n;@M8aD6W^arqylXKI`@D z$j{ch0K45P4l*7wUOCUsx33y{VK_>9GhTK&-@7EwI_C@@<{25iB%wqSL%wvj-sd?{ z3T z-&-fCr{fg8?sF;@WIrRFQ;vLkhw&UmKrwYy%=tpgh}x&+NVks@@rl^wiL>h7caK|* zHDngQ*&O{puAc78_ZHU?9>2@F_iMAQ#`;MluF(PcX=iaY-y^U`l)o+|+i8dWH^~s->I=8$}g>*AREJe+$E!B5&tLpo# zsQgpzY|yk0?c%pvPshEgG1on=R54!OqKB!*Hy*PZkFT{FyUnxsh-wbVG@;Z~$Tb#k~8^@)K zUdU83>@tV4ADXEN`(H90lxOr`Wcs`=L8_$x|7AKTPnL2`gG+kvW1bHwslIZN_vWN} zTNs1lx0kxxXNyy@OX*w#)olj1i%U*CsqR4~SA7=l(H~ab2zR3AJfox1w46Dy3%6D6 z2-g&jx;vJc@v%DpV+_c$!%6L6QvQlT>jX_dZk(slZ|)-t?~NonF^d6t7-~HEyzGEx z@(zVt9+4Lid0)p0cyF{Re%$%iIZy6%QH9APr3&^R&o@aQzbNVFjR8IGrgRITkJH^k z(4%CzDkDv5N!r4Q1a_2)n(%n&u5`3MI6gu z+HlX1F<#EtoW$pc6ga1j`HHRc&ZIYd*F}4i6+b2Reot0?Y()5>2;7QWvP{iiGD*mZ zz~DmDqCxRv{U z_Y_>=ynoy`$y0TOGpWmW$*t!W-4u(^_4JJwt0kIH#<5Y0 z+`=1-OMhb)xRNQ#Ei{L1i=2nMeEIIaOuXdi>uW4z6(8P^VKcSIJ4DUK*V0qh>b~{A zs$uTJ{vWF0L67VI=KHesHK*}qUyifqZ@hih&dK=)@}z}jB)FUxC(qx&^Bt_tc}@Cl zig#|2Rs7X}+Lc^(eNmy@vIkh{^J+%{?;OOhW*lHb92k`-!{w$53P!U-A5^YmSvSSZ zz4VPu@31K?leb$QwpLbg6f`*EIit*-GAtQo9>uUy%J9jgT|=DVLI2q3xyRCCo#SN% zAnWN-G=tvxh_1)bJ!PHoMVz6$%OSvc-O-OQsC<~toc>OiyL3@!6r2Cii{(-@Blh+U z3 zk+aDe1~=#1U;!yJ~!2gMk1$3IdARrTx(yQ8jMcu^Gx+zs(s~ea|cr~hiJc$jIeUb zA=}hrta_+mSJHVT%HrAer=O%YDRO!uex0t(KmB*##2me?n(|KpQ5_(MeR91!mS0(t zkKs3$Upd;4fkt-ru0*px8-ZIi9qcEwyqq#*@+;%N&99`DIMUoLG7p&MPHA1``xJ{i z{>t!j&y~@yv2gwYE5llf!YOcHC(qIdHb-EoMvy+sg}3|m!r(re+Oedn;5_#CM>bV| zL{^8{RJ9Mt%5VZRbao?rZ!zFew801C6Ls)keVmM*$3p3$6 zE54hNdqv8Sdu8{=-Rr}|s52SWrZRFC-!(SJIn@(LyM)?sZ{lH}3TTUpcsz|&pK@OM zvggXe8VVo8Fss-%Mz7R4iIQ^ID6>NqN8#djN>1`I$t#z6hRzzS!lxZXW1Y^UfwZVwsj}2zJ(x^-V@Z}R`#7`g^QP^H z^Okl8Z)tB;c6Y~EUMcl@cl5H)!VY&7X7NV2`DBZX(Q=exv%x6j71qwR-k2+rZ=5;ckwV5~5JqGIG9nx0bho635VDkeU1AXAN@{w#3txWS) zdEkF}t2~<7J%iWE&vOud)RWa6;Si)FbJPb)HjYf+rXQT=G_9BS-1}Kk_tD$SW~;^= z_pC(k>*b}92ob+C1yVllYQO(vRlYffqIKF%YBT` z;4qeQuGaXhy#AA^Dk{ia?uVpmH{EG(ej5RUYwUimeI;Y5Eg7OePi^(H*?Ao!9!^?O zG;)!m@EC?AHWd?%<*k4HK{nki65c`h*5x-8NxYL^oE@9=dn>j8 zWlzw%XYs~xJLBI;K1$PjluwN^UWNJi&CPuL=H|7RCBkceRI-SV-z?(eH(~2&N!Yu! zQ0_D%exrDa%@=T5S2mQ5TIEhd{c;1ABxPlsF`%!{&AQI=NPKapdx@oX;mEkN&c zuk%@5KA&25naW9U#=v_pEA3x2ZQ9RUw>R@imegm`?&U>jd&ro2dmAcq7<%(!UanZo zixyPO2}U$|do&X>TYBFMnNleg`8gboKJaP(<|pn@jc<7aJ7i*rTXub> zC$s*Ey{eIKusU5!3q5JI7eC1!TN6oEZ9>RinSl ziWN}8h=)^_^rD%^I3TRy2=onlpA3M{a-m*8$>q~Z^0Au8Pr|XF<*!e0lDvwOWNEfS z6tS&fJ*PB{^06A}n11V$)Q8)0H@|i#s}cEZ4F^s!&!~E5^{rXC)gGd!#^%UShV^@A zCeG2xTZrMO94~NpBx}vTQT7uw-1IeaM98PkkRta5t58#>`Ccq!u)5>kNme{8hoYnj zk|s_=IXfr&r{~o&`J7`gm4d~$>Qk~#B#7H-o;f1ugs1rfPVH-%m)zjtG|zI634$0= zC&RbIlF{}~c7U>$O^qGrqpDbp>oc;vL$40!^jES*mz*DO#X2*Xp4}y$thDtSBDriv zQ5}ldQy9-kCRK2ZPAmN>9%Y$|IXIp58DlbNB3CmUB~I=gS-G4fYL&#UrFd>ZNYf!3 zWLkk@lb>EnR`Gm{I4mulXCKWNbJaOYdU1rab$eh*O17UrB=1qEf%j)`h+`oUEM(?=_U04JWCf+Fv6iNL z;7;}OEc(zJRHHP_?Nphc8}k=fRfvVjYhW@fip3QvKEtVaRbC1zW;-U#N1zv~7z4>J z89#{dPhp%Z?;43>QW1NUGMA)sr8Ba-lfH8Z1v<_gH6Kj8!#!-24<^b9&N=o3l9wz? z-}~k#xQ=lyN%v+t2Y-|ih-RTt;e8#OG1M)W0j@qZyre36sILCS5uC%RD1X2A}`P8eb+kArbnj7zf<)OaFJxPWRpSzWHqI zJ+G9n-!sbj#yQU?b_d?|#fLimf6g4?e51%a-Z?kd8+8tS!rS1iUhU0x4$Sm!+1;F( zULM$8H9CEDR&-Gzu++X(Vvi$Cn8J=0o~I5VXv99w>UVDmN8OJggnA@$j>9Ne(-;TJb7w}#e# z=~Cga`l|b0-0+e$yngt&Z^f?h46C0w z%B;8V>)5c2xbuiJkMMcOkQwi!zwVXHGtnQPS$5O!&rDi)_RK^wo5)Jnz9LhXqL^n& zqqqchzo_F=kGx{Py8BH0_phBPd+7X`iG_S3ia|?isT*NgXMTKUIkK)6S<|AidTJwJ zWxlxni31z%zW2oqZ&B(mQR?qy{prllD0Hh8sDCrMWBn_?&)IS2&BUM2OqtkshEnaH zckBKQ6Q>Q`Y*SS%vH8tJKdi}RzXtRGkrTIPz^!&Pf|7>e@Y|hANY?w-` ze-Qt9zn-^$K3qn^a;mr9%Brt^>ctJUD7c?rw|YOle?v93n5i=ByQ${oG{H*T(>%WV zg3r9X;clAY2`jDssi%Iw;R(xEf3+toT0brA#>8?2wtw8e`BQwm#Y=zt#zP*v_O?%{ zp)c}n?Da|wtN#ZXjL$BO{loIs|0DK?4Oa%ku~x$VG0neu@|JJK+;~mx8`Nt5W{=9+ zpUsusJmgKQU6lBwo2iG4hog9)zShd94=C(tXZwapo@i!$_ID0$P~Tz=_UYem5TnXc zS@mxcX}Oh7qAXYAorK+u#!sMt);>+NCr)<#OAJiYxLwvu0>)$^S_ax%XrH$oz zA}oMz*UIL*Lg>15)u-LAx_8kYd*!9KYrFCPlf8(I_>o!Yc2R9F3sVNui3KYxxC#p{ zz=A#OfDv3gx4-n&iIJs6^ORLT@ARUg!b?~EVy-RSN^L%g@$ScXPhz~2Fy0FWDZNH* zcFKRBd1aJC77mc)D{%_@?|aoA!-)jbybNP(irGoaiN%EtI(G)I$dI=b|5&D&G=K${W6=vduJs6llbqB;9LSBvcx{emQegYkKbzMA;}~{ zx-H((?hBh+`JM`qQ+4`aPjw8)Wl7QF&5Z7NlfC1c80q(TOW@1=K0!uD ztzpZns8TuG%0fUNnM&0!=LHox8yk6VNkU3~p8e`xj5+g1?Zu+-iJxx0BnxF^Qz4(l zU&I>ZB&*GFh5JWvJ`R zq-J=NR}yEDiIhY#36RNhGO2&6ep4U2zc&yTk-OV}r9%{!fO@9>&V*&nk5&ibEZjBtkjlsAHUFHhg@ z*}eUW^e&H+_hWC^3BTnDIt|ySr#a1q>2sa$K9qUs&1y#x)AE=!iVJPO*>K2Cm98XJ z>TW!EOGK6=?hy~ml=G~bdf9y7x9jM#=|zRN(B+(4C-T)-*-!iIX=N!Mnw!~&bwqY;$FDWDx$(Y9M?(HKm5IL zLe|$cm-B%c-aC+Qqb3evmJS}GL2(pt%sy_VDn`XB$ zsQd`>?je2Z)%lau-p!12)$DfZH!By@(eCH9h}8Cp;Vi~J)SEjtbv|3bk+#&0G`kFT z>}j0;SnIvkx!dnO?rd4-4eUNL#gpsZedHtQ&DS`+zsbrO#|bhYn&XVsJr6Z!tRh6- zFM~m6^&RO`oud1*vwx7tN}C#HWF5)DhX3@@qPZNQN>tgHJYaAhs^6T^YfD z@T7l~bJ?QwN$mXx3Ei^8d*z3F=Sp65hx1t(voP;`K6F$|p63`XMx{=SBwuqvCEr9N zgZMb|ZXTRB<#!IA&&vCMrS(9{=>OC5$``v)QV!}dxsmh*EC03B7rlwUY|5p53hBO5 zHkrax;rdy>(q}dTzM$fa&*|*T+8fsJOOU1sxwFb#{A5b$H4Te@wNeO+V%H9`fb(`@B6q zW|EflKxdI^OAmGwsjl?wwj#AYBd`vi4Ew-L)no_O&s1?cZQV?D&>v_jRY&}}TT0b| z%)r60>K|h738{5MgDoMob7=O)kUBLqur9>wqjpEMM70ge=03D3JJ5E6YR+zEbJU&_ z*dA70Irbhz1Osh|2&Qcbt92vO8p3MJNbz4cDzGQ4;-hkpl&WV&2ir?k^CfoAELAr) zu>ND}z*xKPW4^jPd*=zk2 zDpJwb8QHC2^~?Gt*7p?R2b+x7T#@W8! z(iYb?tox9xe{$bT@?+UWcDV`{x;8PYFwnr~AR36e&$1I9b-?0+h9Ba6B%IseQ!Um6 zC-2VmtD|AnK}d&XA52%RoyGI8<#3*awGO+XJFwVu<+k4lQdRW}O`nG)#|mIqeE1?x1c4UI=yw0Nm>wTwZ1@nh&tet=PqAv7c$f5G@x|)1llJ(kQkzx zG9++cMu2amZ?N|Gjq|kBQwZfyvhtbgG!mX zl2O*jh|-W1*fT_J$r7JESt6@F3-5zj0lr-9i0j>(b(qePS(5o={9kiGHgB-*qmY0!mSzhVNzwJ z@!Xyv682>OuXTT5KO%&^^`rpgYc88aHIHWFu}6*yu!FYRGs+WI9s zd(_DJX|z>M;28@Gvw7TUP3BKtU9lwnmzHz8oSDDzj@bR?zo!Q+XZRP> zANoWdBO8CZ5l{S6F;-yaWhQP;u@Q1XKS4Tc|d(on~k&nASpSr!2FDAZ;FDB;ax(SP>YLD{4 z-&FkP4sX8ug|$~Wm%ZQ}?$n-7AL6hT&D?P6$>BZ`{uSre!i?~S-9H)SMDNJ-4Z9$n zoNoX4?(g26o}T6{R1=+tR;F)r&aF(JF!Bpu{_2;O6kJt!Rl(Pn)8s|Ls zmGo;Tj?vD&U@^--Sbzc z*Lu8s53KXwyKA7)7c#jEB{k@F!1P)Fg_=`?Afy+4g*)r~f;b9ZreytnqY>lRmah}c0 zv8^Jf@y6`jvgxkB*o>IQK)%^WwSTh-mp(#q2Ey}9cvx0J{A4?8vWDcA8JC#l;Mnr_ zliiG^|V?=-RJ-=t&HaX%n!LZ|l6yT#d5 zo1Smo;`|EO^alBfIHk`G&n+~retZVkckVGa5`RcZlP@aXJuKHAaIK*w{msT{JLA4? z=Z-TjS$_@|HPwh(eVg-*HR+elZN5#5w5goI#hGK`wN&Wv)fZLX5)(e*vpT%P*;bpL zYa168k4l&K8|Mn^gG#J2iL3w3`RM)WmwDp<=6nPCd;d)f3p$JU`g03Er(Jop%V5>E zq_Vyv72CSc&b5uJ#o6{?dTwEr4wJuqF}TfpOxmu+I=sbs>i*%m_MIt7qV~jQ<7C11 zfhLqcwkSVE|E?KyOX)8ka5mnWlWPyS+*YYEPPcwW`%Au{lj;icmm4RYv4Pq%E+e;R zz_|_Eu)nBNCY-&s>7zY~FFLWJjE3KOB;8MVyb?Bl39{&Hb^R#q|g^ zE`+YZN=l5gz!|=5M6R2BVwLebMKbDZpgU!o^3GqX1sV}cjB~n{xY0PZQCdr#cV6`8 z+r~5C?6rfrg~nBOQ3g{@25HN529uq|(ssrrgUn#l4V&13$I0U0PDD)!BZL z0`j-Q<)_nx_wYQpb~l>v19!X8RO?0IdrbJMFX`|Cx4NSnjk5(=gKOu2i5^~|qp$FM zFg#(xcYZ~O%NR3Qo$zt(-+GT`fWX1wRXRMX_SEPI?OtV{dSfOU4{e z=Vs?u4>IL#b~Zi8Xy5RN^JnO<*`_6g-4V^-W}IoxxC8!NKA66#k@?Yh7CEK=Mr{~Z zuCsS7Bio}oeVViKUdlh<8jc2q#?^0qa0u@;<Z5!7rx9`j?c|`|Sq2%B(b5E)k{S|-Gq0^mhFO$1* zVOz4?X&!LyO&>B4SngQQ5%teH;T)}KnQ@ww^7obGhtP*;jDbK|&!k*SoYK{FgV%LT z>~|e8jjP^%Olg7)byI3#7p#Uf-jpR!;>|JPQ%!iD2@jg^Udk)+cEaPNYlbW! zrfh`AV6?&n%rOBaCO|<^;0P5j3UokGpcRUsCc|}51g?f6a21pc%VDQdoNnAxjk}#rbbb;4%F7 z!F0G0N(K#3GN^@;!77N{>Ke_;6;R|?YgR^JR5Dn?1A|m~m1bxGlnlzDgqNA{IVQZs zginWODX|SpiPwde|G?b=#oYvd1=kyQ5j;b9(6}j8R6R|E1f!rNXoshXuoM0q_ZIjR zY=C><660TH{7Z~~5S}7kE2X%NcZ;Ad+KNJ~u zGT%tRM#K1jVZylEp_Hr+N(L2B6v~AXzjdEd=vv;Q8LEKdZyRoZUb`y{4?d^e(N*Rl zu~&PnhBuOMiQxp}-_WkZryCx4*2FhF@Eh%p820{JyEj5nv=&MM4l&I#9Zl(g>v@BI zN;?nIFUt)T{0DBnrpWC+WpE|#IgkWXO5kxg)o?QWE$%${1`I&XJE!#jN~!;X2cWq3 z!49|`7Q>dPicH?j1c) zddD94aX1~`0s~Mo?rGEMcS5PTRwy-<_yzsHm4|~o{0yq6v;w_5m3kg`4-|#E3=bRs zc34eKJ)>FKV*EEkiQiz{D|C2uwPxi46FvvNK)jNj^#9#FOvNLDz{#4Gg-{eIfFe*q z$*})Pmz8n&CEQ(_mHVIw+zCbAb|~_iH7mD3k=F!8UJVp^E1snP^H8}E4-vRPv$DuU zEQBJs0E*xs6v5e=l>sP%)1X^ZKhuIcH7gH7k$XV1vIUB~W+?KSqCAMe2F=R#Pz0`n zB5(;5O{+93=R*-#u31?G*As6l6v2g11m{7Ko2ywFgd#TpMeZ|C==jk#9wb2vlmy!~ zE1RJt*a+P!gCD^ifimfoKvAqnvvN9o4*xtT3g&87+VFSy_pn{zlXul|&B`{2;?XIs zJV*u`p-i8Xp>&S`6ze_0M(7;e2gTh4Wjt5_XTw}5>9Y-cb}(Wx62>({4X}ssa>FBR zyvlIzgexuj|7spYzzQe{t2ILrC{;ThN`{5d%}9rr2Q))l*y)L43n5*t0404I zEQfVJ)e0|wb8%0FpNR6%@e@|3a3g#Yu7{Fv9h3}fpeRsn+;gBP8rBT;{#a*xN;7l- zikzKL6k7*H-fAfFs-ehR0;5vpVIIPG*ifoIzFjBWZdd~utjbqth893s%N0V#nDPml zp#XGC3?-wU$CO%(eEw>H+q8S2G+7N4#e$E~|0QDLQJwK&C;{`Kw0Smk!?)=*Ujj}f z!(QVS;tm})ZlQ!9GH#)SAA}-z4-|Q=hAXzw|8GFU>Bb|sSu0YwRVP^ghz_3*Wm1|7 zMZlaN>F|avntZ*#JF6WrJZQMja1R_sK3g_vV+{UK%V~>l)DO*2G@Sa7j&Q0`Gtr>A z0NzA+fpJ%TU;FodPqQ3K)z5*VU=ds?^592d;=8))Tj9ghP>W_Lx{e1imKrDuEP$VY zOV(=vRZs*^H}0v1g-`_NLRoYMjDLTr34GgJm8pN0o? zP1nJUC?3^3NSj17Lrb9a?<&nu5K5cc#y`#YH`FQhL;Tl4Y3mhG;zbPSKzE$b4E6tq zZpvOLO%{hyN!Z1Mu+v0149jtM82=_Hg6p6NT&Ec-GvRX#OQ6InH0}w8xyC>K9UXqa zus*y{2Pp0h&!|%iVZA*EpQ>Ehn6qU43)s2 z;hziXvE@O{P~bcCf4XG3jRy=VD7~Qf+q$iKpp1}R@C5#RF_EkAVJHe8(hMDhqVNID zPzMwn*azwQp$s^kP>gL4 zjEMqJjB6oe6fa+(8JZ89aF=U_=0K^zA}BU68LB7`g*-S!oS+%vGm~zFY$(QOLsxSs zM%Vu>-AfWsjILKRbP9^m^}rt>FRmHtgpy$^lqPG@tgMDnX`2WSFth+ll}^y^@&e#T?=+X>DJ9!WVj%bDsLa~0KSpNaz7K-(EK(YR2C|183@~?cAW@rUuDNtUm8Cn9x zCKf;`z-0J7il;#e5S`MsN|)dWloE76DM5>2#IVq?|LZ!u!>|QP)iy)v9a}U*8=>@$ zCe2VCl!6JD;=j&yN7Z(Dz#~)(rDO}B^y?yc0A@p(RBY&>WNDhA_}7$r9RDNG!vC;l z=n#~2`=FF~BNPQ2peVRrGgJpZ6BSM50R>h;(NHKF))==?6kGvC!7?Zc7Qz>yf}&vJ zt4guPDDTw_9fqRd0mzb}yhAgz4@$Z=7!?hhc#uh?28uCNLuraCD4H*TVl)++q4`j3 zrd%^r2E}OSK&JZgux6+Tiu^Q~1rOF}qiutdPiu|o|6A~ojGCZi)Sww!4<(~I&Coh1 z8P!7R|Eo1aHBd6Dh9YMPl&Y@M3@wCW3kx(u6;MXX`8D)^X3+9-JYZ-JbTx+WVx_(J zXlreOqH!~nnrVQNQ5_V8*J*}op(wmsGqehd!ZnZyw0wnTC;}y)swfX`6+_XuLNhcU zipJ%dp)x2M&wCnm#0x@E(1ucgGwpzHLJyP(amc(<-lZ8jV!~_TP{K=~RP}TyRb2q3s{6mJ*<*MJx-|mb8qo~x zgP)cD-!2c7xD960|AlUi823)?4z)tJMxd0u3Nnh8FVqZGK*px>`I@0JC?zj}Qu66g z6qyXAi3?$rhtLEbq>T$SLwQhYAQv*~mIpOM0VsmHzNF{(Iw%SYMd5YEEtDp#g^v)w z21-p;L#e4MD2goj67?@tQh^5y&4*$n<(i=~=ngE9F{C`K87hLJ$W$n^;RM(ZbD<1m zy?5(w+W{rxb|@L|gpzR!lp5H6w_Z|(n(>et*rFNQ2&D#^AU&+SK{HebCF5Esf>uE( zK#gW-1(X6*Ylb3F3a|vyvC6A7LkpnDnGoecro$uETGK;NG~EX!gJ^6i?TEl@IOFyZSVtyf;B8LBnm(IOtoi8vX`vb$@!w%)@qM9?A4 zP_6N=h7zyDgcq6cAe3``c@;~lT)}^rf-U3kEWuVN=M1$_j4}`Ynf-rtu70QtLNS(X zxP*)Ynjr;6U}CARnI0%L(*?It6P=osN8lmchczn?!8^(L0F;{817)||3Zec8IpJK#5mJ5LkfzZ{)kd{ASj_3It4|}Lg*?0 zrF)h`Sx=N1w@}s-b6`}eyod+Ud@7WLyFm$&`&o4+bJ! z54&OOUAh2U3>QN24;yx22-lLn21-Gzp`>4MC-pB8ryGw6P}~6+gq@4E0*9d_+y`eO zXuEM&K@k*&V$}gC2ba%O>G-XNTMR3p#19xYMHgw02$YNqXizC}E?huHJ=C9M+yO`7 zZa3TmMUhrG5;hxm4V3sx3=<2r`>3P+DnucC1?lz?g|0b#?|&*|_86#t%o z(*iddMxZEM1;xtCjentGuAzcr+zCqcV{D;UGg^6yhu84v(X2cIU&nn|v+@w!O2St7 zIIM-A!N^u?hHBuaaj(z}MW85J2BkNIq1e>E&+2QrS}3+w0M|?Z&*ech4?xi{4T^?J zv$CJuM8Sk+WiOmhm7jvQlW~t`s0&JlhoBgB1C)%);AuD=N(~=CTXFA$Z$^1&=HV2q zHJlGcKsFQsT@{+!p;Yx2C~aH=N1;fy;Sx9%_hGb>@SRXHTnD8_;3JFv>&a zIvyS&!YU{ktbkG@OQ6)qLMSz|K(n#}N{!6dtSpC8BW3UwYGjUPs02!l#BbFFXo50# ztcTNxzwTE0|4uwshT;q;Y zx|@-E2+o78a4y^!<)Ms+6|kHH3k<_hs=f$rr>duGR!)U7|4-JeEQB)uPk=WeqChj0 z3#IAd9HPz3FRqFB3T$k_r<$oYR04nxSea z0;`~ubTX95rckr807}gU4foLw;%keu9Z(`X18*dJ zk7j5x{2qeEawLOJ3`gScfepBKLh)Y@AA(iJ9flHb0u;HquoSsL808^k^YAtS2`oq= z^uRC?;+ml@_zwOZ@C<_2!E9PfDBZ5sxP{W~RzYck8qLabDE&ScN{t0z2>JF+^nV^g zX?RFC>c`?F!ZU^~P!eu8Tm>azjb>;Gl=vmazsUF(8viuoFP1F%#IZz)-ws92Gask^ zC7@MIlz=TzGTdlbYa*`J3{^uBFvs|ZjsH~Rp9@8fn7qV`i>1Ot@CIt|0L1F2xVwz7^4Rr$K4+ofyglxKRuSu7~qrEfk|&tr=Pf zMgDXs@}pDrL#V(6#Kj;8=+X=wf|8)w_-`@(>x_T7@h>y}li_UQ_hUeF;X(Kb*aF3W zmGNJw{i9Q+n}9SEz*i->3EH6~Xn-Q9#`yP89N9=z8!m)W18GpIREi5LcTx&bd_*(3@EqI+zYF)k%V7hQ__c;B45!0S;eVi5 z*GPYnE#-V2$!ees#Va%`tDy|V5zWdf_!#ku;Nx&I zG}&+h6gfdSiVU;i_0Tr{oj4>s0>y^Pua6oHr)!TbPy{x?>quAwrAhj6 zyo7%Oz5!1`$)E>Lg$JMr-VUYY3!s$z84}BQvImOXjZos%8CFD1K)LZKF`R4|gmolH zOw$Pu8SaFi#J>Sb2K)t9S8%oAWcU#jvyD4}mXh%y!*;`Hvw5g8oMSi{ir@*x-F%JF z+;E}c97Ef%hg{u^prk(trQ{t@jBk$#FNagGzbOa#e=^UlP!cvkw?yzb?$yS>%rIbh zghUd*-LM%-lWa6>f-=-LKr!k%DBX3PW@RmWmHEGVwfmqdS3&8oHSlVzd4*;u0>wxd zLdl>4PJ)GSIP4;_uo-S3-bPr!tk|R(S`Wn*j*v(OsKc6-2cg^l_sIhh+6=cFHW@B} z($;g}4tT0ish`3wDCv$E7Q!Cf8?VwVgi~?bP~tDVLciuS6<&k8Wjyu&eI8cxkWH5t zPNd|kj9Z916oFELIq+eaYr=&RKWN-SiJt}~e#_;GZ>wikg`#LJbbAMsaCJHTUjhz} z(-GPYcN(rXtTH@UU~V)Zg)Q&+zgT-8xTvai|9|g212co5sHkWt_%|$3R8&YNQL#{| zFsaBSFtIRE(Xi00853(#Dhza?Qejb{k&PWXa5_|Daqr<2r?99)MLHIBsHmu@LxuYL zJbOLcGtBMY-~E2icfXYP{;cO&&-%01{xf@KuPte>;Pi43jZH0+wC97z5I-4w9*hPj z!9GFK9vX%HpKdO9MasbSAbKaYR??o$dL`>vkaou~kSb1(q7Zw=$n;K-bQ?1poPc;M zCG9C7wY)n5pMbzh2Uh3^R}MY^wxhq$d#P=b_C`?eN^m!20PhDUfat~4C`o%TNPbH9uMj#6%f6f8ZBv`08+-D5wb!Z z%tmGv_D!m288Zx|IKkj(YLKM8Z(i#zBBs z36jHhkSbQd%mdLGF-ai##WLFlN$p`y0qOJ_3R1-i2TG0UI&@tIQvBi&?EiF9aB@N+ zsJ9HHV|ETm7b4k`nJYoM5Xq3tOb4HYUmA!`N==otCxdjzjt2h$Mu9EJ7a^G$4!#2& z7UIxXGP{HETMamNf@5*)#*8IIdpk%ir~=VRG4wJYIyt3*)PfX{DxAbj9v~03O+oSy z9LJc0Ppkx!Xo>>CwXE#to#fv2E zPVjo@?UMF<5L>H?xEASotcTMI670F)6DTMNtX9RV>&(N|=Y;*Wz!8qOkc@eZ^d4;j z2SHbZ&wy1R)wcrFwD{0W@&E&sope=3Dvt<(%uA8y&EO%9y5LCF4pIn zN35bPT7`7VS0VjV%O&k)(m%CC(q05odx_LuCu<@#cRNVUO$X@=5la+Tc>*sUOxxRQ zlFg+@eHAf!J(rHjn)K4$U;v`lNZPAG`r4;T(q0Zy>BQTSUdEaTZO5EIdg-~Ketsf~ zvqtHCXfidn8&t&KP`ds#eFfw|B-0cQ#3+#ZJ{+XJr>P&P@3F4a!>nKyGIN<2ka}yP zLD~irB<)ckZG#9&d#H-fJ=7em=$#o1k|t7TT0!beFRFV4^nlG^If$|J_y&C}jeZ?t ziIP*1OwGfwP9ID3q~ndMa!(<)!6`H?A8b>Vfi#cD3NRb|2I(2#htO%@YA^*Pza;Q9 z?D60yAZ^Q!!D#R!Fbez-3^(I&(}tB$B>Ww;f**hi_&(T+igbaU;JXM+qhJl#22#N- zU>w*4qH_m&z*%5D_zFnFVm3&_;We-lJOR=$_%m2y!r|sWQGg2xGm+o~Uj+-n1~4C- z1LlJ9U^ciCOao7XDd25j5=a$_2iw6|@Dnf^B)?GbCD01~5mY$6(}5wf0SR94IKoz_M_J{-N;Ov6z)EluNPFoJuml_i zy1`QRmBF8Dc^&CCEtL#Kgbz!Y#Om;_D)<3aSsz-Tc2eXK-bMWdI( ztYkWwg&+o7U_RK1+GTTk5;ICtOj{Z}R>a?B4o)O5C$8lvDqZ{$GeC-x29iCA)8jck zmecp;2W5!xLUV8qd3kV!N4e`k%IyXzy^_<*IlY9_ot$3C>G_3+Eu$l4*=T>u= zT=V`VFg!FKmw*&I7S!$C)z)(1yee3_@BeFH{31-QCd&uO#|u)q9%en;-E6Pq^m0xw zp>#3&>fjL(ls%ix8Ej4?GgU47uYpF@?ly<%O;p&oH;AhMqD=$0n}^FrQTM)WnZ)rN<}kf3PMj(9x>P=I<<9DE9u+0Roo23!_qWg} zk@k)ym?Dgc*M{r)qEHb%pYt6H=PNm8M2KA;%uO%(o6*7~TTr;@L{z=kC@*F2#jdXp zMNn=tTM+ftH-8Hp0bKQhHBujQJIt7DN);P+=tFV++kwwJI!1Pmv}zTjFa@PnHmZD- zMQa|_f_3kx4*YE!)jrB>Y8%%+&O6SkHI8c&2PrvvMb83uxPzk^j(3(1!Ouxdp&x^A>;eHqRhQJMUje5=----ExM`Ca)auCS`ms?`?O<8Mu&yU=WMZZF#I z+K%Y=In?I+REt)zQ?1+ycI;H~5;}|KE>dfYz`i2YxeIJ~K=nMJnoVUcwcMpvxRB)8 ztv2o^m))v!54r47Jw#`*T2u_y7po1#o?^9^=z3T!ewgf!s6~$;RL{d|@58F2kJ36H zQM(=i%SzPp60p8hZ72mR%GAm-uw$Rvxes}&%hj54)m@IHo^rLfT(xSh18VUB(0M>D zI)L<=1FD;}=YZNstUsVOP`dYk+CjSKfZ9v!I-qtV-Jw+;RI3gmq4uC!Ppmnpx(_0Q z`>#k{hjt!Qi;jW4&#QgUBfZb9I*;RR z7~O6as~WM#4SUaVwf8vKbzJQ}t~wC-xY~XkjVpOuEqhxne;avg-d5djBdPKowdx(U z`W>vcyrZ_hqqe;RYg3Eb+yeHVRQpbX#qX*m?}A0|sxD&nyJ`)w_&v4cJxYI1b-hO= zzpM7X3wqyEJ4m;^r?xw2rMy+GXr&CTY6-Fa1GV7;u=Ve1+uuRghidVMVAY3e^@s4U z|4?lpx<6EFDc$>#+VK(C^O4$1?D|OUCYF4xmVFFXeXLd!D?U~$iJo?~u^nt}R~>D% z(%i1L5Q{!hU7vtur_}OOVD%}rhFEz@ts=IcQoX0pr!A*ctZKwoN^d%?HlGGPr`1NH z`?Ok1>^P%#o&lYoszslIwV$f>pCW(trz%!8(4p3lq4!g@kJ$8?+WZ;l`Alsjx<6BE ziFl`N7qRa%)!6~|e5Us5eqX4KUqH8hp|%m5zffC<-Cw9Z#G-$wu77}iU#L!EbEn$U z33@x#4q}_5Q*Eb}zE0KoC0O#MT1IqzsTLDGU#X2>fvsPuZN%oU)D~jX*J|_EV9(cT zFR}O=wFIw6(2BlMUBsR)wYLjw`$lco)4x?+-$GY>r&fLkmVKv|6CFk0sV-XS{Xy;f z0bSGegNjv+*!=_2JAPF0k`;9QkE)m0{G-}JZ2C!U{)y6mQoY31pVT&DirF@>{F}y zz|KCks}C;KXVsdss{1S@omIPteP>nYIk4xf+Dp3XoLWt~?VQ?v4$Wykr(#tjworQC zdDZFE;D282JrC{ZIIninO79M>ZwCxb_i4@dY1kWgYGpgM@|{R3+NHU6A*p_s*04+S z>>{rRwB`rET9;Pu0()FqFR|@Gt^Gl;XpiRF1A6vojl|YHS{t!>kJdsgE7r=3HJlY3 z#k7tSL$Ov|3`1M7)=sQ`NUM1Wtb9nTB9=U)l@W^`(Oi#!o)WFGg!H4D>rt@tQLT&U zeN^ipwv=hDWnc&1E=+7M)4W9IKCNgUi1#Sg??VL~)%$22yTU%Lh74`{wf6mB%YLnu z=-IC|5~~ksHHW~~H?+1lz@9g>USd^~R^0@4H)%aO-_m;Cg7!3Pjm=^`OS5Zg}SumM({)~fJ|Ag%AT=ENksTE}Uv z^R$NJZ;e-TQ$m+l>n1jw(L86smL9FO2Q{sJ&{XrF30+xX>Z|}8pD{H(1A3n^bv$Fj z2!F=ZOYC~a)J^Ff&zd@)h3W|WY&0bwQl&$PAh--)$YxSj_1McP)ydpCwCs;p8 z1)v4S#I}7Y(CCqRT-MTZ@?mK=CbONy75Y2p|N0@aJV5k@W`FJBp?Ke%60HDD@uJ?{W|Rg|&&t#N(`=K#w}8fNweBzDHz$#oe+3 z6E{QSCYSq5sq49dOW7X5U3wAg-P_=g8^P8->AwIUlW3DPaRZAv{%|huKlkF!8XU~V zG+m!ljiFt*??%g4RAXpkj<4O+Vr+9`0^w$SN}>WKEWDfp8gq;m)1ZUhm|6HN1{W3Ra>)YT z`Q1FGo_6pkH6|MlL~!cD>fdAm_wlHIf&*A7APO+18@`LjfM9zKkAVZMf8pKq5bKwC zOgzTgF{A_mF$NBE!W!O2ud_bH+g$x#R;Y`Q(%8yM{gtLIlH@-~c1lX@d>g77m^pW_y9izgF5`;vR}+|0_9u zGV2wb{~0|$w*Mp4W$4lzcG%1NbqVjcPm&!y)WQW`zE}Devi}AiR1Mt1a;`uH+c$Ik zF4k%6AN4URjPjMS9FQhNqvf{;Q2@q3{5h%b;Z3mqN2#CVO>ms`Q9g1S^YuOwjQlDZ z#Dh$oA+<5h?sB7tP#%@9^l+CnbHJTk!+feCmfk(G#YtR2!37#~?1rq61sHSf?#Yn4 zXr0V&yFqFf>j$or8WZm7wKleEv_ZD>r&`xK9nN**I|vnJJ^6*6Y&y_fU59US0c-liM5z{wooBGy?v2CirQ6<6?C*6p+#q5xx> z-;ejn3SIWIY(Ose*cjGt@PW#a^qF)R!7Vt<4jZ|PcCqf|O_I(2Uanvl+b6Q!$@c5G z!h`=Q0O2TfBq=yvv(R&I^ot&}?7F_9fin zXIM|*O&HZ7D^x^1MCX5FUf_dVffla7kI`_zZrH|$;t=kklwKL&74G7XxPUd>W24v} z!rR=KS9lA@_j3FXs0SSwG^Ly&lDqUY>!sX4YAu9GZZggFLeql;q+L&i}Cl5kne&J<&t}*5lcJy$KE5DExn8}(a3|7{1jg9$%|1d=s5dEU` z-zN8C#h8%y18iG55E&Ej-eBblHAw$Rw(|tM4kexUOP+C8`HQ|^;zxJ?kQpA}O`v=! zbp`DO*d&I&h3$cCzmj*u4j$CYxCe~6aKEGc*y|?!O7XD&|H8YKF&XXv?g3-A+x!UG z0%O+O@F=N`X>Y$8EOqbSDImU!E|fa9P3ldkEDhom){hO8I+Zk?fA(G>6VljWCf7KY z_v4uH(%#ATE2xJMbk?`BfW^F5(448XtRZ_K_8Pu^_>49oDqzegdx#ncO%u*i`ATBB zOlaWqefOhMx3WG<+ZY*)IcUdH0nklsucaP@?qt1>3P2YdA4cDz3ZOzMdY9vPvR&%@ zK0g0%y&DN=k>@L_4q8UXc4VmJ7L@On_ULb<{d1}iE~T5;{&E_A^rd=~?iOjkcn^T_AAPrOJzdEd(y$YL+S_wqGcFbLj`#Aq^4gAk?t&%`cRnE z#^m^uhDvQrX8+M;QX6yA*K+(s2M5gOfX(Y=zy~;hF&+PPtc~gW1JFBEf$|N~e-#I$ zXV_^8pDyjj`yQUfk(B(SbESP_xYUknw2YSbV`V}i2fUjzr13>$3}>LHL}__*k@Pnv z;2w@kG%BDF6`|$%E2TExM{q7#>J)5yS~jNWdZ=Pdk3TYo18fnud>%N-@tjj?B-<8>Ak5G5O=p2+Xt`dnE5h zsXKX0)z?VejMcnM8{p<$=;l?*+gM=|CnKaoTMJc=<RXT$%}kodWrXYV~+ZkNEx8UEdzWqO6rxY-&i8GF>Cxe zeUo{Va9k*o0~A?;zAA!wo0#+xJN zT`slp;(3j(lG@oM{Y&)S(xW85DfR3trQMk1{`^H!ck!XVY%6|V7U*#( z@n>a*tEfhS&I@O+#4S=s|0eBk(0+}P+s$XU(X<=FKb!TA2c<6IlhuN4Z10o#PwbRB zitUklJcm=s1mrcy+GmdsduE*iLAFxle%Z7 zv}3QLpA24jbJO5)QX8*s8ar0%>^0JVOf2?)%HYkG2@Bc5cs0~FtUFdo`*iGvoCzz-{PmAL;=43iGKD! z`4#x6pZ|i*7xxd~!nX7cxbWWodf@2(`mtT&_VGbu9FJr4`{tYPr*F8YzkSC2{q?Qb zwSDvdGN8YvpBeeu@4(>q(GKP9A^j4R`}_jN&**P&n%-Z3iPN@EK;<2rGkkT0pN{Ak z594UTuKpQb^2^}x)4@1t`4;f=r2cxlUj^GR_q$Npqk44ujqw~m`y+nwgZtNert%lRiXH4f_>>!Q3F%v*`2k}0 zh9P6d30w&Irr+Whr)-i~(v1dA_DdgQ>YwjpzXl)k)1PAV`<64@*rx{W^N-gu0J@2pnq?&wgTw7T5h8 z_~^g+^47X*{uww{3tnE3u%>YJntXactT=yZNVM4VOW?**`%4i6<5q9kym`%rf~?$C zcjT_A`~H`}ll;cmWj~llifaQb!%NS+HB{tJQv*cg&Fb(VyeU?Hh3xVL`}+dt_OGC5 z(bRy@f4%>87T*85AWJNJ(^@Ic{~9<<*!u!kU055UhZi{!YN*)K7x>FK`99m!8MCso z3UW7ZyesR!yx3NpydG}_jhY^mEPg#3_;ml$|4-g+Ti1OqaQeUbZrt#fc3Xn~%@`x< z@Qa)4)l&EJfu*t6jV+D6DL@SR53{Y*bEB>F`DZ7CBkP_wS@KnJ&&;5Ny7>W?UE0Y1 z_Qk)#8Kj1nCcPLUKDAh0`@ioVf$kYmXR}$xsrzqM2bPxXyI8zqvxJHby@5fZdwt+k zT@IYNWy8kJIcqkr$;rB7O@TOE8f+hz6-Q%f@$%Vti}2OUv*Oom+OjGyt6-y%5|<^8 zXRE{OHV0YmSO15m?0C;Svb3ajfEXHVIr{H5WnzeBw-%PQdgF#$*WQ-pJLtu$_Xb}r zejQ|K`!~xFmkqZ3D*6(F28)?PEDI;jn4Xoje#>V3n~}9~bJps$1zYuwS)aOocHE*x ztMeA6Exl8ms0q4Acs{et5U0;r(!|R{EPp6{xFJx~{1`ZSXtk)e`?U>Gyea3TK4&(_^hnWYp8JX{ZPwVF=(D@2}ZaLYYGdp@;9$tFWk=ug`=-S z>h21&92glg{lAno@qbm8m^Q^SvUJ(zk=o3x(p9fVL{OPo8#d;wp^Bv4sSk)599*1< zu?!7}!y#g6>G9WJ`9t=Ca{S~y){^pXZU+l`_L}v}(@NEv2_kEFfTeEgbW4{et~g~~ zZdo{E(X~sL#~ZdSgQow^5RUs_HJ09*J$vnjJJxJ2Sai+u*&^wb^~OO7`CHK9TUM=J zN9D|}I~-^EY4T0~?f$xmHgNiKV-x?*o4j?0#s9s7IB}IaqSR9xbWsBKz^pq8Ru!zl zZWoL9f-lqu`*Ng+9YyNM((dNLrQg0i1RPK6BLBHyXWcznmKAEmdhDoid28;tWAS=@ z8zrpD%iFkmRl&y1i`R=yt1M&G_2T67!K+IvI|i3dbKWKnt+HJ6Z*C{?YH`2_^D=a! zNE>W9B&@3~Rr-s@XOu?aCGhJ-*24kUUwGa4@PD(k(%AI@;=>%vvi}!xk{%8S7u|1| zhyOpTo=CgZ(uy{Rlm<)@X}4MG<@#u`B10WQo454gm*$J?te};3r*bWOR60?VUbEtA zoH#SMsu^-chi5>TI#MA|OI|Dgp+IGZg_7 zh4(wlFtKt;$T*SJXB{qnEx-Y?=!t-#7S14ETQFd>STrDVg!t`=fMMdBjX@WS(8_?J zqU1#IP<4*Tt_&D1nzvYjL}_Kf7_mA(C`?3kTdok*XRR0G9qNYu7OkX-JTGsw!Z#_@FU(*#2#n zNx#?8BmZ>)rEd^A&@7s|dShPBzEIn6oZW-IGGv}vHxDn#lP1WpG!{JV`6@QE%HK7iI=upu2~tM6|3morLQbW zUp#%5k(4l-Z3#0CCw)bCGLmK*D+x0UCu3#0$iBz&zuIZUse3Gof3FFH{I-Y4xz{pv zKs>5!>>WNdm1uz$AK$3ihlk_C4lQKR!162bMWQCA zuFz%-GiDG4{6G%QNQY zIX7uhquuQ1!7U_O+SsoUe!2A8(4nF-boAIn+%%*oW2uF>0iblQ)SqHis&VtriF+rw zV?`4$>dSKcrT^41JMn)q6*sdk`##I8fh*onPxf3BaOCXWfk)2nRQLX9vL5X;(VGpF zvd;1I6Y$h+oY&;BXKPM-=YXVNa)Xp#ygN@MX5Y6Xv2z6UaOgp>4}^U@^f=|0vv*%{ zr01GN;`HsNv7+)J%aqU`P0CU9N*K{=3Og#Ax7l#GY#3mUu2y>IbY^)HIzg{#=us-z zJ1FTFy#h$jJ%4_-*Oc^&$V;|PsLLy})CS~yi7-JcJqf#!Xa4A^ zOHL?zFC9BSVah6HuR~Qf?N)amT$<@g7@+pfSqPSGXc(P^>)#^X~fY_O~dte;xl*&K11&% zpZiqlbN;+?6(z9<-_Eu|Q zd!85w9jv535vY9sT&R+6Q=IQUq1m2G!g`Qu+q2~RtoX?R%E_!(luh%`pHJVf+4h_c zRE~Gfd1BP$K}xN4kTT~9ZTQz;!rz59TaiZ%Q1*Umn(WXkNyf4n`wwnFV<`A;I4v2e zoTPz3f%hPwBS!h&`P=#QrO2E&EGglMpOj5wQP{e{NePb6)lDbPu`L9)ed?z9(Ccil zJ+E#$b5`2c1wh}UZdyfp?b>+9Lk@M5%Emmj;)$o#O*{IK@k~~{tGRI*%feQQz+hN$wDw|eeecp7u<@9IS8*ffkP9FW~d@XjzUtDO+ z6PopFlTArK<1pFYas^VC40}sK%Q|_OG|f;>dUtvfzr^kmr8@0jVK=(Oru;G)!)EO` zHGu+@&2SwoKYxA%_1F@mFHytZGI}gawS8Ur!};_9)LYar-ZXl{{)!$!?~pBH9a`pq zaTOBgg`mdYbB%*gi#Vn)_>HO2#NI~AEeMWtyUXd0-TG%7+B_nL-bpOE8DR6b%E zDGonrxk$7;WHO7^9$ep;#qn{bp|{RZpoY)xuoFrVM4o!lQ67VhXV znU4Ns(vJRYQjgMsh-z~N#O=7fvL^EAGjg@Echnn5b0j5<7_AKb@!6y!=QXtU#qxvE z$6W_Cj6yH(!JVg*62js0^Ti8}M4FUMCtf_@<1u?p)#Lv^@jnXNM1)3%g(afidp)MC52%Bvs`cmSKqwC!cj@c0y4~2C zWS9S)*M0*n`r?P^FAc=^Fx|Hi$R zlrS7VUp_bg$Z2%I*yG}0$^{=;tDkU=*rzIS1*$>=b*~kN-!CKQCtNaGSvK_RnG26( zA^0j}y3ePc&E=$O=x z@xjGfJ|H-vN$&ya`lDYD2;I${Z3-7fCkBTuqBj2%8`|i}&#*2x_;5ZP4?Eg{AsAqm zhhRD^Qju1i8fasm8IQgboAy{DBYK`nIwH3}9m)AJDp+%aS+=3GDPcEG9MtTYMdP*06t~W)CCpK6^>fA3_9x_MG%inTK-rCh!>(*?w zEBJ7;cEfF1cdlJ?*JYR8vE>$Mv8>iIA(8_Q;Lu5|53w#i{Pv`};B|qaYTa`$S=MM` zP`za$?vvueW1cA47MLPD_XdWVa6Kk|`@}Tbl!BFuPMPfE+i=X3cl2^vL6G#NoEGs_|l6O znl*qH+NFH$DQusB^CvAC_>cIwu+bNB{^KD*;>MRPBi3fHXDBWTXrcLYd?V$uJ#L`1 z2gB}LQ~LUimZU+FG{=c={xY_wiQ|t2MNTdlEZtfF-_D}18EHwzcTBWI>#v<(OS6N~ zl7=F&AuqJQo!uLTN_z$1TWSy6Gs2{u`p>sG^9-3zUoO(3U#^Z=P{0y1i}WiloZVCD z@P&mYEkJuOR40k;9b?4Hf3}Qqn4@$zrO2-{jEAjUg-$XE{;<(QQE^$58ra4 zX3BIAzM`Zh9d_Sl#Y=mT5{9p7Xz_?MI|IY5tir^O9f2ckoh+lIv`&`3W^n?((X6_W zi0BuZn8ro-u|1eGUm`PyiIYEtg-5eW@yndgnRD1aMV$GoE!@f~Rvg}qYBl3qKw5&u z;88=ut*p|;iAP|Zf-k0MNfY0H3?r+RV%58db3vVOqOtgDh?ZmlHNsYSLC&9xjI83M zv74n;oO#n0u1*sNUmqMPDr-V4s#UByfzZ64TE(WVp+0JG56;rCJ%-uNf|+J!AA(YP z7q|rM02hO8U@BP8>26N1;`DM5-wekTusx6M*=$c|dn|Yr{2UXoLIs4e12v8U^q@be z0<9o9(yXQAR}WGJYC!TU2g$FH?fD>8G>6mEnYl4|Dgf!p%vcax*pWI#uGm|#tsa0w z6L=*BxKsvC2ai%l@NuyG5*)-qniI4V+zuYX&(;eiGYdc$((^%l6i&^Pv}c1mkxp}n zJ_)9R^wwQ{GSS@#kc0#i1t0I zNH2EDP&_?Kya#?gtclQenl04@KbkF-8bGt9Iw)g3C%D0*&_&E#a2<3C_#BwX?70{Z z*FkrKvL+=EBcMn)Xeqa$8 zJznY}9D}K#?I3M)+z!B?Qev{fJs3kPB{MU?1JLP`nQ0(yW5lF7u!8$0sVS27B#^dg z96Q8=E2a^QhutG-r|Dg(M?6f28!JbU5CN_M zz4)~NwTR|zCC4V#^{gwvCy|~HI>0<;4oEGh`CX~y8IqalAhkSAGBX*(i8e1u(vg{n z6&EtbgSgF<8YgL=0zQHC3EBRJj6x@(|pQ7ecR#WaJ| zAl&)U8`KC=gFKR%wIDUfO{D$51}oH}Y7mF-)GA4P1xPJQ1?hAd3sQxm!6+~Sq>A-~ zNOmwQnJzF7cA5*A{Nh16O9q1!#|%1f)Qss1mJS|f4M=CX3NRm}$3-YWGN(s@6d)Xo z07Kc{I)LMVl%J-lqx{7n`4@pWG{h8uGzM}8;N12UR#qZm4=PI&)q$DmAaxl{SO;b% zgVbe7l9`F1OW2z%BNkH!g+dmBGjtUUD?sesG5O$8Fb$+b1nvUs*Cd@*>DLObMS2rRe$^oP z#j$+?NN0G(icwDm^jdh@evk^P1@pi%)D1`)nGAL1yY4+DrTy%6Qqjea5_!JOq!-*Cel>Q#5RyB-U3p^ zX)0!_coRq!ZNM`LoYu|;Dsan{&s{H+*UiND2TOYoOwuf*JA<6FjKBCv_xiI`SwN)_g< zY@55+nU@4L(j6_Wtfd@dG&JCad`v&x|i4DZ|m~5RJv4e=f*x@H8gI3@6MZu~828LyVXgm|2QfxbfymIyqk08*sc~fciW-1zYWz6qVbZvKq*v0>a)vZ| z;MzE;uOMx~_BVcqREio8LVyfz5q(x6oy+=0*4eCg*yMAW#+OW~*hKUkr}6y=U6PS* zrAq)TnXKtrm6jr1hj|p^i;Qq~uuc}2v{@!OqVNHo0t8Q%8BXAULfV)#;7e>G(rr_u z{Z(8{&=ctsf~B5-PkE$`ZwXiF`Ef@G`DpoYkhB}$-O&ej@=roK7Kd^dI~ZSsCE((P z9E>mX_6?P~V~n0b8OPcjCG{cpH@>gB&!`Z(o)-E%P4P0gLK=FQG<_b@j|?|(i;V9v zmjy|O?cBoWSR3D=c5?w9Jpf+lsc#~joDi6n7QIJtwTQ7q%W>>sdV#1oEsIe&Y5i+P z+A}ZWgk)r-k1UZxQm`d_&6Y6na&E+!alfJ6zUim@be~1+ju(?IQg+9Qlu;x5;{Ew8_3tU zz|Z#qKYgVb+ir=x_8&Nf`es||=SClZeeI+CiW%akCz{3OA4Lq;pFYzv&Enw2ArI9} z`@}Lyv&G-KB`+^)%jUe&W#5hv-mfeJ>aIU+c~})6?Fe31cjXz&O!ey38`rPjvH{Pf zWaY2ge8-wBBMbdl*RZT#weI%~G7J6|ti|I!C`cSX85$<04zt)q*H3{1MCGUWEz;@H z+CZ^n(tu0qyq{WxS$td_Jn`Zyu1ihLy6)Q4tgGf{WTh^;`pWbaC2Qd|S6y}8)r-?_ z%vvyi`64olQAdKu)Vcm)`B+usOP94JirDO+hwD6FTCULs&+#u`EE54yr6rl8MCFws zp~Cx#C8%ytm!;e!{(dN6g6R6qdYSn0JN(42V!?n=v3C`IdaD0&d`!0`MZC2nWT<%M z_23Zk_?`Hb?fu=BB(d_1;83ytF8r|jGm^#+xTo&RvxSQKt(Fk+^DARUigOF`bM~pK zZIqfN4jjRc*mtY8sJh4>EGIPaVKYMXH3tWYfAm=5{)gY@9{yl(xH&d%Mtt3MKUu!h z)bhGTy_WU?YFORl#|NZnb+-pvFSV+d*XioJ#I)mrpl>*;tL3L?Mtg*R{`UN;m`3$v7aEPPR zSlV53AHK;h^#iP*#eqV*PS1CXC)>hV^&V|Mf70UkKlHEstLiwSiq}`!J&OrhS zOf8fFuUjbfBdljr2G}FEO8d=J0Q6?oW4MB^u>bR1LGvc*pUwIAaD@(W{?~PlgYeh5 zf}j@UafUPFp>aGpxlIO`jH^ttm$3bD@<#>QIN%DpazPK`hnf2FCu$J9 zT*237OM5NZ=_HUvEk=eW&Jay4f^K7tA8YCbY+oY_$fXv*-pTg%cxV(bn+tGL1)$?u zPiB1?YkJQy*%P7l%fn^dg_pC#_1tAitoL%4Enr>7J&?@$2)FPG*6+s40#aDdX1#>< zI_~kb&9Z#u^99IY!nG84DZM6`bQ0^=C;(bqvRU@PjwEP&f-z=>eT-TNdkx!f;vVxX zlm7P(*8P=c)(fZy;NQJjZ$EzI&0TK2LuMGygD8S^(>z&V)a}x~mh7lN0qZL2A?PO7 z&r%DaBi2d(2I?VbW75wgY7lg|BTqVf&Iyg2VJUCpIIh6e++|&?OQ^yKP{jG~6WjRUr_Ln&1N`)T?%S>sjI0_fvgrGAhKfc}Z~M6N&w=ieFzI}S`?*Gm5% zxk2VTrKVR(kEQ^jT)^kt#bN9aCHB@^r#Xgj+;~oo=AO)EO)q4na#C-WadSpXox$3B zk<>Y?S8zQFIsY%SVaML+V*T?1smrByC~L>c0PZYVKwgy8wXBD6g{nD#mkVfMeI@G_ z)?dt)1$3}3rH(`cJbVtDLH^JMy2cQ>mkK~aEjzqM9R(fD`YhX%S<~yw$zH+wEe@E+ z`fln`*sEEeqzXYhS+`IRK!=k?`N~{5_LLHK*i1tO4ldS3G`65~SkL7atz=EV^dNgW z>rSpfBJ0n|j*(!T^?#-OIEZF%<5R?VO29>F{C2qs?xg~8G48oX>ZbAX!>kJ#Xi35m zmp(@nr%An8|LEmW3RyoLB<;qB)hpOu$w%m|Y_CaE#N0nyCpyZf$P8`{kigpbu=?b1 zX*XuLE5;`Py#nYdT1;pi>4F=j_L!tDyiMxYFOk~CC)x9prFPz`+a1cE;$%WBSKv@A zX?)Sfr~Uj{Qn%m&ik9H%Qqz}Zw0y$4iuL!w(q21E+6xUkdX<)Xw$u0V`bKyvlrngf zD0CGqkr*^of%th+k3%5R*%&LdEYy1hqZoscme1H;fo)C8sAKKotrbBFk6q3FLpo>pfd^eG>uC0`M_1Bvn{LOYCb#%g zu8?=Bv^TOnj`eXIIrSb{qPHI(*Qd%F)?={Ha_I=EllTz-4BKl)O8eeX?7vFtiQFUM zYo(?s_Nm1+tPc#3x;Iza>74;&?{shi-8t1WtdtJ7j+453h1Ao-rEbcSdUmwb?b%Yl zHc9GwJ{LT8vD8WIAB&1p{G4>@|Ik3G9qG$u!bA!X=)7AM-`_**99(T_yvj!Idw)XOpGNathQ)3Qp}*#7i$G+OAPGjfQ) zwxVTNsMI-EOZ`3vjAGrux{CE#*6ypMe>skX6fc7Z-6D?Ph65fgyU5=G2ltgSVIdc2 z+!gx<4lrL>Cdc;INf_0>_J_><^`RO4^_oli>*c7(RlWhvVHEr7(xLtJ=R^AIt7H1> zYH|34HPZ3@l>W(Q!usn(zc7RS+VPTKJJ#UH?^`Xpcjl{G1Nv!)!Y`=2&~Xd=IwBi; zpKpM1e)^1GKXec7@Bfoug{u15F{EgUaNhzl{PY6seZKZGza9$581=QU^3(6&XyI$$ zWbx7HJ>?ZK{|#JWzJMX<>o*Jgl&?NJzQ4ZGuVE1wvcCSUe)a)=@uv5;I~2_?z%IW{ z`)&UWN|m2p<+lqI_~}>u{P*;0@IpoT0XwsA3!YriUw`MfC1U-yRHiuIi1#*r5mJX+ ze~BhBu*sSwc3m1YsP0&k_3)JbQ$Z}k>=2_%&vk4n9rMNdy32mHCi+ebabcc^p?*)I z)AR%4@I-Y|Y0GbyiIhJFkHJcO-TGeZvszf;)yrpO&Dy*QQ%fyQ&6~c6{)?*@3>Zs_{88HKc{@h`Nq>4qiADux`aG9jUnl*%d{N+i zS^9Z?!!?_jzZ=h`N3GPN>}>(6(#Z|QSyl1aRAn<_d!tk(38zmw{HMuK^ph6-zYDw+ zK3B>I(@O@}t_xK!7XugC@DN#q?JBxOxMG}bK#16yY#q9?2hZ>Qga@;K#`A_K3ePpF zcoMUioAC@Ho^Ql6hmh5n=RbrH7O!;CLV^Ke4Fi}I_EUoe`q4+ zOKm8gFy)HjfkF9zVgHK#^>tafuN+AUOHsM5x}+mGMQz$~eE-2GkMBPC)oD(jvp`SZ ziON+Wy>s6Dgdl3nALR3tYf$0)VKXXC4ey+HVn{*?o?7fYEuUVbrx=^W^nI4_(UI~w zNwVnAO%7#E&pDnGSC?4Eh|2bWVOCW8Q~W<8R^4NoDDGW9U{KxK8MZ{t*cQ!kwtMK_ z?7fR^r3d6SJTW_7?7iRiJ}Psi%N7~%Uv3!31=e}XY(ESeq8;tQSg|(Xv35Lassv+N zf$SQ~DDmnVYw-VM93+1Ee!#-I@aJq(0<78c0b@MuKhG2|4;p{_;f6O+vH?Z9cAK#cA zY_GuCg%-2^RHCSV-B!M!mu+;pNejis!Q?`HtVi@@B@`zTT54g0>jiuG5%K67w##iR z*{@uv1A<35@>w=V$ZS!5*{a!2PlM1x2Qc3%x3j%tJ}pps+1|n*yGu|2KE++=fn=PwXvyYJ*;ds4LIY5~lEV(U ziMpTC3;0%`*a%>kcK-@ivfZ2{?ZLhUAgO`vp$nxw7Y#5fsGqXh*`Y8+I#|VtKjW9j z&c(kUFRVtU7pF1--#sgi?crBRyVK8}%=U7&n$4$#T=q<)(e zcW~Gq&1chOF&2+zF3#f*Xc@SS$7sH=fhyR&@di``OESV;*g(+jY`5O1o0UYdqik?^ z3M-en|8VGH@8(SDO*h1RhfC=5GQD86Bp!o(&lVodsteJ4>z2XZIk`sJ%f1c^x2}~c zMVzpNg{y1DM}M)6vE{5YB4RF_a0e?lhO=)(7e@@=AiZg{`L?#3?V%f`oqkyBYquVg z>GAoJRj~WEFN*Ds_D#~E)z2ZG9o% z`%dW)@7IDd4$yfQ2M}lO4;-X zp9MBvh7v~)HixTsi&1adBCT{($D${Wa2Pjd%aP<;Wbg~Rm(s%SUO#&j+ueMN!rvap z_L6&L9DjR?vb^m$KBQozE=IrY3pKR zuzcWpr+nbr^HJ!92d;~ZHqZmtYLU3?Z`cZ)$PcV1vowpn?GfS8th$jA)lZiBaSM>^ z9>2?|`{75?53JyvX>(t*}Db1`SGVfz&QnRIbtXmF&h>wXz9R6i=;()WOrE>r*g zx`IO#?UD9;#2@5SKM&jKb%(Sx!0vlo=w!S5VR6}ROl_Q2BDUQJuGATsQX;dZ1HLs^ z+%mm^?JhriDBF|wO22$D))hEHtrcTGH$~d&ITbSn(D-U*DTlv}o+hJ3ZuqrkmUKWt z<1h4lEEmyMD&76xjwL15$4H>&R?&FFGjgVY>Uc@ZOW74<&!wAR6Z$l7Ky`@JmD#s zT>g|yPLq3h=LLD3r`@h0LK zBMjBZmd$dCUO|!lrOB#J5i0Fw7c6I}Jr~scJ+M71g9MAc*_cy-l~oL05E?GjaO`1m z7t&}H;)5EcYN^kPmmSu~e`VOaZ0LpT+{Xp|a@w|EDC+PoD+e#{=OU8AgRPkCVeDPl zBy0&2hxZ0XsvNSVpTE}jtiBsMT9KTi;`t$F1-Kl#ko8Jt6f+on1dpef!N);*SoSfn z2Ypikc7qtPF)wevX)0tdL_3NRHJYInulO$gv!xfG&{yoFMsa z2gxrVB)>e6{8obGmkvG*rhwS}V-mpyy!{FA|hIPt|qV}&wA zfs`Q_qzp7@DFZ!wP8nJ-_{pycB)?ja{M;bbW3{r+@kXjH0QVYUBYJnA`7WCl2Lit)j@@odk&%@~zoL6#3rZM9WqyH&T7$@{qO5M(+BO%!vL3}M9Qvp7RA25_l+Vk0-3#P-K4Tj?3FJg$Q zq^@L5gtn)FHi4jTpw=)Zw6F|!Eh>|OID|iPI%#!w&1Nw`GVm#m`q&G;~i#RK zO_H=v0jHwiK8#SRP!jk9Dj3Ra#K1y>95HUJ9D`vyh&z%o1>i2|G>{HN@!;#wT^Mns zio;H}8l1m@sgWQ-dt zw2dk#0d#?PAYhTCJ&Em8*gk?b`|Qe zDp245bFuO(3{l`R7(&4{pywgE?Y1-N4YO207qgRD0a8WECGAd-DzIJBo(lek_=(^q zq{mCz@uaLnH#Fhek^(eJ+C3mSHb~m5K^ja>kZN8CQcvYE(>OgA9EXCYfMcmaZ1?V# z{)r$punYU}wP4GGy#HH~K!yffW>U+0ACQ4sK`N-0nF5l%{(jjr`OHR)*juoTJ(8IX z;3Dz^H=-f6lJ;s)AFSY7*vlMPp+Qs(UWouM$;={fCA3pAb33>mEh+$M@MMDv!F2FO zFb%v7OaZCFN#GJN4x|F7fK*^Oh@){#7`P4LPv4lhhtbdpD@z zh}I=(?*u7O2Y5RIc_r;_AjP2%H+luYP#h8Cz#!z0Wle;3*r#BHx+)5!numjwv3sZf zEO{z@)WU5EyB8#T8#n``k84CXNG+@eZ$pKgAnnGn%oZFJNEd;$`$mK7prb%D_W#rf zeMPZ{gDcSGVUn4NJMj4wj%H@8Qyv%EdCW91J)nMI0jVN6oZfe@qMV=}0;#}u(2O2v zleD*jE3p6Pwa|*HWHx{_xN4ZiAO$P}spSzM1vG;cuqbdd6=N!n9E zDlkPdGYrgyUnoesC%s5E2W;CW>2_eH0RgMPcfm4n6C9i%6;KFL0R@uwe2@yrlgx|* zseo9J3Wxw}5yuQtoZhXH9U#SP2Ps~gq}|bq6$;oQnOOo-z+#XBZU?Ex>C7qMpHV;* z*bIh(R8U``ET9LZ0=gybT_DA$clv^vjo_v5ZvYY35mPQ#VhX`J1f-w$-;4ta{U#i= z({IaZY(#+6f-sN@2$i%4gH(W3GPC0@SwK6u67gC=YCsLMf|;)q=l={&NCE4SVG3wR zhG0-{*`1P|AXUH%QU%&2?QI}cpj9%n2Ba2NfmE=QNxy>qBjU$_E6~7LBF3LR8Y|R7 z`nfF?(6>bv&?{;00jYp)$;?`iHjx{oO;iR_L7`wL3g|81Z4Oerc91GeKZpkHtsuo~ z0UcP$EXE22bb%Bw52S!$;AsR@KbQh?Ycb zvBiQ)b)u+Lsl}FB+81@=%WKh~qM{BFoBREqwN39sls1q>ioCF}*?LIABG187o)K?YC* zG64SY7xY(w41mAe<%p(%41mAOWdPH72w=lC;3)hCLHaEWVTTU$K{^Zw{dpiA<_gOu zuMvk6ARP{YbXW_10EZPI9r8!=^j8SdUxCn{57J*iSe61F3A>O7J9IRSrwejK*$?tr zF9O;_spT!;Vl>#Ga-FhVSq##?NaX^MBby1H1A0^*ue8fUSp(SN>~99ygD}V*mV*qa z2;_a<3uc4Ucx>_Ffr3Pijl!79BxL_2cns`@K>F=ewkrA8jNhOGMd0(8e_8SN4kim8 zW1fd1PZ_L`Au0hGNB}$@dLN!LDD!FKQ^+G%N{?GX23`-I3f6%uaGvvr!JxlV%PYV% zl$Vn{|4XoQ0xA@MCxaQ_iD2Xkd9Wx3*<+F)DvMMm`B||5WKT=VC9rsqf$=Xvyd778 zJk}K;120f|LHqgN(1Nkc#9&xC2(rf=AfIsZKn6lGkX)5X29g6ZkWBD6aA+fbcmj5k z=y8Y8-wbX*dBH}EKZh!S0uEK4(4PsiJRRgv`Go%IpajwcG7yr1G^$K8kb01Tgh2+9 z0Wy$y68;RKfBaI6KO-5zou(f>?Jtv|ih%5K7(55%l^}<%7(5$tK;;au5ORvj<+$T; z4&{Qh&rptkN8~<`hbBkm^pNh1;f}xx5s(JOD(5TR%ILR6pAG&e?9#y~mpbVvAhYdz9l8QYbD|D#bta7QcP?@Jh5+2)r;zD6m*{cjIOO-xl z%>}l7D62wtvhtK3Wwclv4k{zcCS?i8z%!KbT0VKcf%N!z!~*RRRdy;>H0PZbHL}I&j4QlM==v=Hv-xLDjUFF=)>TjLH@p%cBSBHkc&auXDZW_ z6PU^N`9F*u8uWp`M+N@iHxF_XNJkB-FIVP)&%!Pn8~{^5+Ic{J$mA(<6&S;*u?{z# zNnzOpSO$4qST1Y`h3;3?2& zYk7)tc)i$%l~u~3^%#FT3TVOPSt5@r+m+4W6Kojdu`W=KufuO+AxFU+_^AZHrXHLI zQJWAqc_qj|N|iq4G-eXZBTD|XnsTeMT$!sJJwx>U%4%h~GDDfF z47qh@;B=|j3$mwO%4U!emn#Dx1IPwHK{?;yplm4n@F*iU45<67`RCXvElyxBOO2E~qpUd(P3fyWC!_;LD zdzBsFi!f|exlzgYZfKXQ9QI2<10Vw`1=&zBNV@>I7=C;z^99Nd$irZW&t^T?;q9^< zWW`dD6$_Poqmr_roWOWf?o#rlR?1Dv29SO{Do>vz`iQaFKvt*#IWj>lFH)u`nM%nH6r^1Q%tpOdEw54rg`uo;El2@5DcmYEixnMpDO;5R z@H5!ufSm1pM@vIYGDZ7#a541t;Bk-(K~C~akdr)mlzgb|0`2erJFvrwEy@OEkup;` zlqHTkLE4vsd`K+@S70&~3H<>r&jr&^o+I>UfDC9XQvza^`73z-H(-YjYn3I+0;Nwm ze54$bc4ZjkNK}LN0}5zgkU$Q7> zDfE|vtXHf|RgQd150SDCWKXL=+VdSSez%kX;^$*o(NMZXJ`5tLtbUM&&B}UZsWKmY z3w8#066^*Kk@|eCmi2m+?aF#(m9kiwsysb%uvF|+hLsh{o>lVUv;pK?r~^4dVWGc@ zS6P>Iaog$|7X}$RnN( zB2wm#57~e6NkPH)q428V=s|{Y4W2ofR30p2GI=^?GI*Xe%=OO8LI*lUyLMq&Ge~_c zh~M30F(bMOPGTC+ZbDer4>FJlKg!c_Ckn`FWx29gnW~&TQ0$ljo%YR2Wfri64SUO9PyROnZ>E1Q)WATJ+jAbaWsKY=`vD)pkuHf1r$v)HGcUM63mD5Y5;R%opH-Q|&29Q0k0eJ;yCUjmfndzKYaAr8?q3Q%bfW8jo zvFH28m~PhdUnb78Ct)rM=*Xw6OOc8Zc(pGRAocCaW@V`|R~g-30$~Po+B1VW&w(I_ z=}^c_=Age&%k%d)tkYtNi$aWga*;$m1TyMokWT8A<;r4ZrgGFP!`!80((%vOOOT_* zq~g5Fdq8$39_069(f#E6F(eni%KmbMS;qMV*#HQeHj-bE^@GQtLL*2AOgc^n zS*0LHsQ~1$%>Y@i$0PQgAnhAK+U03^j+QguH_OAxQolfn1Z_vyS6C+6z77T*N@mdJ zm6w^bSUcPD1(21lN#};2I-&1{?)YVZCW*xHiwW{Ld#W3tfof$$9co zydQiV9c~BDLU|i#cLZd-VUTf^YdI5KQ_fUQ#*54Z*Nl4*d=zmH2<-&djJppq=C$tZ6^Im00#+xe~8di(;)LOcujn`X8hQ(hZ9!-TP8W2Zjk+n;x<_bM!<`}W)Rbf>8&xH zR=j}e#Nt8}vp={A+SAF6Sr;^on_K4zJK^pw{NfR8~&Bb*NlCqNwkg;5X#w{RGo4-SG1v>)x;6(cBULxmm?Bfqc{WQA4` zf1z2}45E>RjUX%5gMWuz7|euR13nG83PdODeBeieLCF6=c?tL*U=g?rECAmH0~@gO zGInyoSHNuWNiYK(27O>W9C^V@KwPe#9K-)vK-JM~%f8;py_9vvc zDoyLCA0eH$Z7@;j@mV!6u>CML`ibG@K|jp8X`3~=l2W8XYd?~g2e-T3));DKQX7C3 zYx$IhGKLGG?FF>GgtxmZyj%~0rCbNXE-gq}l4}+1aC;cpHMQ|em)lNpJE2qFR%o6L zLjMcsC6LHD7@XW!6FdjdJc!!~%}YSrJ6LIzy^3E6S6UzLa1(bylDgi<-6<&e{l0?0 z&G9LAvtb-0`l$iwrwU|PH{-0Uv=+Q(`^%b_q>hR34Xqi& zna-LMAVS@=DSl}l+loMDTRp1pwDmYIw465>yQ`|NQoS`OO}@3S$q5wL9l8Hpk4}Te z4toT%&h;#_CsfEDpE8Kj<02%eN7#4mE2v^JdwW%4 zQDh*2lmD#&{IVUrTo`udLG&wcwpI0~`S$eL^r!hcJ(%5I+6%nCAYgUox;&`<;UAqT z;Jd{RaK-E5&(2@Z1I_&PmDbnid!DeiB_#zzqs=cYW87bMj zF7CM)`QCOFjGD111BjXvQOGT$X6q>Arctwb6ms;R=IB2m5C78~`6uMz z_sx;_ArHQ94!sXKw%eTE4S8a>IT_k*x?G*(X4g0rBOjX451|a2{a^r;AGy%DO z!fcp;UH^nRFoE)kPt3_rP(JpFIsOUc;m^#G&ma$eW)6J@d2-T>O+p@@G$$q@4}M_| zeF3@u3v=KL$UR@0yryx(p z%oye7J!Z=u$c=lRior zF0{AVHL)4&tPi=c5;TTvi)(ZXIJw0YBgeP6CdkIEuBNT1SbwLh;Z87or>l;P-0AAO z6Z)<@T|H!<omhsO7On`+7Qv^m5Ob3`2^yAM00YS1#a)iuPtJpk%A>en zbMw=|4Ec(4A>(nZvs3hUQ$IugQL)Go)u+)P{P(K=cZzV2XTe@ASg}D28h#@VPdG>9 zcU68*^;s&P&jt_>-Zi(kL$!gc)!)}eQh%Guds!d$&MWCBY``-H3!Hb*&tQX)op;W; zUJr*Xw^bVW!P%mB-Z{TPdpxK;ELS zRVy&FC@VOxpPzT3I2=~}?b<`<_4C`bhjr@jX&qtb?ehD-BlR=4F#sI@zi5v}v_iwh zV&J@PwM6yK>*crLa4f{VIk#JS#3W1{G3TARTXh8T9~b?^dg#%Ca+Sw*1njrT(Y_JW z2Gbvr3eW0@IIokxs}VaddOfWp;=E3Ny^etM_V^iU@4N~2n2vz+y7(^o$643+Tf6^; z5z!tx?~A)s&hL0y>)KJsVItf&L&5sC=)Ez_`}G=@{te;fseZ zY2SE4d+dEe9F}SiowvT3c8VI1gL4)d#(E4GmZ!#bk zj=%H1?(dF40VYLxmpE9C;o^Dj)${xs=qP8X?B=9{O+*`bO#^b?-!1(X^a!Y^QTz=b zB66w9TvwJ^YK;mlI9Vg+Uv9be>Ll^1yi{eM%JYvDhh6uGebNykJMZ#NXNVmCQ_d^{Oof84Ij~*)a&ilM)UnKUUYCokN zDpLD)?LbiF{V(DD9|1V8=7w;5ImE3x!~@!(^PX+mQPSW*tMtg5C2~~d_)L+D9~S-X z3;-isq7N{OibQtaGo5eW|6$;~>-i~XG2~XQFo+?d+^g~xob`~?RQ?Bh1UabkQ`$hg z$|dXo^o1&K;H-!2b?iet?@>^z1v%=_d7t&rOGIC59S`|9@G^tdePF`HAjbLtYd;9A zbFA7ssbrX`p7otnFsCGo>?A+w#^1Q8caoi4s&YQA3HD8xU-#Pqd`}wOsu4TMcK&># z=$))bm#LhJiI1yH@AIgB1TN@ zuh5Zj5;wiJQsQtDJ$>7rym)hYsnjnxMfCAU+H%Mkwj02gLpb-jy`};1MKEqZ(6eP! z<*U@*Nwf3*8gW>^MC=DOpuWRI-g}bhoy<|6;V;1URKfklK6t;=h@8w$Lpr;iJUw;V zfRhpHWIG^SWp%C{c(6Eha_hwG5yhRhNCLP;5ACoX+P^Or`zqKO*6nFYi$ibQM>k?P z*|YwxSB&A?#l`t&uu8<}Lsf2>Iz9PrD!28kL|@$^a;J`2b*so7deHh+PFy7R#Uh7{ zX?2*d277&C(D@U?-ZG9B*~$9xPMXM0@`@`?5ZTFI@llS*PCAc)<3)DTcf7qqWXVE; zGiVA&k3Af}LmKR-gM{$D3;CJHi9DzmcCMzi5*PYLl`mN*`uc9Ezm58N;e9uTG}ecF zN|)%lsNTwXSPqaf&)~rVF}UF=F{q_M0`3Ibz$d(fK^{~2Uv@ya#;UxQ^`S4)03Kfo z867IrL%8vLd>44b2@#JDDaPt5B{XCJK1WV~!A-9UwxCg4elmF;T zwRaLOoe3K@;N(xbQ0<+3NH1UxP>&ZR?Dma=HNu=Q-P?Z#u??ETwrFfo2X(iI>|Z7h zGFAUi^q3B#;?8(c<#Dy=LTc1I$$+@r66M@lt4%VR+m@YlL(e+) zH%LG*75f`SPSSv#1WjD8kp8lNEc&(D;g*mV{7f4dR2hHZw=2ZgszdaEjX2vYI4x(& zAzV3T%3M5eru+STpwmS%2!8@pY%1e z^dUuv*n^WBg5O^%nrPfGE=r5Td`#S z&_8j#oN2Rjj?dL|Msq!`VKeQ&m}Ac$Jj~Q@!}WKjJdip|zTDV|Ytl@E17{l;H_n+3 zznWveKb}%%*8lUW+47cE)&~b*@v=>G0{Pc`>zRXbYkq!Cc?>7aOnw}LI4zdb-N!~U5Ma?%_e6%i`Eq`ZLZ6slsb9nw>eLFpArFmZW57U$WkO% zUHjScB;P!wR`$-$w*1$5IaM3KcfECP#{9tlm|OX$-I1%3R-0Bd=Dzs;WoCjE-p_pO z-l}&OSiVBD+4@W8y#1{YpY!Z*efyfEJnNk2J&UYgKR9oJ_1kNbzGYp~GHtM&H>C0Gwl%s)&PM7wm`yv5c_ z*Cj2su01Vzu{F7E-ZCrW`lKIP>uTm7ZUy^1hgko(KIu^F)xGWo*6}}ptEc-A?fq@@ zjl>5Gy05t@X~lf&q}SjedaGDGuIyFckp zdbhzT*>(1U)c9kL$<47o&riO5vF{_h6(b`~2w75H=f3off}bx7KEZzyB-OQ}b5dOg z`|ZEI8U?FN!zG!f?Nrys>*7Otf+y8CG}X11|B;TdmArxf%t>{fVz3aKON}yyU~}$< z1!+un?OWmgi>Q0_RLy?V&a!5dAOYh*F0Ts{k$mo zyKZ~;D&uJ@yfkrR_xH|CzQ|=YrzCo;qK^4)JlGmdx853B8|_Q zx8&)n^XP|xEi_Kg>&xB z4RWwp*JQ?rmy44RE~>`(G?_-%XGk-?@h5>8(~YxV_oAN8bh9l`Y2cv4{MvUkmUtl&v;Dfi{NQ!1GV`!jf?_a<0=j%Xg95mg0h z)FYxX^f>XZGh^Ga&m*=Kk>aOTUB}~SRh%S8Yi`u{&FP8dy(cBTJNJU*#3ae&?n>y+ zzasft?*C8ab6?hd*0ss+nBD8HO^%xXKTGI-z;(&RNW6Z{b;)-!tNNasl3ygB+myU} zzi$$v1Xs^;BrZsj%y0T#BG~?K1D7ehl^6O(diBJ)NAA*6%_F{gpdcyW_y;BdZI@ zOOYoZv^t9umnVtW#kgQmKH%8H&_aaYY*9 z+<`~tl$m5KV|b^^syZ(&#m7_nU5pNU^T>3dc@LRl`^@*tqIbCRG zPnn(85zi$blqZ9;WXHQR1_-xgduVyZrn_%s=^o3RgP3Xs?pUzYI;c1KD*H}6*1e@S z`8CtZ`F*m_j9MFSNn9EFV$N{ffZ@QqZ%_HqP71pE-|8_8__@+OZ(<18J+^7z2{9b| z?u4i5o$$v6)@#qz)^V~&& z?OUv!-%VV+6rZDcQrT8Qfelsa; z|HWZm^~4(n{Yh7%^{Y3NE?aoR!Nxt$yNunwc%yhrg0=7&SL*&LJ2KGwB=lB#ZxmaD zr?^v#zlZT~`d@+bBDgKE-Hra^j|l%`|GDM|SGI`GmvMRCg>l6}w4!&;&kMN`-Qk97 z$GUk&7e==W-=a*djW@U+0fk}9fbPUgCv14PVLX+D zfRDu7y&3@>iH`jHfR6fKBKqG3WK}(%+;0_r?D8z)CED2Uo7+>pwegO`Z!N_7Be=L6 z%=Jg`&wl#0R=kXlbCVY)djCzOGf)ZBik0H$aEkf_J!{ z82isPtMLrCI(pkql)Zp5ISC#s_UmMy6|8clxR&Cbg%^|G zw!#IAJb}eHO6R+bu8OUJr}5?Xb-Qpn@lY0C)Y2*FS+NZS;6%Q@e4mkH~8G*cEekWb@6_2M=ZT@f1~SfNr9=G@MJWG zb9$w@bhi&TnJ-<3oWGw@9K$E0FXEQ%c=%T1o@KZ3*1OEU2(`Z(Snd8#WA#aKfvM#< zQQcS*C=qM_oQjh$>9DoiPsX}G7d>hH4BtPc6yP+kPcpjp;N-O$KS)kZy!E&Te}*>? zy4G3mqn(AJxU}7ueZ9B10M07DwvO<|J>y#QdG~wXxOCGx;rxW{!0P&Ujn({YuoR!>mLksjx4(&V>!EA6pNKdw zcrt9q*^dS?@Fmv5xF1+pKk0kc7w5$7ZR{iVs5`H zu(|?aPT@{-(%pgBKirsn814sfHU1vLDF}g0+xGw3`&4L? zvHeH56C4+3;Nux??`9x*s-CAJ4&yD3-Gpwx};}%*)KXQ3I&Sz$y;Ty6Bd*>Z$ zo$*QX#a7jo_;zdcmXrg;lD9YwemhqPU$ZFGo9wrdTj?Xu80{Ct1UUP=r{pKF5UA5orRi1DKoK8O6I%GOIeewUg)|1h_b~^cG zSA3(h*pr!IUAH&+_|PhIS(>{w&TKHf&2eU%ncW^|4w-z4#aBo6w)a8XX2Q0?Lr070 zZHY5GOunqO&t|W=$f$=@Z+aSBMiY#r?p|}DQG&0~JQJo--fI`4FN=&}><*jW$@wm0 z%tX00y*D{4^xz^hG1FadnmJXDZ`(Qy>#*q=av2RK-}S;~k4Qe!MSIJQ;&fYHU=-ks zKyRyQl$1I)|6Q2}uCS_SgV}~2z|Vn3Gki9io~Oc|1?EZ&b(`rPNH99ge*9?^1`CZ$ z{IS_Rj3?P1mxV6o9fZ;}V-WQRUGAQEW7stW7aj53?}|_De%%y8n@dHAFr4&Z@)Qe zJ)G!SX=NYpa$Eh09&cj&%c8Hdc0sYOK3T+S08wC2WXn{u>3^foQ+*YllDW0nOA;ie zEO5k6ZpLyUDz)3|Wh4gi*CTEP)|0m*{l7|KYuBH#>W)`*x%jlXaz-45+D;IEnc&uD z-E+YH%M#=9hY4fO)BfPeUoZeO1|)E8gl=Egrs&eTU#-}Z0x zgR1x8k5b%n624iziE>o+iN!vO_MM^DeO>(Q4veaP z;#kqgV@}VX(ZP&}jDM=bsThi)#GR5+^0*%DwPJ)}rqHQZrQQ zz|o!=T&-FcREqVaEH#t&eG#$74_BTkRWa4t^-AKh#OiAt*Ib;7Njtf9Vy4Y>-Jz~C zt`q&>oN-HeP0C}}3vox8)salqkMTN)EntsJDB}hZ$L0)UzM6Y)5`7xN!O-lRRu!se z>R)czbLuy!zVt_;_s!9_iT)~sS(Lf8%`xav2hPgat+1IHz@X|QH?y`}>fmBufTOC< z+a&rN>z19kcc|2AaS0}ONOYZYCy7|*{QB>fIC_gOpZur!QHvgDK_O<4BYI-A9^!d|;4L#0l{F!Lf;s?8K_5$hCd zPx_A@DRZC_4EWG7r9dY&kD>Jlo0`1Qnuu6?|XsaC>zKYs-J67#cNf*xI%jMh( z@NZ`22CZD6`b>M_B)3Xl`w`&qx(2(hQIE+w^Zaem%AQ8i%Suj(K7Or*P1b>0+x}A# zYtin^%TPpXr#Fe7&x$j>kEuR-pS_@#iAN4Bd1W>Tnyppu#VtF=_@h)Fw}kb;%>H>* zU)?NvSuN15Qi^@?I=n%}5!lR}{XVT+@qkoLw;smqKfK{V5p(BsC{NA%T0}426NgnC zupa(nV(O}&i?Rxs8E8-|r~JZNjzykUJtSrfX{K3~n#H$T7jrGBN5o9ez-blZt%qM& zlxq3*V4XHyCyQ&}a0^-w{{?GQW!qgzY9yzGUq=VlF5lj`dq zxAtsFI4H5C!TI0rQi@z2L50rP~GEwy8EHU;Q5ixDgUy+)@(;b@!*XEg%rCnQa zeszec#ufB_C)Q2Ynkp>*rIJ@}NzN}OR-@^r0|tRR*+y-&REb_CHY z6?3hJ-@sL0rDADhWS?kWgv-?L#kAPkbt^80`y?(vmN-0H%zSR2E!}=2-G+Y0)W*Iy>=TdS!iXvrSf9L)Y1${I zILjN*b(M571yoFtW#&}El4xAhY?i63s?WaQC)R2)7AyM``n*rFpO+eDiLNH8<7(Bq z9jZ>X?wR-|GF_B$U{>A4p?%`cp>NtrlNB5L>=hf`FG<(j)urAHaBoJKslgY>;*y5Vw=8cyJqNfDeO{m|(5wThz*MO{71#SZ?LDma`tXB-OULnYKazP89%(Akv(}98%5SQ#M54Zyy!O230 zLm>4%AoZOf^M7#i~zNeHyq8_Nm~*pa*0?2KZ12JF$pFGzxMkM?encAjqNY z138qfAOmU!+4CBZ0abwvC?DJo=7DT57i%a`86fNXK-NzKS>Fw^emuzf)4!MclOXGlhp(_v+Uk$Q;5M=#QkoAi})-MEEKa`Ih zI>-d+ARVLwA4mrtkPdj?qXPq^gBb3SbT9$3{xHbNIS8`;0La-M0b9W?@FB1rtJI)S%k#B77v$NH3G!@61?edEtkj_nDdk@6=`$gOt;i**K?de>kV*Vb1{;bhd%+qw>=F9gK$8vOYQi3MD7_%-Pvc}~ z{V|YpW>n~}1GjQ1#|Tbp8ult{KsLbF;6Q&dh@@;;`QS|`4+#BkEg!_ifc67Ie?LgO zKB2!8JOlN)ie?@C3jNvObcn}{J7DJnNGA!yRuFgOta6aYwiIL!vcaoCuQJ+fr!XxX z5&9$GRnXUiw66l$u~LX81nH(F>F)1K^_7wAWOT+ zyM@Cb%X`4XQLhWk0NYhxq3pxeh9grA@+>O>>8J57DbEjKheO8IFX^Zhq(Po?5GOVD zLGWgrC8ff$5-^DJV(~eq^&@+yOn8 z0bP!O28I3s@CFQdzp$(pT)>Ee*omQH><+oqb|~|~KcU#z@i41nnd7(kBDpA9mAOkr6n7z)F18kZ$HngrQUD@aETARX0#bQBi)Ye71y5tfyM z^uyEz^b-K-Cmp08=0%_%z70-4@j^c?haub1^li4ovN4d32EmU}u^(i`2*`@PLVpj) z0QkB&Sk?lL!@dcmeFI4QdZE7#qERH_9!9_v)NfX~UKv(afxJ1z!iKRwI0o`=+7Gg$O(EbdQK3Hyvd1IB zvUZS;xc(;{HG+L`R0GmcrLqL1!y=IW3WfdxkpA+8Wn3;&Hexk@JkXaE^NJ&B6`I_xk2 zF5d~3m4FPO2n?cP0mue?O0RMhw@Q`|D0{(|Vc!liZEiitLsX+I2N^&q$N);f5O(}r zrjr2_3ClbnkGUJW|+jfpgVO1|9_&I9Jsqo0N6R z>Kpa+DbfNj$jG>g<^pgSWPlNHH`oC(z-EvEHi8VWLFlgs8DO2TtN>(yDIf#zfb`GR zH95k=A?z?xds$5|0-1(fRgK;29^ghFsznI`g1@A zmMttBzFqP%5mK6DG!6`;2_AM>eKQLFa-^_sNAHiR+fXb52{=OGT=OrNw2xe zCgtgCNj(3LV28)39b``$L5@f@$UwO4CZ7jy?Edt4@M8&DwB}?@gUwS%o@E$ z{6>_WO8z;22ptH5{8Mt4n>%0+o-WAzN;&`YKn9QwUV!qnt3{r^N}gheLH4u-WWaUc zUtw1Ro(s7GyZ|f!&jUxlC(jQxAUi;Y4nYs9wSa`|uLL%q z4y#Nu;2MwtSAp-Kej#`@-jAN$)WFSGX2yzj~ z1|}{QMwM;KT9A&?LDoyta<9tMxK6WvMA@lqRhBD5#k!NP^eIQn)Pb^2IZ`U+1Ikuq zl`>zMqfA%Ez9aQ|!D$5EAjDFRswe^XK#`|%x-vyMe6cua0_mV!836Y{&H<RF%2- zWhLa+Z%cWjvRIi3PGkOM4V1_b_JN!O6(B#!lz{xCQUsRc%2g;VD*$UC=L^fYD(2;| z%L5BA*>Z*cY>*#RGC=xmy~r?LMWBt~AHZtxPa*7NV~58u17r_8U?Iw5IHxF&fjk?A zKwkO!wY*2!u54D;EAv46Gz8gj8u$+A0eJ-+$GK@g|2JcY0W^YV!JwWB$U$Y1GF{2l zHmQ#lOHanY3n34H^wS4&1baY^U?<2C>=2eQ_a#TLtr+8vow8OGa0pvK4q>y<-w1LD z>p}LQ7Ua+sfD9m8nGUkyH1G`Qr*RHbA62#~3zd1wwDU3kH1ude1SdP?PGz;S9DI%q zgPeRR%ARv&>BbIl1M200iC`+|2FGwNlLO!xU=PUp^~(GZb{Kgc$c8hO6B|SxP&O*V z$|@z7`(!=0vg;g?o0a*>9A)fm(T^!Z!@AR=bu7O>K%Bj@T!PuGjwH*{gpzRPml@3)d0Z)OPud+|+RZe2c`=O75_Q)vP!90{V zsa&coRC-BP;5ow{O`a|s16e+->{m7@Yr!Jut3b|yA}!}KpRAXyoXi(_6g-FiK-%|c zd3(Oz|C_a-R9OhJLI7k1AIJbxR3ERL#PMQ-J<4{Fb}iuV85qd=F&rP3kAt)yQ4Xxt z^S@CG!fIFz(m{!q7btU;9_2U=sO?bM3bH{SXqJ!SxTb(Z;Ik;t2U(t@jD>KZSuh52 z_D7XN;0usDmGvMUS1LUq>rG=~kmJe`Wv7zMe^MV-Rwzr9p)}p`fIHE^G-eVT>HvGe zI+e@8@550JNQddlNz5?HL&`p7A^0`w2S8pK2TzvMw;!Z^7np)wd;L!qRHC346$(M# zoiaf_(+!^_6$e4u^@6CF)uM7W$a=*f>*au~7hNs&hC$ZrS2ieXZQ}f|)Phtc(@3(y zfU*?j%15s)L<26EO{fV^o1LG&Su%Y!nI3}tFacihUMEcskt4W19fe9(SA01;zW4rn{j za-Wuaw0tmA^t~YKb%CtMWkUZ7R;s>K8S?2)>`3WRpRyI?MWh%&_9X(ta0Y8A;4)R;AI*<-) zLByU_2{Mp8Wv0?lj%COf6>VTK?5aW5uLSM;f2kG}+68c=OjE`y#}1Q*I+Uf#LZwgX zR+b+sAH$14_Ba4C&}`-8w;;1%06Xk)v$7gwk4r#4R_B0>I1{9OnlhR$a*MJFq+Jz= z&SzDCh&?L^)}x%uf!YCpTk!auNf|q3DXM5WL}qUo;ad7 z0nu0H3CmJI2H*u5!0;+*xL?_+Oj(8VpA8lHB=Q1~5#}nVSBlIuuq+=@wkm6t0i{nl ze2_M%Y*1DzV=F{I#>7N)%!NG37G;By3wg4vLgH20oH% z7~ckmKpd6AL7|`d5vlJ2FW~v#fSvUys09&xVU5rq0BM-3%mDcpj?QKBr<6j_OFhWH zkd!W!0E$7@%TuN+yOv0Ki?Yxr?*Hl7S%eCcDe|RJ1mu@Q0T2_lFi+_Bffu7Z6~x3Y zL^=`DKfG8R_ksz~SAm?=d0;Z=2CoK(_7}fJApI7Sn1B8p?9g#0h>2gAA@rw#yo)iT zB8PGUARSbL{A+d)gBy9%yZF;rJ%$Lm(np}wUpFPZ3 zDEH&(1wt+W%7(ZADD{QPZ17z;ob-r(9HhQo$%Q|uuLL<6%Rx+=tWuEv3PIXuf(*#( z!T7U6ELkc{fS4~?V<0OGgRIa4vO)_;`#NP9WJ5I|?Msve%3NhONIT}eroYJ~d5{?l zVaGs0L|G2r2{|9+5P87{Fq(*p7~&D3e+Z<$8l-)Ol1q#7F4+dMUX!v!%X3xEP=@%j z>^taDJje*A=i_YP2!QqofQ+~k7>T zB}YMf$o*!^|Z|Is9&r!#rvVBTByI$?_Uyg)$pt zcQQbZX1dT`a+IT)2APKecY<%09L4YzdZ1u`NXYh*qwG%<=i7I|2zWi%3}QOjOO95^ zlA|mxL^1n=%c4D_7r|0zsz}d`d$ZO_lP9vQ-&Y29<$;?)a33GKwb+ zI*uq?m0@L2X*J&v?<xE_m}4!(@_Q*Ci#y(hz4g9|u~szAHcXC`GY?7@Jsv=iKHS_l0Qt4rG2qC!uu z#dTIa6vMbVvwf=*CF=B*gj6pVBw%%};B8lP_a~%@-n$(M;!3bos=3+vem_>9wDsBB z-9D=or7pDgE3C#U?r{sZ-4~qg(dt#K9zNS6UAX@bXsiY|XuAg>o!y_bq{2$A#Tu`+ zYwv5zv#r5DBzV#&8n{Q&WeoQ~J3`Pli_|(j7{o%V_up&>$c0kHOZc_r84GJLee9M% z#Az>rx>*)M-E7&5pxUmu2&&X-#WJes=gk|iDkv(veslamb`(L}2pPr9B~tCGVJwVl zSG|8ze1^9fw=uTI`=YJR*c6|}al=Za2eZHj1^wWE(konX>|TLrk4v8}r(Fax?Pe~0 z%7KR7j)ylo>T9$G+yO$p=YElnbaL60Ua&g}vUX zZHo0yeO3|IJ4Nw9k0c#y1RZaMB>mhB2VBN?=`tjfKLniRw_VkYQ`L2QV zLHxHVqd5bsPG&^#-&po^Har|Nbj*BL^w`m3vC8Ce(c|Lddh;XsefjZmlc&c{pFTZ4 zuA`u{psN6DPi{9`wqq5}j_qdW_7Ha3wwvwSu^eYlr`g*Hd7#rAB>Ote{!Wy)y=t~| zWz_I%X5DMx%8M`r9J$m1WG6QPe#F#M@G@+k@iKQ)Iwg%9sSB2<2s`wUzt&^Gum;7tMd-5*4cK4tNjkFwpqW~ z)vy_Ic(bdH>x=eoc10+6ZFYrvSWs8*s;@`E^k!F>>yC!Exazh*j@7%SDR*sk^>Dq> zj;*fFt&j)rb`9MPx&Ln0z}=AR?{ziY3l7}t8YHKiT;cn`#`|1NWc_`v2D0NmSLc1G z*LJ_F{eH0J{*bGcJ0thIqW8mK@P5}2IrgY){82FasB4rQdCV1k3~c+QtNoW?^ht2C%N67LqXXTp!EUgx+tp7-dR%>6k94xf6(dJO&$`B* z#ZJ%juHNUtiC0{cuOOhYS6t)d@GGv7S6E@p6&nL1AGrEH0LOP@om$Ety81o@hd;zJ zwP4dnuI7($ZQ!=_D%=`zzB^kIuKf$~$e8`QlpJ{(9lq;GZ&SJP63FPlpxWR4J;+$Otq~swxNR-NlLZYX)!;Pk zkxv~g!7acZ!ZxY!Oo`Y#`GRh|TpUiQ!O^5V3l`qsGji~&fj@Sz-|BD@h|2~yRPKAuie)Ss?P-xX_>+fkwWdk$r^Eo%F8&!&^u{?{>6YHJ86La!GIwD^>?wy&}*I_s3FYlgi2S^EVv<$?Jok%wH+~oL9zo(H{cL z{=N8rMMua^^Mm${_jClDygr}lA#n2eTuK=ZoHxJ=X%D&fd2#qVZLm%oNM{eBcV0yQ zF%KDJ=S_6}u7`5?Ih_A=aSD5Yf*}-e%VdO*Gqizsbja<*LC~*J*~t?$tpOB7SR0@I zbcmhAKnE~jOv1rI(Ju)?<{|onv~R?#gSLnmyro0zWCrr-c{`^1J{@w+3}o;ZM|9{U z0{V-NsFV2T!ppQnJH>xgM>ykEyZ=brTrLK6YOpmZa#ZEz+G8j6&*urY-spcs95!hK zc`t~3rA|U86_AVm5TKLn=Z_jdN9a|lFrW=MDS75QL;RZPKi39owSk8=io<->FV_pr zpz1&7F-L<=&Yc{s?|o7Hy{aP|a+2;0szI?DJgGz6rVSKo1I`=%cj=HjId>vD+h!{ z;$&?(FaSL+wRQO9$_;-Ov6qlc9TIsH4$s06CQ`N*Y}OHo42Z!8+G8hCOT)3K04HHf z^zLIsZcw@IXpx&$K1`3X@kg;=rH9nX*|Kgm@BeVb7Q$WDfqBXk6V>hHr+-v2QSPS%xY@qvi}IcZZG^cZ{k zB%nBT;N(mBKzromNBI>#Ff+g+4e*B=pp)HX#qr`l^>^aWn}g%e1~awb@;GtOsSbXt z$J9w;a@xsaU(hWM&NxZrUX`=-5Jt6!uP|WvFV+qWZxlIGpEn9ZYTzW9xH>2XP7aD@ z&T2TQQwIn0kU;jTgJd~vjXu>c;xUJQMCC)YzFY05ROXu>+(OA5LR5%pg%*{oRK9hi z=mRQm(*}Yn^QV%m->PyAM+|bU%55AG$W1CgsvT$$Ib}P(e1BUb@??(a&s{6>(8(fiQ@y0?!x^?%4Weg> z0e@P>2AqU~zbp{hNg#MklE_Yiy$e^0>?G7nP`M>2{=SzedM8O<=b0k69RNMrH_p@o zC&%Sq^^jF+kK6T-@%=t-S1+f7h*5Km$QN9s4Wx?vIKFCQ2l!19x2sh4sGNY4g8B@v zK4D#Po)kDKIsd6Wh+ifSPCZ}r{L>4!SMY}&I&d~pBp!V)ATs~x#O<%@-&ytb z7Ojs&r0hTbQl$=@%%=AS#6c&9j@tqDA&3|)-xWEEZ`n8kPI|Wc?IDgBZ8}01&KG@) z%3rCzNoBK8{MTei`$i;F3Y@f_i`I$kqz!#jD>zv-@30%dB*aj1dr2GMtC`%EX@Fss zSE{{}Fl}&l;v=;PzgY=%e?E%-{9VKD|lg-yKRh6fshMAq8ihAqE)_iM%LZWGA`Yt?44y zKQ8*_43QgtB{B~k8*tL7J+w+>Cv95m!6N7Pi2ZL*fgC~wCnef8zdHD}7(8{D$WD&5 z`qM>rlDYlz5S6>c{$mZe?dKx@_z2NE3H(mNp=E&8cZz=ZX*mB^CWQChe5z4EdH644 z5LzR0v&z4_K;;NLhgvzVw0D7OwzbO_ur~$;|63QNpZ54-4L*CECvTD^L&%>3HvXcPwJzOCvSA|8N zwj?A4PEJiO2uy?MEoz_+y|CkUi^`a{_Vy>Wui7MXEx`J|n??SQMIuL3PE);;^Yr?V z8aP=?PgDaZ>1m_tovfufxMs5fC&lWeYF}`NH2AFA=WZ5xNCVDS{e>#0s~kE>3tF|p zFI4WjT^uaN{hAF9-6rzO8i13)bCb$W!cLgl_D&kruQVVhD{8UInnpEb@b@-sz@rU} ztHVl_A5*zihE?VJv6!_7wmkoJw|aG{vlBi{FC${+8mV8>@XJtu&t zkD6`oI(oKTHAjCXZf3TBy&W6-S)W|(IXYA{C-5wseKX_u3eTo9<=CmS<->4x&D8tn z$jfmu&D38uTRzQ5n$yuY<}|oyj>9u%J1}YztY;3vQqKRFGX*+vmd|YX#W^y6%QjP= zFsI|=xX{kj8&}PiTX1iip+C;}bk1esAUwa!Ffbm#f&9NJI~VAvs&kLeOkR`888VN& zUkn%!lwckZ12Pz15;SN)TtTshXCCgwyon%smcJ@iv;Rzg?czB%ID&$Q`X zuAirL{juF`Uo4Ie#%7C8Znx)&Q{TqcGk2O8^@x@kKfmglCG!`HX^&_>66LqsKQaq9 zcRe~iM-<*_pRMnERQn{RtGDBN=~P_liL0{D)yqG2X4~~$o3!E-;n}6#BVJ6k`^6_= z?3Ig?F*5Pm>x*himo9Hw_Sk*c&o_JvGS7syz=iaeuh9Z7%f3d;`wR~W1md(|BD}&L zulMZIdR^ku8zc8!My@83-_WYg@4Fm+1G`5w?a{W_|Ihm_>)3aBm|ZlLC;dkB>e@Ha z+bz$MJ21=X)qTCaNfg{*zg^t7z&=|fSGYU>KYKAxfAMAQhZxcI1U4q_U76r_Oq?Xn z&2i^rL*nGCvCG{RuVL3=Y`pB}7R;B3^{;88#NQ!e3+A5Jv|A#!U_SkighWx?tDzw) zVBXVtNq#z@m-UPJ2GQq=CNY?wzxd; zr`NSKF}K;3EykX6rgE?~F?BzxEjXZEX>7(EI-s%4iC@K~iJb>v{_x!i+2YUv?E$fH z6?Uz~#>^gW%-qcFm>)5AU0!}jn}l7L1LBLP;yn6+L)y)D;kg^F7*ecF37x?cAIrXNeSbY~=j3o%=KU#Z>%4=jMf6W9`M_S?t>k ze1BLQDeeooQZ8CnGk#@j%XsYE%w3zy0_T&tYx6`=biXGfcEY6638LXGZHeEB&l9FG zF$N(U`!8?C2O1MI9AF_n7hb?K7dJbKbId$V73wWEY>wf33%5v45pw(G9;lfL4cLxN zG`3$JQ2J{6MRJzn*U;^k%O=V^+g%21wPl8Hwf%CB%H~67OvjMT)-WnH-`E1W$CWsO z0Y^*$^|9-jY?yiQ9dZTV8|@c?g^oaw;k&hP#TMa%%694o`A$h&buG%`&pv$rVbHrw z)9{-=TwdbW|4WVSmwWoPjZR}n(+Y=J|B*J_K1EEeOU%*_ex&WU8$z1i{THplfjvuOB5jB3-_iODN8{@+BPsP|)JEgxg z0T-HcL|b{+z!KZW$7VSfi(wyYSI8Z3UD3AFb9%AZ>#W5#X^FPxIv!V-zXV?suwmtV z(OH+cP=qJ@GCclrsIS~VX(#p-{gm5eZap5CA_~i$m2%%YQ5M7o=+))U91l0~Gz3|THu9%yD&BeQ_t98rw<3*VP{UE znqKBw6Hf_d0{%2M(gKygGr^FD=GUwId@+ri@MOffGm2W3?ijwcF?FIrkv@;B{6;KU zFyRH>c=5Sd9p_G;ep==632~-XD2uAC_MBDuIrxIZR0zkB`Q=68ojCVYFC)a54&nGN7uUWZz*8$dp)vFg~X@*6pqk;V;af1k?VgzrpD6GSt%1aT^9rtyAk zd?RK`!AsroPG}`z^N{J-b;6SPhG2h;oanx zB;TZMNw^ZNyc98F-FYb>PP~D4SX&kHb=qUMD)9{37uoM)s$ZS>`p=}v!uD+O?VE|i zol1!gt#YQ%If%b;m8QWVc=vjfQsZ3kee+7vj5o2L zmloiS@|GnhQfL#zE%+yFt70uU1#&fr`+LDma57j9P6EroGLUX)X9HRO6q-!ClOXLF zWl6i;Anh1!Nq?On?KXh0!@GrXK*I$f4J$z!mVq>!0Mg+&kalM=a<0R@7%O*bdTBHAn{w3!SE87eP8$ous3}l1a0uYzrpfZ4W zi-0zeJz*^2TyQ7Y2)2W_feRGNz!2nokmbI{orvrK+rXV5{cHmLV5^cV!D}Fo1D^u5 zJb2*{?ZvH*1$#gi>;_q|P00&D7MuXGU<$}`ojGb~6?bPF<#&J^kiQvx5ZnOLe>=Dp zYyzi)%NSdT(^=VQFjx!z0vXldHn0kuiTp}~!3ywp$TJNF8G}eiQ@~Nsml+Heg7nj! zWi(_1=s{b_X4rKoIm81ff(%w%j{*!<+yXX%>_G*Hc~v>nV6aT(7lLb%pRdTzCoEwV z3}zbd7H4qda+Eu5Fn9_S_~*?@gNqJ=H8_g}`*83p6x;){K##%TZjc3b8eG%~atJ#> z%)^3JAR9VGah&3r3`4&U< zKnJV9C&6VP?G}KXBZW#oPU&;NpF*#JoGYiXPSXwU1%D0h0DlR#g7Y!|WK<+afKibg zfhvQ+N>E?~Dhw`Slq4OT#rL84;3>skkPX}mvSB+Fmnkk(WHcr7J&H~+#DaY(M#f&n zHpOL%3l$k%DII{7DCbl>i($A9@@eo|unMHz7{x)KF%p}=A3@&%vcq8?#-BZIg8~e$ z0@>qMgNrIaHn1F|!$Ktolpg-$1@=t=*rPIbfSf#=Ku(@kkS#3-dG5!7tbkFN3&7JL^~XRCX+O9eJ?8Q;80-T% zq6ZBw+61ye8$is@f>0X{*yC!&3PnxvtcGV@klzh11~-AMh+&+|QBjA%U^~c)!Uh*H ztdkWntP}H0hIO)@gGokxdkltTSSB;L!p@<54}Wp6;9-#ZeTv;o}9c91JEGJBOwLJsZ$e*$}kd(v+c8NxxWQsjd-LFS+89Dxjw6?~mwRB#ex z1tcpNP%_C1j)SaV2gvf>Aj_`;*MN;6%abf$4~D4V0UgwWbT9^_g8;}+E5?tq$BZ8( z`xF^JN||vo{IKc(`C-*=P{xn)!>SE37!^L+3_Zd|vt^4IKMIA6AAJap${{~&6R+ML zo91}bCZ@g?pBCB#Jp)Mjsm1`(t3f{TvjJoPsq8Vx28Tg5i~*$7Gk}yb14u~*kdh1_ zWy2Xj%7!z56qEs^Z1_RQY`6>{oo)n>E@lIEAcF-NK#Bq~fD{ZefRqIpK+1v)9OVd; z>~UlODLc#n(ud%O0i+v229R#FiEDe~NBg=V55yQ9W&|nfX9TINpAn?2zX!Yuei=au z$_UbBkas`^qasLKppT3og)AdTS$`w!S^rE)an7utA*AdtLr77-3?Y?YDirtD_5@3F#A|ocZK^_CnL_6~h$`DeHXAWeJCn6`$hmdkW zBST0zpiUUFLjyLsi$p;$_;bjcLCh$Ika9-ly^a}0eI@klP!7l$m4k*qjwsC?@I#g> zvVo;|PK^zm2nN6+FasnJlRa%|_0cWz7@=eLBWH6e2yT~nbq#N3%XPha(L)s|caU_1X z7@6y?2=$cgDRDY>mh3J;v{1(v%8E5gEp==0mnmK^q?^PbFOmOtgJv(MeImVF3?kF%C=wJsN7=LqNbh7G!xe)rL8p3`xWJgs6=Bm&e>;Wn?mxY!YJ+hVK z4#f1F*#RZrZ8liRZu$?wLC<)jK&dh?S4nGCh34YYr>aNhDrqO}W3d>3TOU(08vyw& zb&3wDe6PwsOBo|#u0~!2IYfoIwD`8Fz+6fkn}Grt_NSGDS5OI`5$?r3mx+(&Qa+~S z?P>%9YQ%0*vblIz;D0dpz)`{9Pn zbIo+GX2|BcG9S5N1?IBz-(&^7wwWUg`$?+&L4KAawdbnxAX3N{hjT~=0oB6)!HU6f z45|75h1q$AybP0v=>YDO%s2nX@T9B&Z!$!~n6hLA*n%y=kolr1+MEA}d036m7+D|A zKZ6(90Ir!ZedaRcG0-!uh9l-{D*r`{6y;ha{~hO>a$G_F&kh$vYV*nHq0%i-=-P) z=Gwjs-wtTM`C_Af+w*Wlg}G|ZilxKRhJ!4XZ?4QApQBEhM-A~UkohQ| z`G2qg8bR6oztX!ZztV^E&lH16MFak^$HYHVDVzT+!=aS>P%+ahDxa^OV447#HfG8n z5iPI5DU8f-#vG8cs^9jHAq76iERHPj~Zq=OV(UXk)+Bcf$qZjt$YxN$|wcVUDh<)D#o)o*JVGKAxYRCpZcHqt@Y z5c#21ONWfuiXrx+hRA%$Mx_5F zc6jc2f3_=!7`!p0#eW{sz-Ms%Mmo$*6qDEZ@-Dk=h#x!`wz8q6rm1DctxK0R)bPoW zJ6cylUb>=XY3ph+t;n6?G!=Saw5!M=t{dyh5lz$Lri#K#+`ksX`kXc5H-AYOE~Z@S z-Xk*G?7803+lD%N_E2oTX!<5TTO7I9o~ln7=YA?y?77FD=PN4xms*?NbB+;Lk4_q+ zk1KNjHBCMp6fUv5#fCnoM{N6V=ld6WCMbDM<1Cr3y}qnw&h@jpruB>x1B1?U;)glz z(R}pBg9lNLY>6unPj}<_pwr(u@kr0~q#W@|wZq4Ud;H?r)Mh@FBD~MLvc%ZC z5rh|Aw29M+TMb(!HE>ZtwLV@^nMpBx1=Nk9kl%jv>`>eb8%uKH6 zS&N;{bH}@r#Lp|;^)MXT?^#Rv2+mRA{%69lOg;`%c>ZaafcZGg1hN0Ms9UUweBd*nT_r)cbxPrwibIc^=(hO zTN2MJvh?Cz?mAbb;<@MDH@PC5WawjGbr&ZnlR%3%Ltk~s{nuo}q(q$fgEwQ{>i6CH z>i37;e8egk_M?T{li!JpEI6<$`Q4aEMgP;uYh%nZM;D+>^RvlU4?Ay?ssH)4p*36h%67I|9C z0qJQ#EvuJ!K1_-#)V#v8J|RkzdAG-BY2<{exF0V^GHs-$XT9fnbw-MfNf&;?bAG<8 zS%ERB+vWL>w5U>JdOb1Geku-oE|Qa%Gf{f(c*paQ)vXiu+zh?wq~~>OCe|HtXXxIq zJU{c8p1rR^vp>fBd2ECxUGH;xx93J^GV}*WdtKJ>$WqT<>iyh2vHf3QcwQ>@{@9$H zQZX&){fRXhXw?S8t=P~D4X)d-WgWiu*uN3-R8Z=n)XsN zsm1%_M59!Ru!j=T_1=5EE7jRjnyPhPziN-tgdg=Dh%Qz8xc7kaENw75cX*4{2$qOv zL#_;c<8$6$srD$%yuIE}EsdQ(PUR$a2xH-GSct9n`@>R!#mnK)JKe_o3BO zS!!#E?^1OxWP7sp)3bg5P;*y#9=y)?e5$&x&Sd*D^*0)PS>^=~MV9Vp^=-38r+GB4 z@BKgVEw<{GnrZ8N|7DF%^E+tYyoY@)zDU0(p7edIZiXcSCb%;{?YqS~ebPylun1SU_3{_i@z2xnla3!AXX=lCp5>Brjx|ZYz5kQ7 zJ>M+DYi(UPCtDBaFP2ye4JSS#-3};KbDf$ z?beuFzdB7y(?2Y?Yl&G(UP5bHa^G2QYTy|+tbyF0U1e%U@N3J6i}Oa_YHFlS-NSj` P7MdDqv-;J%8?XIe*C;+J delta 184020 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�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� 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 delta 55109 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

    BiYxpU5mR-1Om0DCLUH`{-brwi%3MOuA9XHQu z9ak}+)V&a;F2081itPc3Qddj%x{A5&;!yIA8@{N}3xkPy#ZCBK#RPLU)GLU)5-VR^ xCsovw?Pm{GzPrgZ%_r=ie^{A+cYH+ll=&48#78{-)cky3e8d~a=fAb|e*rh-qDlY& diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.h index 9ba7c4bd1..00eda9e23 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.h +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.h @@ -118,8 +118,8 @@ bool config_needs_dump(const config_object* conf); /// Struct containing a list of C strings. Typically where this is returned by this API it must be /// freed (via `free()`) when done with it. typedef struct config_string_list { - char** value; // array of null-terminated C strings - size_t len; // length of `value` + char** value; // array of null-terminated C strings + size_t len; // length of `value` } config_string_list; /// Obtains the current active hashes. Note that this will be empty if the current hash is unknown diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/community.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/community.h index e21f6c018..9530af967 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/community.h +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/community.h @@ -31,7 +31,11 @@ bool community_parse_full_url( // may be NULL in which case it is not set (typically both pubkey arguments would be null for cases // where you don't care at all about the pubkey). bool community_parse_partial_url( - const char* full_url, char* base_url, char* room_token, unsigned char* pubkey, bool* has_pubkey); + const char* full_url, + char* base_url, + char* room_token, + unsigned char* pubkey, + bool* has_pubkey); // Produces a standard full URL from a given base_url (c string), room token (c string), and pubkey // (fixed-length 32 byte buffer). The full URL is written to `full_url`, which must be at least diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.h index 7a9e800a1..9a49320b2 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.h +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.h @@ -56,6 +56,12 @@ int user_profile_get_nts_priority(const config_object* conf); // Sets the current note-to-self priority level. Should be >= 0 (negatives will be set to 0). void user_profile_set_nts_priority(config_object* conf, int priority); +// Gets the current note-to-self priority level. Will always be >= 0. +bool user_profile_get_nts_hidden(const config_object* conf); + +// Sets the current note-to-self priority level. Should be >= 0 (negatives will be set to 0). +void user_profile_set_nts_hidden(config_object* conf, bool hidden); + #ifdef __cplusplus } // extern "C" #endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.hpp index f19377119..57e2badc0 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.hpp +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.hpp @@ -16,6 +16,8 @@ namespace session::config { /// q - user profile decryption key (binary) /// + - the priority value for the "Note to Self" pseudo-conversation (higher = higher in the /// conversation list). Omitted when 0. +/// h - the "hidden" value for the "Note to Self" pseudo-conversation (true = hide). Omitted when +/// false. class UserProfile final : public ConfigBase { @@ -55,11 +57,18 @@ class UserProfile final : public ConfigBase { void set_profile_pic(std::string_view url, ustring_view key); void set_profile_pic(profile_pic pic); - /// Gets/sets the Note-to-self conversation priority. Will always be >= 0. + /// Gets the Note-to-self conversation priority. Will always be >= 0. int get_nts_priority() const; /// Sets the Note-to-self conversation priority. Should be >= 0 (negatives will be set to 0). void set_nts_priority(int priority); + + /// Gets the Note-to-self hidden flag; true means the Note-to-self "conversation" should be + /// hidden from the conversation list. + bool get_nts_hidden() const; + + /// Sets or clears the `hidden` flag that hides the Note-to-self from the conversation list. + void set_nts_hidden(bool hidden); }; } // namespace session::config diff --git a/SessionMessagingKit/Messages/Message.swift b/SessionMessagingKit/Messages/Message.swift index 4c6333c45..88cfd0ce5 100644 --- a/SessionMessagingKit/Messages/Message.swift +++ b/SessionMessagingKit/Messages/Message.swift @@ -8,12 +8,10 @@ import SessionUtilitiesKit /// Abstract base class for `VisibleMessage` and `ControlMessage`. public class Message: Codable { public var id: String? - public var threadId: String? public var sentTimestamp: UInt64? public var receivedTimestamp: UInt64? public var recipient: String? public var sender: String? - public var groupPublicKey: String? public var openGroupServerMessageId: UInt64? public var serverHash: String? @@ -34,7 +32,6 @@ public class Message: Codable { public init( id: String? = nil, - threadId: String? = nil, sentTimestamp: UInt64? = nil, receivedTimestamp: UInt64? = nil, recipient: String? = nil, @@ -44,12 +41,10 @@ public class Message: Codable { serverHash: String? = nil ) { self.id = id - self.threadId = threadId self.sentTimestamp = sentTimestamp self.receivedTimestamp = receivedTimestamp self.recipient = recipient self.sender = sender - self.groupPublicKey = groupPublicKey self.openGroupServerMessageId = openGroupServerMessageId self.serverHash = serverHash } @@ -68,14 +63,13 @@ public class Message: Codable { // MARK: - Message Parsing/Processing public typealias ProcessedMessage = ( - threadId: String?, + threadId: String, + threadVariant: SessionThread.Variant, proto: SNProtoContent, messageInfo: MessageReceiveJob.Details.MessageInfo ) public extension Message { - static let nonThreadMessageId: String = "NON_THREAD_MESSAGE" - enum Variant: String, Codable { case readReceipt case typingIndicator @@ -485,7 +479,7 @@ public extension Message { handleClosedGroupKeyUpdateMessages: Bool, dependencies: SMKDependencies = SMKDependencies() ) throws -> ProcessedMessage? { - let (message, proto, threadId) = try MessageReceiver.parse( + let (message, proto, threadId, threadVariant) = try MessageReceiver.parse( db, envelope: envelope, serverExpirationTimestamp: serverExpirationTimestamp, @@ -511,7 +505,12 @@ public extension Message { case let closedGroupControlMessage as ClosedGroupControlMessage: switch closedGroupControlMessage.kind { case .encryptionKeyPair: - try MessageReceiver.handleClosedGroupControlMessage(db, closedGroupControlMessage) + try MessageReceiver.handleClosedGroupControlMessage( + db, + threadId: threadId, + threadVariant: threadVariant, + message: closedGroupControlMessage + ) return nil default: break @@ -540,10 +539,12 @@ public extension Message { return ( threadId, + threadVariant, proto, try MessageReceiveJob.Details.MessageInfo( message: message, variant: variant, + threadVariant: threadVariant, serverExpirationTimestamp: serverExpirationTimestamp, proto: proto ) diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index 4d95acfa6..540b5cb4c 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -229,17 +229,19 @@ public final class OpenGroupManager { let threadId: String = OpenGroup.idFor(roomToken: roomToken, server: targetServer) // Optionally try to insert a new version of the OpenGroup (it will fail if there is already an - // inactive one but that won't matter as we then activate it - _ = try? SessionThread.fetchOrCreate(db, id: threadId, variant: .community) - - // If we didn't add this open group via config handling then flag it to be visible (if it did - // come via config handling then we want to wait until it actually has messages before making - // it visible) - if !calledFromConfigHandling { - _ = try? SessionThread - .filter(id: threadId) - .updateAll(db, SessionThread.Columns.shouldBeVisible.set(to: true)) - } + // inactive one but that won't matter as we then activate it) + _ = try? SessionThread + .fetchOrCreate( + db, + id: threadId, + variant: .community, + /// If we didn't add this open group via config handling then flag it to be visible (if it did come via config handling then + /// we want to wait until it actually has messages before making it visible) + /// + /// **Note:** We **MUST** provide a `nil` value if this method was called from the config handling as updating + /// the `shouldVeVisible` state can trigger a config update which could result in an infinite loop in the future + shouldBeVisible: (calledFromConfigHandling ? nil : true) + ) if (try? OpenGroup.exists(db, id: threadId)) == false { try? OpenGroup @@ -641,10 +643,11 @@ public final class OpenGroupManager { if let messageInfo: MessageReceiveJob.Details.MessageInfo = processedMessage?.messageInfo { try MessageReceiver.handle( db, + threadId: openGroup.id, + threadVariant: .community, message: messageInfo.message, serverExpirationTimestamp: messageInfo.serverExpirationTimestamp, associatedWithProto: try SNProtoContent.parseData(messageInfo.serializedProtoData), - openGroupId: openGroup.id, dependencies: dependencies ) } @@ -805,10 +808,11 @@ public final class OpenGroupManager { if let messageInfo: MessageReceiveJob.Details.MessageInfo = processedMessage?.messageInfo { try MessageReceiver.handle( db, + threadId: (lookup.sessionId ?? lookup.blindedId), + threadVariant: .contact, // Technically not open group messages message: messageInfo.message, serverExpirationTimestamp: messageInfo.serverExpirationTimestamp, associatedWithProto: try SNProtoContent.parseData(messageInfo.serializedProtoData), - openGroupId: nil, // Intentionally nil as they are technically not open group messages dependencies: dependencies ) } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift index 15cf7b7fb..cd3a1f3e5 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift @@ -7,7 +7,15 @@ import SessionUtilitiesKit import SessionSnodeKit extension MessageReceiver { - public static func handleCallMessage(_ db: Database, message: CallMessage) throws { + public static func handleCallMessage( + _ db: Database, + threadId: String, + threadVariant: SessionThread.Variant, + message: CallMessage + ) throws { + // Only support calls from contact threads + guard threadVariant == .contact else { return } + switch message.kind { case .preOffer: try MessageReceiver.handleNewCallMessage(db, message: message) case .offer: MessageReceiver.handleOfferCallMessage(db, message: message) @@ -43,12 +51,18 @@ extension MessageReceiver { guard CurrentAppContext().isMainApp, let sender: String = message.sender, - (try? Contact.fetchOne(db, id: sender))?.isApproved == true + (try? Contact + .filter(id: sender) + .select(.isApproved) + .asRequest(of: Bool.self) + .fetchOne(db)) + .defaulting(to: false) else { return } guard let timestamp = message.sentTimestamp, TimestampUtils.isWithinOneMinute(timestamp: timestamp) else { // Add missed call message for call offer messages from more than one minute if let interaction: Interaction = try MessageReceiver.insertCallInfoMessage(db, for: message, state: .missed) { - let thread: SessionThread = try SessionThread.fetchOrCreate(db, id: sender, variant: .contact) + let thread: SessionThread = try SessionThread + .fetchOrCreate(db, id: sender, variant: .contact, shouldBeVisible: nil) Environment.shared?.notificationsManager.wrappedValue? .notifyUser( @@ -62,7 +76,8 @@ extension MessageReceiver { guard db[.areCallsEnabled] else { if let interaction: Interaction = try MessageReceiver.insertCallInfoMessage(db, for: message, state: .permissionDenied) { - let thread: SessionThread = try SessionThread.fetchOrCreate(db, id: sender, variant: .contact) + let thread: SessionThread = try SessionThread + .fetchOrCreate(db, id: sender, variant: .contact, shouldBeVisible: nil) Environment.shared?.notificationsManager.wrappedValue? .notifyUser( diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift index 1f4e1c678..5b604c568 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift @@ -68,7 +68,6 @@ extension MessageReceiver { guard hasApprovedAdmin else { return } // Create the group - let groupAlreadyExisted: Bool = ((try? SessionThread.exists(db, id: groupPublicKey)) ?? false) let thread: SessionThread = try SessionThread .fetchOrCreate(db, id: groupPublicKey, variant: .legacyGroup) .with(shouldBeVisible: true) @@ -84,39 +83,23 @@ extension MessageReceiver { try closedGroup.zombies.deleteAll(db) } - // Notify the user - if !groupAlreadyExisted { - // Create the GroupMember records - try members.forEach { memberId in - try GroupMember( - groupId: groupPublicKey, - profileId: memberId, - role: .standard, - isHidden: false - ).save(db) - } - - try admins.forEach { adminId in - try GroupMember( - groupId: groupPublicKey, - profileId: adminId, - role: .admin, - isHidden: false - ).save(db) - } - - // Note: We don't provide a `serverHash` in this case as we want to allow duplicates - // to avoid the following situation: - // • The app performed a background poll or received a push notification - // • This method was invoked and the received message timestamps table was updated - // • Processing wasn't finished - // • The user doesn't see the new closed group - _ = try Interaction( - threadId: thread.id, - authorId: getUserHexEncodedPublicKey(db), - variant: .infoClosedGroupCreated, - timestampMs: Int64(messageSentTimestamp) - ).inserted(db) + // Create the GroupMember records if needed + try members.forEach { memberId in + try GroupMember( + groupId: groupPublicKey, + profileId: memberId, + role: .standard, + isHidden: false + ).save(db) + } + + try admins.forEach { adminId in + try GroupMember( + groupId: groupPublicKey, + profileId: adminId, + role: .admin, + isHidden: false + ).save(db) } // Update the DisappearingMessages config @@ -194,12 +177,20 @@ extension MessageReceiver { } do { - try ClosedGroupKeyPair( + let keyPair: ClosedGroupKeyPair = ClosedGroupKeyPair( threadId: groupPublicKey, publicKey: proto.publicKey.removingIdPrefixIfNeeded(), secretKey: proto.privateKey, receivedTimestamp: (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) - ).insert(db) + ) + try keyPair.insert(db) + + // Update libSession + try? SessionUtil.update( + db, + groupPublicKey: groupPublicKey, + latestKeyPair: keyPair + ) } catch { if case DatabaseError.SQLITE_CONSTRAINT_UNIQUE = error { @@ -236,6 +227,13 @@ extension MessageReceiver { SnodeAPI.currentOffsetTimestampMs() ) ).inserted(db) + + // Update libSession + try? SessionUtil.update( + db, + groupPublicKey: id, + name: name + ) } } @@ -243,12 +241,16 @@ extension MessageReceiver { guard case let .membersAdded(membersAsData) = message.kind else { return } try performIfValid(db, message: message) { id, sender, thread, closedGroup in - guard let groupMembers: [GroupMember] = try? closedGroup.members.fetchAll(db) else { return } - guard let groupAdmins: [GroupMember] = try? closedGroup.admins.fetchAll(db) else { return } + guard let allGroupMembers: [GroupMember] = try? closedGroup.allMembers.fetchAll(db) else { + return + } // Update the group let addedMembers: [String] = membersAsData.map { $0.toHexString() } - let currentMemberIds: Set = groupMembers.map { $0.profileId }.asSet() + let currentMemberIds: Set = allGroupMembers + .filter { $0.role == .standard } + .map { $0.profileId } + .asSet() let members: Set = currentMemberIds.union(addedMembers) // Create records for any new members @@ -278,7 +280,7 @@ extension MessageReceiver { // generated by the admin when they saw the member removed message. let userPublicKey: String = getUserHexEncodedPublicKey(db) - if groupAdmins.contains(where: { $0.profileId == userPublicKey }) { + if allGroupMembers.contains(where: { $0.role == .admin && $0.profileId == userPublicKey }) { addedMembers.forEach { memberId in MessageSender.sendLatestEncryptionKeyPair(db, to: memberId, for: id) } @@ -292,7 +294,7 @@ extension MessageReceiver { .deleteAll(db) // Notify the user if needed - guard members != Set(groupMembers.map { $0.profileId }) else { return } + guard members != currentMemberIds else { return } _ = try Interaction( serverHash: message.serverHash, @@ -303,7 +305,7 @@ extension MessageReceiver { .membersAdded( members: addedMembers .asSet() - .subtracting(groupMembers.map { $0.profileId }) + .subtracting(currentMemberIds) .map { Data(hex: $0) } ) .infoMessage(db, sender: sender), @@ -312,6 +314,21 @@ extension MessageReceiver { SnodeAPI.currentOffsetTimestampMs() ) ).inserted(db) + + // Update libSession + try? SessionUtil.update( + db, + groupPublicKey: id, + members: allGroupMembers + .filter { $0.role == .standard || $0.role == .zombie } + .map { $0.profileId } + .asSet() + .union(addedMembers), + admins: allGroupMembers + .filter { $0.role == .admin } + .map { $0.profileId } + .asSet() + ) } } @@ -325,17 +342,22 @@ extension MessageReceiver { try performIfValid(db, message: message) { id, sender, thread, closedGroup in // Check that the admin wasn't removed - guard let groupMembers: [GroupMember] = try? closedGroup.members.fetchAll(db) else { return } - guard let groupAdmins: [GroupMember] = try? closedGroup.admins.fetchAll(db) else { return } + guard let allGroupMembers: [GroupMember] = try? closedGroup.allMembers.fetchAll(db) else { + return + } let removedMembers = membersAsData.map { $0.toHexString() } - let members = Set(groupMembers.map { $0.profileId }).subtracting(removedMembers) + let currentMemberIds: Set = allGroupMembers + .filter { $0.role == .standard } + .map { $0.profileId } + .asSet() + let members = currentMemberIds.subtracting(removedMembers) - guard let firstAdminId: String = groupAdmins.first?.profileId, members.contains(firstAdminId) else { + guard let firstAdminId: String = allGroupMembers.filter({ $0.role == .admin }).first?.profileId, members.contains(firstAdminId) else { return SNLog("Ignoring invalid closed group update.") } // Check that the message was sent by the group admin - guard groupAdmins.contains(where: { $0.profileId == sender }) else { + guard allGroupMembers.filter({ $0.role == .admin }).contains(where: { $0.profileId == sender }) else { return SNLog("Ignoring invalid closed group update.") } @@ -368,7 +390,7 @@ extension MessageReceiver { } // Notify the user if needed - guard members != Set(groupMembers.map { $0.profileId }) else { return } + guard members != currentMemberIds else { return } _ = try Interaction( serverHash: message.serverHash, @@ -379,7 +401,7 @@ extension MessageReceiver { .membersRemoved( members: removedMembers .asSet() - .intersection(groupMembers.map { $0.profileId }) + .intersection(currentMemberIds) .map { Data(hex: $0) } ) .infoMessage(db, sender: sender), @@ -388,6 +410,21 @@ extension MessageReceiver { SnodeAPI.currentOffsetTimestampMs() ) ).inserted(db) + + // Update libSession + try? SessionUtil.update( + db, + groupPublicKey: id, + members: allGroupMembers + .filter { $0.role == .standard || $0.role == .zombie } + .map { $0.profileId } + .asSet() + .subtracting(removedMembers), + admins: allGroupMembers + .filter { $0.role == .admin } + .map { $0.profileId } + .asSet() + ) } } @@ -466,6 +503,23 @@ extension MessageReceiver { SnodeAPI.currentOffsetTimestampMs() ) ).inserted(db) + + // Update libSession + try? SessionUtil.update( + db, + groupPublicKey: id, + members: allGroupMembers + .filter { + ($0.role == .standard || $0.role == .zombie) && + !membersToRemove.contains($0) + } + .map { $0.profileId } + .asSet(), + admins: allGroupMembers + .filter { $0.role == .admin } + .map { $0.profileId } + .asSet() + ) } } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+DataExtractionNotification.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+DataExtractionNotification.swift index a28346f6d..c1edbbe6a 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+DataExtractionNotification.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+DataExtractionNotification.swift @@ -5,17 +5,21 @@ import GRDB import SessionSnodeKit extension MessageReceiver { - internal static func handleDataExtractionNotification(_ db: Database, message: DataExtractionNotification) throws { + internal static func handleDataExtractionNotification( + _ db: Database, + threadId: String, + threadVariant: SessionThread.Variant, + message: DataExtractionNotification + ) throws { guard + threadVariant == .contact, let sender: String = message.sender, - let messageKind: DataExtractionNotification.Kind = message.kind, - let thread: SessionThread = try? SessionThread.fetchOne(db, id: sender), - thread.variant == .contact + let messageKind: DataExtractionNotification.Kind = message.kind else { return } _ = try Interaction( serverHash: message.serverHash, - threadId: thread.id, + threadId: threadId, authorId: sender, variant: { switch messageKind { diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ExpirationTimers.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ExpirationTimers.swift index bb5f3bfaa..201eccf48 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ExpirationTimers.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ExpirationTimers.swift @@ -5,12 +5,16 @@ import GRDB import SessionUtilitiesKit extension MessageReceiver { - internal static func handleExpirationTimerUpdate(_ db: Database, message: ExpirationTimerUpdate) throws { - // Get the target thread + internal static func handleExpirationTimerUpdate( + _ db: Database, + threadId: String, + threadVariant: SessionThread.Variant, + message: ExpirationTimerUpdate + ) throws { guard - let targetId: String = MessageReceiver.threadInfo(db, message: message, openGroupId: nil)?.id, - let sender: String = message.sender, - let thread: SessionThread = try? SessionThread.fetchOne(db, id: targetId) + // Only process these for contact and legacy groups (new groups handle it separately) + (threadVariant == .contact || threadVariant == .legacyGroup), + let sender: String = message.sender else { return } // Update the configuration @@ -18,9 +22,10 @@ extension MessageReceiver { // Note: Messages which had been sent during the previous configuration will still // use it's settings (so if you enable, send a message and then disable disappearing // message then the message you had sent will still disappear) - let config: DisappearingMessagesConfiguration = try thread.disappearingMessagesConfiguration + let config: DisappearingMessagesConfiguration = try DisappearingMessagesConfiguration + .filter(id: threadId) .fetchOne(db) - .defaulting(to: DisappearingMessagesConfiguration.defaultWith(thread.id)) + .defaulting(to: DisappearingMessagesConfiguration.defaultWith(threadId)) .with( // If there is no duration then we should disable the expiration timer isEnabled: ((message.duration ?? 0) > 0), @@ -33,7 +38,7 @@ extension MessageReceiver { // Add an info message for the user _ = try Interaction( serverHash: nil, // Intentionally null so sync messages are seen as duplicates - threadId: thread.id, + threadId: threadId, authorId: sender, variant: .infoDisappearingMessagesUpdate, body: config.messageInfoString( diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift index 264430004..ca1c2ec37 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift @@ -45,7 +45,8 @@ extension MessageReceiver { } // Prep the unblinded thread - let unblindedThread: SessionThread = try SessionThread.fetchOrCreate(db, id: senderId, variant: .contact) + let unblindedThread: SessionThread = try SessionThread + .fetchOrCreate(db, id: senderId, variant: .contact, shouldBeVisible: nil) // Need to handle a `MessageRequestResponse` sent to a blinded thread (ie. check if the sender matches // the blinded ids of any threads) diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+TypingIndicators.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+TypingIndicators.swift index 46807cf60..4d4c39329 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+TypingIndicators.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+TypingIndicators.swift @@ -5,17 +5,19 @@ import GRDB import SessionUtilitiesKit extension MessageReceiver { - internal static func handleTypingIndicator(_ db: Database, message: TypingIndicator) throws { - guard - let senderPublicKey: String = message.sender, - let thread: SessionThread = try SessionThread.fetchOne(db, id: senderPublicKey) - else { return } + internal static func handleTypingIndicator( + _ db: Database, + threadId: String, + threadVariant: SessionThread.Variant, + message: TypingIndicator + ) throws { + guard let thread: SessionThread = try SessionThread.fetchOne(db, id: threadId) else { return } switch message.kind { case .started: let needsToStartTypingIndicator: Bool = TypingIndicators.didStartTypingNeedsToStart( - threadId: thread.id, - threadVariant: thread.variant, + threadId: threadId, + threadVariant: threadVariant, threadIsMessageRequest: thread.isMessageRequest(db), direction: .incoming, timestampMs: message.sentTimestamp.map { Int64($0) } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+UnsendRequests.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+UnsendRequests.swift index f09b3f487..433c1734f 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+UnsendRequests.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+UnsendRequests.swift @@ -6,7 +6,12 @@ import SessionSnodeKit import SessionUtilitiesKit extension MessageReceiver { - public static func handleUnsendRequest(_ db: Database, message: UnsendRequest) throws { + public static func handleUnsendRequest( + _ db: Database, + threadId: String, + threadVariant: SessionThread.Variant, + message: UnsendRequest + ) throws { let userPublicKey: String = getUserHexEncodedPublicKey(db) guard message.sender == message.author || userPublicKey == message.sender else { return } @@ -19,12 +24,7 @@ extension MessageReceiver { guard let interactionId: Int64 = maybeInteraction?.id, - let interaction: Interaction = maybeInteraction, - let threadVariant: SessionThread.Variant = try SessionThread - .filter(id: interaction.threadId) - .select(.variant) - .asRequest(of: SessionThread.Variant.self) - .fetchOne(db) + let interaction: Interaction = maybeInteraction else { return } // Mark incoming messages as read and remove any of their notifications diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift index d7f5fd938..505ae32d9 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift @@ -8,9 +8,10 @@ import SessionUtilitiesKit extension MessageReceiver { @discardableResult public static func handleVisibleMessage( _ db: Database, + threadId: String, + threadVariant: SessionThread.Variant, message: VisibleMessage, associatedWithProto proto: SNProtoContent, - openGroupId: String?, dependencies: Dependencies = Dependencies() ) throws -> Int64 { guard let sender: String = message.sender, let dataMessage = proto.dataMessage else { @@ -53,8 +54,12 @@ extension MessageReceiver { // Store the message variant so we can run variant-specific behaviours let currentUserPublicKey: String = getUserHexEncodedPublicKey(db, dependencies: dependencies) let thread: SessionThread = try SessionThread - .fetchOrCreate(db, id: threadInfo.id, variant: threadInfo.variant) - let maybeOpenGroup: OpenGroup? = openGroupId.map { try? OpenGroup.fetchOne(db, id: $0) } + .fetchOrCreate(db, id: threadId, variant: threadVariant, shouldBeVisible: nil) + let maybeOpenGroup: OpenGroup? = { + guard threadVariant == .community else { return nil } + + return try? OpenGroup.fetchOne(db, id: threadId) + }() let variant: Interaction.Variant = { guard let senderSessionId: SessionId = SessionId(from: sender), @@ -94,7 +99,14 @@ extension MessageReceiver { }() // Handle emoji reacts first (otherwise it's essentially an invalid message) - if let interactionId: Int64 = try handleEmojiReactIfNeeded(db, message: message, associatedWithProto: proto, sender: sender, messageSentTimestamp: messageSentTimestamp, openGroupId: openGroupId, thread: thread) { + if let interactionId: Int64 = try handleEmojiReactIfNeeded( + db, + thread: thread, + message: message, + associatedWithProto: proto, + sender: sender, + messageSentTimestamp: messageSentTimestamp + ) { return interactionId } @@ -314,12 +326,11 @@ extension MessageReceiver { private static func handleEmojiReactIfNeeded( _ db: Database, + thread: SessionThread, message: VisibleMessage, associatedWithProto proto: SNProtoContent, sender: String, - messageSentTimestamp: TimeInterval, - openGroupId: String?, - thread: SessionThread + messageSentTimestamp: TimeInterval ) throws -> Int64? { guard let reaction: VisibleMessage.VMReaction = message.reaction, @@ -347,7 +358,7 @@ extension MessageReceiver { switch reaction.kind { case .react: - let reaction = Reaction( + let reaction: Reaction = try Reaction( interactionId: interactionId, serverHash: message.serverHash, timestampMs: Int64(messageSentTimestamp * 1000), @@ -355,8 +366,8 @@ extension MessageReceiver { emoji: reaction.emoji, count: 1, sortId: sortId - ) - try reaction.insert(db) + ).inserted(db) + if sender != getUserHexEncodedPublicKey(db) { Environment.shared?.notificationsManager.wrappedValue? .notifyUser( diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift index da9e460c9..1d37db55d 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift @@ -15,7 +15,7 @@ extension MessageSender { _ db: Database, name: String, members: Set - ) -> AnyPublisher { + ) throws -> AnyPublisher { let userPublicKey: String = getUserHexEncodedPublicKey(db) var members: Set = members @@ -30,89 +30,85 @@ extension MessageSender { // Create the group members.insert(userPublicKey) // Ensure the current user is included in the member list - let membersAsData = members.map { Data(hex: $0) } - let admins = [ userPublicKey ] - let adminsAsData = admins.map { Data(hex: $0) } + let membersAsData: [Data] = members.map { Data(hex: $0) } + let admins: Set = [ userPublicKey ] + let adminsAsData: [Data] = admins.map { Data(hex: $0) } let formationTimestamp: TimeInterval = (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) - let thread: SessionThread - let memberSendData: [MessageSender.PreparedSendData] - do { - // Create the relevant objects in the database - thread = try SessionThread - .fetchOrCreate(db, id: groupPublicKey, variant: .legacyGroup) - try ClosedGroup( - threadId: groupPublicKey, - name: name, - formationTimestamp: formationTimestamp + // Create the relevant objects in the database + let thread: SessionThread = try SessionThread + .fetchOrCreate(db, id: groupPublicKey, variant: .legacyGroup, shouldBeVisible: true) + try ClosedGroup( + threadId: groupPublicKey, + name: name, + formationTimestamp: formationTimestamp + ).insert(db) + + // Store the key pair + let latestKeyPairReceivedTimestamp: TimeInterval = (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) + try ClosedGroupKeyPair( + threadId: groupPublicKey, + publicKey: encryptionKeyPair.publicKey, + secretKey: encryptionKeyPair.privateKey, + receivedTimestamp: latestKeyPairReceivedTimestamp + ).insert(db) + + // Create the member objects + try admins.forEach { adminId in + try GroupMember( + groupId: groupPublicKey, + profileId: adminId, + role: .admin, + isHidden: false ).insert(db) - - // Store the key pair - try ClosedGroupKeyPair( - threadId: groupPublicKey, - publicKey: encryptionKeyPair.publicKey, - secretKey: encryptionKeyPair.privateKey, - receivedTimestamp: (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) + } + + try members.forEach { memberId in + try GroupMember( + groupId: groupPublicKey, + profileId: memberId, + role: .standard, + isHidden: false ).insert(db) - - // Create the member objects - try admins.forEach { adminId in - try GroupMember( - groupId: groupPublicKey, - profileId: adminId, - role: .admin, - isHidden: false - ).insert(db) - } - - try members.forEach { memberId in - try GroupMember( - groupId: groupPublicKey, - profileId: memberId, - role: .standard, - isHidden: false - ).insert(db) - } - - // Notify the user - // - // Note: Intentionally don't want a 'serverHash' for closed group creation - _ = try Interaction( - threadId: thread.id, - authorId: userPublicKey, - variant: .infoClosedGroupCreated, - timestampMs: SnodeAPI.currentOffsetTimestampMs() - ).inserted(db) - - memberSendData = try members - .map { memberId -> MessageSender.PreparedSendData in - try MessageSender.preparedSendData( - db, - message: ClosedGroupControlMessage( - kind: .new( - publicKey: Data(hex: groupPublicKey), - name: name, - encryptionKeyPair: Box.KeyPair( - publicKey: encryptionKeyPair.publicKey.bytes, - secretKey: encryptionKeyPair.privateKey.bytes - ), - members: membersAsData, - admins: adminsAsData, - expirationTimer: 0 + } + + // Update libSession + try SessionUtil.add( + db, + groupPublicKey: groupPublicKey, + name: name, + latestKeyPairPublicKey: encryptionKeyPair.publicKey, + latestKeyPairSecretKey: encryptionKeyPair.privateKey, + latestKeyPairReceivedTimestamp: latestKeyPairReceivedTimestamp, + members: members, + admins: admins + ) + + let memberSendData: [MessageSender.PreparedSendData] = try members + .map { memberId -> MessageSender.PreparedSendData in + try MessageSender.preparedSendData( + db, + message: ClosedGroupControlMessage( + kind: .new( + publicKey: Data(hex: groupPublicKey), + name: name, + encryptionKeyPair: Box.KeyPair( + publicKey: encryptionKeyPair.publicKey.bytes, + secretKey: encryptionKeyPair.privateKey.bytes ), - // Note: We set this here to ensure the value matches - // the 'ClosedGroup' object we created - sentTimestampMs: UInt64(floor(formationTimestamp * 1000)) + members: membersAsData, + admins: adminsAsData, + expirationTimer: 0 ), - to: .contact(publicKey: memberId), - interactionId: nil - ) - } - } - catch { - return Fail(error: error) - .eraseToAnyPublisher() - } + // Note: We set this here to ensure the value matches + // the 'ClosedGroup' object we created + sentTimestampMs: UInt64(floor(formationTimestamp * 1000)) + ), + to: .contact(publicKey: memberId), + namespace: Message.Destination.contact(publicKey: memberId).defaultNamespace, + interactionId: nil + ) + } return Publishers .MergeMany( @@ -207,6 +203,7 @@ extension MessageSender { ) ), to: try Message.Destination.from(db, thread: thread), + namespace: try Message.Destination.from(db, thread: thread).defaultNamespace, interactionId: nil ) } @@ -225,6 +222,21 @@ extension MessageSender { try newKeyPair.insert(db) } + // Update libSession + try? SessionUtil.update( + db, + groupPublicKey: closedGroup.threadId, + latestKeyPair: newKeyPair, + members: allGroupMembers + .filter { $0.role == .standard || $0.role == .zombie } + .map { $0.profileId } + .asSet(), + admins: allGroupMembers + .filter { $0.role == .admin } + .map { $0.profileId } + .asSet() + ) + distributingKeyPairs.mutate { if let index = ($0[closedGroup.id] ?? []).firstIndex(of: newKeyPair) { $0[closedGroup.id] = ($0[closedGroup.id] ?? []) @@ -284,6 +296,13 @@ extension MessageSender { interactionId: interactionId, in: thread ) + + // Update libSession + try? SessionUtil.update( + db, + groupPublicKey: closedGroup.threadId, + name: name + ) } } catch { @@ -386,6 +405,20 @@ extension MessageSender { guard let interactionId: Int64 = interaction.id else { throw StorageError.objectNotSaved } + // Update libSession + try? SessionUtil.update( + db, + groupPublicKey: closedGroup.threadId, + members: allGroupMembers + .filter { $0.role == .standard || $0.role == .zombie } + .map { $0.profileId } + .asSet(), + admins: allGroupMembers + .filter { $0.role == .admin } + .map { $0.profileId } + .asSet() + ) + // Send the update to the group try MessageSender.send( db, @@ -399,7 +432,7 @@ extension MessageSender { try addedMembers.forEach { member in // Send updates to the new members individually let thread: SessionThread = try SessionThread - .fetchOrCreate(db, id: member, variant: .contact) + .fetchOrCreate(db, id: member, variant: .contact, shouldBeVisible: nil) try MessageSender.send( db, @@ -505,6 +538,7 @@ extension MessageSender { ) ), to: try Message.Destination.from(db, thread: thread), + namespace: try Message.Destination.from(db, thread: thread).defaultNamespace, interactionId: interactionId ) ) @@ -571,6 +605,7 @@ extension MessageSender { kind: .memberLeft ), to: try Message.Destination.from(db, thread: thread), + namespace: try Message.Destination.from(db, thread: thread).defaultNamespace, interactionId: interactionId ) @@ -594,6 +629,12 @@ extension MessageSender { } } catch { + try? ClosedGroup.removeKeysAndUnsubscribe( + db, + threadId: groupPublicKey, + removeGroupData: false, + calledFromConfigHandling: false + ) return Fail(error: error) .eraseToAnyPublisher() } @@ -651,7 +692,7 @@ extension MessageSender { ).build() let plaintext = try proto.serializedData() let thread: SessionThread = try SessionThread - .fetchOrCreate(db, id: publicKey, variant: .contact) + .fetchOrCreate(db, id: publicKey, variant: .contact, shouldBeVisible: nil) let ciphertext = try MessageSender.encryptWithSessionProtocol( db, plaintext: plaintext, diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index f14a16855..899f2b929 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -19,7 +19,7 @@ public enum MessageReceiver { isOutgoing: Bool? = nil, otherBlindedPublicKey: String? = nil, dependencies: SMKDependencies = SMKDependencies() - ) throws -> (Message, SNProtoContent, String) { + ) throws -> (Message, SNProtoContent, String, SessionThread.Variant) { let userPublicKey: String = getUserHexEncodedPublicKey(db, dependencies: dependencies) let isOpenGroupMessage: Bool = (openGroupId != nil) @@ -147,7 +147,6 @@ public enum MessageReceiver { message.recipient = userPublicKey message.sentTimestamp = envelope.timestamp message.receivedTimestamp = UInt64(SnodeAPI.currentOffsetTimestampMs()) - message.groupPublicKey = groupPublicKey message.openGroupServerMessageId = openGroupMessageServerId.map { UInt64($0) } // Validate @@ -161,28 +160,29 @@ public enum MessageReceiver { } // Extract the proper threadId for the message - let threadId: String = { - if let groupPublicKey: String = groupPublicKey { return groupPublicKey } - if let openGroupId: String = openGroupId { return openGroupId } + let (threadId, threadVariant): (String, SessionThread.Variant) = { + if let groupPublicKey: String = groupPublicKey { return (groupPublicKey, .legacyGroup) } + if let openGroupId: String = openGroupId { return (openGroupId, .community) } switch message { - case let message as VisibleMessage: return (message.syncTarget ?? sender) - case let message as ExpirationTimerUpdate: return (message.syncTarget ?? sender) - default: return sender + case let message as VisibleMessage: return ((message.syncTarget ?? sender), .contact) + case let message as ExpirationTimerUpdate: return ((message.syncTarget ?? sender), .contact) + default: return (sender, .contact) } }() - return (message, proto, threadId) + return (message, proto, threadId, threadVariant) } // MARK: - Handling public static func handle( _ db: Database, + threadId: String, + threadVariant: SessionThread.Variant, message: Message, serverExpirationTimestamp: TimeInterval?, associatedWithProto proto: SNProtoContent, - openGroupId: String?, dependencies: SMKDependencies = SMKDependencies() ) throws { switch message { @@ -194,35 +194,70 @@ public enum MessageReceiver { ) case let message as TypingIndicator: - try MessageReceiver.handleTypingIndicator(db, message: message) + try MessageReceiver.handleTypingIndicator( + db, + threadId: threadId, + threadVariant: threadVariant, + message: message + ) case let message as ClosedGroupControlMessage: - try MessageReceiver.handleClosedGroupControlMessage(db, message) + try MessageReceiver.handleClosedGroupControlMessage( + db, + threadId: threadId, + threadVariant: threadVariant, + message: message + ) case let message as DataExtractionNotification: - try MessageReceiver.handleDataExtractionNotification(db, message: message) + try MessageReceiver.handleDataExtractionNotification( + db, + threadId: threadId, + threadVariant: threadVariant, + message: message + ) case let message as ExpirationTimerUpdate: - try MessageReceiver.handleExpirationTimerUpdate(db, message: message) + try MessageReceiver.handleExpirationTimerUpdate( + db, + threadId: threadId, + threadVariant: threadVariant, + message: message + ) case let message as ConfigurationMessage: try MessageReceiver.handleConfigurationMessage(db, message: message) case let message as UnsendRequest: - try MessageReceiver.handleUnsendRequest(db, message: message) + try MessageReceiver.handleUnsendRequest( + db, + threadId: threadId, + threadVariant: threadVariant, + message: message + ) case let message as CallMessage: - try MessageReceiver.handleCallMessage(db, message: message) + try MessageReceiver.handleCallMessage( + db, + threadId: threadId, + threadVariant: threadVariant, + message: message + ) case let message as MessageRequestResponse: - try MessageReceiver.handleMessageRequestResponse(db, message: message, dependencies: dependencies) + try MessageReceiver.handleMessageRequestResponse( + db, + message: message, + dependencies: dependencies + ) case let message as VisibleMessage: try MessageReceiver.handleVisibleMessage( db, + threadId: threadId, + threadVariant: threadVariant, message: message, - associatedWithProto: proto, - openGroupId: openGroupId + associatedWithProto: proto ) // SharedConfigMessages should be handled by the 'SharedUtil' instead of this @@ -232,13 +267,13 @@ public enum MessageReceiver { } // Perform any required post-handling logic - try MessageReceiver.postHandleMessage(db, message: message, openGroupId: openGroupId) + try MessageReceiver.postHandleMessage(db, threadId: threadId, message: message) } public static func postHandleMessage( _ db: Database, - message: Message, - openGroupId: String? + threadId: String, + message: Message ) throws { // When handling any non-typing indicator message we want to make sure the thread becomes // visible (the only other spot this flag gets set is when sending messages) @@ -246,14 +281,12 @@ public enum MessageReceiver { case is TypingIndicator: break default: - guard let threadInfo: (id: String, variant: SessionThread.Variant) = threadInfo(db, message: message, openGroupId: openGroupId) else { - return - } - - _ = try SessionThread - .fetchOrCreate(db, id: threadInfo.id, variant: threadInfo.variant) - .with(shouldBeVisible: true) - .saved(db) + try SessionThread + .filter(id: threadId) + .updateAllAndConfig( + db, + SessionThread.Columns.shouldBeVisible.set(to: true) + ) } } @@ -281,36 +314,4 @@ public enum MessageReceiver { try reaction.with(interactionId: interactionId).insert(db) } } - - // MARK: - Convenience - - internal static func threadInfo(_ db: Database, message: Message, openGroupId: String?) -> (id: String, variant: SessionThread.Variant)? { - if let openGroupId: String = openGroupId { - // Note: We don't want to create a thread for an open group if it doesn't exist - if (try? SessionThread.exists(db, id: openGroupId)) != true { return nil } - - return (openGroupId, .community) - } - - if let groupPublicKey: String = message.groupPublicKey { - // Note: We don't want to create a thread for a closed group if it doesn't exist - if (try? SessionThread.exists(db, id: groupPublicKey)) != true { return nil } - - return (groupPublicKey, .legacyGroup) - } - - // Extract the 'syncTarget' value if there is one - let maybeSyncTarget: String? - - switch message { - case let message as VisibleMessage: maybeSyncTarget = message.syncTarget - case let message as ExpirationTimerUpdate: maybeSyncTarget = message.syncTarget - default: maybeSyncTarget = nil - } - - // Note: We don't want to create a thread for a closed group if it doesn't exist - guard let contactId: String = (maybeSyncTarget ?? message.sender) else { return nil } - - return (contactId, .contact) - } } diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift index 799c9f0a5..52fa670f1 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift @@ -284,7 +284,7 @@ public class Poller { return nil } } - .grouped { threadId, _, _ in (threadId ?? Message.nonThreadMessageId) } + .grouped { threadId, _, _, _ in threadId } .forEach { threadId, threadMessages in messageCount += threadMessages.count processedMessages += threadMessages.map { $0.messageInfo.message } diff --git a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift index cd202a332..b613ba67f 100644 --- a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift +++ b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift @@ -787,7 +787,7 @@ public extension SessionThreadViewModel { let thread: TypedTableAlias = TypedTableAlias() let interaction: TypedTableAlias = TypedTableAlias() - return SQL("\(thread[.isPinned]) DESC, IFNULL(\(interaction[.timestampMs]), (\(thread[.creationDateTimestamp]) * 1000)) DESC") + return SQL("(IFNULL(\(thread[.pinnedPriority]), 0) > 0) DESC, IFNULL(\(interaction[.timestampMs]), (\(thread[.creationDateTimestamp]) * 1000)) DESC") }() static let messageRequetsOrderSQL: SQL = { diff --git a/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserGroupsSpec.swift b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserGroupsSpec.swift index 7e98a285c..1812b8f0b 100644 --- a/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserGroupsSpec.swift +++ b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserGroupsSpec.swift @@ -206,12 +206,13 @@ class ConfigUserGroupsSpec { .to(equal("00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff")) // The new data doesn't get stored until we call this: - user_groups_set_legacy_group(conf, legacyGroup2) + user_groups_set_free_legacy_group(conf, legacyGroup2) let legacyGroup3: UnsafeMutablePointer? = user_groups_get_legacy_group(conf, &cDefinitelyRealId) expect(legacyGroup3?.pointee).toNot(beNil()) expect(config_needs_push(conf)).to(beTrue()) expect(config_needs_dump(conf)).to(beTrue()) + ugroups_legacy_group_free(legacyGroup3) let communityPubkey: String = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" var cCommunityPubkey: [UInt8] = Data(hex: communityPubkey).cArray @@ -307,6 +308,7 @@ class ConfigUserGroupsSpec { } ugroups_legacy_members_free(membersIt3) + ugroups_legacy_group_free(legacyGroup4) expect(membersSeen3).to(equal([ "050000000000000000000000000000000000000000000000000000000000000000": false, @@ -436,7 +438,7 @@ class ConfigUserGroupsSpec { expect(config_needs_dump(conf2)).to(beFalse()) pushData9.deallocate() - user_groups_set_legacy_group(conf2, legacyGroup5) + user_groups_set_free_legacy_group(conf2, legacyGroup5) expect(config_needs_push(conf2)).to(beTrue()) expect(config_needs_dump(conf2)).to(beTrue()) diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index e4f205a32..3a61aa8b3 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -75,23 +75,14 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension return } - let maybeVariant: SessionThread.Variant? = processedMessage.threadId - .map { threadId in - try? SessionThread - .filter(id: threadId) - .select(.variant) - .asRequest(of: SessionThread.Variant.self) - .fetchOne(db) - } - let isOpenGroup: Bool = (maybeVariant == .community) - switch processedMessage.messageInfo.message { case let visibleMessage as VisibleMessage: let interactionId: Int64 = try MessageReceiver.handleVisibleMessage( db, + threadId: processedMessage.threadId, + threadVariant: processedMessage.threadVariant, message: visibleMessage, - associatedWithProto: processedMessage.proto, - openGroupId: (isOpenGroup ? processedMessage.threadId : nil) + associatedWithProto: processedMessage.proto ) // Remove the notifications if there is an outgoing messages from a linked device @@ -111,19 +102,40 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension } case let unsendRequest as UnsendRequest: - try MessageReceiver.handleUnsendRequest(db, message: unsendRequest) + try MessageReceiver.handleUnsendRequest( + db, + threadId: processedMessage.threadId, + threadVariant: processedMessage.threadVariant, + message: unsendRequest + ) case let closedGroupControlMessage as ClosedGroupControlMessage: - try MessageReceiver.handleClosedGroupControlMessage(db, closedGroupControlMessage) + try MessageReceiver.handleClosedGroupControlMessage( + db, + threadId: processedMessage.threadId, + threadVariant: processedMessage.threadVariant, + message: closedGroupControlMessage + ) case let callMessage as CallMessage: - try MessageReceiver.handleCallMessage(db, message: callMessage) + try MessageReceiver.handleCallMessage( + db, + threadId: processedMessage.threadId, + threadVariant: processedMessage.threadVariant, + message: callMessage + ) guard case .preOffer = callMessage.kind else { return self.completeSilenty() } if !db[.areCallsEnabled] { if let sender: String = callMessage.sender, let interaction: Interaction = try MessageReceiver.insertCallInfoMessage(db, for: callMessage, state: .permissionDenied) { - let thread: SessionThread = try SessionThread.fetchOrCreate(db, id: sender, variant: .contact) + let thread: SessionThread = try SessionThread + .fetchOrCreate( + db, + id: sender, + variant: .contact, + shouldBeVisible: nil + ) Environment.shared?.notificationsManager.wrappedValue? .notifyUser( @@ -146,7 +158,7 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension try SessionUtil.handleConfigMessages( db, messages: [sharedConfigMessage], - publicKey: (processedMessage.threadId ?? "") + publicKey: processedMessage.threadId ) default: break @@ -155,8 +167,8 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension // Perform any required post-handling logic try MessageReceiver.postHandleMessage( db, - message: processedMessage.messageInfo.message, - openGroupId: (isOpenGroup ? processedMessage.threadId : nil) + threadId: processedMessage.threadId, + message: processedMessage.messageInfo.message ) } catch { From e28b4b453112159d08c6d3045236b1651b775fcd Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Mon, 6 Mar 2023 15:20:15 +1100 Subject: [PATCH 035/135] Fixed a number of bugs with the config handling Added a number of feature flag checks to config updates Added legacy group disappearing message timer handling Updated the string linter to clean up the build logs a little Split the initial config dump generation into it's own migration so it can run the launch after the feature flag is toggled Fixed a few issues with the initial config dump creation Fixed an issue where "shadow" conversations would be left in the database by opening a thread and never sending a message Fixed a bug where duplicate members could be added to legacy groups Fixed a bug with using animated images for the avatar Fixed a bug where avatar images which were already on disk could be re-downloaded --- Scripts/LintLocalizableStrings.swift | 58 +- Session.xcodeproj/project.pbxproj | 4 + .../ConversationVC+Interaction.swift | 32 +- Session/Conversations/ConversationVC.swift | 19 + ...isappearingMessagesSettingsViewModel.swift | 17 + .../Settings/ThreadSettingsViewModel.swift | 1 + Session/Home/HomeVC.swift | 2 +- Session/Home/HomeViewModel.swift | 20 +- Session/Settings/ImagePickerHandler.swift | 6 +- Session/Settings/SettingsViewModel.swift | 2 - Session/Utilities/MockDataGenerator.swift | 12 +- SessionMessagingKit/Configuration.swift | 10 +- .../Migrations/_012_SharedUtilChanges.swift | 288 +++---- .../_013_GenerateInitialUserConfigDumps.swift | 188 +++++ .../Database/Models/ClosedGroupKeyPair.swift | 9 + .../SessionUtil+Contacts.swift | 228 +++--- .../SessionUtil+ConvoInfoVolatile.swift | 45 +- .../Config Handling/SessionUtil+Shared.swift | 278 ++++--- .../SessionUtil+UserGroups.swift | 156 +++- .../SessionUtil+UserProfile.swift | 64 +- .../QueryInterfaceRequest+Utilities.swift | 35 +- .../LibSessionUtil/SessionUtil.swift | 97 ++- .../libsession-util.xcframework/Info.plist | 24 +- .../ios-arm64/libsession-util.a | Bin 2083072 -> 2083088 bytes .../libsession-util.a | Bin 4588408 -> 4588424 bytes .../Open Groups/OpenGroupManager.swift | 16 +- .../MessageReceiver+ClosedGroups.swift | 765 ++++++++++-------- ...essageReceiver+ConfigurationMessages.swift | 3 +- .../MessageReceiver+ExpirationTimers.swift | 13 + .../MessageReceiver+MessageRequests.swift | 6 +- .../MessageReceiver+VisibleMessages.swift | 17 +- .../MessageSender+ClosedGroups.swift | 25 +- .../Sending & Receiving/MessageReceiver.swift | 11 + .../Utilities/ProfileManager.swift | 20 +- .../Base.lproj/MainInterface.storyboard | 8 +- .../Utilities/Database+Utilities.swift | 4 + 36 files changed, 1518 insertions(+), 965 deletions(-) create mode 100644 SessionMessagingKit/Database/Migrations/_013_GenerateInitialUserConfigDumps.swift diff --git a/Scripts/LintLocalizableStrings.swift b/Scripts/LintLocalizableStrings.swift index 3f0860735..956822df3 100755 --- a/Scripts/LintLocalizableStrings.swift +++ b/Scripts/LintLocalizableStrings.swift @@ -1,11 +1,6 @@ #!/usr/bin/xcrun --sdk macosx swift -// -// ListLocalizableStrings.swift -// Archa -// -// Created by Morgan Pretty on 18/5/20. -// Copyright © 2020 Archa. All rights reserved. +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. // // This script is based on https://github.com/ginowu7/CleanSwiftLocalizableExample the main difference // is canges to the localized usage regex @@ -56,7 +51,6 @@ var executableFiles: [String] = { /// - Parameter path: path of file /// - Returns: content in file func contents(atPath path: String) -> String { - print("Path: \(path)") guard let data = fileManager.contents(atPath: path), let content = String(data: data, encoding: .utf8) else { fatalError("Could not read from path: \(path)") } @@ -109,8 +103,6 @@ func localizedStringsInCode() -> [LocalizationCodeFile] { /// /// - Parameter files: list of localizable files to validate func validateMatchKeys(_ files: [LocalizationStringsFile]) { - print("------------ Validating keys match in all localizable files ------------") - guard let base = files.first, files.count > 1 else { return } let files = Array(files.dropFirst()) @@ -128,8 +120,6 @@ func validateMatchKeys(_ files: [LocalizationStringsFile]) { /// - codeFiles: Array of LocalizationCodeFile /// - localizationFiles: Array of LocalizableStringFiles func validateMissingKeys(_ codeFiles: [LocalizationCodeFile], localizationFiles: [LocalizationStringsFile]) { - print("------------ Checking for missing keys -----------") - guard let baseFile = localizationFiles.first else { fatalError("Could not locate base localization file") } @@ -150,8 +140,6 @@ func validateMissingKeys(_ codeFiles: [LocalizationCodeFile], localizationFiles: /// - codeFiles: Array of LocalizationCodeFile /// - localizationFiles: Array of LocalizableStringFiles func validateDeadKeys(_ codeFiles: [LocalizationCodeFile], localizationFiles: [LocalizationStringsFile]) { - print("------------ Checking for any dead keys in localizable file -----------") - guard let baseFile = localizationFiles.first else { fatalError("Could not locate base localization file") } @@ -174,14 +162,18 @@ protocol Pathable { struct LocalizationStringsFile: Pathable { let path: String let kv: [String: String] + let duplicates: [(key: String, path: String)] var keys: [String] { return Array(kv.keys) } init(path: String) { + let result = ContentParser.parse(path) + self.path = path - self.kv = ContentParser.parse(path) + self.kv = result.kv + self.duplicates = result.duplicates } /// Writes back to localizable file with sorted keys and removed whitespaces and new lines @@ -204,9 +196,7 @@ struct ContentParser { /// /// - Parameter path: Localizable file paths /// - Returns: localizable key and value for content at path - static func parse(_ path: String) -> [String: String] { - print("------------ Checking for duplicate keys: \(path) ------------") - + static func parse(_ path: String) -> (kv: [String: String], duplicates: [(key: String, path: String)]) { let content = contents(atPath: path) let trimmed = content .replacingOccurrences(of: "\n+", with: "", options: .regularExpression, range: nil) @@ -218,13 +208,18 @@ struct ContentParser { fatalError("Error parsing contents: Make sure all keys and values are in correct format (this could be due to extra spaces between keys and values)") } - return zip(keys, values).reduce(into: [String: String]()) { results, keyValue in - if results[keyValue.0] != nil { - printPretty("error: Found duplicate key: \(keyValue.0) in file: \(path)") - abort() + var duplicates: [(key: String, path: String)] = [] + let kv: [String: String] = zip(keys, values) + .reduce(into: [:]) { results, keyValue in + guard results[keyValue.0] == nil else { + duplicates.append((keyValue.0, path)) + return + } + + results[keyValue.0] = keyValue.1 } - results[keyValue.0] = keyValue.1 - } + + return (kv, duplicates) } } @@ -232,20 +227,27 @@ func printPretty(_ string: String) { print(string.replacingOccurrences(of: "\\", with: "")) } -let stringFiles = create() +// MARK: - Processing + +let stringFiles: [LocalizationStringsFile] = create() if !stringFiles.isEmpty { - print("------------ Found \(stringFiles.count) file(s) ------------") + print("------------ Found \(stringFiles.count) file(s) - checking for duplicate, extra, missing and dead keys ------------") + + stringFiles.forEach { file in + file.duplicates.forEach { key, path in + printPretty("error: Found duplicate key: \(key) in file: \(path)") + } + } - stringFiles.forEach { print($0.path) } validateMatchKeys(stringFiles) // Note: Uncomment the below file to clean out all comments from the localizable file (we don't want this because comments make it readable...) // stringFiles.forEach { $0.cleanWrite() } - let codeFiles = localizedStringsInCode() + let codeFiles: [LocalizationCodeFile] = localizedStringsInCode() validateMissingKeys(codeFiles, localizationFiles: stringFiles) validateDeadKeys(codeFiles, localizationFiles: stringFiles) } -print("------------ SUCCESS ------------") +print("------------ Complete ------------") diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 5c13a263f..c7903d7ee 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -708,6 +708,7 @@ FD7728982849E8110018502F /* UITableView+ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7728972849E8110018502F /* UITableView+ReusableView.swift */; }; FD77289A284AF1BD0018502F /* Sodium+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD772899284AF1BD0018502F /* Sodium+Utilities.swift */; }; FD77289E284EF1C50018502F /* Sodium+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD77289D284EF1C50018502F /* Sodium+Utilities.swift */; }; + FD778B6429B189FF001BAC6B /* _013_GenerateInitialUserConfigDumps.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD778B6329B189FF001BAC6B /* _013_GenerateInitialUserConfigDumps.swift */; }; FD83B9B327CF200A005E1583 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; platformFilter = ios; }; FD83B9BB27CF20AF005E1583 /* SessionIdSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9BA27CF20AF005E1583 /* SessionIdSpec.swift */; }; FD83B9BF27CF2294005E1583 /* TestConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9BD27CF2243005E1583 /* TestConstants.swift */; }; @@ -1835,6 +1836,7 @@ FD7728972849E8110018502F /* UITableView+ReusableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITableView+ReusableView.swift"; sourceTree = ""; }; FD772899284AF1BD0018502F /* Sodium+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Sodium+Utilities.swift"; sourceTree = ""; }; FD77289D284EF1C50018502F /* Sodium+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Sodium+Utilities.swift"; sourceTree = ""; }; + FD778B6329B189FF001BAC6B /* _013_GenerateInitialUserConfigDumps.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _013_GenerateInitialUserConfigDumps.swift; sourceTree = ""; }; FD83B9AF27CF200A005E1583 /* SessionUtilitiesKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SessionUtilitiesKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; FD83B9BA27CF20AF005E1583 /* SessionIdSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionIdSpec.swift; sourceTree = ""; }; FD83B9BD27CF2243005E1583 /* TestConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestConstants.swift; sourceTree = ""; }; @@ -3629,6 +3631,7 @@ FD7115F128C6CB3900B47552 /* _010_AddThreadIdToFTS.swift */, FD432431299C6933008A0213 /* _011_AddPendingReadReceipts.swift */, FD8ECF7C2934293A00C0D1BB /* _012_SharedUtilChanges.swift */, + FD778B6329B189FF001BAC6B /* _013_GenerateInitialUserConfigDumps.swift */, ); path = Migrations; sourceTree = ""; @@ -5791,6 +5794,7 @@ FD09796E27FA6D0000936362 /* Contact.swift in Sources */, C38D5E8D2575011E00B6A65C /* MessageSender+ClosedGroups.swift in Sources */, FD5C72F7284F0E560029977D /* MessageReceiver+ReadReceipts.swift in Sources */, + FD778B6429B189FF001BAC6B /* _013_GenerateInitialUserConfigDumps.swift in Sources */, C32C598A256D0664003C73A2 /* SNProtoEnvelope+Conversion.swift in Sources */, FDC438CB27BB7DB100C60D73 /* UpdateMessageRequest.swift in Sources */, FD8ECF7F2934298100C0D1BB /* SharedConfigDump.swift in Sources */, diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index b4a5b213d..054eed615 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -454,10 +454,12 @@ extension ConversationVC: // Let the viewModel know we are about to send a message self?.viewModel.sentMessageBeforeUpdate = true - // Update the thread to be visible - _ = try SessionThread - .filter(id: threadId) - .updateAllAndConfig(db, SessionThread.Columns.shouldBeVisible.set(to: true)) + // Update the thread to be visible (if it isn't already) + if !thread.shouldBeVisible { + _ = try SessionThread + .filter(id: threadId) + .updateAllAndConfig(db, SessionThread.Columns.shouldBeVisible.set(to: true)) + } let authorId: String = { if let blindedId = self?.viewModel.threadData.currentUserBlindedPublicKey { @@ -585,10 +587,12 @@ extension ConversationVC: // Let the viewModel know we are about to send a message self?.viewModel.sentMessageBeforeUpdate = true - // Update the thread to be visible - _ = try SessionThread - .filter(id: threadId) - .updateAllAndConfig(db, SessionThread.Columns.shouldBeVisible.set(to: true)) + // Update the thread to be visible (if it isn't already) + if !thread.shouldBeVisible { + _ = try SessionThread + .filter(id: threadId) + .updateAllAndConfig(db, SessionThread.Columns.shouldBeVisible.set(to: true)) + } // Create the interaction let interaction: Interaction = try Interaction( @@ -1301,7 +1305,7 @@ extension ConversationVC: .suffix(19)) .appending(sentTimestamp) } - // TODO: Need to test emoji reacts for both open groups and one-to-one to make sure this isn't broken + // Perform the sending logic Storage.shared .writePublisherFlatMap(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db -> AnyPublisher in @@ -1311,10 +1315,12 @@ extension ConversationVC: .eraseToAnyPublisher() } - // Update the thread to be visible - _ = try SessionThread - .filter(id: thread.id) - .updateAllAndConfig(db, SessionThread.Columns.shouldBeVisible.set(to: true)) + // Update the thread to be visible (if it isn't already) + if !thread.shouldBeVisible { + _ = try SessionThread + .filter(id: thread.id) + .updateAllAndConfig(db, SessionThread.Columns.shouldBeVisible.set(to: true)) + } let pendingReaction: Reaction? = { if remove { diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index b7b6707a0..39a0014d9 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -528,6 +528,25 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl mediaCache.removeAllObjects() hasReloadedThreadDataAfterDisappearance = false viewIsDisappearing = false + + // If the user just created this thread but didn't send a message then we want to delete the + // "shadow" thread since it's not actually in use (this is to prevent it from taking up database + // space or unintentionally getting synced via libSession in the future) + let threadId: String = viewModel.threadData.threadId + + if + viewModel.threadData.threadShouldBeVisible == false && + !SessionUtil.conversationExistsInConfig( + threadId: threadId, + threadVariant: viewModel.threadData.threadVariant + ) + { + Storage.shared.writeAsync { db in + _ = try SessionThread + .filter(id: threadId) + .deleteAll(db) + } + } } @objc func applicationDidBecomeActive(_ notification: Notification) { diff --git a/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift b/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift index 1b7682185..69e6b2495 100644 --- a/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift +++ b/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift @@ -30,6 +30,7 @@ class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel @@ -39,10 +40,12 @@ class ThreadDisappearingMessagesSettingsViewModel: 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 W^4A)xFCBX{>8~|5E**Q_mH!84z5$c~ delta 36082 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 MEuM}q`}ybp2fs7?BLDyZ delta 54450 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 n*Os1o&Sx#KzD{1Y)r|R=9b`C9j{yYB-ZWYP< diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index 540b5cb4c..a490e219d 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -488,43 +488,43 @@ public final class OpenGroupManager { .deleteAll(db) try roomDetails.admins.forEach { adminId in - _ = try GroupMember( + try GroupMember( groupId: threadId, profileId: adminId, role: .admin, isHidden: false - ).saved(db) + ).save(db) } try roomDetails.hiddenAdmins .defaulting(to: []) .forEach { adminId in - _ = try GroupMember( + try GroupMember( groupId: threadId, profileId: adminId, role: .admin, isHidden: true - ).saved(db) + ).save(db) } try roomDetails.moderators.forEach { moderatorId in - _ = try GroupMember( + try GroupMember( groupId: threadId, profileId: moderatorId, role: .moderator, isHidden: false - ).saved(db) + ).save(db) } try roomDetails.hiddenModerators .defaulting(to: []) .forEach { moderatorId in - _ = try GroupMember( + try GroupMember( groupId: threadId, profileId: moderatorId, role: .moderator, isHidden: true - ).saved(db) + ).save(db) } } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift index 5b604c568..dd19bd05b 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift @@ -8,16 +8,56 @@ import SessionUtilitiesKit import SessionSnodeKit extension MessageReceiver { - public static func handleClosedGroupControlMessage(_ db: Database, _ message: ClosedGroupControlMessage) throws { + public static func handleClosedGroupControlMessage( + _ db: Database, + threadId: String, + threadVariant: SessionThread.Variant, + message: ClosedGroupControlMessage + ) throws { switch message.kind { case .new: try handleNewClosedGroup(db, message: message) - case .encryptionKeyPair: try handleClosedGroupEncryptionKeyPair(db, message: message) - case .nameChange: try handleClosedGroupNameChanged(db, message: message) - case .membersAdded: try handleClosedGroupMembersAdded(db, message: message) - case .membersRemoved: try handleClosedGroupMembersRemoved(db, message: message) - case .memberLeft: try handleClosedGroupMemberLeft(db, message: message) - case .encryptionKeyPairRequest: - handleClosedGroupEncryptionKeyPairRequest(db, message: message) // Currently not used + + case .encryptionKeyPair: + try handleClosedGroupEncryptionKeyPair( + db, + threadId: threadId, + threadVariant: threadVariant, + message: message + ) + + case .nameChange: + try handleClosedGroupNameChanged( + db, + threadId: threadId, + threadVariant: threadVariant, + message: message + ) + + case .membersAdded: + try handleClosedGroupMembersAdded( + db, + threadId: threadId, + threadVariant: threadVariant, + message: message + ) + + case .membersRemoved: + try handleClosedGroupMembersRemoved( + db, + threadId: threadId, + threadVariant: threadVariant, + message: message + ) + + case .memberLeft: + try handleClosedGroupMemberLeft( + db, + threadId: threadId, + threadVariant: threadVariant, + message: message + ) + + case .encryptionKeyPairRequest: break // Currently not used default: throw MessageReceiverError.invalidMessage } @@ -39,7 +79,8 @@ extension MessageReceiver { members: membersAsData.map { $0.toHexString() }, admins: adminsAsData.map { $0.toHexString() }, expirationTimer: expirationTimer, - messageSentTimestamp: sentTimestamp + messageSentTimestamp: sentTimestamp, + calledFromConfigHandling: false ) } @@ -51,7 +92,8 @@ extension MessageReceiver { members: [String], admins: [String], expirationTimer: UInt32, - messageSentTimestamp: UInt64 + messageSentTimestamp: UInt64, + calledFromConfigHandling: Bool ) throws { // With new closed groups we only want to create them if the admin creating the closed group is an // approved contact (to prevent spam via closed groups getting around message requests if users are @@ -65,13 +107,14 @@ extension MessageReceiver { } } - guard hasApprovedAdmin else { return } + // If the group came from the updated config handling then it doesn't matter if we + // have an approved admin - we should add it regardless (as it's been synced from + // antoher device) + guard hasApprovedAdmin || calledFromConfigHandling else { return } // Create the group let thread: SessionThread = try SessionThread - .fetchOrCreate(db, id: groupPublicKey, variant: .legacyGroup) - .with(shouldBeVisible: true) - .saved(db) + .fetchOrCreate(db, id: groupPublicKey, variant: .legacyGroup, shouldBeVisible: true) let closedGroup: ClosedGroup = try ClosedGroup( threadId: groupPublicKey, name: name, @@ -103,7 +146,7 @@ extension MessageReceiver { } // Update the DisappearingMessages config - try thread.disappearingMessagesConfiguration + let disappearingConfig: DisappearingMessagesConfiguration = try thread.disappearingMessagesConfiguration .fetchOne(db) .defaulting(to: DisappearingMessagesConfiguration.defaultWith(thread.id)) .with( @@ -113,15 +156,38 @@ extension MessageReceiver { (24 * 60 * 60) ) ) - .save(db) + .saved(db) - // Store the key pair - try ClosedGroupKeyPair( + // Store the key pair if it doesn't already exist + let receivedTimestamp: TimeInterval = (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) + let newKeyPair: ClosedGroupKeyPair = ClosedGroupKeyPair( threadId: groupPublicKey, publicKey: Data(encryptionKeyPair.publicKey), secretKey: Data(encryptionKeyPair.secretKey), - receivedTimestamp: (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) - ).insert(db) + receivedTimestamp: receivedTimestamp + ) + let keyPairExists: Bool = ClosedGroupKeyPair + .filter(ClosedGroupKeyPair.Columns.threadKeyPairHash == newKeyPair.threadKeyPairHash) + .isNotEmpty(db) + + if !keyPairExists { + try newKeyPair.insert(db) + } + + if !calledFromConfigHandling { + // Update libSession + try? SessionUtil.add( + db, + groupPublicKey: groupPublicKey, + name: name, + latestKeyPairPublicKey: Data(encryptionKeyPair.publicKey), + latestKeyPairSecretKey: Data(encryptionKeyPair.secretKey), + latestKeyPairReceivedTimestamp: receivedTimestamp, + disappearingConfig: disappearingConfig, + members: members.asSet(), + admins: admins.asSet() + ) + } // Start polling ClosedGroupPoller.shared.startIfNeeded(for: groupPublicKey) @@ -132,18 +198,24 @@ extension MessageReceiver { /// Extracts and adds the new encryption key pair to our list of key pairs if there is one for our public key, AND the message was /// sent by the group admin. - private static func handleClosedGroupEncryptionKeyPair(_ db: Database, message: ClosedGroupControlMessage) throws { - guard - case let .encryptionKeyPair(explicitGroupPublicKey, wrappers) = message.kind, - let groupPublicKey: String = (explicitGroupPublicKey?.toHexString() ?? message.groupPublicKey) - else { return } + private static func handleClosedGroupEncryptionKeyPair( + _ db: Database, + threadId: String, + threadVariant: SessionThread.Variant, + message: ClosedGroupControlMessage + ) throws { + guard case let .encryptionKeyPair(explicitGroupPublicKey, wrappers) = message.kind else { + return + } + + let groupPublicKey: String = (explicitGroupPublicKey?.toHexString() ?? threadId) + guard let userKeyPair: Box.KeyPair = Identity.fetchUserKeyPair(db) else { return SNLog("Couldn't find user X25519 key pair.") } - guard let thread: SessionThread = try? SessionThread.fetchOne(db, id: groupPublicKey) else { + guard let closedGroup: ClosedGroup = try? ClosedGroup.fetchOne(db, id: groupPublicKey) else { return SNLog("Ignoring closed group encryption key pair for nonexistent group.") } - guard let closedGroup: ClosedGroup = try? thread.closedGroup.fetchOne(db) else { return } guard let groupAdmins: [GroupMember] = try? closedGroup.admins.fetchAll(db) else { return } guard let sender: String = message.sender, groupAdmins.contains(where: { $0.profileId == sender }) else { return SNLog("Ignoring closed group encryption key pair from non-admin.") @@ -203,368 +275,355 @@ extension MessageReceiver { SNLog("Received a new closed group encryption key pair.") } - private static func handleClosedGroupNameChanged(_ db: Database, message: ClosedGroupControlMessage) throws { - guard case let .nameChange(name) = message.kind else { return } + private static func handleClosedGroupNameChanged( + _ db: Database, + threadId: String, + threadVariant: SessionThread.Variant, + message: ClosedGroupControlMessage + ) throws { + guard + let messageKind: ClosedGroupControlMessage.Kind = message.kind, + case let .nameChange(name) = message.kind + else { return } - try performIfValid(db, message: message) { id, sender, thread, closedGroup in - _ = try ClosedGroup - .filter(id: id) - .updateAll(db, ClosedGroup.Columns.name.set(to: name)) - - // Notify the user if needed - guard name != closedGroup.name else { return } - - _ = try Interaction( - serverHash: message.serverHash, - threadId: thread.id, - authorId: sender, - variant: .infoClosedGroupUpdated, - body: ClosedGroupControlMessage.Kind - .nameChange(name: name) - .infoMessage(db, sender: sender), - timestampMs: ( - message.sentTimestamp.map { Int64($0) } ?? - SnodeAPI.currentOffsetTimestampMs() + try processIfValid( + db, + threadId: threadId, + threadVariant: threadVariant, + message: message, + messageKind: messageKind, + infoMessageVariant: .infoClosedGroupUpdated, + legacyGroupChanges: { sender, closedGroup, allMembers in + // Update libSession + try? SessionUtil.update( + db, + groupPublicKey: threadId, + name: name ) - ).inserted(db) - - // Update libSession - try? SessionUtil.update( - db, - groupPublicKey: id, - name: name - ) - } + + _ = try ClosedGroup + .filter(id: threadId) + .updateAll( // Explicit config update so no need to use 'updateAllAndConfig' + db, + ClosedGroup.Columns.name.set(to: name) + ) + } + ) } - private static func handleClosedGroupMembersAdded(_ db: Database, message: ClosedGroupControlMessage) throws { - guard case let .membersAdded(membersAsData) = message.kind else { return } + private static func handleClosedGroupMembersAdded( + _ db: Database, + threadId: String, + threadVariant: SessionThread.Variant, + message: ClosedGroupControlMessage + ) throws { + guard + let messageKind: ClosedGroupControlMessage.Kind = message.kind, + case let .membersAdded(membersAsData) = message.kind + else { return } - try performIfValid(db, message: message) { id, sender, thread, closedGroup in - guard let allGroupMembers: [GroupMember] = try? closedGroup.allMembers.fetchAll(db) else { - return - } - - // Update the group - let addedMembers: [String] = membersAsData.map { $0.toHexString() } - let currentMemberIds: Set = allGroupMembers - .filter { $0.role == .standard } - .map { $0.profileId } - .asSet() - let members: Set = currentMemberIds.union(addedMembers) - - // Create records for any new members - try addedMembers - .filter { !currentMemberIds.contains($0) } - .forEach { memberId in - try GroupMember( - groupId: id, - profileId: memberId, - role: .standard, - isHidden: false - ).insert(db) - } - - // Send the latest encryption key pair to the added members if the current user is - // the admin of the group - // - // This fixes a race condition where: - // • A member removes another member. - // • A member adds someone to the group and sends them the latest group key pair. - // • The admin is offline during all of this. - // • When the admin comes back online they see the member removed message and generate + - // distribute a new key pair, but they don't know about the added member yet. - // • Now they see the member added message. - // - // Without the code below, the added member(s) would never get the key pair that was - // generated by the admin when they saw the member removed message. - let userPublicKey: String = getUserHexEncodedPublicKey(db) - - if allGroupMembers.contains(where: { $0.role == .admin && $0.profileId == userPublicKey }) { - addedMembers.forEach { memberId in - MessageSender.sendLatestEncryptionKeyPair(db, to: memberId, for: id) - } - } - - // Remove any 'zombie' versions of the added members (in case they were re-added) - _ = try GroupMember - .filter(GroupMember.Columns.groupId == id) - .filter(GroupMember.Columns.role == GroupMember.Role.zombie) - .filter(addedMembers.contains(GroupMember.Columns.profileId)) - .deleteAll(db) - - // Notify the user if needed - guard members != currentMemberIds else { return } - - _ = try Interaction( - serverHash: message.serverHash, - threadId: thread.id, - authorId: sender, - variant: .infoClosedGroupUpdated, - body: ClosedGroupControlMessage.Kind - .membersAdded( - members: addedMembers - .asSet() - .subtracting(currentMemberIds) - .map { Data(hex: $0) } - ) - .infoMessage(db, sender: sender), - timestampMs: ( - message.sentTimestamp.map { Int64($0) } ?? - SnodeAPI.currentOffsetTimestampMs() + try processIfValid( + db, + threadId: threadId, + threadVariant: threadVariant, + message: message, + messageKind: messageKind, + infoMessageVariant: .infoClosedGroupUpdated, + legacyGroupChanges: { sender, closedGroup, allMembers in + // Update the group + let addedMembers: [String] = membersAsData.map { $0.toHexString() } + let currentMemberIds: Set = allMembers + .filter { $0.role == .standard } + .map { $0.profileId } + .asSet() + + // Update libSession + try? SessionUtil.update( + db, + groupPublicKey: threadId, + members: allMembers + .filter { $0.role == .standard || $0.role == .zombie } + .map { $0.profileId } + .asSet() + .union(addedMembers), + admins: allMembers + .filter { $0.role == .admin } + .map { $0.profileId } + .asSet() ) - ).inserted(db) - - // Update libSession - try? SessionUtil.update( - db, - groupPublicKey: id, - members: allGroupMembers - .filter { $0.role == .standard || $0.role == .zombie } - .map { $0.profileId } - .asSet() - .union(addedMembers), - admins: allGroupMembers - .filter { $0.role == .admin } - .map { $0.profileId } - .asSet() - ) - } + + // Create records for any new members + try addedMembers + .filter { !currentMemberIds.contains($0) } + .forEach { memberId in + try GroupMember( + groupId: threadId, + profileId: memberId, + role: .standard, + isHidden: false + ).save(db) + } + + // Send the latest encryption key pair to the added members if the current user is + // the admin of the group + // + // This fixes a race condition where: + // • A member removes another member. + // • A member adds someone to the group and sends them the latest group key pair. + // • The admin is offline during all of this. + // • When the admin comes back online they see the member removed message and generate + + // distribute a new key pair, but they don't know about the added member yet. + // • Now they see the member added message. + // + // Without the code below, the added member(s) would never get the key pair that was + // generated by the admin when they saw the member removed message. + let userPublicKey: String = getUserHexEncodedPublicKey(db) + + if allMembers.contains(where: { $0.role == .admin && $0.profileId == userPublicKey }) { + addedMembers.forEach { memberId in + MessageSender.sendLatestEncryptionKeyPair(db, to: memberId, for: threadId) + } + } + + // Remove any 'zombie' versions of the added members (in case they were re-added) + _ = try GroupMember + .filter(GroupMember.Columns.groupId == threadId) + .filter(GroupMember.Columns.role == GroupMember.Role.zombie) + .filter(addedMembers.contains(GroupMember.Columns.profileId)) + .deleteAll(db) + } + ) } - + /// Removes the given members from the group IF /// • it wasn't the admin that was removed (that should happen through a `MEMBER_LEFT` message). /// • the admin sent the message (only the admin can truly remove members). /// If we're among the users that were removed, delete all encryption key pairs and the group public key, unsubscribe /// from push notifications for this closed group, and remove the given members from the zombie list for this group. - private static func handleClosedGroupMembersRemoved(_ db: Database, message: ClosedGroupControlMessage) throws { - guard case let .membersRemoved(membersAsData) = message.kind else { return } + private static func handleClosedGroupMembersRemoved( + _ db: Database, + threadId: String, + threadVariant: SessionThread.Variant, + message: ClosedGroupControlMessage + ) throws { + guard + let messageKind: ClosedGroupControlMessage.Kind = message.kind, + case let .membersRemoved(membersAsData) = messageKind + else { return } - try performIfValid(db, message: message) { id, sender, thread, closedGroup in - // Check that the admin wasn't removed - guard let allGroupMembers: [GroupMember] = try? closedGroup.allMembers.fetchAll(db) else { - return - } - - let removedMembers = membersAsData.map { $0.toHexString() } - let currentMemberIds: Set = allGroupMembers - .filter { $0.role == .standard } - .map { $0.profileId } - .asSet() - let members = currentMemberIds.subtracting(removedMembers) - - guard let firstAdminId: String = allGroupMembers.filter({ $0.role == .admin }).first?.profileId, members.contains(firstAdminId) else { - return SNLog("Ignoring invalid closed group update.") - } - // Check that the message was sent by the group admin - guard allGroupMembers.filter({ $0.role == .admin }).contains(where: { $0.profileId == sender }) else { - return SNLog("Ignoring invalid closed group update.") - } - - // Delete the removed members - try GroupMember - .filter(GroupMember.Columns.groupId == id) - .filter(removedMembers.contains(GroupMember.Columns.profileId)) - .filter([ GroupMember.Role.standard, GroupMember.Role.zombie ].contains(GroupMember.Columns.role)) - .deleteAll(db) - - // If the current user was removed: - // • Stop polling for the group - // • Remove the key pairs associated with the group - // • Notify the PN server - let userPublicKey: String = getUserHexEncodedPublicKey(db) - let wasCurrentUserRemoved: Bool = !members.contains(userPublicKey) - - if wasCurrentUserRemoved { - ClosedGroupPoller.shared.stopPolling(for: id) + let userPublicKey: String = getUserHexEncodedPublicKey(db) + let removedMemberIds: [String] = membersAsData.map { $0.toHexString() } + + try processIfValid( + db, + threadId: threadId, + threadVariant: threadVariant, + message: message, + messageKind: messageKind, + infoMessageVariant: (removedMemberIds.contains(userPublicKey) ? + .infoClosedGroupCurrentUserLeft : + .infoClosedGroupUpdated + ), + legacyGroupChanges: { sender, closedGroup, allMembers in + let removedMembers = membersAsData.map { $0.toHexString() } + let currentMemberIds: Set = allMembers + .filter { $0.role == .standard } + .map { $0.profileId } + .asSet() + let members = currentMemberIds.subtracting(removedMembers) - _ = try closedGroup - .keyPairs + // Check that the group creator is still a member and that the message was + // sent by a group admin + guard + let firstAdminId: String = allMembers.filter({ $0.role == .admin }) + .first? + .profileId, + members.contains(firstAdminId), + allMembers + .filter({ $0.role == .admin }) + .contains(where: { $0.profileId == sender }) + else { return SNLog("Ignoring invalid closed group update.") } + + // Update libSession + try? SessionUtil.update( + db, + groupPublicKey: threadId, + members: allMembers + .filter { $0.role == .standard || $0.role == .zombie } + .map { $0.profileId } + .asSet() + .subtracting(removedMembers), + admins: allMembers + .filter { $0.role == .admin } + .map { $0.profileId } + .asSet() + ) + + // Delete the removed members + try GroupMember + .filter(GroupMember.Columns.groupId == threadId) + .filter(removedMembers.contains(GroupMember.Columns.profileId)) + .filter([ GroupMember.Role.standard, GroupMember.Role.zombie ].contains(GroupMember.Columns.role)) .deleteAll(db) - let _ = PushNotificationAPI.performOperation( - .unsubscribe, - for: id, - publicKey: userPublicKey - ) - } - - // Notify the user if needed - guard members != currentMemberIds else { return } - - _ = try Interaction( - serverHash: message.serverHash, - threadId: thread.id, - authorId: sender, - variant: (wasCurrentUserRemoved ? .infoClosedGroupCurrentUserLeft : .infoClosedGroupUpdated), - body: ClosedGroupControlMessage.Kind - .membersRemoved( - members: removedMembers - .asSet() - .intersection(currentMemberIds) - .map { Data(hex: $0) } + // If the current user was removed: + // • Stop polling for the group + // • Remove the key pairs associated with the group + // • Notify the PN server + let wasCurrentUserRemoved: Bool = !members.contains(userPublicKey) + + if wasCurrentUserRemoved { + ClosedGroupPoller.shared.stopPolling(for: threadId) + + _ = try closedGroup + .keyPairs + .deleteAll(db) + + let _ = PushNotificationAPI.performOperation( + .unsubscribe, + for: threadId, + publicKey: userPublicKey ) - .infoMessage(db, sender: sender), - timestampMs: ( - message.sentTimestamp.map { Int64($0) } ?? - SnodeAPI.currentOffsetTimestampMs() - ) - ).inserted(db) - - // Update libSession - try? SessionUtil.update( - db, - groupPublicKey: id, - members: allGroupMembers - .filter { $0.role == .standard || $0.role == .zombie } - .map { $0.profileId } - .asSet() - .subtracting(removedMembers), - admins: allGroupMembers - .filter { $0.role == .admin } - .map { $0.profileId } - .asSet() - ) - } + } + } + ) } /// If a regular member left: /// • Mark them as a zombie (to be removed by the admin later). /// If the admin left: /// • Unsubscribe from PNs, delete the group public key, etc. as the group will be disbanded. - private static func handleClosedGroupMemberLeft(_ db: Database, message: ClosedGroupControlMessage) throws { - guard case .memberLeft = message.kind else { return } + private static func handleClosedGroupMemberLeft( + _ db: Database, + threadId: String, + threadVariant: SessionThread.Variant, + message: ClosedGroupControlMessage + ) throws { + guard + let messageKind: ClosedGroupControlMessage.Kind = message.kind, + case .memberLeft = messageKind + else { return } - try performIfValid(db, message: message) { id, sender, thread, closedGroup in - guard let allGroupMembers: [GroupMember] = try? closedGroup.allMembers.fetchAll(db) else { - return - } - - let userPublicKey: String = getUserHexEncodedPublicKey(db) - let didAdminLeave: Bool = allGroupMembers.contains(where: { member in - member.role == .admin && member.profileId == sender - }) - let members: [GroupMember] = allGroupMembers.filter { $0.role == .standard } - let membersToRemove: [GroupMember] = members - .filter { member in - didAdminLeave || // If the admin leaves the group is disbanded - member.profileId == sender - } - let updatedMemberIds: Set = members - .map { $0.profileId } - .asSet() - .subtracting(membersToRemove.map { $0.profileId }) - - // Delete the members to remove - try GroupMember - .filter(GroupMember.Columns.groupId == id) - .filter(updatedMemberIds.contains(GroupMember.Columns.profileId)) - .deleteAll(db) - - if didAdminLeave || sender == userPublicKey { - // Remove the group from the database and unsubscribe from PNs - ClosedGroupPoller.shared.stopPolling(for: id) - - _ = try closedGroup - .keyPairs - .deleteAll(db) - - let _ = PushNotificationAPI.performOperation( - .unsubscribe, - for: id, - publicKey: userPublicKey - ) - } - - // Re-add the removed member as a zombie (unless the admin left which disbands the - // group) - if !didAdminLeave { - try GroupMember( - groupId: id, - profileId: sender, - role: .zombie, - isHidden: false - ).insert(db) - } - - // Notify the user if needed - guard updatedMemberIds != Set(members.map { $0.profileId }) else { return } - - _ = try Interaction( - serverHash: message.serverHash, - threadId: thread.id, - authorId: sender, - variant: .infoClosedGroupUpdated, - body: ClosedGroupControlMessage.Kind - .memberLeft - .infoMessage(db, sender: sender), - timestampMs: ( - message.sentTimestamp.map { Int64($0) } ?? - SnodeAPI.currentOffsetTimestampMs() - ) - ).inserted(db) - - // Update libSession - try? SessionUtil.update( - db, - groupPublicKey: id, - members: allGroupMembers - .filter { - ($0.role == .standard || $0.role == .zombie) && - !membersToRemove.contains($0) + try processIfValid( + db, + threadId: threadId, + threadVariant: threadVariant, + message: message, + messageKind: messageKind, + infoMessageVariant: .infoClosedGroupUpdated, + legacyGroupChanges: { sender, closedGroup, allMembers in + let userPublicKey: String = getUserHexEncodedPublicKey(db) + let didAdminLeave: Bool = allMembers.contains(where: { member in + member.role == .admin && member.profileId == sender + }) + let members: [GroupMember] = allMembers.filter { $0.role == .standard } + let membersToRemove: [GroupMember] = members + .filter { member in + didAdminLeave || // If the admin leaves the group is disbanded + member.profileId == sender } - .map { $0.profileId } - .asSet(), - admins: allGroupMembers - .filter { $0.role == .admin } + let updatedMemberIds: Set = members .map { $0.profileId } .asSet() - ) - } - } - - private static func handleClosedGroupEncryptionKeyPairRequest(_ db: Database, message: ClosedGroupControlMessage) { - /* - guard case .encryptionKeyPairRequest = message.kind else { return } - let transaction = transaction as! YapDatabaseReadWriteTransaction - guard let groupPublicKey = message.groupPublicKey else { return } - performIfValid(for: message, using: transaction) { groupID, _, group in - let publicKey = message.sender! - // Guard against self-sends - guard publicKey != getUserHexEncodedPublicKey() else { - return SNLog("Ignoring invalid closed group update.") + .subtracting(membersToRemove.map { $0.profileId }) + + // Update libSession + try? SessionUtil.update( + db, + groupPublicKey: threadId, + members: allMembers + .filter { + ($0.role == .standard || $0.role == .zombie) && + !membersToRemove.contains($0) + } + .map { $0.profileId } + .asSet(), + admins: allMembers + .filter { $0.role == .admin } + .map { $0.profileId } + .asSet() + ) + + // Delete the members to remove + try GroupMember + .filter(GroupMember.Columns.groupId == threadId) + .filter(updatedMemberIds.contains(GroupMember.Columns.profileId)) + .deleteAll(db) + + if didAdminLeave || sender == userPublicKey { + try ClosedGroup.removeKeysAndUnsubscribe( + db, + threadId: threadId, + removeGroupData: false, + calledFromConfigHandling: false + ) + } + + // Re-add the removed member as a zombie (unless the admin left which disbands the + // group) + if !didAdminLeave { + try GroupMember( + groupId: threadId, + profileId: sender, + role: .zombie, + isHidden: false + ).save(db) + } } - MessageSender.sendLatestEncryptionKeyPair(to: publicKey, for: groupPublicKey, using: transaction) - } - */ + ) } // MARK: - Convenience - private static func performIfValid( + private static func processIfValid( _ db: Database, + threadId: String, + threadVariant: SessionThread.Variant, message: ClosedGroupControlMessage, - _ update: (String, String, SessionThread, ClosedGroup - ) throws -> Void) throws { - guard let groupPublicKey: String = message.groupPublicKey else { return } - guard let thread: SessionThread = try? SessionThread.fetchOne(db, id: groupPublicKey) else { - return SNLog("Ignoring closed group update for nonexistent group.") - } - guard let closedGroup: ClosedGroup = try? thread.closedGroup.fetchOne(db) else { return } - - // Check that the message isn't from before the group was created - guard Double(message.sentTimestamp ?? 0) > closedGroup.formationTimestamp else { - return SNLog("Ignoring closed group update from before thread was created.") - } - + messageKind: ClosedGroupControlMessage.Kind, + infoMessageVariant: Interaction.Variant, + legacyGroupChanges: (String, ClosedGroup, [GroupMember]) throws -> () + ) throws { guard let sender: String = message.sender else { return } - guard let members: [GroupMember] = try? closedGroup.members.fetchAll(db) else { return } - - // Check that the sender is a member of the group - guard members.contains(where: { $0.profileId == sender }) else { - return SNLog("Ignoring closed group update from non-member.") + guard let closedGroup: ClosedGroup = try? ClosedGroup.fetchOne(db, id: threadId) else { + return SNLog("Ignoring group update for nonexistent group.") } - try update(groupPublicKey, sender, thread, closedGroup) + // Legacy groups used these control messages for making changes, new groups only use them + // for information purposes + switch threadVariant { + case .legacyGroup: + // Check that the message isn't from before the group was created + guard Double(message.sentTimestamp ?? 0) > closedGroup.formationTimestamp else { + return SNLog("Ignoring legacy group update from before thread was created.") + } + + // If these values are missing then we probably won't be able to validly handle the message + guard + let allMembers: [GroupMember] = try? closedGroup.allMembers.fetchAll(db), + allMembers.contains(where: { $0.profileId == sender }) + else { return SNLog("Ignoring legacy group update from non-member.") } + + try legacyGroupChanges(sender, closedGroup, allMembers) + + case .group: + break + + default: return // Ignore as invalid + } + + // Insert the info message for this group control message + _ = try Interaction( + serverHash: message.serverHash, + threadId: threadId, + authorId: sender, + variant: infoMessageVariant, + body: messageKind + .infoMessage(db, sender: sender), + timestampMs: ( + message.sentTimestamp.map { Int64($0) } ?? + SnodeAPI.currentOffsetTimestampMs() + ) + ).inserted(db) } } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift index eafa7d974..634f0cdd5 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift @@ -189,7 +189,8 @@ extension MessageReceiver { members: [String](closedGroup.members), admins: [String](closedGroup.admins), expirationTimer: closedGroup.expirationTimer, - messageSentTimestamp: message.sentTimestamp! + messageSentTimestamp: message.sentTimestamp!, + calledFromConfigHandling: false // Legacy config isn't an issue ) } } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ExpirationTimers.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ExpirationTimers.swift index 201eccf48..1d8ef1033 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ExpirationTimers.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ExpirationTimers.swift @@ -35,6 +35,19 @@ extension MessageReceiver { ) ) + // Legacy closed groups need to update the SessionUtil + switch threadVariant { + case .legacyGroup: + try SessionUtil + .update( + db, + groupPublicKey: threadId, + disappearingConfig: config + ) + + default: break + } + // Add an info message for the user _ = try Interaction( serverHash: nil, // Intentionally null so sync messages are seen as duplicates diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift index ca1c2ec37..cdd7ff633 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift @@ -65,7 +65,8 @@ extension MessageReceiver { // Loop through all blinded threads and extract any interactions relating to the user accepting // the message request try pendingBlindedIdLookups.forEach { blindedIdLookup in - // If the sessionId matches the blindedId then this thread needs to be converted to an un-blinded thread + // If the sessionId matches the blindedId then this thread needs to be converted to an + // un-blinded thread guard dependencies.sodium.sessionId( senderId, @@ -111,6 +112,9 @@ extension MessageReceiver { .filter(ids: blindedContactIds) .deleteAll(db) + try? SessionUtil + .remove(db, contactIds: blindedContactIds) + try updateContactApprovalStatusIfNeeded( db, senderSessionId: userPublicKey, diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift index 505ae32d9..c2e5ba2c4 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift @@ -46,9 +46,20 @@ extension MessageReceiver { ) } - // Get or create thread - guard let threadInfo: (id: String, variant: SessionThread.Variant) = MessageReceiver.threadInfo(db, message: message, openGroupId: openGroupId) else { - throw MessageReceiverError.noThread + switch threadVariant { + case .contact: break // Always continue + + case .community: + // Only process visible messages for communities if they have an existing thread + guard (try? SessionThread.exists(db, id: threadId)) == true else { + throw MessageReceiverError.noThread + } + + case .legacyGroup, .group: + // Only process visible messages for groups if they have a ClosedGroup record + guard (try? ClosedGroup.exists(db, id: threadId)) == true else { + throw MessageReceiverError.noThread + } } // Store the message variant so we can run variant-specific behaviours diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift index 1d37db55d..6f9e396e0 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift @@ -60,7 +60,7 @@ extension MessageSender { profileId: adminId, role: .admin, isHidden: false - ).insert(db) + ).save(db) } try members.forEach { memberId in @@ -69,7 +69,7 @@ extension MessageSender { profileId: memberId, role: .standard, isHidden: false - ).insert(db) + ).save(db) } // Update libSession @@ -80,6 +80,7 @@ extension MessageSender { latestKeyPairPublicKey: encryptionKeyPair.publicKey, latestKeyPairSecretKey: encryptionKeyPair.privateKey, latestKeyPairReceivedTimestamp: latestKeyPairReceivedTimestamp, + disappearingConfig: DisappearingMessagesConfiguration.defaultWith(groupPublicKey), members: members, admins: admins ) @@ -462,7 +463,7 @@ extension MessageSender { profileId: member, role: .standard, isHidden: false - ).insert(db) + ).save(db) } } @@ -629,12 +630,18 @@ extension MessageSender { } } catch { - try? ClosedGroup.removeKeysAndUnsubscribe( - db, - threadId: groupPublicKey, - removeGroupData: false, - calledFromConfigHandling: false - ) + switch error { + case MessageSenderError.noKeyPair, MessageSenderError.encryptionFailed: + try? ClosedGroup.removeKeysAndUnsubscribe( + db, + threadId: groupPublicKey, + removeGroupData: false, + calledFromConfigHandling: false + ) + + default: break + } + return Fail(error: error) .eraseToAnyPublisher() } diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index 899f2b929..b692dc706 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -281,6 +281,17 @@ public enum MessageReceiver { case is TypingIndicator: break default: + // Only update the `shouldBeVisible` flag if the thread is currently not visible + // as we don't want to trigger a config update if not needed + let isCurrentlyVisible: Bool = try SessionThread + .filter(id: threadId) + .select(.shouldBeVisible) + .asRequest(of: Bool.self) + .fetchOne(db) + .defaulting(to: false) + + guard !isCurrentlyVisible else { return } + try SessionThread .filter(id: threadId) .updateAllAndConfig( diff --git a/SessionMessagingKit/Utilities/ProfileManager.swift b/SessionMessagingKit/Utilities/ProfileManager.swift index faf722a04..74b09a715 100644 --- a/SessionMessagingKit/Utilities/ProfileManager.swift +++ b/SessionMessagingKit/Utilities/ProfileManager.swift @@ -558,23 +558,25 @@ public struct ProfileManager { profileChanges.append(Profile.Columns.profilePictureFileName.set(to: nil)) case .updateTo(let url, let key, let fileName): - if - ( - url != profile.profilePictureUrl || - key != profile.profileEncryptionKey - ) && - key.count == ProfileManager.avatarAES256KeyByteLength && - key != profile.profileEncryptionKey - { + if url != profile.profilePictureUrl { profileChanges.append(Profile.Columns.profilePictureUrl.set(to: url)) - profileChanges.append(Profile.Columns.profileEncryptionKey.set(to: key)) avatarNeedsDownload = true targetAvatarUrl = url } + if key != profile.profileEncryptionKey && key.count == ProfileManager.avatarAES256KeyByteLength { + profileChanges.append(Profile.Columns.profileEncryptionKey.set(to: key)) + } + // Profile filename (this isn't synchronized between devices) if let fileName: String = fileName { profileChanges.append(Profile.Columns.profilePictureFileName.set(to: fileName)) + + // If we have already downloaded the image then no need to download it again + avatarNeedsDownload = ( + avatarNeedsDownload && + !ProfileManager.hasProfileImageData(with: fileName) + ) } } } diff --git a/SessionShareExtension/Base.lproj/MainInterface.storyboard b/SessionShareExtension/Base.lproj/MainInterface.storyboard index b2f1bc5ef..d34ca84c3 100644 --- a/SessionShareExtension/Base.lproj/MainInterface.storyboard +++ b/SessionShareExtension/Base.lproj/MainInterface.storyboard @@ -1,17 +1,17 @@ - + - + - + - + diff --git a/SessionUtilitiesKit/Database/Utilities/Database+Utilities.swift b/SessionUtilitiesKit/Database/Utilities/Database+Utilities.swift index ae921fb3f..53c30f9c6 100644 --- a/SessionUtilitiesKit/Database/Utilities/Database+Utilities.swift +++ b/SessionUtilitiesKit/Database/Utilities/Database+Utilities.swift @@ -27,6 +27,10 @@ public extension Database { } } + func drop(table: T.Type) throws where T: TableRecord { + try drop(table: T.databaseTableName) + } + func createIndex( withCustomName customName: String? = nil, on table: T.Type, From 499b20db6d9f91a37c947825335712af7f73742d Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Mon, 6 Mar 2023 17:53:30 +1100 Subject: [PATCH 036/135] Fixed a couple of bugs found during testing Removed image dimension byte checking for attachments (we have file size checks already) Fixed a couple of bugs where the message status for uploading attachments wasn't reflecting the current status Fixed a bug where the conversation 'jump to bottom' was jumping to the bottom of the current page instead of to the end of the conversation --- .../Jobs/Types/AttachmentUploadJob.swift | 43 ++++++++++++++++++- .../Jobs/Types/MessageSendJob.swift | 11 ++--- .../MessageReceiver+ClosedGroups.swift | 7 +-- .../Sending & Receiving/MessageSender.swift | 2 +- .../SessionThreadViewModel.swift | 3 +- .../Utilities/ProfileManager.swift | 2 +- SessionUtilitiesKit/Media/NSData+Image.m | 11 +---- 7 files changed, 56 insertions(+), 23 deletions(-) diff --git a/SessionMessagingKit/Jobs/Types/AttachmentUploadJob.swift b/SessionMessagingKit/Jobs/Types/AttachmentUploadJob.swift index 9b1a8cfec..4b99eb8fd 100644 --- a/SessionMessagingKit/Jobs/Types/AttachmentUploadJob.swift +++ b/SessionMessagingKit/Jobs/Types/AttachmentUploadJob.swift @@ -48,6 +48,25 @@ public enum AttachmentUploadJob: JobExecutor { return } + // If this upload is related to sending a message then trigger the 'handleMessageWillSend' logic + // as if this is a retry the logic wouldn't run until after the upload has completed resulting in + // a potentially incorrect delivery status + Storage.shared.write { db in + guard + let sendJob: Job = try Job.fetchOne(db, id: details.messageSendJobId), + let sendJobDetails: Data = sendJob.details, + let details: MessageSendJob.Details = try? JSONDecoder() + .decode(MessageSendJob.Details.self, from: sendJobDetails) + else { return } + + MessageSender.handleMessageWillSend( + db, + message: details.message, + interactionId: interactionId, + isSyncMessage: details.isSyncMessage + ) + } + // Note: In the AttachmentUploadJob we intentionally don't provide our own db instance to prevent // reentrancy issues when the success/failure closures get called before the upload as the JobRunner // will attempt to update the state of the job immediately @@ -58,7 +77,29 @@ public enum AttachmentUploadJob: JobExecutor { .sinkUntilComplete( receiveCompletion: { result in switch result { - case .failure(let error): failure(job, error, false) + case .failure(let error): + // If this upload is related to sending a message then trigger the + // 'handleFailedMessageSend' logic as we want to ensure the message + // has the correct delivery status + Storage.shared.read { db in + guard + let sendJob: Job = try Job.fetchOne(db, id: details.messageSendJobId), + let sendJobDetails: Data = sendJob.details, + let details: MessageSendJob.Details = try? JSONDecoder() + .decode(MessageSendJob.Details.self, from: sendJobDetails) + else { return } + + MessageSender.handleFailedMessageSend( + db, + message: details.message, + with: .other(error), + interactionId: interactionId, + isSyncMessage: details.isSyncMessage + ) + } + + failure(job, error, false) + case .finished: success(job, false) } } diff --git a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift index 18b990112..04608630f 100644 --- a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift +++ b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift @@ -168,7 +168,8 @@ public enum MessageSendJob: JobExecutor { message: details.message, to: details.destination, namespace: details.destination.defaultNamespace, - interactionId: job.interactionId + interactionId: job.interactionId, + isSyncMessage: details.isSyncMessage ) } .map { sendData in sendData.with(fileIds: messageFileIds) } @@ -227,7 +228,7 @@ extension MessageSendJob { public let destination: Message.Destination public let message: Message - public let isSyncMessage: Bool? + public let isSyncMessage: Bool public let variant: Message.Variant? // MARK: - Initialization @@ -235,7 +236,7 @@ extension MessageSendJob { public init( destination: Message.Destination, message: Message, - isSyncMessage: Bool? = nil + isSyncMessage: Bool = false ) { self.destination = destination self.message = message @@ -256,7 +257,7 @@ extension MessageSendJob { self = Details( destination: try container.decode(Message.Destination.self, forKey: .destination), message: try variant.decode(from: container, forKey: .message), - isSyncMessage: try? container.decode(Bool.self, forKey: .isSyncMessage) + isSyncMessage: ((try? container.decode(Bool.self, forKey: .isSyncMessage)) ?? false) ) } @@ -270,7 +271,7 @@ extension MessageSendJob { try container.encode(destination, forKey: .destination) try container.encode(message, forKey: .message) - try container.encodeIfPresent(isSyncMessage, forKey: .isSyncMessage) + try container.encode(isSyncMessage, forKey: .isSyncMessage) try container.encode(variant, forKey: .variant) } } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift index dd19bd05b..8390da206 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift @@ -522,10 +522,7 @@ extension MessageReceiver { didAdminLeave || // If the admin leaves the group is disbanded member.profileId == sender } - let updatedMemberIds: Set = members - .map { $0.profileId } - .asSet() - .subtracting(membersToRemove.map { $0.profileId }) + let memberIdsToRemove: [String] = members.map { $0.profileId } // Update libSession try? SessionUtil.update( @@ -547,7 +544,7 @@ extension MessageReceiver { // Delete the members to remove try GroupMember .filter(GroupMember.Columns.groupId == threadId) - .filter(updatedMemberIds.contains(GroupMember.Columns.profileId)) + .filter(memberIdsToRemove.contains(GroupMember.Columns.profileId)) .deleteAll(db) if didAdminLeave || sender == userPublicKey { diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index a96288d71..cf9adf92b 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -986,7 +986,7 @@ public final class MessageSender { ) } - @discardableResult private static func handleFailedMessageSend( + @discardableResult internal static func handleFailedMessageSend( _ db: Database, message: Message, with error: MessageSenderError, diff --git a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift index b613ba67f..9ee596b59 100644 --- a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift +++ b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift @@ -861,6 +861,7 @@ public extension SessionThreadViewModel { \(openGroup[.permissions]) AS \(ViewModel.openGroupPermissionsKey), \(Interaction.self).\(ViewModel.interactionIdKey), + \(Interaction.self).\(ViewModel.interactionTimestampMsKey), \(SQL("\(userPublicKey)")) AS \(ViewModel.currentUserPublicKeyKey) @@ -871,7 +872,7 @@ public extension SessionThreadViewModel { SELECT \(interaction[.id]) AS \(ViewModel.interactionIdKey), \(interaction[.threadId]), - MAX(\(interaction[.timestampMs])), + MAX(\(interaction[.timestampMs])) AS \(ViewModel.interactionTimestampMsKey), SUM(\(interaction[.wasRead]) = false) AS \(ViewModel.threadUnreadCountKey) diff --git a/SessionMessagingKit/Utilities/ProfileManager.swift b/SessionMessagingKit/Utilities/ProfileManager.swift index 74b09a715..becfb80a2 100644 --- a/SessionMessagingKit/Utilities/ProfileManager.swift +++ b/SessionMessagingKit/Utilities/ProfileManager.swift @@ -34,6 +34,7 @@ public struct ProfileManager { // Before encrypting and submitting we NULL pad the name data to this length. private static let nameDataLength: UInt = 64 public static let maxAvatarDiameter: CGFloat = 640 + private static let maxAvatarBytes: UInt = (5 * 1000 * 1000) public static let avatarAES256KeyByteLength: Int = 32 private static let avatarNonceLength: Int = 12 private static let avatarTagLength: Int = 16 @@ -370,7 +371,6 @@ public struct ProfileManager { // If the profile avatar was updated or removed then encrypt with a new profile key // to ensure that other users know that our profile picture was updated let newProfileKey: Data - let maxAvatarBytes: UInt = (5 * 1000 * 1000) let avatarImageData: Data? do { diff --git a/SessionUtilitiesKit/Media/NSData+Image.m b/SessionUtilitiesKit/Media/NSData+Image.m index fb13b9d20..cda178f5e 100644 --- a/SessionUtilitiesKit/Media/NSData+Image.m +++ b/SessionUtilitiesKit/Media/NSData+Image.m @@ -157,14 +157,6 @@ typedef struct { return CGSizeZero; } - const CGFloat kExpectedBytePerPixel = 4; - CGFloat kMaxValidImageDimension = OWSMediaUtils.kMaxAnimatedImageDimensions; - CGFloat kMaxBytes = kMaxValidImageDimension * kMaxValidImageDimension * kExpectedBytePerPixel; - - if (data.length > kMaxBytes) { - return CGSizeZero; - } - return imageSize; } @@ -176,7 +168,8 @@ typedef struct { ImageDimensionInfo dimensionInfo = [self ows_imageDimensionWithImageSource:imageSource isAnimated:isAnimated]; CFRelease(imageSource); - if (![self ows_isValidImageDimension:dimensionInfo.pixelSize depthBytes:dimensionInfo.depthBytes isAnimated:isAnimated]) { + if (dimensionInfo.pixelSize.width < 1 || dimensionInfo.pixelSize.height < 1 || dimensionInfo.depthBytes < 1) { + // Invalid metadata. return CGSizeZero; } From 972519d7d915d6d8afe0e1fb89e1243f77387196 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Mon, 6 Mar 2023 18:11:10 +1100 Subject: [PATCH 037/135] Fixed a couple of build issues --- Session/Conversations/Settings/ThreadSettingsViewModel.swift | 5 ++++- .../Settings/ThreadDisappearingMessagesViewModelSpec.swift | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Session/Conversations/Settings/ThreadSettingsViewModel.swift b/Session/Conversations/Settings/ThreadSettingsViewModel.swift index 96d3d58ca..59c86b887 100644 --- a/Session/Conversations/Settings/ThreadSettingsViewModel.swift +++ b/Session/Conversations/Settings/ThreadSettingsViewModel.swift @@ -222,7 +222,10 @@ class ThreadSettingsViewModel: SessionTableViewModel Date: Wed, 8 Mar 2023 17:27:07 +1100 Subject: [PATCH 038/135] Finished off a few remaining bits and pieces Added the 'outdated client' warning banner Added a unit test to validate the 'group(by:)' method maintains ordering Added an error when trying to message a non-standard session id directly Removed the "hide" logic for groups (don't want it) Removed some unneeded thread fetching Updated the logic to use the 'lastHash' when fetching config messages Updated the logic to use the libSession value restrictions instead of hard-coded values Fixed an issue where members weren't getting removed from legacy groups --- Session.xcodeproj/project.pbxproj | 20 +- Session/Closed Groups/EditClosedGroupVC.swift | 2 +- Session/Closed Groups/NewClosedGroupVC.swift | 2 +- .../ConversationVC+Interaction.swift | 108 ++--- ...isappearingMessagesSettingsViewModel.swift | 7 +- .../Settings/ThreadSettingsViewModel.swift | 3 +- Session/Home/HomeVC.swift | 409 +++++++++++------- Session/Home/New Conversation/NewDMVC.swift | 43 +- .../MediaPageViewController.swift | 10 +- Session/Meta/AppDelegate.swift | 28 +- .../Translations/de.lproj/Localizable.strings | 1 + .../Translations/en.lproj/Localizable.strings | 1 + .../Translations/es.lproj/Localizable.strings | 1 + .../Translations/fa.lproj/Localizable.strings | 1 + .../Translations/fi.lproj/Localizable.strings | 1 + .../Translations/fr.lproj/Localizable.strings | 1 + .../Translations/hi.lproj/Localizable.strings | 1 + .../Translations/hr.lproj/Localizable.strings | 1 + .../id-ID.lproj/Localizable.strings | 1 + .../Translations/it.lproj/Localizable.strings | 1 + .../Translations/ja.lproj/Localizable.strings | 1 + .../Translations/nl.lproj/Localizable.strings | 1 + .../Translations/pl.lproj/Localizable.strings | 1 + .../pt_BR.lproj/Localizable.strings | 1 + .../Translations/ru.lproj/Localizable.strings | 1 + .../Translations/si.lproj/Localizable.strings | 1 + .../Translations/sk.lproj/Localizable.strings | 1 + .../Translations/sv.lproj/Localizable.strings | 1 + .../Translations/th.lproj/Localizable.strings | 1 + .../vi-VN.lproj/Localizable.strings | 1 + .../zh-Hant.lproj/Localizable.strings | 1 + .../zh_CN.lproj/Localizable.strings | 1 + Session/Notifications/AppNotifications.swift | 3 +- Session/Onboarding/RegisterVC.swift | 3 +- SessionMessagingKit/Calls/WebRTCSession.swift | 30 +- SessionMessagingKit/Configuration.swift | 2 +- ...es.swift => _012_SessionUtilChanges.swift} | 24 +- .../_013_GenerateInitialUserConfigDumps.swift | 10 +- .../SessionUtil+Contacts.swift | 69 ++- .../SessionUtil+ConvoInfoVolatile.swift | 25 +- .../Config Handling/SessionUtil+Shared.swift | 4 +- .../SessionUtil+UserGroups.swift | 152 ++++--- .../LibSessionUtil/SessionUtil.swift | 4 +- .../Messages/Message+Destination.swift | 17 +- SessionMessagingKit/Messages/Message.swift | 11 +- .../MessageReceiver+Calls.swift | 6 +- .../MessageReceiver+ClosedGroups.swift | 12 +- ...essageReceiver+ConfigurationMessages.swift | 5 +- .../MessageReceiver+MessageRequests.swift | 3 - .../MessageSender+ClosedGroups.swift | 76 ++-- .../Sending & Receiving/MessageReceiver.swift | 2 +- .../MessageSender+Convenience.swift | 45 +- .../Typing Indicators/TypingIndicators.swift | 16 +- .../Utilities/ProfileManager.swift | 7 +- SessionShareExtension/ThreadPickerVC.swift | 13 +- SessionSnodeKit/Models/SnodeMessage.swift | 2 - SessionSnodeKit/Networking/SnodeAPI.swift | 4 +- SessionSnodeKit/Types/SnodeAPINamespace.swift | 10 +- .../Components/TopBannerController.swift | 188 ++++++++ .../Utilities/UIView+Constraints.swift | 24 + .../Crypto/Data+SecureRandom.swift | 15 - .../General/SNUserDefaults.swift | 1 + .../Utilities/Randomness.swift | 10 +- .../General/ArrayUtilitiesSpec.swift | 86 ++++ 64 files changed, 1022 insertions(+), 511 deletions(-) rename SessionMessagingKit/Database/Migrations/{_012_SharedUtilChanges.swift => _012_SessionUtilChanges.swift} (86%) create mode 100644 SessionUIKit/Components/TopBannerController.swift delete mode 100644 SessionUtilitiesKit/Crypto/Data+SecureRandom.swift create mode 100644 SessionUtilitiesKitTests/General/ArrayUtilitiesSpec.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 130e9dd85..f74fe6670 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -449,7 +449,6 @@ C3C2A74D2553A39700C340D1 /* VisibleMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A74C2553A39700C340D1 /* VisibleMessage.swift */; }; C3C2A75F2553A3C500C340D1 /* VisibleMessage+LinkPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A75E2553A3C500C340D1 /* VisibleMessage+LinkPreview.swift */; }; C3C2A7852553AAF300C340D1 /* SessionProtos.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A7832553AAF300C340D1 /* SessionProtos.pb.swift */; }; - C3C2ABD22553C6C900C340D1 /* Data+SecureRandom.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2ABD12553C6C900C340D1 /* Data+SecureRandom.swift */; }; C3C2AC2E2553CBEB00C340D1 /* String+Trimming.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2AC2D2553CBEB00C340D1 /* String+Trimming.swift */; }; C3CA3AA2255CDADA00F4C6D4 /* english.txt in Resources */ = {isa = PBXBuildFile; fileRef = C3CA3AA1255CDADA00F4C6D4 /* english.txt */; }; C3CA3AB4255CDAE600F4C6D4 /* japanese.txt in Resources */ = {isa = PBXBuildFile; fileRef = C3CA3AB3255CDAE600F4C6D4 /* japanese.txt */; }; @@ -522,6 +521,8 @@ FD09C5E828264937000CE219 /* MediaDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09C5E728264937000CE219 /* MediaDetailViewController.swift */; }; FD09C5EA282A1BB2000CE219 /* ThreadTypingIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09C5E9282A1BB2000CE219 /* ThreadTypingIndicator.swift */; }; FD09C5EC282B8F18000CE219 /* AttachmentError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09C5EB282B8F17000CE219 /* AttachmentError.swift */; }; + FD0B77B029B69A65009169BA /* TopBannerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0B77AF29B69A65009169BA /* TopBannerController.swift */; }; + FD0B77B229B82B7A009169BA /* ArrayUtilitiesSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0B77B129B82B7A009169BA /* ArrayUtilitiesSpec.swift */; }; FD17D79927F40AB800122BE0 /* _003_YDBToGRDBMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79827F40AB800122BE0 /* _003_YDBToGRDBMigration.swift */; }; FD17D79C27F40B2E00122BE0 /* SMKLegacy.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79B27F40B2E00122BE0 /* SMKLegacy.swift */; }; FD17D7A027F40CC800122BE0 /* _001_InitialSetupMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79F27F40CC800122BE0 /* _001_InitialSetupMigration.swift */; }; @@ -737,7 +738,7 @@ FD87DD0428B8727D00AF0F98 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD87DD0328B8727D00AF0F98 /* Configuration.swift */; }; FD8ECF7929340F7200C0D1BB /* libsession-util.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FD8ECF7829340F7100C0D1BB /* libsession-util.xcframework */; }; FD8ECF7B29340FFD00C0D1BB /* SessionUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7A29340FFD00C0D1BB /* SessionUtil.swift */; }; - FD8ECF7D2934293A00C0D1BB /* _012_SharedUtilChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7C2934293A00C0D1BB /* _012_SharedUtilChanges.swift */; }; + FD8ECF7D2934293A00C0D1BB /* _012_SessionUtilChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7C2934293A00C0D1BB /* _012_SessionUtilChanges.swift */; }; FD8ECF7F2934298100C0D1BB /* SharedConfigDump.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7E2934298100C0D1BB /* SharedConfigDump.swift */; }; FD8ECF822934387A00C0D1BB /* ConfigUserProfileSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF812934387A00C0D1BB /* ConfigUserProfileSpec.swift */; }; FD8ECF892935AB7200C0D1BB /* SessionUtilError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF882935AB7200C0D1BB /* SessionUtilError.swift */; }; @@ -1622,7 +1623,6 @@ C3C2A7702553A41E00C340D1 /* ControlMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlMessage.swift; sourceTree = ""; }; C3C2A7822553AAF200C340D1 /* SNProto.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SNProto.swift; sourceTree = ""; }; C3C2A7832553AAF300C340D1 /* SessionProtos.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionProtos.pb.swift; sourceTree = ""; }; - C3C2ABD12553C6C900C340D1 /* Data+SecureRandom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+SecureRandom.swift"; sourceTree = ""; }; C3C2AC2D2553CBEB00C340D1 /* String+Trimming.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Trimming.swift"; sourceTree = ""; }; C3C3CF8824D8EED300E1CCE7 /* TextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextView.swift; sourceTree = ""; }; C3CA3AA1255CDADA00F4C6D4 /* english.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = english.txt; sourceTree = ""; }; @@ -1693,6 +1693,8 @@ FD09C5E728264937000CE219 /* MediaDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaDetailViewController.swift; sourceTree = ""; }; FD09C5E9282A1BB2000CE219 /* ThreadTypingIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadTypingIndicator.swift; sourceTree = ""; }; FD09C5EB282B8F17000CE219 /* AttachmentError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentError.swift; sourceTree = ""; }; + FD0B77AF29B69A65009169BA /* TopBannerController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopBannerController.swift; sourceTree = ""; }; + FD0B77B129B82B7A009169BA /* ArrayUtilitiesSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayUtilitiesSpec.swift; sourceTree = ""; }; FD17D79527F3E04600122BE0 /* _001_InitialSetupMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _001_InitialSetupMigration.swift; sourceTree = ""; }; FD17D79827F40AB800122BE0 /* _003_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _003_YDBToGRDBMigration.swift; sourceTree = ""; }; FD17D79B27F40B2E00122BE0 /* SMKLegacy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMKLegacy.swift; sourceTree = ""; }; @@ -1867,7 +1869,7 @@ FD87DD0328B8727D00AF0F98 /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; FD8ECF7829340F7100C0D1BB /* libsession-util.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = "libsession-util.xcframework"; sourceTree = ""; }; FD8ECF7A29340FFD00C0D1BB /* SessionUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionUtil.swift; sourceTree = ""; }; - FD8ECF7C2934293A00C0D1BB /* _012_SharedUtilChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _012_SharedUtilChanges.swift; sourceTree = ""; }; + FD8ECF7C2934293A00C0D1BB /* _012_SessionUtilChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _012_SessionUtilChanges.swift; sourceTree = ""; }; FD8ECF7E2934298100C0D1BB /* SharedConfigDump.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedConfigDump.swift; sourceTree = ""; }; FD8ECF812934387A00C0D1BB /* ConfigUserProfileSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigUserProfileSpec.swift; sourceTree = ""; }; FD8ECF882935AB7200C0D1BB /* SessionUtilError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionUtilError.swift; sourceTree = ""; }; @@ -2541,7 +2543,6 @@ isa = PBXGroup; children = ( FDE658A029418C7900A33BC1 /* CryptoKit+Utilities.swift */, - C3C2ABD12553C6C900C340D1 /* Data+SecureRandom.swift */, B88FA7FA26114EA70049422F /* Hex.swift */, FDE658A229418E2F00A33BC1 /* KeyPair.swift */, C3A71F882558BA9F0043A11F /* Mnemonic.swift */, @@ -2928,6 +2929,7 @@ B86BD08323399ACF000F5AE3 /* Modal.swift */, FD52090628B49738006098F6 /* ConfirmationModal.swift */, FD71165A28E6DDBC00B47552 /* StyledNavigationController.swift */, + FD0B77AF29B69A65009169BA /* TopBannerController.swift */, ); path = Components; sourceTree = ""; @@ -3630,7 +3632,7 @@ 7BAA7B6528D2DE4700AE1489 /* _009_OpenGroupPermission.swift */, FD7115F128C6CB3900B47552 /* _010_AddThreadIdToFTS.swift */, FD432431299C6933008A0213 /* _011_AddPendingReadReceipts.swift */, - FD8ECF7C2934293A00C0D1BB /* _012_SharedUtilChanges.swift */, + FD8ECF7C2934293A00C0D1BB /* _012_SessionUtilChanges.swift */, FD778B6329B189FF001BAC6B /* _013_GenerateInitialUserConfigDumps.swift */, ); path = Migrations; @@ -4013,6 +4015,7 @@ FD83B9B927CF20A5005E1583 /* General */ = { isa = PBXGroup; children = ( + FD0B77B129B82B7A009169BA /* ArrayUtilitiesSpec.swift */, FD83B9BA27CF20AF005E1583 /* SessionIdSpec.swift */, ); path = General; @@ -5363,6 +5366,7 @@ FD37EA0128A60473003AE748 /* UIKit+Theme.swift in Sources */, FD37E9CF28A1EB1B003AE748 /* Theme.swift in Sources */, C331FFB92558FA8D00070591 /* UIView+Constraints.swift in Sources */, + FD0B77B029B69A65009169BA /* TopBannerController.swift in Sources */, FD37E9F628A5F106003AE748 /* Configuration.swift in Sources */, FDBB25E72988BBBE00F1508E /* UIContextualAction+Theming.swift in Sources */, C331FFE02558FB0000070591 /* SearchBar.swift in Sources */, @@ -5555,7 +5559,6 @@ FDBB25E32988B13800F1508E /* _004_AddJobPriority.swift in Sources */, C352A36D2557858E00338F3E /* NSTimer+Proxying.m in Sources */, 7B7CB192271508AD0079FF93 /* CallRingTonePlayer.swift in Sources */, - C3C2ABD22553C6C900C340D1 /* Data+SecureRandom.swift in Sources */, FD848B8B283DC509000E298B /* PagedDatabaseObserver.swift in Sources */, B8856E09256F1676001CE70E /* UIDevice+featureSupport.swift in Sources */, B8856DEF256F161F001CE70E /* NSString+SSK.m in Sources */, @@ -5749,7 +5752,7 @@ FD43EE9F297E2EE0009C87C5 /* SessionUtil+ConvoInfoVolatile.swift in Sources */, B8EB20EE2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift in Sources */, FDF8487F29405994007DCAE5 /* HTTPHeader+OpenGroup.swift in Sources */, - FD8ECF7D2934293A00C0D1BB /* _012_SharedUtilChanges.swift in Sources */, + FD8ECF7D2934293A00C0D1BB /* _012_SessionUtilChanges.swift in Sources */, FD17D7A227F40F0500122BE0 /* _001_InitialSetupMigration.swift in Sources */, FD245C5D2850660F00B966DD /* OWSAudioPlayer.m in Sources */, FDF0B7582807F368004C14C5 /* MessageReceiverError.swift in Sources */, @@ -6073,6 +6076,7 @@ FD2AAAEE28ED3E1100A49611 /* MockGeneralCache.swift in Sources */, FD9B30F3293EA0BF008DEE3E /* BatchResponseSpec.swift in Sources */, FD37EA1528AB42CB003AE748 /* IdentitySpec.swift in Sources */, + FD0B77B229B82B7A009169BA /* ArrayUtilitiesSpec.swift in Sources */, FD1A94FE2900D2EA000D73D3 /* PersistableRecordUtilitiesSpec.swift in Sources */, FDC290AA27D9B6FD005DAE71 /* Mock.swift in Sources */, ); diff --git a/Session/Closed Groups/EditClosedGroupVC.swift b/Session/Closed Groups/EditClosedGroupVC.swift index c215c86f0..345f98d59 100644 --- a/Session/Closed Groups/EditClosedGroupVC.swift +++ b/Session/Closed Groups/EditClosedGroupVC.swift @@ -346,7 +346,7 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat guard !updatedName.isEmpty else { return showError(title: "vc_create_closed_group_group_name_missing_error".localized()) } - guard updatedName.count < 64 else { + guard updatedName.utf8CString.count < SessionUtil.libSessionMaxGroupNameByteLength else { return showError(title: "vc_create_closed_group_group_name_too_long_error".localized()) } diff --git a/Session/Closed Groups/NewClosedGroupVC.swift b/Session/Closed Groups/NewClosedGroupVC.swift index c123794a5..a55a1c16a 100644 --- a/Session/Closed Groups/NewClosedGroupVC.swift +++ b/Session/Closed Groups/NewClosedGroupVC.swift @@ -320,7 +320,7 @@ final class NewClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegate else { return showError(title: "vc_create_closed_group_group_name_missing_error".localized()) } - guard name.count < 30 else { + guard name.utf8CString.count < SessionUtil.libSessionMaxGroupNameByteLength else { return showError(title: "vc_create_closed_group_group_name_too_long_error".localized()) } guard selectedContacts.count >= 1 else { diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index c6182ebd0..f2895a882 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -431,6 +431,7 @@ extension ConversationVC: // use it to determine if the user is creating a new thread and update the 'isApproved' // flags appropriately let threadId: String = self.viewModel.threadData.threadId + let threadVariant: SessionThread.Variant = self.viewModel.threadData.threadVariant let oldThreadShouldBeVisible: Bool = (self.viewModel.threadData.threadShouldBeVisible == true) let sentTimestampMs: Int64 = SnodeAPI.currentOffsetTimestampMs() let linkPreviewDraft: LinkPreviewDraft? = snInputView.linkPreviewInfo?.draft @@ -447,15 +448,11 @@ extension ConversationVC: // Send the message Storage.shared .writePublisher(receiveOn: DispatchQueue.global(qos: .userInitiated)) { [weak self] db in - guard let thread: SessionThread = try SessionThread.fetchOne(db, id: threadId) else { - return - } - // Let the viewModel know we are about to send a message self?.viewModel.sentMessageBeforeUpdate = true // Update the thread to be visible (if it isn't already) - if !thread.shouldBeVisible { + if self?.viewModel.threadData.threadShouldBeVisible == false { _ = try SessionThread .filter(id: threadId) .updateAllAndConfig(db, SessionThread.Columns.shouldBeVisible.set(to: true)) @@ -515,7 +512,8 @@ extension ConversationVC: try MessageSender.send( db, interaction: interaction, - in: thread + threadId: threadId, + threadVariant: threadVariant ) } .sinkUntilComplete( @@ -566,6 +564,7 @@ extension ConversationVC: // use it to determine if the user is creating a new thread and update the 'isApproved' // flags appropriately let threadId: String = self.viewModel.threadData.threadId + let threadVariant: SessionThread.Variant = self.viewModel.threadData.threadVariant let oldThreadShouldBeVisible: Bool = (self.viewModel.threadData.threadShouldBeVisible == true) let sentTimestampMs: Int64 = SnodeAPI.currentOffsetTimestampMs() @@ -580,15 +579,11 @@ extension ConversationVC: // Send the message Storage.shared .writePublisher(receiveOn: DispatchQueue.global(qos: .userInitiated)) { [weak self] db in - guard let thread: SessionThread = try SessionThread.fetchOne(db, id: threadId) else { - return - } - // Let the viewModel know we are about to send a message self?.viewModel.sentMessageBeforeUpdate = true // Update the thread to be visible (if it isn't already) - if !thread.shouldBeVisible { + if self?.viewModel.threadData.threadShouldBeVisible == false { _ = try SessionThread .filter(id: threadId) .updateAllAndConfig(db, SessionThread.Columns.shouldBeVisible.set(to: true)) @@ -621,13 +616,13 @@ extension ConversationVC: for: interactionId ) - // Prepare the message send data - try MessageSender - .send( - db, - interaction: interaction, - in: thread - ) + // Send the message + try MessageSender.send( + db, + interaction: interaction, + threadId: threadId, + threadVariant: threadVariant + ) } .sinkUntilComplete( receiveCompletion: { [weak self] _ in @@ -1308,17 +1303,11 @@ extension ConversationVC: // Perform the sending logic Storage.shared - .writePublisherFlatMap(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db -> AnyPublisher in - guard let thread: SessionThread = try SessionThread.fetchOne(db, id: cellViewModel.threadId) else { - return Just(nil) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - } - + .writePublisherFlatMap(receiveOn: DispatchQueue.global(qos: .userInitiated)) { [weak self] db -> AnyPublisher in // Update the thread to be visible (if it isn't already) - if !thread.shouldBeVisible { + if self?.viewModel.threadData.threadShouldBeVisible == false { _ = try SessionThread - .filter(id: thread.id) + .filter(id: cellViewModel.threadId) .updateAllAndConfig(db, SessionThread.Columns.shouldBeVisible.set(to: true)) } @@ -1386,8 +1375,11 @@ extension ConversationVC: kind: (remove ? .remove : .react) ) ), - to: try Message.Destination.from(db, thread: thread), - namespace: try Message.Destination.from(db, thread: thread).defaultNamespace, + to: try Message.Destination + .from(db, threadId: cellViewModel.threadId, threadVariant: cellViewModel.threadVariant), + namespace: try Message.Destination + .from(db, threadId: cellViewModel.threadId, threadVariant: cellViewModel.threadVariant) + .defaultNamespace, interactionId: cellViewModel.id ) @@ -1640,8 +1632,8 @@ extension ConversationVC: Storage.shared.writeAsync { [weak self] db in guard let threadId: String = self?.viewModel.threadData.threadId, - let interaction: Interaction = try? Interaction.fetchOne(db, id: cellViewModel.id), - let thread: SessionThread = try SessionThread.fetchOne(db, id: threadId) + let threadVariant: SessionThread.Variant = self?.viewModel.threadData.threadVariant, + let interaction: Interaction = try? Interaction.fetchOne(db, id: cellViewModel.id) else { return } if @@ -1664,7 +1656,8 @@ extension ConversationVC: try MessageSender.send( db, interaction: interaction, - in: thread, + threadId: threadId, + threadVariant: threadVariant, isSyncMessage: (cellViewModel.state == .failedToSync) ) } @@ -1749,7 +1742,6 @@ extension ConversationVC: case .standardOutgoing, .standardIncoming: break } - let threadId: String = self.viewModel.threadData.threadId let threadName: String = self.viewModel.threadData.displayName let userPublicKey: String = getUserHexEncodedPublicKey() @@ -1800,7 +1792,7 @@ extension ConversationVC: .filter(id: cellViewModel.id) .asRequest(of: Int64.self) .fetchOne(db), - try OpenGroup.fetchOne(db, id: threadId) + try OpenGroup.fetchOne(db, id: cellViewModel.threadId) ) } @@ -1915,7 +1907,7 @@ extension ConversationVC: .send( db, message: unsendRequest, - threadId: threadId, + threadId: cellViewModel.threadId, interactionId: nil, to: .contact(publicKey: userPublicKey) ) @@ -1938,7 +1930,7 @@ extension ConversationVC: .send( db, message: unsendRequest, - threadId: threadId, + threadId: cellViewModel.threadId, interactionId: nil, to: .contact(publicKey: userPublicKey) ) @@ -1964,23 +1956,20 @@ extension ConversationVC: from: self, request: SnodeAPI .deleteMessages( - publicKey: threadId, + publicKey: cellViewModel.threadId, serverHashes: [serverHash] ) .map { _ in () } .eraseToAnyPublisher() ) { [weak self] in Storage.shared.writeAsync { db in - guard let thread: SessionThread = try SessionThread.fetchOne(db, id: threadId) else { - return - } - try MessageSender .send( db, message: unsendRequest, interactionId: nil, - in: thread + threadId: cellViewModel.threadId, + threadVariant: cellViewModel.threadVariant ) } @@ -2042,17 +2031,17 @@ extension ConversationVC: } let threadId: String = self.viewModel.threadData.threadId + let threadVariant: SessionThread.Variant = self.viewModel.threadData.threadVariant Storage.shared.writeAsync { db in - guard let thread: SessionThread = try SessionThread.fetchOne(db, id: threadId) else { return } - try MessageSender.send( db, message: DataExtractionNotification( kind: .mediaSaved(timestamp: UInt64(cellViewModel.timestampMs)) ), interactionId: nil, - in: thread + threadId: threadId, + threadVariant: threadVariant ) } } @@ -2310,17 +2299,17 @@ extension ConversationVC: guard self.viewModel.threadData.threadVariant == .contact else { return } let threadId: String = self.viewModel.threadData.threadId + let threadVariant: SessionThread.Variant = self.viewModel.threadData.threadVariant Storage.shared.writeAsync { db in - guard let thread: SessionThread = try SessionThread.fetchOne(db, id: threadId) else { return } - try MessageSender.send( db, message: DataExtractionNotification( kind: .screenshot ), interactionId: nil, - in: thread + threadId: threadId, + threadVariant: threadVariant ) } } @@ -2380,17 +2369,9 @@ extension ConversationVC { // (it'll be updated with correct profile info if they accept the message request so this // shouldn't cause weird behaviours) guard - let approvalData: (contact: Contact, thread: SessionThread?) = Storage.shared.read({ db in - return ( - Contact.fetchOrCreate(db, id: threadId), - try SessionThread.fetchOne(db, id: threadId) - ) - }), - let thread: SessionThread = approvalData.thread, - !approvalData.contact.isApproved - else { - return - } + let contact: Contact = Storage.shared.read({ db in Contact.fetchOrCreate(db, id: threadId) }), + !contact.isApproved + else { return } Storage.shared .writePublisher(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db in @@ -2405,19 +2386,20 @@ extension ConversationVC { sentTimestampMs: UInt64(timestampMs) ), interactionId: nil, - in: thread + threadId: threadId, + threadVariant: threadVariant ) } // Default 'didApproveMe' to true for the person approving the message request - try approvalData.contact.save(db) + try contact.save(db) try Contact - .filter(id: approvalData.contact.id) + .filter(id: contact.id) .updateAllAndConfig( db, Contact.Columns.isApproved.set(to: true), Contact.Columns.didApproveMe - .set(to: approvalData.contact.didApproveMe || !isNewThread) + .set(to: contact.didApproveMe || !isNewThread) ) } .sinkUntilComplete( diff --git a/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift b/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift index 69e6b2495..cce21bf56 100644 --- a/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift +++ b/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift @@ -148,10 +148,6 @@ class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel UISwipeActionsConfiguration? { let section: HomeViewModel.SectionModel = self.viewModel.threadData[indexPath.section] - let unswipeAnimationDelay: DispatchTimeInterval = .milliseconds(500) switch section.model { case .threads: - let threadViewModel: SessionThreadViewModel = section.elements[indexPath.row] - let isUnread: Bool = ( - threadViewModel.threadWasMarkedUnread == true || - (threadViewModel.threadUnreadCount ?? 0) > 0 - ) - let changeReadStatus: UIContextualAction = UIContextualAction( - title: (isUnread ? - "MARK_AS_READ".localized() : - "MARK_AS_UNREAD".localized() - ), - icon: (isUnread ? - UIImage(systemName: "envelope.open") : - UIImage(systemName: "envelope.badge") - ), - themeTintColor: .textPrimary, - themeBackgroundColor: .conversationButton_swipeRead, - side: .leading, - actionIndex: 0, - indexPath: indexPath, - tableView: tableView - ) { [weak self] _, _, completionHandler in - // Delay the change to give the cell "unswipe" animation some time to complete - DispatchQueue.global(qos: .default).asyncAfter(deadline: .now() + unswipeAnimationDelay) { - switch isUnread { - case true: - self?.viewModel.markAsRead( - threadViewModel: threadViewModel, - target: .threadAndInteractions( - interactionsBeforeInclusive: threadViewModel.interactionId - ) - ) - - case false: - self?.viewModel.markAsUnread(threadViewModel: threadViewModel) - } - } - completionHandler(true) + // Cannot properly sync outgoing blinded message requests so don't provide the option + guard SessionId(from: section.elements[indexPath.row].threadId)?.prefix == .standard else { + return nil } - return UISwipeActionsConfiguration(actions: [changeReadStatus]) + return generateSwipeActions( + [.toggleReadStatus], + for: .leading, + indexPath: indexPath, + tableView: tableView + ) default: return nil } @@ -683,152 +653,255 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, SeedRemi func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { let section: HomeViewModel.SectionModel = self.viewModel.threadData[indexPath.section] - let unswipeAnimationDelay: DispatchTimeInterval = .milliseconds(500) switch section.model { case .messageRequests: - let hide: UIContextualAction = UIContextualAction( - title: "TXT_HIDE_TITLE".localized(), - icon: UIImage(systemName: "eye.slash"), - themeTintColor: .textPrimary, - themeBackgroundColor: .conversationButton_swipeDestructive, - side: .trailing, - actionIndex: 0, - indexPath: indexPath, - tableView: tableView - ) { _, _, completionHandler in - Storage.shared.write { db in db[.hasHiddenMessageRequests] = true } - completionHandler(true) - } - - return UISwipeActionsConfiguration(actions: [hide]) + return generateSwipeActions([.hide], for: .trailing, indexPath: indexPath, tableView: tableView) case .threads: let threadViewModel: SessionThreadViewModel = section.elements[indexPath.row] + let sessionIdPrefix: SessionId.Prefix? = SessionId(from: threadViewModel.threadId)?.prefix + + // Cannot properly sync outgoing blinded message requests and can only block contact + // threads so only provide these options if valid let shouldHaveBlockAction: Bool = ( threadViewModel.threadVariant == .contact && - !threadViewModel.threadIsNoteToSelf + !threadViewModel.threadIsNoteToSelf && + sessionIdPrefix != .blinded ) - let delete: UIContextualAction = UIContextualAction( - title: "TXT_DELETE_TITLE".localized(), - icon: UIImage(named: "icon_bin"), - themeTintColor: .textPrimary, - themeBackgroundColor: .conversationButton_swipeDestructive, - side: .trailing, - actionIndex: 2, - indexPath: indexPath, - tableView: tableView - ) { [weak self] _, _, completionHandler in - let confirmationModal: ConfirmationModal = ConfirmationModal( - info: ConfirmationModal.Info( - title: "CONVERSATION_DELETE_CONFIRMATION_ALERT_TITLE".localized(), - explanation: (threadViewModel.currentUserIsClosedGroupAdmin == true ? - "admin_group_leave_warning".localized() : - "CONVERSATION_DELETE_CONFIRMATION_ALERT_MESSAGE".localized() - ), - confirmTitle: "TXT_DELETE_TITLE".localized(), - confirmStyle: .danger, - cancelStyle: .alert_text, - dismissOnConfirm: true, - onConfirm: { [weak self] _ in - self?.viewModel.deleteOrLeave( - threadId: threadViewModel.threadId, - threadVariant: threadViewModel.threadVariant - ) - self?.dismiss(animated: true, completion: nil) - - completionHandler(true) - }, - afterClosed: { completionHandler(false) } - ) - ) - - self?.present(confirmationModal, animated: true, completion: nil) - } - - let pin: UIContextualAction = UIContextualAction( - title: (threadViewModel.threadPinnedPriority > 0 ? - "UNPIN_BUTTON_TEXT".localized() : - "PIN_BUTTON_TEXT".localized() - ), - icon: (threadViewModel.threadPinnedPriority > 0 ? - UIImage(systemName: "pin.slash") : - UIImage(systemName: "pin") - ), - themeTintColor: .textPrimary, - themeBackgroundColor: .conversationButton_swipeTertiary, - side: .trailing, - actionIndex: (shouldHaveBlockAction ? 0 : 1), - indexPath: indexPath, - tableView: tableView - ) { _, _, completionHandler in - (tableView.cellForRow(at: indexPath) as? FullConversationCell)?.optimisticUpdate( - isPinned: !(threadViewModel.threadPinnedPriority > 0) - ) - completionHandler(true) - - // Delay the change to give the cell "unswipe" animation some time to complete - DispatchQueue.global(qos: .default).asyncAfter(deadline: .now() + unswipeAnimationDelay) { - Storage.shared.writeAsync { db in - try SessionThread - .filter(id: threadViewModel.threadId) - .updateAllAndConfig( - db, - SessionThread.Columns.pinnedPriority - .set(to: (threadViewModel.threadPinnedPriority == 0 ? 1 : 0)) - ) - } - } - } - guard shouldHaveBlockAction else { - return UISwipeActionsConfiguration(actions: [ delete, pin ]) - } - - let block: UIContextualAction = UIContextualAction( - title: (threadViewModel.threadIsBlocked == true ? - "BLOCK_LIST_UNBLOCK_BUTTON".localized() : - "BLOCK_LIST_BLOCK_BUTTON".localized() - ), - icon: UIImage(named: "table_ic_block"), - themeTintColor: .textPrimary, - themeBackgroundColor: .conversationButton_swipeSecondary, - side: .trailing, - actionIndex: 1, + return generateSwipeActions( + [ + (sessionIdPrefix == .blinded ? nil : .pin), + (!shouldHaveBlockAction ? nil : .block), + .delete + ].compactMap { $0 }, + for: .trailing, indexPath: indexPath, tableView: tableView - ) { _, _, completionHandler in - (tableView.cellForRow(at: indexPath) as? FullConversationCell)?.optimisticUpdate( - isBlocked: (threadViewModel.threadIsBlocked == false) - ) - completionHandler(true) - - // Delay the change to give the cell "unswipe" animation some time to complete - DispatchQueue.global(qos: .default).asyncAfter(deadline: .now() + unswipeAnimationDelay) { - Storage.shared - .writePublisher(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db in - try Contact - .filter(id: threadViewModel.threadId) - .updateAllAndConfig( - db, - Contact.Columns.isBlocked.set( - to: (threadViewModel.threadIsBlocked == false ? - true: - false - ) - ) - ) - } - .sinkUntilComplete() - } - } - - return UISwipeActionsConfiguration(actions: [ delete, block, pin ]) + ) default: return nil } } + // MARK: - Swipe action generation + + private enum SwipeAction { + case toggleReadStatus + case hide + case pin + case block + case delete + } + + private func generateSwipeActions( + _ actions: [SwipeAction], + for side: UIContextualAction.Side, + indexPath: IndexPath, + tableView: UITableView + ) -> UISwipeActionsConfiguration? { + guard !actions.isEmpty else { return nil } + + let section: HomeViewModel.SectionModel = self.viewModel.threadData[indexPath.section] + let threadViewModel: SessionThreadViewModel = section.elements[indexPath.row] + let unswipeAnimationDelay: DispatchTimeInterval = .milliseconds(500) + + // Note: for some reason the `UISwipeActionsConfiguration` expects actions to be left-to-right + // for leading actions, but right-to-left for trailing actions... + let targetActions: [SwipeAction] = (side == .trailing ? actions.reversed() : actions) + + return UISwipeActionsConfiguration( + actions: targetActions + .enumerated() + .map { index, action -> UIContextualAction in + // Even though we have to reverse the actions above, the indexes in the view hierarchy + // are in the expected order + let targetIndex: Int = (side == .trailing ? (targetActions.count - index) : index) + + switch action { + // MARK: -- toggleReadStatus + + case .toggleReadStatus: + let isUnread: Bool = ( + threadViewModel.threadWasMarkedUnread == true || + (threadViewModel.threadUnreadCount ?? 0) > 0 + ) + + return UIContextualAction( + title: (isUnread ? + "MARK_AS_READ".localized() : + "MARK_AS_UNREAD".localized() + ), + icon: (isUnread ? + UIImage(systemName: "envelope.open") : + UIImage(systemName: "envelope.badge") + ), + themeTintColor: .white, + themeBackgroundColor: .conversationButton_swipeRead, + side: side, + actionIndex: targetIndex, + indexPath: indexPath, + tableView: tableView + ) { [weak self] _, _, completionHandler in + // Delay the change to give the cell "unswipe" animation some time to complete + DispatchQueue.global(qos: .default).asyncAfter(deadline: .now() + unswipeAnimationDelay) { + switch isUnread { + case true: + self?.viewModel.markAsRead( + threadViewModel: threadViewModel, + target: .threadAndInteractions( + interactionsBeforeInclusive: threadViewModel.interactionId + ) + ) + + case false: + self?.viewModel.markAsUnread(threadViewModel: threadViewModel) + } + } + completionHandler(true) + } + + // MARK: -- hide + + case .hide: + return UIContextualAction( + title: "TXT_HIDE_TITLE".localized(), + icon: UIImage(systemName: "eye.slash"), + themeTintColor: .white, + themeBackgroundColor: .conversationButton_swipeDestructive, + side: side, + actionIndex: targetIndex, + indexPath: indexPath, + tableView: tableView + ) { _, _, completionHandler in + Storage.shared.write { db in db[.hasHiddenMessageRequests] = true } + completionHandler(true) + } + + // MARK: -- pin + + case .pin: + return UIContextualAction( + title: (threadViewModel.threadPinnedPriority > 0 ? + "UNPIN_BUTTON_TEXT".localized() : + "PIN_BUTTON_TEXT".localized() + ), + icon: (threadViewModel.threadPinnedPriority > 0 ? + UIImage(systemName: "pin.slash") : + UIImage(systemName: "pin") + ), + themeTintColor: .white, + themeBackgroundColor: .conversationButton_swipeTertiary, + side: side, + actionIndex: targetIndex, + indexPath: indexPath, + tableView: tableView + ) { _, _, completionHandler in + (tableView.cellForRow(at: indexPath) as? FullConversationCell)?.optimisticUpdate( + isPinned: !(threadViewModel.threadPinnedPriority > 0) + ) + completionHandler(true) + + // Delay the change to give the cell "unswipe" animation some time to complete + DispatchQueue.global(qos: .default).asyncAfter(deadline: .now() + unswipeAnimationDelay) { + Storage.shared.writeAsync { db in + try SessionThread + .filter(id: threadViewModel.threadId) + .updateAllAndConfig( + db, + SessionThread.Columns.pinnedPriority + .set(to: (threadViewModel.threadPinnedPriority == 0 ? 1 : 0)) + ) + } + } + } + + // MARK: -- block + + case .block: + return UIContextualAction( + title: (threadViewModel.threadIsBlocked == true ? + "BLOCK_LIST_UNBLOCK_BUTTON".localized() : + "BLOCK_LIST_BLOCK_BUTTON".localized() + ), + icon: UIImage(named: "table_ic_block"), + themeTintColor: .white, + themeBackgroundColor: .conversationButton_swipeSecondary, + side: .trailing, + actionIndex: 1, + indexPath: indexPath, + tableView: tableView + ) { _, _, completionHandler in + (tableView.cellForRow(at: indexPath) as? FullConversationCell)?.optimisticUpdate( + isBlocked: (threadViewModel.threadIsBlocked == false) + ) + completionHandler(true) + + // Delay the change to give the cell "unswipe" animation some time to complete + DispatchQueue.global(qos: .default).asyncAfter(deadline: .now() + unswipeAnimationDelay) { + Storage.shared + .writePublisher(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db in + try Contact + .filter(id: threadViewModel.threadId) + .updateAllAndConfig( + db, + Contact.Columns.isBlocked.set( + to: (threadViewModel.threadIsBlocked == false ? + true: + false + ) + ) + ) + } + .sinkUntilComplete() + } + } + + // MARK: -- delete + + case .delete: + return UIContextualAction( + title: "TXT_DELETE_TITLE".localized(), + icon: UIImage(named: "icon_bin"), + themeTintColor: .white, + themeBackgroundColor: .conversationButton_swipeDestructive, + side: side, + actionIndex: targetIndex, + indexPath: indexPath, + tableView: tableView + ) { [weak self] _, _, completionHandler in + let confirmationModal: ConfirmationModal = ConfirmationModal( + info: ConfirmationModal.Info( + title: "CONVERSATION_DELETE_CONFIRMATION_ALERT_TITLE".localized(), + explanation: (threadViewModel.currentUserIsClosedGroupAdmin == true ? + "admin_group_leave_warning".localized() : + "CONVERSATION_DELETE_CONFIRMATION_ALERT_MESSAGE".localized() + ), + confirmTitle: "TXT_DELETE_TITLE".localized(), + confirmStyle: .danger, + cancelStyle: .alert_text, + dismissOnConfirm: true, + onConfirm: { [weak self] _ in + self?.viewModel.deleteOrLeave( + threadId: threadViewModel.threadId, + threadVariant: threadViewModel.threadVariant + ) + self?.dismiss(animated: true, completion: nil) + + completionHandler(true) + }, + afterClosed: { completionHandler(false) } + ) + ) + + self?.present(confirmationModal, animated: true, completion: nil) + } + } + } + ) + } + // MARK: - Interaction func handleContinueButtonTapped(from seedReminderView: SeedReminderView) { diff --git a/Session/Home/New Conversation/NewDMVC.swift b/Session/Home/New Conversation/NewDMVC.swift index 58ab7d88c..ac6ba19d7 100644 --- a/Session/Home/New Conversation/NewDMVC.swift +++ b/Session/Home/New Conversation/NewDMVC.swift @@ -173,8 +173,35 @@ final class NewDMVC: BaseVC, UIPageViewControllerDataSource, UIPageViewControlle fileprivate func startNewDMIfPossible(with onsNameOrPublicKey: String) { let maybeSessionId: SessionId? = SessionId(from: onsNameOrPublicKey) - if KeyPair.isValidHexEncodedPublicKey(candidate: onsNameOrPublicKey) && maybeSessionId?.prefix == .standard { - startNewDM(with: onsNameOrPublicKey) + if KeyPair.isValidHexEncodedPublicKey(candidate: onsNameOrPublicKey) { + switch maybeSessionId?.prefix { + case .standard: + startNewDM(with: onsNameOrPublicKey) + + case .blinded: + let modal: ConfirmationModal = ConfirmationModal( + targetView: self.view, + info: ConfirmationModal.Info( + title: "ALERT_ERROR_TITLE".localized(), + explanation: "DM_ERROR_DIRECT_BLINDED_ID".localized(), + cancelTitle: "BUTTON_OK".localized(), + cancelStyle: .alert_text + ) + ) + self.present(modal, animated: true) + + default: + let modal: ConfirmationModal = ConfirmationModal( + targetView: self.view, + info: ConfirmationModal.Info( + title: "ALERT_ERROR_TITLE".localized(), + explanation: "DM_ERROR_INVALID".localized(), + cancelTitle: "BUTTON_OK".localized(), + cancelStyle: .alert_text + ) + ) + self.present(modal, animated: true) + } return } @@ -198,22 +225,12 @@ final class NewDMVC: BaseVC, UIPageViewControllerDataSource, UIPageViewControlle default: break } } - let message: String = { - if let messageOrNil: String = messageOrNil { - return messageOrNil - } - - return (maybeSessionId?.prefix == .blinded ? - "DM_ERROR_DIRECT_BLINDED_ID".localized() : - "DM_ERROR_INVALID".localized() - ) - }() let modal: ConfirmationModal = ConfirmationModal( targetView: self?.view, info: ConfirmationModal.Info( title: "ALERT_ERROR_TITLE".localized(), - explanation: message, + explanation: (messageOrNil ?? "DM_ERROR_INVALID".localized()), cancelTitle: "BUTTON_OK".localized(), cancelStyle: .alert_text ) diff --git a/Session/Media Viewing & Editing/MediaPageViewController.swift b/Session/Media Viewing & Editing/MediaPageViewController.swift index 218655338..38f3dc749 100644 --- a/Session/Media Viewing & Editing/MediaPageViewController.swift +++ b/Session/Media Viewing & Editing/MediaPageViewController.swift @@ -530,11 +530,10 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou self.viewModel.threadVariant == .contact else { return } + let threadId: String = self.viewModel.threadId + let threadVariant: SessionThread.Variant = self.viewModel.threadVariant + Storage.shared.write { db in - guard let thread: SessionThread = try SessionThread.fetchOne(db, id: self.viewModel.threadId) else { - return - } - try MessageSender.send( db, message: DataExtractionNotification( @@ -543,7 +542,8 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou ) ), interactionId: nil, // Show no interaction for the current user - in: thread + threadId: threadId, + threadVariant: threadVariant ) } } diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 1a0683458..c812a2419 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -404,18 +404,22 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD } self.hasInitialRootViewController = true - self.window?.rootViewController = StyledNavigationController( - rootViewController: { - guard Identity.userExists() else { return LandingVC() } - guard !Profile.fetchOrCreateCurrentUser().name.isEmpty else { - // If we have no display name then collect one (this can happen if the - // app crashed during onboarding which would leave the user in an invalid - // state with no display name) - return DisplayNameVC(flow: .register) - } - - return HomeVC() - }() + self.window?.rootViewController = TopBannerController( + child: StyledNavigationController( + rootViewController: { + guard Identity.userExists() else { return LandingVC() } + guard !Profile.fetchOrCreateCurrentUser().name.isEmpty else { + // If we have no display name then collect one (this can happen if the + // app crashed during onboarding which would leave the user in an invalid + // state with no display name) + return DisplayNameVC(flow: .register) + } + + return HomeVC() + }() + ), + cachedWarning: UserDefaults.sharedLokiProject?[.topBannerWarningToShow] + .map { rawValue in TopBannerController.Warning(rawValue: rawValue) } ) UIViewController.attemptRotationToDeviceOrientation() diff --git a/Session/Meta/Translations/de.lproj/Localizable.strings b/Session/Meta/Translations/de.lproj/Localizable.strings index bbabfae45..b92b9d79a 100644 --- a/Session/Meta/Translations/de.lproj/Localizable.strings +++ b/Session/Meta/Translations/de.lproj/Localizable.strings @@ -614,3 +614,4 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index 62fd1062e..4b00d63ca 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -614,3 +614,4 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/es.lproj/Localizable.strings b/Session/Meta/Translations/es.lproj/Localizable.strings index a9c9b8d2c..a960a8ef9 100644 --- a/Session/Meta/Translations/es.lproj/Localizable.strings +++ b/Session/Meta/Translations/es.lproj/Localizable.strings @@ -614,3 +614,4 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/fa.lproj/Localizable.strings b/Session/Meta/Translations/fa.lproj/Localizable.strings index 0b02cd928..c909428cf 100644 --- a/Session/Meta/Translations/fa.lproj/Localizable.strings +++ b/Session/Meta/Translations/fa.lproj/Localizable.strings @@ -614,3 +614,4 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/fi.lproj/Localizable.strings b/Session/Meta/Translations/fi.lproj/Localizable.strings index 74d7f0951..6be88f937 100644 --- a/Session/Meta/Translations/fi.lproj/Localizable.strings +++ b/Session/Meta/Translations/fi.lproj/Localizable.strings @@ -614,3 +614,4 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/fr.lproj/Localizable.strings b/Session/Meta/Translations/fr.lproj/Localizable.strings index 11133ba43..200b528f8 100644 --- a/Session/Meta/Translations/fr.lproj/Localizable.strings +++ b/Session/Meta/Translations/fr.lproj/Localizable.strings @@ -614,3 +614,4 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/hi.lproj/Localizable.strings b/Session/Meta/Translations/hi.lproj/Localizable.strings index ae7635f00..d02a780fc 100644 --- a/Session/Meta/Translations/hi.lproj/Localizable.strings +++ b/Session/Meta/Translations/hi.lproj/Localizable.strings @@ -614,3 +614,4 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/hr.lproj/Localizable.strings b/Session/Meta/Translations/hr.lproj/Localizable.strings index 8cd2f97ae..dc559901c 100644 --- a/Session/Meta/Translations/hr.lproj/Localizable.strings +++ b/Session/Meta/Translations/hr.lproj/Localizable.strings @@ -614,3 +614,4 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/id-ID.lproj/Localizable.strings b/Session/Meta/Translations/id-ID.lproj/Localizable.strings index dd293f84a..01696a223 100644 --- a/Session/Meta/Translations/id-ID.lproj/Localizable.strings +++ b/Session/Meta/Translations/id-ID.lproj/Localizable.strings @@ -614,3 +614,4 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/it.lproj/Localizable.strings b/Session/Meta/Translations/it.lproj/Localizable.strings index 73415feef..baf144a01 100644 --- a/Session/Meta/Translations/it.lproj/Localizable.strings +++ b/Session/Meta/Translations/it.lproj/Localizable.strings @@ -614,3 +614,4 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/ja.lproj/Localizable.strings b/Session/Meta/Translations/ja.lproj/Localizable.strings index 3bacd6efd..5100121f5 100644 --- a/Session/Meta/Translations/ja.lproj/Localizable.strings +++ b/Session/Meta/Translations/ja.lproj/Localizable.strings @@ -614,3 +614,4 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/nl.lproj/Localizable.strings b/Session/Meta/Translations/nl.lproj/Localizable.strings index 63a624779..b487ef47f 100644 --- a/Session/Meta/Translations/nl.lproj/Localizable.strings +++ b/Session/Meta/Translations/nl.lproj/Localizable.strings @@ -614,3 +614,4 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/pl.lproj/Localizable.strings b/Session/Meta/Translations/pl.lproj/Localizable.strings index eac8266a8..f6a87bc54 100644 --- a/Session/Meta/Translations/pl.lproj/Localizable.strings +++ b/Session/Meta/Translations/pl.lproj/Localizable.strings @@ -614,3 +614,4 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings index 4a85c0a49..0840cdd50 100644 --- a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings +++ b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings @@ -614,3 +614,4 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/ru.lproj/Localizable.strings b/Session/Meta/Translations/ru.lproj/Localizable.strings index 633ac9e1d..52f2b7a6d 100644 --- a/Session/Meta/Translations/ru.lproj/Localizable.strings +++ b/Session/Meta/Translations/ru.lproj/Localizable.strings @@ -614,3 +614,4 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/si.lproj/Localizable.strings b/Session/Meta/Translations/si.lproj/Localizable.strings index 3fa8d1d91..041426c6c 100644 --- a/Session/Meta/Translations/si.lproj/Localizable.strings +++ b/Session/Meta/Translations/si.lproj/Localizable.strings @@ -614,3 +614,4 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/sk.lproj/Localizable.strings b/Session/Meta/Translations/sk.lproj/Localizable.strings index e4c38ecbb..2ffedda83 100644 --- a/Session/Meta/Translations/sk.lproj/Localizable.strings +++ b/Session/Meta/Translations/sk.lproj/Localizable.strings @@ -614,3 +614,4 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/sv.lproj/Localizable.strings b/Session/Meta/Translations/sv.lproj/Localizable.strings index f8e09b738..c7385b194 100644 --- a/Session/Meta/Translations/sv.lproj/Localizable.strings +++ b/Session/Meta/Translations/sv.lproj/Localizable.strings @@ -614,3 +614,4 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/th.lproj/Localizable.strings b/Session/Meta/Translations/th.lproj/Localizable.strings index 2d7380d1e..5c4427c05 100644 --- a/Session/Meta/Translations/th.lproj/Localizable.strings +++ b/Session/Meta/Translations/th.lproj/Localizable.strings @@ -614,3 +614,4 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings index 038e2d82c..0b89fd2f2 100644 --- a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings +++ b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings @@ -614,3 +614,4 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings index f0636ea33..30cfd9650 100644 --- a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings @@ -614,3 +614,4 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings index 067e07d0d..da56b17ec 100644 --- a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings @@ -614,3 +614,4 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Notifications/AppNotifications.swift b/Session/Notifications/AppNotifications.swift index 79575da2f..0e374700f 100644 --- a/Session/Notifications/AppNotifications.swift +++ b/Session/Notifications/AppNotifications.swift @@ -566,7 +566,8 @@ class NotificationActionHandler { return try MessageSender.preparedSendData( db, interaction: interaction, - in: thread + threadId: thread.id, + threadVariant: thread.variant ) } .flatMap { MessageSender.sendImmediate(preparedSendData: $0) } diff --git a/Session/Onboarding/RegisterVC.swift b/Session/Onboarding/RegisterVC.swift index 655d6cd7b..56979231f 100644 --- a/Session/Onboarding/RegisterVC.swift +++ b/Session/Onboarding/RegisterVC.swift @@ -160,8 +160,9 @@ final class RegisterVC : BaseVC { } // MARK: Updating + private func updateSeed() { - seed = Data.getSecureRandomData(ofSize: 16)! + seed = try! Randomness.generateRandomBytes(numberBytes: 16) } private func updateKeyPair() { diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index 09ea50b33..86c8c281d 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -134,8 +134,10 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { .preparedSendData( db, message: message, - to: try Message.Destination.from(db, thread: thread), - namespace: try Message.Destination.from(db, thread: thread).defaultNamespace, + to: try Message.Destination.from(db, threadId: thread.id, threadVariant: thread.variant), + namespace: try Message.Destination + .from(db, threadId: thread.id, threadVariant: thread.variant) + .defaultNamespace, interactionId: interactionId ) ) @@ -194,8 +196,10 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { sdps: [ sdp.sdp ], sentTimestampMs: UInt64(SnodeAPI.currentOffsetTimestampMs()) ), - to: try Message.Destination.from(db, thread: thread), - namespace: try Message.Destination.from(db, thread: thread) + to: try Message.Destination + .from(db, threadId: thread.id, threadVariant: thread.variant), + namespace: try Message.Destination + .from(db, threadId: thread.id, threadVariant: thread.variant) .defaultNamespace, interactionId: nil ) @@ -259,8 +263,10 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { kind: .answer, sdps: [ sdp.sdp ] ), - to: try Message.Destination.from(db, thread: thread), - namespace: try Message.Destination.from(db, thread: thread) + to: try Message.Destination + .from(db, threadId: thread.id, threadVariant: thread.variant), + namespace: try Message.Destination + .from(db, threadId: thread.id, threadVariant: thread.variant) .defaultNamespace, interactionId: nil ) @@ -318,8 +324,10 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { ), sdps: candidates.map { $0.sdp } ), - to: try Message.Destination.from(db, thread: thread), - namespace: try Message.Destination.from(db, thread: thread) + to: try Message.Destination + .from(db, threadId: thread.id, threadVariant: thread.variant), + namespace: try Message.Destination + .from(db, threadId: thread.id, threadVariant: thread.variant) .defaultNamespace, interactionId: nil ) @@ -344,8 +352,10 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { kind: .endCall, sdps: [] ), - to: try Message.Destination.from(db, thread: thread), - namespace: try Message.Destination.from(db, thread: thread).defaultNamespace, + to: try Message.Destination.from(db, threadId: thread.id, threadVariant: thread.variant), + namespace: try Message.Destination + .from(db, threadId: thread.id, threadVariant: thread.variant) + .defaultNamespace, interactionId: nil ) diff --git a/SessionMessagingKit/Configuration.swift b/SessionMessagingKit/Configuration.swift index 545bfd549..85b566d71 100644 --- a/SessionMessagingKit/Configuration.swift +++ b/SessionMessagingKit/Configuration.swift @@ -28,7 +28,7 @@ public enum SNMessagingKit { // Just to make the external API nice ], // Add job priorities [ _011_AddPendingReadReceipts.self, - _012_SharedUtilChanges.self, + _012_SessionUtilChanges.self, // Wait until the feature is turned on before doing the migration that generates // the config dump data (Features.useSharedUtilForUserConfig ? diff --git a/SessionMessagingKit/Database/Migrations/_012_SharedUtilChanges.swift b/SessionMessagingKit/Database/Migrations/_012_SessionUtilChanges.swift similarity index 86% rename from SessionMessagingKit/Database/Migrations/_012_SharedUtilChanges.swift rename to SessionMessagingKit/Database/Migrations/_012_SessionUtilChanges.swift index 3fcf501bb..a4331fdf9 100644 --- a/SessionMessagingKit/Database/Migrations/_012_SharedUtilChanges.swift +++ b/SessionMessagingKit/Database/Migrations/_012_SessionUtilChanges.swift @@ -7,9 +7,9 @@ import SessionUtil import SessionUtilitiesKit /// This migration makes the neccessary changes to support the updated user config syncing system -enum _012_SharedUtilChanges: Migration { +enum _012_SessionUtilChanges: Migration { static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "SharedUtilChanges" + static let identifier: String = "SessionUtilChanges" static let needsConfigSync: Bool = true static let minExpectedRunDuration: TimeInterval = 0.1 @@ -163,10 +163,24 @@ enum _012_SharedUtilChanges: Migration { // If we don't have an ed25519 key then no need to create cached dump data let userPublicKey: String = getUserHexEncodedPublicKey(db) - // There was previously a bug which allowed users to fully delete the 'Note to Self' - // conversation but we don't want that, so create it again if it doesn't exists + // Remove any hidden threads to avoid syncing them (they are basically shadow threads created + // by starting a conversation but not sending a message so can just be cleared out) try SessionThread - .fetchOrCreate(db, id: userPublicKey, variant: .contact, shouldBeVisible: false) + .filter( + SessionThread.Columns.shouldBeVisible == false && + SessionThread.Columns.id != userPublicKey + ) + .deleteAll(db) + + /// There was previously a bug which allowed users to fully delete the 'Note to Self' conversation but we don't want that, so + /// create it again if it doesn't exists + /// + /// **Note:** Since migrations are run when running tests creating a random SessionThread will result in unexpected thread + /// counts so don't do this when running tests (this logic is the same as in `MainAppContext.isRunningTests` + if ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] == nil { + try SessionThread + .fetchOrCreate(db, id: userPublicKey, variant: .contact, shouldBeVisible: false) + } Storage.update(progress: 1, for: self, in: target) // In case this is the last migration } diff --git a/SessionMessagingKit/Database/Migrations/_013_GenerateInitialUserConfigDumps.swift b/SessionMessagingKit/Database/Migrations/_013_GenerateInitialUserConfigDumps.swift index 0bf3a30dc..a84910b4e 100644 --- a/SessionMessagingKit/Database/Migrations/_013_GenerateInitialUserConfigDumps.swift +++ b/SessionMessagingKit/Database/Migrations/_013_GenerateInitialUserConfigDumps.swift @@ -55,10 +55,18 @@ enum _013_GenerateInitialUserConfigDumps: Migration { try SessionUtil .config(for: .contacts, publicKey: userPublicKey) .mutate { conf in + // Exclude community, group and outgoing blinded message requests + let validContactIds: [String] = allThreads + .values + .filter { thread in + thread.variant == .contact && + SessionId(from: thread.id)?.prefix == .standard + } + .map { $0.id } let contactsData: [ContactInfo] = try Contact .filter( Contact.Columns.isBlocked == true || - allThreads.keys.contains(Contact.Columns.id) + validContactIds.contains(Contact.Columns.id) ) .including(optional: Contact.profile) .asRequest(of: ContactInfo.self) diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift index 39772a335..c4191fd26 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift @@ -5,6 +5,16 @@ import GRDB import SessionUtil import SessionUtilitiesKit +// MARK: - Size Restrictions + +public extension SessionUtil { + static var libSessionMaxNameByteLength: Int { CONTACT_MAX_NAME_LENGTH } + static var libSessionMaxNicknameByteLength: Int { CONTACT_MAX_NAME_LENGTH } + static var libSessionMaxProfileUrlByteLength: Int { PROFILE_PIC_MAX_URL_LENGTH } +} + +// MARK: - Contacts Handling + internal extension SessionUtil { static let columnsRelatedToContacts: [ColumnExpression] = [ Contact.Columns.isApproved, @@ -155,15 +165,14 @@ internal extension SessionUtil { .asRequest(of: PriorityVisibilityInfo.self) .fetchOne(db) let threadExists: Bool = (threadInfo != nil) - let threadIsVisible: Bool = (threadInfo?.shouldBeVisible ?? false) - switch (data.shouldBeVisible, threadExists, threadIsVisible) { - case (false, true, _): + switch (data.shouldBeVisible, threadExists) { + case (false, true): try SessionThread .filter(id: contact.id) .deleteAll(db) - case (true, false, _): + case (true, false): try SessionThread( id: contact.id, variant: .contact, @@ -171,9 +180,11 @@ internal extension SessionUtil { pinnedPriority: data.priority ).save(db) - case (true, true, false): + case (true, true): let changes: [ConfigColumnAssignment] = [ - SessionThread.Columns.shouldBeVisible.set(to: data.shouldBeVisible), + (threadInfo?.shouldBeVisible == data.shouldBeVisible ? nil : + SessionThread.Columns.shouldBeVisible.set(to: data.shouldBeVisible) + ), (threadInfo?.pinnedPriority == data.priority ? nil : SessionThread.Columns.pinnedPriority.set(to: data.priority) ) @@ -186,9 +197,33 @@ internal extension SessionUtil { changes ) - default: break + case (false, false): break } } + + // Delete any contact records which have been removed + let syncedContactIds: [String] = targetContactData + .map { $0.key } + .appending(userPublicKey) + let contactIdsToRemove: [String] = try Contact + .filter(!syncedContactIds.contains(Contact.Columns.id)) + .select(.id) + .asRequest(of: String.self) + .fetchAll(db) + + if !contactIdsToRemove.isEmpty { + try Contact + .filter(ids: contactIdsToRemove) + .deleteAll(db) + + // Also need to remove any 'nickname' values since they are associated to contact data + try Profile + .filter(ids: contactIdsToRemove) + .updateAll( + db, + Profile.Columns.nickname.set(to: nil) + ) + } } // MARK: - Outgoing Changes @@ -199,10 +234,14 @@ internal extension SessionUtil { ) throws { guard conf != nil else { throw SessionUtilError.nilConfigObject } - // The current users contact data doesn't need to sync so exclude it + // The current users contact data doesn't need to sync so exclude it, we also don't want to sync + // blinded message requests so exclude those as well let userPublicKey: String = getUserHexEncodedPublicKey() let targetContacts: [SyncedContactInfo] = contactData - .filter { $0.id != userPublicKey } + .filter { + $0.id != userPublicKey && + SessionId(from: $0.id)?.prefix == .standard + } // If we only updated the current user contact then no need to continue guard !targetContacts.isEmpty else { return } @@ -268,9 +307,14 @@ internal extension SessionUtil { static func updatingContacts(_ db: Database, _ updated: [T]) throws -> [T] { guard let updatedContacts: [Contact] = updated as? [Contact] else { throw StorageError.generic } - // The current users contact data doesn't need to sync so exclude it + // The current users contact data doesn't need to sync so exclude it, we also don't want to sync + // blinded message requests so exclude those as well let userPublicKey: String = getUserHexEncodedPublicKey(db) - let targetContacts: [Contact] = updatedContacts.filter { $0.id != userPublicKey } + let targetContacts: [Contact] = updatedContacts + .filter { + $0.id != userPublicKey && + SessionId(from: $0.id)?.prefix == .standard + } // If we only updated the current user contact then no need to continue guard !targetContacts.isEmpty else { return updated } @@ -339,6 +383,7 @@ internal extension SessionUtil { let targetProfiles: [Profile] = updatedProfiles .filter { $0.id != userPublicKey && + SessionId(from: $0.id)?.prefix == .standard && existingContactIds.contains($0.id) } @@ -392,8 +437,6 @@ public extension SessionUtil { } static func remove(_ db: Database, contactIds: [String]) throws { - // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard Features.useSharedUtilForUserConfig else { return } guard !contactIds.isEmpty else { return } try SessionUtil.performAndPushChange( diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift index c323d59b8..0ce09f050 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift @@ -129,7 +129,7 @@ internal extension SessionUtil { // update the cached config state accordingly guard let lastReadTimestampMs: Int64 = threadInfo.changes.lastReadTimestampMs, - lastReadTimestampMs > (localThreadInfo?.changes.lastReadTimestampMs ?? 0) + lastReadTimestampMs >= (localThreadInfo?.changes.lastReadTimestampMs ?? 0) else { // We only want to return the 'lastReadTimestampMs' change, since the local state // should win in that case, so ignore all others @@ -299,25 +299,22 @@ public extension SessionUtil { threadVariant: SessionThread.Variant, lastReadTimestampMs: Int64 ) throws { - // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard Features.useSharedUtilForUserConfig else { return } - - let change: VolatileThreadInfo = VolatileThreadInfo( - threadId: threadId, - variant: threadVariant, - openGroupUrlInfo: (threadVariant != .community ? nil : - try OpenGroupUrlInfo.fetchOne(db, id: threadId) - ), - changes: [.lastReadTimestampMs(lastReadTimestampMs)] - ) - try SessionUtil.performAndPushChange( db, for: .convoInfoVolatile, publicKey: getUserHexEncodedPublicKey(db) ) { conf in try upsert( - convoInfoVolatileChanges: [change], + convoInfoVolatileChanges: [ + VolatileThreadInfo( + threadId: threadId, + variant: threadVariant, + openGroupUrlInfo: (threadVariant != .community ? nil : + try OpenGroupUrlInfo.fetchOne(db, id: threadId) + ), + changes: [.lastReadTimestampMs(lastReadTimestampMs)] + ) + ], in: conf ) } diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Shared.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Shared.swift index 5b2eaf06e..e82fc39a2 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Shared.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Shared.swift @@ -33,6 +33,9 @@ internal extension SessionUtil { publicKey: String, change: (UnsafeMutablePointer?) throws -> () ) throws { + // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent + guard Features.useSharedUtilForUserConfig else { return } + // Since we are doing direct memory manipulation we are using an `Atomic` // type which has blocking access in it's `mutate` closure let needsPush: Bool @@ -172,7 +175,6 @@ internal extension SessionUtil { .map { thread in LegacyGroupInfo( id: thread.id, - hidden: !thread.shouldBeVisible, priority: thread.pinnedPriority .map { Int32($0 == 0 ? 0 : max($0, 1)) } .defaulting(to: 0) diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift index c7e013da1..578902573 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift @@ -6,7 +6,18 @@ import Sodium import SessionUtil import SessionUtilitiesKit import SessionSnodeKit -// TODO: Expose 'GROUP_NAME_MAX_LENGTH', 'COMMUNITY_URL_MAX_LENGTH' & 'COMMUNITY_ROOM_MAX_LENGTH' + +// MARK: - Size Restrictions + +public extension SessionUtil { + static var libSessionMaxGroupNameByteLength: Int { GROUP_NAME_MAX_LENGTH } + static var libSessionMaxGroupBaseUrlByteLength: Int { COMMUNITY_BASE_URL_MAX_LENGTH } + static var libSessionMaxGroupFullUrlByteLength: Int { COMMUNITY_FULL_URL_MAX_LENGTH } + static var libSessionMaxCommunityRoomByteLength: Int { COMMUNITY_ROOM_MAX_LENGTH } +} + +// MARK: - UserGroups Handling + internal extension SessionUtil { static let columnsRelatedToUserGroups: [ColumnExpression] = [ ClosedGroup.Columns.name @@ -51,18 +62,7 @@ internal extension SessionUtil { } else if user_groups_it_is_legacy_group(groupsIterator, &legacyGroup) { let groupId: String = String(libSessionVal: legacyGroup.session_id) - let membersIt: OpaquePointer = ugroups_legacy_members_begin(&legacyGroup) - var members: [String: Bool] = [:] - var maybeMemberSessionId: UnsafePointer? = nil - var memberAdmin: Bool = false - - while ugroups_legacy_members_next(membersIt, &maybeMemberSessionId, &memberAdmin) { - guard let memberSessionId: UnsafePointer = maybeMemberSessionId else { - continue - } - - members[String(cString: memberSessionId)] = memberAdmin - } + let members: [String: Bool] = SessionUtil.memberInfo(in: &legacyGroup) legacyGroups.append( LegacyGroupInfo( @@ -106,7 +106,6 @@ internal extension SessionUtil { isHidden: false ) }, - hidden: legacyGroup.hidden, priority: legacyGroup.priority ) ) @@ -306,21 +305,12 @@ internal extension SessionUtil { } // Make any thread-specific changes if needed - let threadChanges: [ConfigColumnAssignment] = [ - (existingThreadInfo[group.id]?.shouldBeVisible == (group.hidden == false) ? nil : - SessionThread.Columns.shouldBeVisible.set(to: (group.hidden == false)) - ), - (existingThreadInfo[group.id]?.pinnedPriority == group.priority ? nil : - SessionThread.Columns.pinnedPriority.set(to: group.priority) - ) - ].compactMap { $0 } - - if !threadChanges.isEmpty { + if existingThreadInfo[group.id]?.pinnedPriority != group.priority { _ = try? SessionThread .filter(id: group.id) .updateAll( // Handling a config update so don't use `updateAllAndConfig` db, - threadChanges + SessionThread.Columns.pinnedPriority.set(to: group.priority) ) } } @@ -342,6 +332,23 @@ internal extension SessionUtil { // TODO: Add this } + fileprivate static func memberInfo(in legacyGroup: UnsafeMutablePointer) -> [String: Bool] { + let membersIt: OpaquePointer = ugroups_legacy_members_begin(legacyGroup) + var members: [String: Bool] = [:] + var maybeMemberSessionId: UnsafePointer? = nil + var memberAdmin: Bool = false + + while ugroups_legacy_members_next(membersIt, &maybeMemberSessionId, &memberAdmin) { + guard let memberSessionId: UnsafePointer = maybeMemberSessionId else { + continue + } + + members[String(cString: memberSessionId)] = memberAdmin + } + + return members + } + // MARK: - Outgoing Changes static func upsert( @@ -384,19 +391,54 @@ internal extension SessionUtil { user_groups_set_legacy_group(conf, userGroup) } - // Add the group members and admins - legacyGroup.groupMembers?.forEach { member in - var cProfileId: [CChar] = member.profileId.cArray - ugroups_legacy_member_add(userGroup, &cProfileId, false) + // Add/Remove the group members and admins + let existingMembers: [String: Bool] = { + guard legacyGroup.groupMembers != nil || legacyGroup.groupAdmins != nil else { return [:] } + + return SessionUtil.memberInfo(in: userGroup) + }() + + if let groupMembers: [GroupMember] = legacyGroup.groupMembers { + let memberIds: Set = groupMembers.map { $0.profileId }.asSet() + let existingMemberIds: Set = Array(existingMembers + .filter { _, isAdmin in !isAdmin } + .keys) + .asSet() + let membersIdsToAdd: Set = memberIds.subtracting(existingMemberIds) + let membersIdsToRemove: Set = existingMemberIds.subtracting(memberIds) + + membersIdsToAdd.forEach { memberId in + var cProfileId: [CChar] = memberId.cArray + ugroups_legacy_member_add(userGroup, &cProfileId, false) + } + + membersIdsToRemove.forEach { memberId in + var cProfileId: [CChar] = memberId.cArray + ugroups_legacy_member_remove(userGroup, &cProfileId) + } } - legacyGroup.groupAdmins?.forEach { member in - var cProfileId: [CChar] = member.profileId.cArray - ugroups_legacy_member_add(userGroup, &cProfileId, true) + if let groupAdmins: [GroupMember] = legacyGroup.groupAdmins { + let adminIds: Set = groupAdmins.map { $0.profileId }.asSet() + let existingAdminIds: Set = Array(existingMembers + .filter { _, isAdmin in isAdmin } + .keys) + .asSet() + let adminIdsToAdd: Set = adminIds.subtracting(existingAdminIds) + let adminIdsToRemove: Set = existingAdminIds.subtracting(adminIds) + + adminIdsToAdd.forEach { adminId in + var cProfileId: [CChar] = adminId.cArray + ugroups_legacy_member_add(userGroup, &cProfileId, true) + } + + adminIdsToRemove.forEach { adminId in + var cProfileId: [CChar] = adminId.cArray + ugroups_legacy_member_remove(userGroup, &cProfileId) + } } // Store the updated group (can't be sure if we made any changes above) - userGroup.pointee.hidden = (legacyGroup.hidden ?? userGroup.pointee.hidden) userGroup.pointee.priority = (legacyGroup.priority ?? userGroup.pointee.priority) // Note: Need to free the legacy group pointer @@ -441,9 +483,6 @@ public extension SessionUtil { rootToken: String, publicKey: String ) throws { - // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard Features.useSharedUtilForUserConfig else { return } - try SessionUtil.performAndPushChange( db, for: .userGroups, @@ -466,9 +505,6 @@ public extension SessionUtil { } static func remove(_ db: Database, server: String, roomToken: String) throws { - // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard Features.useSharedUtilForUserConfig else { return } - try SessionUtil.performAndPushChange( db, for: .userGroups, @@ -495,9 +531,6 @@ public extension SessionUtil { members: Set, admins: Set ) throws { - // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard Features.useSharedUtilForUserConfig else { return } - try SessionUtil.performAndPushChange( db, for: .userGroups, @@ -549,9 +582,6 @@ public extension SessionUtil { members: Set? = nil, admins: Set? = nil ) throws { - // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard Features.useSharedUtilForUserConfig else { return } - try SessionUtil.performAndPushChange( db, for: .userGroups, @@ -589,29 +619,7 @@ public extension SessionUtil { } } - static func hide(_ db: Database, legacyGroupIds: [String]) throws { - guard !legacyGroupIds.isEmpty else { return } - - try SessionUtil.performAndPushChange( - db, - for: .userGroups, - publicKey: getUserHexEncodedPublicKey(db) - ) { conf in - try SessionUtil.upsert( - legacyGroups: legacyGroupIds.map { groupId in - LegacyGroupInfo( - id: groupId, - hidden: true - ) - }, - in: conf - ) - } - } - static func remove(_ db: Database, legacyGroupIds: [String]) throws { - // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard Features.useSharedUtilForUserConfig else { return } guard !legacyGroupIds.isEmpty else { return } try SessionUtil.performAndPushChange( @@ -630,13 +638,7 @@ public extension SessionUtil { // MARK: -- Group Changes - static func hide(_ db: Database, groupIds: [String]) throws { - guard !groupIds.isEmpty else { return } - } - static func remove(_ db: Database, groupIds: [String]) throws { - // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard Features.useSharedUtilForUserConfig else { return } guard !groupIds.isEmpty else { return } } } @@ -653,7 +655,6 @@ extension SessionUtil { case disappearingConfig case groupMembers case groupAdmins - case hidden case priority } @@ -665,7 +666,6 @@ extension SessionUtil { let disappearingConfig: DisappearingMessagesConfiguration? let groupMembers: [GroupMember]? let groupAdmins: [GroupMember]? - let hidden: Bool? let priority: Int32? init( @@ -675,7 +675,6 @@ extension SessionUtil { disappearingConfig: DisappearingMessagesConfiguration? = nil, groupMembers: [GroupMember]? = nil, groupAdmins: [GroupMember]? = nil, - hidden: Bool? = nil, priority: Int32? = nil ) { self.threadId = id @@ -684,7 +683,6 @@ extension SessionUtil { self.disappearingConfig = disappearingConfig self.groupMembers = groupMembers self.groupAdmins = groupAdmins - self.hidden = hidden self.priority = priority } diff --git a/SessionMessagingKit/LibSessionUtil/SessionUtil.swift b/SessionMessagingKit/LibSessionUtil/SessionUtil.swift index cf25acc99..03d5fd441 100644 --- a/SessionMessagingKit/LibSessionUtil/SessionUtil.swift +++ b/SessionMessagingKit/LibSessionUtil/SessionUtil.swift @@ -233,7 +233,7 @@ public enum SessionUtil { bytes: cPushData.pointee.config, count: cPushData.pointee.config_len ) - let hashesToRemove: [String] = [String]( + let obsoleteHashes: [String] = [String]( pointer: cPushData.pointee.obsolete, count: cPushData.pointee.obsolete_len, defaultValue: [] @@ -248,7 +248,7 @@ public enum SessionUtil { data: pushData ), namespace: variant.namespace, - obsoleteHashes: hashesToRemove + obsoleteHashes: obsoleteHashes ) } } diff --git a/SessionMessagingKit/Messages/Message+Destination.swift b/SessionMessagingKit/Messages/Message+Destination.swift index 5b6902b08..9b713e295 100644 --- a/SessionMessagingKit/Messages/Message+Destination.swift +++ b/SessionMessagingKit/Messages/Message+Destination.swift @@ -28,30 +28,31 @@ public extension Message { public static func from( _ db: Database, - thread: SessionThread, + threadId: String, + threadVariant: SessionThread.Variant, fileIds: [String]? = nil ) throws -> Message.Destination { - switch thread.variant { + switch threadVariant { case .contact: - if SessionId.Prefix(from: thread.id) == .blinded { - guard let lookup: BlindedIdLookup = try? BlindedIdLookup.fetchOne(db, id: thread.id) else { + if SessionId.Prefix(from: threadId) == .blinded { + guard let lookup: BlindedIdLookup = try? BlindedIdLookup.fetchOne(db, id: threadId) else { preconditionFailure("Attempting to send message to blinded id without the Open Group information") } return .openGroupInbox( server: lookup.openGroupServer, openGroupPublicKey: lookup.openGroupPublicKey, - blindedPublicKey: thread.id + blindedPublicKey: threadId ) } - return .contact(publicKey: thread.id) + return .contact(publicKey: threadId) case .legacyGroup, .group: - return .closedGroup(groupPublicKey: thread.id) + return .closedGroup(groupPublicKey: threadId) case .community: - guard let openGroup: OpenGroup = try thread.openGroup.fetchOne(db) else { + guard let openGroup: OpenGroup = try OpenGroup.fetchOne(db, id: threadId) else { throw StorageError.objectNotFound } diff --git a/SessionMessagingKit/Messages/Message.swift b/SessionMessagingKit/Messages/Message.swift index 88cfd0ce5..3519ac487 100644 --- a/SessionMessagingKit/Messages/Message.swift +++ b/SessionMessagingKit/Messages/Message.swift @@ -205,7 +205,16 @@ public extension Message { // Ensure we actually want to de-dupe messages for this namespace, otherwise just // succeed early - guard rawMessage.namespace.shouldDedupeMessages else { return processedMessage } + guard rawMessage.namespace.shouldDedupeMessages else { + // If we want to track the last hash then upsert the raw message info (don't + // want to fail if it already exsits because we don't want to dedupe messages + // in this namespace) + if rawMessage.namespace.shouldFetchSinceLastHash { + _ = try rawMessage.info.saved(db) + } + + return processedMessage + } // Retrieve the number of entries we have for the hash of this message let numExistingHashes: Int = (try? SnodeReceivedMessageInfo diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift index cd3a1f3e5..8737f2643 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift @@ -220,8 +220,10 @@ extension MessageReceiver { sdps: [], sentTimestampMs: nil // Explicitly nil as it's a separate message from above ), - to: try Message.Destination.from(db, thread: thread), - namespace: try Message.Destination.from(db, thread: thread).defaultNamespace, + to: try Message.Destination.from(db, threadId: thread.id, threadVariant: thread.variant), + namespace: try Message.Destination + .from(db, threadId: thread.id, threadVariant: thread.variant) + .defaultNamespace, interactionId: nil // Explicitly nil as it's a separate message from above ) ) diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift index 8390da206..5cd353d33 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift @@ -517,24 +517,22 @@ extension MessageReceiver { member.role == .admin && member.profileId == sender }) let members: [GroupMember] = allMembers.filter { $0.role == .standard } - let membersToRemove: [GroupMember] = members + let memberIdsToRemove: [String] = members .filter { member in didAdminLeave || // If the admin leaves the group is disbanded member.profileId == sender } - let memberIdsToRemove: [String] = members.map { $0.profileId } + .map { $0.profileId } // Update libSession try? SessionUtil.update( db, groupPublicKey: threadId, members: allMembers - .filter { - ($0.role == .standard || $0.role == .zombie) && - !membersToRemove.contains($0) - } + .filter { $0.role == .standard || $0.role == .zombie } .map { $0.profileId } - .asSet(), + .asSet() + .subtracting(memberIdsToRemove), admins: allMembers .filter { $0.role == .admin } .map { $0.profileId } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift index 634f0cdd5..f679385c2 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift @@ -3,12 +3,13 @@ import Foundation import GRDB import Sodium +import SessionUIKit import SessionUtilitiesKit extension MessageReceiver { - internal static func handleConfigurationMessage(_ db: Database, message: ConfigurationMessage) throws { + internal static func handleLegacyConfigurationMessage(_ db: Database, message: ConfigurationMessage) throws { guard !Features.useSharedUtilForUserConfig else { - // TODO: Show warning prompt for X days + TopBannerController.show(warning: .outdatedUserConfig) return } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift index cdd7ff633..7cf96a268 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift @@ -112,9 +112,6 @@ extension MessageReceiver { .filter(ids: blindedContactIds) .deleteAll(db) - try? SessionUtil - .remove(db, contactIds: blindedContactIds) - try updateContactApprovalStatusIfNeeded( db, senderSessionId: userPublicKey, diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift index 6f9e396e0..b686bdb5e 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift @@ -152,8 +152,7 @@ extension MessageSender { targetMembers: Set, userPublicKey: String, allGroupMembers: [GroupMember], - closedGroup: ClosedGroup, - thread: SessionThread + closedGroup: ClosedGroup ) -> AnyPublisher { guard allGroupMembers.contains(where: { $0.role == .admin && $0.profileId == userPublicKey }) else { return Fail(error: MessageSenderError.invalidClosedGroupUpdate) @@ -203,8 +202,11 @@ extension MessageSender { } ) ), - to: try Message.Destination.from(db, thread: thread), - namespace: try Message.Destination.from(db, thread: thread).defaultNamespace, + to: try Message.Destination + .from(db, threadId: closedGroup.threadId, threadVariant: .legacyGroup), + namespace: try Message.Destination + .from(db, threadId: closedGroup.threadId, threadVariant: .legacyGroup) + .defaultNamespace, interactionId: nil ) } @@ -257,12 +259,12 @@ extension MessageSender { name: String ) -> AnyPublisher { // Get the group, check preconditions & prepare - guard let thread: SessionThread = try? SessionThread.fetchOne(db, id: groupPublicKey) else { + guard (try? SessionThread.exists(db, id: groupPublicKey)) == true else { SNLog("Can't update nonexistent closed group.") return Fail(error: MessageSenderError.noThread) .eraseToAnyPublisher() } - guard let closedGroup: ClosedGroup = try? thread.closedGroup.fetchOne(db) else { + guard let closedGroup: ClosedGroup = try? ClosedGroup.fetchOne(db, id: groupPublicKey) else { return Fail(error: MessageSenderError.invalidClosedGroupUpdate) .eraseToAnyPublisher() } @@ -279,7 +281,7 @@ extension MessageSender { // Notify the user let interaction: Interaction = try Interaction( - threadId: thread.id, + threadId: groupPublicKey, authorId: userPublicKey, variant: .infoClosedGroupUpdated, body: ClosedGroupControlMessage.Kind @@ -295,7 +297,8 @@ extension MessageSender { db, message: ClosedGroupControlMessage(kind: .nameChange(name: name)), interactionId: interactionId, - in: thread + threadId: groupPublicKey, + threadVariant: .legacyGroup ) // Update libSession @@ -330,8 +333,7 @@ extension MessageSender { addedMembers: addedMembers, userPublicKey: userPublicKey, allGroupMembers: allGroupMembers, - closedGroup: closedGroup, - thread: thread + closedGroup: closedGroup ) } catch { @@ -350,8 +352,7 @@ extension MessageSender { removedMembers: removedMembers, userPublicKey: userPublicKey, allGroupMembers: allGroupMembers, - closedGroup: closedGroup, - thread: thread + closedGroup: closedGroup ) } catch { @@ -373,10 +374,9 @@ extension MessageSender { addedMembers: Set, userPublicKey: String, allGroupMembers: [GroupMember], - closedGroup: ClosedGroup, - thread: SessionThread + closedGroup: ClosedGroup ) throws { - guard let disappearingMessagesConfig: DisappearingMessagesConfiguration = try thread.disappearingMessagesConfiguration.fetchOne(db) else { + guard let disappearingMessagesConfig: DisappearingMessagesConfiguration = try DisappearingMessagesConfiguration.fetchOne(db, id: closedGroup.threadId) else { throw StorageError.objectNotFound } guard let encryptionKeyPair: ClosedGroupKeyPair = try closedGroup.fetchLatestKeyPair(db) else { @@ -395,7 +395,7 @@ extension MessageSender { // Notify the user let interaction: Interaction = try Interaction( - threadId: thread.id, + threadId: closedGroup.threadId, authorId: userPublicKey, variant: .infoClosedGroupUpdated, body: ClosedGroupControlMessage.Kind @@ -413,7 +413,8 @@ extension MessageSender { members: allGroupMembers .filter { $0.role == .standard || $0.role == .zombie } .map { $0.profileId } - .asSet(), + .asSet() + .union(addedMembers), admins: allGroupMembers .filter { $0.role == .admin } .map { $0.profileId } @@ -427,13 +428,13 @@ extension MessageSender { kind: .membersAdded(members: addedMembers.map { Data(hex: $0) }) ), interactionId: interactionId, - in: thread + threadId: closedGroup.threadId, + threadVariant: .legacyGroup ) try addedMembers.forEach { member in // Send updates to the new members individually - let thread: SessionThread = try SessionThread - .fetchOrCreate(db, id: member, variant: .contact, shouldBeVisible: nil) + try SessionThread.fetchOrCreate(db, id: member, variant: .contact, shouldBeVisible: nil) try MessageSender.send( db, @@ -454,7 +455,8 @@ extension MessageSender { ) ), interactionId: nil, - in: thread + threadId: closedGroup.threadId, + threadVariant: .legacyGroup ) // Add the users to the group @@ -478,8 +480,7 @@ extension MessageSender { removedMembers: Set, userPublicKey: String, allGroupMembers: [GroupMember], - closedGroup: ClosedGroup, - thread: SessionThread + closedGroup: ClosedGroup ) throws -> AnyPublisher { guard !removedMembers.contains(userPublicKey) else { SNLog("Invalid closed group update.") @@ -500,7 +501,7 @@ extension MessageSender { // Update zombie & member list try GroupMember - .filter(GroupMember.Columns.groupId == thread.id) + .filter(GroupMember.Columns.groupId == closedGroup.threadId) .filter(removedMembers.contains(GroupMember.Columns.profileId)) .filter([ GroupMember.Role.standard, GroupMember.Role.zombie ].contains(GroupMember.Columns.role)) .deleteAll(db) @@ -510,7 +511,7 @@ extension MessageSender { // Notify the user if needed (not if only zombie members were removed) if !removedMembers.subtracting(groupZombieIds).isEmpty { let interaction: Interaction = try Interaction( - threadId: thread.id, + threadId: closedGroup.threadId, authorId: userPublicKey, variant: .infoClosedGroupUpdated, body: ClosedGroupControlMessage.Kind @@ -538,8 +539,11 @@ extension MessageSender { members: removedMembers.map { Data(hex: $0) } ) ), - to: try Message.Destination.from(db, thread: thread), - namespace: try Message.Destination.from(db, thread: thread).defaultNamespace, + to: try Message.Destination + .from(db, threadId: closedGroup.threadId, threadVariant: .legacyGroup), + namespace: try Message.Destination + .from(db, threadId: closedGroup.threadId, threadVariant: .legacyGroup) + .defaultNamespace, interactionId: interactionId ) ) @@ -549,8 +553,7 @@ extension MessageSender { targetMembers: members, userPublicKey: userPublicKey, allGroupMembers: allGroupMembers, - closedGroup: closedGroup, - thread: thread + closedGroup: closedGroup ) } .eraseToAnyPublisher() @@ -605,8 +608,10 @@ extension MessageSender { message: ClosedGroupControlMessage( kind: .memberLeft ), - to: try Message.Destination.from(db, thread: thread), - namespace: try Message.Destination.from(db, thread: thread).defaultNamespace, + to: try Message.Destination.from(db, threadId: groupPublicKey, threadVariant: .legacyGroup), + namespace: try Message.Destination + .from(db, threadId: groupPublicKey, threadVariant: .legacyGroup) + .defaultNamespace, interactionId: interactionId ) @@ -631,6 +636,9 @@ extension MessageSender { } catch { switch error { + // There are some cases where the keys for a ClosedGroup can be lost or become invalid, in + // those cases we don't want to prevent the user from being able to leave a group so catch + // them and just remove the group from the users devices case MessageSenderError.noKeyPair, MessageSenderError.encryptionFailed: try? ClosedGroup.removeKeysAndUnsubscribe( db, @@ -638,6 +646,9 @@ extension MessageSender { removeGroupData: false, calledFromConfigHandling: false ) + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() default: break } @@ -721,7 +732,8 @@ extension MessageSender { ) ), interactionId: nil, - in: thread + threadId: thread.id, + threadVariant: thread.variant ) } catch {} diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index b692dc706..c94e99a95 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -226,7 +226,7 @@ public enum MessageReceiver { ) case let message as ConfigurationMessage: - try MessageReceiver.handleConfigurationMessage(db, message: message) + try MessageReceiver.handleLegacyConfigurationMessage(db, message: message) case let message as UnsendRequest: try MessageReceiver.handleUnsendRequest( diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift b/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift index ff71f31d3..f28d33886 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift @@ -9,7 +9,13 @@ extension MessageSender { // MARK: - Durable - public static func send(_ db: Database, interaction: Interaction, in thread: SessionThread, isSyncMessage: Bool = false) throws { + public static func send( + _ db: Database, + interaction: Interaction, + threadId: String, + threadVariant: SessionThread.Variant, + isSyncMessage: Bool = false + ) throws { // Only 'VisibleMessage' types can be sent via this method guard interaction.variant == .standardOutgoing else { throw MessageSenderError.invalidMessage } guard let interactionId: Int64 = interaction.id else { throw StorageError.objectNotSaved } @@ -17,25 +23,39 @@ extension MessageSender { send( db, message: VisibleMessage.from(db, interaction: interaction), - threadId: thread.id, + threadId: threadId, interactionId: interactionId, - to: try Message.Destination.from(db, thread: thread), + to: try Message.Destination.from(db, threadId: threadId, threadVariant: threadVariant), isSyncMessage: isSyncMessage ) } - public static func send(_ db: Database, message: Message, interactionId: Int64?, in thread: SessionThread, isSyncMessage: Bool = false) throws { + public static func send( + _ db: Database, + message: Message, + interactionId: Int64?, + threadId: String, + threadVariant: SessionThread.Variant, + isSyncMessage: Bool = false + ) throws { send( db, message: message, - threadId: thread.id, + threadId: threadId, interactionId: interactionId, - to: try Message.Destination.from(db, thread: thread), + to: try Message.Destination.from(db, threadId: threadId, threadVariant: threadVariant), isSyncMessage: isSyncMessage ) } - public static func send(_ db: Database, message: Message, threadId: String?, interactionId: Int64?, to destination: Message.Destination, isSyncMessage: Bool = false) { + public static func send( + _ db: Database, + message: Message, + threadId: String?, + interactionId: Int64?, + to destination: Message.Destination, + isSyncMessage: Bool = false + ) { // If it's a sync message then we need to make some slight tweaks before sending so use the proper // sync message sending process instead of the standard process guard !isSyncMessage else { @@ -70,17 +90,20 @@ extension MessageSender { public static func preparedSendData( _ db: Database, interaction: Interaction, - in thread: SessionThread + threadId: String, + threadVariant: SessionThread.Variant ) throws -> PreparedSendData { // Only 'VisibleMessage' types can be sent via this method guard interaction.variant == .standardOutgoing else { throw MessageSenderError.invalidMessage } guard let interactionId: Int64 = interaction.id else { throw StorageError.objectNotSaved } - + return try MessageSender.preparedSendData( db, message: VisibleMessage.from(db, interaction: interaction), - to: try Message.Destination.from(db, thread: thread), - namespace: try Message.Destination.from(db, thread: thread).defaultNamespace, + to: try Message.Destination.from(db, threadId: threadId, threadVariant: threadVariant), + namespace: try Message.Destination + .from(db, threadId: threadId, threadVariant: threadVariant) + .defaultNamespace, interactionId: interactionId ) } diff --git a/SessionMessagingKit/Sending & Receiving/Typing Indicators/TypingIndicators.swift b/SessionMessagingKit/Sending & Receiving/Typing Indicators/TypingIndicators.swift index 2d40d29db..d3891d087 100644 --- a/SessionMessagingKit/Sending & Receiving/Typing Indicators/TypingIndicators.swift +++ b/SessionMessagingKit/Sending & Receiving/Typing Indicators/TypingIndicators.swift @@ -15,6 +15,7 @@ public class TypingIndicators { private class Indicator { fileprivate let threadId: String + fileprivate let threadVariant: SessionThread.Variant fileprivate let direction: Direction fileprivate let timestampMs: Int64 @@ -45,6 +46,7 @@ public class TypingIndicators { else { return nil } self.threadId = threadId + self.threadVariant = threadVariant self.direction = direction self.timestampMs = (timestampMs ?? SnodeAPI.currentOffsetTimestampMs()) } @@ -75,15 +77,12 @@ public class TypingIndicators { switch direction { case .outgoing: - guard let thread: SessionThread = try? SessionThread.fetchOne(db, id: self.threadId) else { - return - } - try? MessageSender.send( db, message: TypingIndicator(kind: .stopped), interactionId: nil, - in: thread + threadId: threadId, + threadVariant: threadVariant ) case .incoming: @@ -111,15 +110,12 @@ public class TypingIndicators { private func scheduleRefreshCallback(_ db: Database, shouldSend: Bool = true) { if shouldSend { - guard let thread: SessionThread = try? SessionThread.fetchOne(db, id: self.threadId) else { - return - } - try? MessageSender.send( db, message: TypingIndicator(kind: .started), interactionId: nil, - in: thread + threadId: threadId, + threadVariant: threadVariant ) } diff --git a/SessionMessagingKit/Utilities/ProfileManager.swift b/SessionMessagingKit/Utilities/ProfileManager.swift index becfb80a2..f860d65e6 100644 --- a/SessionMessagingKit/Utilities/ProfileManager.swift +++ b/SessionMessagingKit/Utilities/ProfileManager.swift @@ -32,7 +32,6 @@ public struct ProfileManager { // The max bytes for a user's profile name, encoded in UTF8. // Before encrypting and submitting we NULL pad the name data to this length. - private static let nameDataLength: UInt = 64 public static let maxAvatarDiameter: CGFloat = 640 private static let maxAvatarBytes: UInt = (5 * 1000 * 1000) public static let avatarAES256KeyByteLength: Int = 32 @@ -45,7 +44,11 @@ public struct ProfileManager { // MARK: - Functions public static func isToLong(profileName: String) -> Bool { - return ((profileName.data(using: .utf8)?.count ?? 0) > nameDataLength) + return (profileName.utf8CString.count > SessionUtil.libSessionMaxNameByteLength) + } + + public static func isToLong(profileUrl: String) -> Bool { + return (profileUrl.utf8CString.count > SessionUtil.libSessionMaxProfileUrlByteLength) } public static func profileAvatar(_ db: Database? = nil, id: String) -> Data? { diff --git a/SessionShareExtension/ThreadPickerVC.swift b/SessionShareExtension/ThreadPickerVC.swift index 52b366fcc..4ef64aa8f 100644 --- a/SessionShareExtension/ThreadPickerVC.swift +++ b/SessionShareExtension/ThreadPickerVC.swift @@ -190,9 +190,13 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView Storage.shared .writePublisher(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db -> MessageSender.PreparedSendData in - guard let thread: SessionThread = try SessionThread.fetchOne(db, id: threadId) else { - throw MessageSenderError.noThread - } + guard + let threadVariant: SessionThread.Variant = try SessionThread + .filter(id: threadId) + .select(.variant) + .asRequest(of: SessionThread.Variant.self) + .fetchOne(db) + else { throw MessageSenderError.noThread } // Create the interaction let interaction: Interaction = try Interaction( @@ -245,7 +249,8 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView .preparedSendData( db, interaction: interaction, - in: thread + threadId: threadId, + threadVariant: threadVariant ) } .flatMap { diff --git a/SessionSnodeKit/Models/SnodeMessage.swift b/SessionSnodeKit/Models/SnodeMessage.swift index 69cff264e..07cf76f09 100644 --- a/SessionSnodeKit/Models/SnodeMessage.swift +++ b/SessionSnodeKit/Models/SnodeMessage.swift @@ -9,7 +9,6 @@ public final class SnodeMessage: Codable { case data case ttl case timestampMs = "timestamp" - case nonce } /// The hex encoded public key of the recipient. @@ -57,6 +56,5 @@ extension SnodeMessage { try container.encode(data, forKey: .data) try container.encode(ttl, forKey: .ttl) try container.encode(timestampMs, forKey: .timestampMs) - try container.encode("", forKey: .nonce) } } diff --git a/SessionSnodeKit/Networking/SnodeAPI.swift b/SessionSnodeKit/Networking/SnodeAPI.swift index 7bdcaa858..794366691 100644 --- a/SessionSnodeKit/Networking/SnodeAPI.swift +++ b/SessionSnodeKit/Networking/SnodeAPI.swift @@ -305,7 +305,7 @@ public final class SnodeAPI { .map { _ -> [SnodeAPI.Namespace: String] in namespaces .reduce(into: [:]) { result, namespace in - guard namespace.shouldDedupeMessages else { return } + guard namespace.shouldFetchSinceLastHash else { return } // Prune expired message hashes for this namespace on this service node SnodeReceivedMessageInfo.pruneExpiredMessageHashInfo( @@ -472,7 +472,7 @@ public final class SnodeAPI { } .tryFlatMap { lastHash -> AnyPublisher<(info: ResponseInfoType, data: GetMessagesResponse?, lastHash: String?), Error> in - guard namespace.requiresWriteAuthentication else { + guard namespace.requiresReadAuthentication else { return SnodeAPI .send( request: SnodeRequest( diff --git a/SessionSnodeKit/Types/SnodeAPINamespace.swift b/SessionSnodeKit/Types/SnodeAPINamespace.swift index 4d75897f7..726377bc2 100644 --- a/SessionSnodeKit/Types/SnodeAPINamespace.swift +++ b/SessionSnodeKit/Types/SnodeAPINamespace.swift @@ -18,6 +18,7 @@ public extension SnodeAPI { var requiresReadAuthentication: Bool { switch self { + // Legacy closed groups don't support authenticated retrieval case .legacyClosedGroup: return false default: return true } @@ -25,12 +26,17 @@ public extension SnodeAPI { var requiresWriteAuthentication: Bool { switch self { - // Not in use until we can batch delete and store config messages - case .default, .legacyClosedGroup: return false + // Legacy closed groups don't support authenticated storage + case .legacyClosedGroup: return false default: return true } } + /// This flag indicates whether we should provide a `lastHash` when retrieving messages from the specified + /// namespace, when `true` we will only receive messages added since the provided `lastHash`, otherwise + /// we will retrieve **all** messages from the namespace + public var shouldFetchSinceLastHash: Bool { true } + /// This flag indicates whether we should dedupe messages from the specified namespace, when `true` we will /// store a `SnodeReceivedMessageInfo` record for the message and check for a matching record whenever /// we receive a message from this namespace diff --git a/SessionUIKit/Components/TopBannerController.swift b/SessionUIKit/Components/TopBannerController.swift new file mode 100644 index 000000000..6264471f4 --- /dev/null +++ b/SessionUIKit/Components/TopBannerController.swift @@ -0,0 +1,188 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import UIKit +import SessionUtilitiesKit + +public class TopBannerController: UIViewController { + public enum Warning: String, Codable { + case outdatedUserConfig + + var text: String { + switch self { + case .outdatedUserConfig: return "USER_CONFIG_OUTDATED_WARNING".localized() + } + } + } + + private static var lastInstance: TopBannerController? + private let child: UIViewController + private var initialCachedWarning: Warning? + + // MARK: - UI + + private lazy var bottomConstraint: NSLayoutConstraint = bannerLabel + .pin(.bottom, to: .bottom, of: bannerContainer, withInset: -Values.verySmallSpacing) + + private let contentStackView: UIStackView = { + let result: UIStackView = UIStackView() + result.translatesAutoresizingMaskIntoConstraints = false + result.axis = .vertical + result.distribution = .fill + result.alignment = .fill + + return result + }() + + private let bannerContainer: UIView = { + let result: UIView = UIView() + result.translatesAutoresizingMaskIntoConstraints = false + result.themeBackgroundColor = .primary + result.isHidden = true + + return result + }() + + private let bannerLabel: UILabel = { + let result: UILabel = UILabel() + result.translatesAutoresizingMaskIntoConstraints = false + result.setContentHuggingPriority(.required, for: .vertical) + result.font = .systemFont(ofSize: Values.verySmallFontSize) + result.textAlignment = .center + result.themeTextColor = .black + result.numberOfLines = 0 + + return result + }() + + private lazy var closeButton: UIButton = { + let result: UIButton = UIButton() + result.translatesAutoresizingMaskIntoConstraints = false + result.setImage( + UIImage(systemName: "xmark", withConfiguration: UIImage.SymbolConfiguration(pointSize: 12, weight: .bold))? + .withRenderingMode(.alwaysTemplate), + for: .normal + ) + result.contentMode = .center + result.themeTintColor = .black + result.addTarget(self, action: #selector(dismissBanner), for: .touchUpInside) + + return result + }() + + // MARK: - Initialization + + public init( + child: UIViewController, + cachedWarning: Warning? = nil + ) { + self.child = child + self.initialCachedWarning = cachedWarning + + super.init(nibName: nil, bundle: nil) + + TopBannerController.lastInstance = self + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Lifecycle + + public override func loadView() { + super.loadView() + + view.addSubview(contentStackView) + + contentStackView.addArrangedSubview(bannerContainer) + + child.willMove(toParent: self) + addChild(child) + contentStackView.addArrangedSubview(child.view) + child.didMove(toParent: self) + + bannerContainer.addSubview(bannerLabel) + bannerContainer.addSubview(closeButton) + + setupLayout() + + // If we had an initial warning then show it + if let warning: Warning = self.initialCachedWarning { + UIView.performWithoutAnimation { + TopBannerController.show(warning: warning) + } + + self.initialCachedWarning = nil + } + } + + private func setupLayout() { + contentStackView.pin(.top, to: .top, of: view.safeAreaLayoutGuide) + contentStackView.pin(.leading, to: .leading, of: view) + contentStackView.pin(.trailing, to: .trailing, of: view) + contentStackView.pin(.bottom, to: .bottom, of: view) + + bannerLabel.pin(.top, to: .top, of: view.safeAreaLayoutGuide, withInset: Values.verySmallSpacing) + bannerLabel.pin(.leading, to: .leading, of: bannerContainer, withInset: Values.veryLargeSpacing) + bannerLabel.pin(.trailing, to: .trailing, of: bannerContainer, withInset: -Values.veryLargeSpacing) + bottomConstraint.isActive = false + + let buttonSize: CGFloat = (12 + (Values.smallSpacing * 2)) + closeButton.center(.vertical, in: bannerLabel) + closeButton.pin(.trailing, to: .trailing, of: bannerContainer, withInset: -Values.smallSpacing) + closeButton.set(.width, to: buttonSize) + closeButton.set(.height, to: buttonSize) + } + + // MARK: - Actions + + @objc private func dismissBanner() { + // Remove the cached warning + UserDefaults.sharedLokiProject?[.topBannerWarningToShow] = nil + + UIView.animate( + withDuration: 0.3, + animations: { [weak self] in + self?.bottomConstraint.isActive = false + self?.contentStackView.setNeedsLayout() + self?.contentStackView.layoutIfNeeded() + }, + completion: { [weak self] _ in + self?.bannerContainer.isHidden = true + } + ) + } + + // MARK: - Functions + + public static func show(warning: Warning, inWindowFor view: UIView? = nil) { + guard Thread.isMainThread else { + DispatchQueue.main.async { + TopBannerController.show(warning: warning, inWindowFor: view) + } + return + } + + // Not an ideal approach but should allow + guard let instance: TopBannerController = ((view?.window?.rootViewController as? TopBannerController) ?? TopBannerController.lastInstance) else { + return + } + + // Cache the banner to show (so we can show it on re-launch) + UserDefaults.sharedLokiProject?[.topBannerWarningToShow] = warning.rawValue + + UIView.performWithoutAnimation { + instance.bannerLabel.text = warning.text + instance.bannerLabel.setNeedsLayout() + instance.bannerLabel.layoutIfNeeded() + instance.bottomConstraint.isActive = false + instance.bannerContainer.isHidden = false + } + + UIView.animate(withDuration: 0.3) { [weak instance] in + instance?.bottomConstraint.isActive = true + instance?.contentStackView.setNeedsLayout() + instance?.contentStackView.layoutIfNeeded() + } + } +} diff --git a/SessionUIKit/Utilities/UIView+Constraints.swift b/SessionUIKit/Utilities/UIView+Constraints.swift index 2ab9187d2..dbeee0877 100644 --- a/SessionUIKit/Utilities/UIView+Constraints.swift +++ b/SessionUIKit/Utilities/UIView+Constraints.swift @@ -76,6 +76,18 @@ public extension Anchorable { .setting(isActive: true) } + @discardableResult + func pin(_ constraineeEdge: UIView.HorizontalEdge, greaterThanOrEqualTo constrainerEdge: UIView.HorizontalEdge, of anchorable: Anchorable, withInset inset: CGFloat = 0) -> NSLayoutConstraint { + (self as? UIView)?.translatesAutoresizingMaskIntoConstraints = false + + return anchor(from: constraineeEdge) + .constraint( + greaterThanOrEqualTo: anchorable.anchor(from: constrainerEdge), + constant: inset + ) + .setting(isActive: true) + } + @discardableResult func pin(_ constraineeEdge: UIView.VerticalEdge, to constrainerEdge: UIView.VerticalEdge, of anchorable: Anchorable, withInset inset: CGFloat = 0) -> NSLayoutConstraint { (self as? UIView)?.translatesAutoresizingMaskIntoConstraints = false @@ -87,6 +99,18 @@ public extension Anchorable { ) .setting(isActive: true) } + + @discardableResult + func pin(_ constraineeEdge: UIView.VerticalEdge, greaterThanOrEqualTo constrainerEdge: UIView.VerticalEdge, of anchorable: Anchorable, withInset inset: CGFloat = 0) -> NSLayoutConstraint { + (self as? UIView)?.translatesAutoresizingMaskIntoConstraints = false + + return anchor(from: constraineeEdge) + .constraint( + greaterThanOrEqualTo: anchorable.anchor(from: constrainerEdge), + constant: inset + ) + .setting(isActive: true) + } } // MARK: - View extensions diff --git a/SessionUtilitiesKit/Crypto/Data+SecureRandom.swift b/SessionUtilitiesKit/Crypto/Data+SecureRandom.swift deleted file mode 100644 index fa9b1d7f2..000000000 --- a/SessionUtilitiesKit/Crypto/Data+SecureRandom.swift +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import Foundation - -public extension Data { - - /// Returns `size` bytes of random data generated using the default secure random number generator. See - /// [SecRandomCopyBytes](https://developer.apple.com/documentation/security/1399291-secrandomcopybytes) for more information. - static func getSecureRandomData(ofSize size: UInt) -> Data? { - var data = Data(count: Int(size)) - let result = data.withUnsafeMutableBytes { SecRandomCopyBytes(kSecRandomDefault, Int(size), $0.baseAddress!) } - guard result == errSecSuccess else { return nil } - return data - } -} diff --git a/SessionUtilitiesKit/General/SNUserDefaults.swift b/SessionUtilitiesKit/General/SNUserDefaults.swift index eda9cbcba..82f18bd64 100644 --- a/SessionUtilitiesKit/General/SNUserDefaults.swift +++ b/SessionUtilitiesKit/General/SNUserDefaults.swift @@ -59,6 +59,7 @@ public enum SNUserDefaults { public enum String : Swift.String { case deviceToken + case topBannerWarningToShow } } diff --git a/SessionUtilitiesKit/Utilities/Randomness.swift b/SessionUtilitiesKit/Utilities/Randomness.swift index a242d93dc..6b6de608a 100644 --- a/SessionUtilitiesKit/Utilities/Randomness.swift +++ b/SessionUtilitiesKit/Utilities/Randomness.swift @@ -3,17 +3,19 @@ import Foundation public enum Randomness { + /// Returns `size` bytes of random data generated using the default secure random number generator. See + /// [SecRandomCopyBytes](https://developer.apple.com/documentation/security/1399291-secrandomcopybytes) for more information. public static func generateRandomBytes(numberBytes: Int) throws -> Data { - var randomByes: Data = Data(count: numberBytes) - let result = randomByes.withUnsafeMutableBytes { + var randomBytes: Data = Data(count: numberBytes) + let result = randomBytes.withUnsafeMutableBytes { SecRandomCopyBytes(kSecRandomDefault, numberBytes, $0.baseAddress!) } - guard result == errSecSuccess, randomByes.count == numberBytes else { + guard result == errSecSuccess, randomBytes.count == numberBytes else { print("Problem generating random bytes") throw GeneralError.randomGenerationFailed } - return randomByes + return randomBytes } } diff --git a/SessionUtilitiesKitTests/General/ArrayUtilitiesSpec.swift b/SessionUtilitiesKitTests/General/ArrayUtilitiesSpec.swift new file mode 100644 index 000000000..b2d9b74dd --- /dev/null +++ b/SessionUtilitiesKitTests/General/ArrayUtilitiesSpec.swift @@ -0,0 +1,86 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +import Quick +import Nimble + +@testable import SessionUtilitiesKit + +class ArrayUtilitiesSpec: QuickSpec { + private struct TestType: Equatable { + let stringValue: String + let intValue: Int + } + + // MARK: - Spec + + override func spec() { + describe("an Array") { + context("when grouping") { + it("maintains the original array ordering") { + let data: [TestType] = [ + TestType(stringValue: "b", intValue: 5), + TestType(stringValue: "A", intValue: 2), + TestType(stringValue: "z", intValue: 1), + TestType(stringValue: "x", intValue: 3), + TestType(stringValue: "7", intValue: 6), + TestType(stringValue: "A", intValue: 7), + TestType(stringValue: "z", intValue: 8), + TestType(stringValue: "7", intValue: 9), + TestType(stringValue: "7", intValue: 4), + TestType(stringValue: "h", intValue: 2), + TestType(stringValue: "z", intValue: 1), + TestType(stringValue: "m", intValue: 2) + ] + + let result1: [String: [TestType]] = data.grouped(by: \.stringValue) + let result2: [Int: [TestType]] = data.grouped(by: \.intValue) + + expect(result1).to(equal( + [ + "b": [TestType(stringValue: "b", intValue: 5)], + "A": [ + TestType(stringValue: "A", intValue: 2), + TestType(stringValue: "A", intValue: 7) + ], + "z": [ + TestType(stringValue: "z", intValue: 1), + TestType(stringValue: "z", intValue: 8), + TestType(stringValue: "z", intValue: 1) + ], + "x": [TestType(stringValue: "x", intValue: 3)], + "7": [ + TestType(stringValue: "7", intValue: 6), + TestType(stringValue: "7", intValue: 9), + TestType(stringValue: "7", intValue: 4) + ], + "h": [TestType(stringValue: "h", intValue: 2)], + "m": [TestType(stringValue: "m", intValue: 2)] + ] + )) + expect(result2).to(equal( + [ + 1: [ + TestType(stringValue: "z", intValue: 1), + TestType(stringValue: "z", intValue: 1), + ], + 2: [ + TestType(stringValue: "A", intValue: 2), + TestType(stringValue: "h", intValue: 2), + TestType(stringValue: "m", intValue: 2) + ], + 3: [TestType(stringValue: "x", intValue: 3)], + 4: [TestType(stringValue: "7", intValue: 4)], + 5: [TestType(stringValue: "b", intValue: 5)], + 6: [TestType(stringValue: "7", intValue: 6)], + 7: [TestType(stringValue: "A", intValue: 7)], + 9: [TestType(stringValue: "7", intValue: 9)], + 8: [TestType(stringValue: "z", intValue: 8)] + ] + )) + } + } + } + } +} From a6699f0c585a9ed4143a9303df5e299c744280b4 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 9 Mar 2023 15:46:45 +1100 Subject: [PATCH 039/135] Made a few small bug fixes & improvements Updated the conversation empty state to appear in all conversation types Recreating the interaction FTS table if it doesn't exist (somehow was removed from some of my DBs...) Fixed a couple of incorrect swipe action colours Fixed a few issues causing unneeded conversation cell layouts which could impact performance Fixed a bug where the in-conversation search loading indicator wouldn't disappear when pressing the clear button --- Session/Closed Groups/EditClosedGroupVC.swift | 2 +- .../Conversations/ConversationSearch.swift | 1 + Session/Conversations/ConversationVC.swift | 67 +++++-------------- .../MessageRequestsViewController.swift | 6 +- .../Translations/de.lproj/Localizable.strings | 2 +- .../Translations/en.lproj/Localizable.strings | 2 +- .../Translations/es.lproj/Localizable.strings | 2 +- .../Translations/fa.lproj/Localizable.strings | 2 +- .../Translations/fi.lproj/Localizable.strings | 2 +- .../Translations/fr.lproj/Localizable.strings | 2 +- .../Translations/hi.lproj/Localizable.strings | 2 +- .../Translations/hr.lproj/Localizable.strings | 2 +- .../id-ID.lproj/Localizable.strings | 2 +- .../Translations/it.lproj/Localizable.strings | 2 +- .../Translations/ja.lproj/Localizable.strings | 2 +- .../Translations/nl.lproj/Localizable.strings | 2 +- .../Translations/pl.lproj/Localizable.strings | 2 +- .../pt_BR.lproj/Localizable.strings | 2 +- .../Translations/ru.lproj/Localizable.strings | 2 +- .../Translations/si.lproj/Localizable.strings | 2 +- .../Translations/sk.lproj/Localizable.strings | 2 +- .../Translations/sv.lproj/Localizable.strings | 2 +- .../Translations/th.lproj/Localizable.strings | 2 +- .../vi-VN.lproj/Localizable.strings | 2 +- .../zh-Hant.lproj/Localizable.strings | 2 +- .../zh_CN.lproj/Localizable.strings | 2 +- .../Migrations/_012_SessionUtilChanges.swift | 12 ++++ 27 files changed, 57 insertions(+), 75 deletions(-) diff --git a/Session/Closed Groups/EditClosedGroupVC.swift b/Session/Closed Groups/EditClosedGroupVC.swift index 345f98d59..decd0b236 100644 --- a/Session/Closed Groups/EditClosedGroupVC.swift +++ b/Session/Closed Groups/EditClosedGroupVC.swift @@ -258,7 +258,7 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat let delete: UIContextualAction = UIContextualAction( title: "GROUP_ACTION_REMOVE".localized(), icon: UIImage(named: "icon_bin"), - themeTintColor: .textPrimary, + themeTintColor: .white, themeBackgroundColor: .conversationButton_swipeDestructive, side: .trailing, actionIndex: 0, diff --git a/Session/Conversations/ConversationSearch.swift b/Session/Conversations/ConversationSearch.swift index c98e650f9..e62f2d2f2 100644 --- a/Session/Conversations/ConversationSearch.swift +++ b/Session/Conversations/ConversationSearch.swift @@ -317,6 +317,7 @@ public final class SearchResultsBar: UIView { label.text = "" downButton.isEnabled = false upButton.isEnabled = false + stopLoading() return } diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index 39a0014d9..e6c0c3ee9 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -162,6 +162,8 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl result.register(view: InfoMessageCell.self) result.register(view: TypingIndicatorCell.self) result.register(view: CallMessageCell.self) + result.estimatedSectionHeaderHeight = ConversationVC.loadingHeaderHeight + result.sectionFooterHeight = 0 result.dataSource = self result.delegate = self @@ -209,7 +211,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl private lazy var emptyStateLabel: UILabel = { let text: String = String( - format: "GROUP_CONVERSATION_EMPTY_STATE".localized(), + format: "CONVERSATION_EMPTY_STATE".localized(), self.viewModel.threadData.displayName ) @@ -228,10 +230,6 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl result.textAlignment = .center result.lineBreakMode = .byWordWrapping result.numberOfLines = 0 - result.isHidden = ( - self.viewModel.threadData.threadVariant != .legacyGroup && - self.viewModel.threadData.threadVariant != .group - ) return result }() @@ -401,8 +399,8 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl view.addSubview(messageRequestStackView) emptyStateLabel.pin(.top, to: .top, of: view, withInset: Values.largeSpacing) - emptyStateLabel.pin(.leading, to: .leading, of: view, withInset: Values.largeSpacing) - emptyStateLabel.pin(.trailing, to: .trailing, of: view, withInset: -Values.largeSpacing) + emptyStateLabel.pin(.leading, to: .leading, of: view, withInset: Values.veryLargeSpacing) + emptyStateLabel.pin(.trailing, to: .trailing, of: view, withInset: -Values.veryLargeSpacing) messageRequestStackView.addArrangedSubview(messageRequestBlockButton) messageRequestStackView.addArrangedSubview(messageRequestDescriptionContainerView) @@ -620,7 +618,9 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl // PagedDatabaseObserver won't have them so we need to force a re-fetch of the current // data to ensure everything is up to date if didReturnFromBackground { - self?.viewModel.pagedDataObserver?.reload() + DispatchQueue.global(qos: .background).async { + self?.viewModel.pagedDataObserver?.reload() + } } } } @@ -674,7 +674,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl // Update the empty state let text: String = String( - format: "GROUP_CONVERSATION_EMPTY_STATE".localized(), + format: "CONVERSATION_EMPTY_STATE".localized(), updatedThreadData.displayName ) emptyStateLabel.attributedText = NSAttributedString(string: text) @@ -775,6 +775,13 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl changeset: StagedChangeset<[ConversationViewModel.SectionModel]>, initialLoad: Bool = false ) { + // Determine if we have any messages for the empty state + let hasMessages: Bool = (updatedData + .filter { $0.model == .messages } + .first? + .elements + .isEmpty == false) + // Ensure the first load or a load when returning from a child screen runs without // animations (if we don't do this the cells will animate in from a frame of // CGRect.zero or have a buggy transition) @@ -785,17 +792,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl self.viewModel.updateInteractionData(updatedData) // Update the empty state - let hasMessages: Bool = (updatedData - .filter { $0.model == .messages } - .first? - .elements - .isEmpty == false) - self.emptyStateLabel.isHidden = ( - hasMessages || ( - self.viewModel.threadData.threadVariant != .legacyGroup && - self.viewModel.threadData.threadVariant != .group - ) - ) + self.emptyStateLabel.isHidden = hasMessages UIView.performWithoutAnimation { self.tableView.reloadData() @@ -806,12 +803,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl } // Update the empty state - self.emptyStateLabel.isHidden = ( - !updatedData.isEmpty || ( - self.viewModel.threadData.threadVariant != .legacyGroup && - self.viewModel.threadData.threadVariant != .group - ) - ) + self.emptyStateLabel.isHidden = hasMessages // Update the ReactionListSheet (if one exists) if let messageUpdates: [MessageViewModel] = updatedData.first(where: { $0.model == .messages })?.elements { @@ -1428,14 +1420,6 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl } // MARK: - UITableViewDelegate - - func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { - return UITableView.automaticDimension - } - - func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - return UITableView.automaticDimension - } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { let section: ConversationViewModel.SectionModel = viewModel.interactionData[section] @@ -1507,15 +1491,6 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl return } - // Note: In this case we need to force a tableView layout to ensure updating the - // scroll position has the correct offset (otherwise there are some cases where - // the screen will jump up - eg. when sending a reply while the soft keyboard - // is visible) - UIView.performWithoutAnimation { - self.tableView.setNeedsLayout() - self.tableView.layoutIfNeeded() - } - let targetIndexPath: IndexPath = IndexPath( row: (self.viewModel.interactionData[messagesSectionIndex].elements.count - 1), section: messagesSectionIndex @@ -1762,12 +1737,6 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl return } - // Note: If the tableView needs to layout then we should do it first without an animation - // to prevent an annoying issue where the screen jumps slightly after the scroll completes - UIView.performWithoutAnimation { - self.tableView.layoutIfNeeded() - } - let targetIndexPath: IndexPath = IndexPath( row: targetMessageIndex, section: messageSectionIndex diff --git a/Session/Home/Message Requests/MessageRequestsViewController.swift b/Session/Home/Message Requests/MessageRequestsViewController.swift index 0167aca1e..268878979 100644 --- a/Session/Home/Message Requests/MessageRequestsViewController.swift +++ b/Session/Home/Message Requests/MessageRequestsViewController.swift @@ -408,7 +408,7 @@ class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDat let delete: UIContextualAction = UIContextualAction( title: "TXT_DELETE_TITLE".localized(), icon: UIImage(named: "icon_bin"), - themeTintColor: .textPrimary, + themeTintColor: .white, themeBackgroundColor: .conversationButton_swipeDestructive, side: .trailing, actionIndex: (threadVariant == .contact ? 1 : 0), @@ -428,8 +428,8 @@ class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDat let block: UIContextualAction = UIContextualAction( title: "BLOCK_LIST_BLOCK_BUTTON".localized(), icon: UIImage(named: "table_ic_block"), - themeTintColor: .textPrimary, - themeBackgroundColor: .conversationButton_swipeDestructive, + themeTintColor: .white, + themeBackgroundColor: .conversationButton_swipeSecondary, side: .trailing, actionIndex: 0, indexPath: indexPath, diff --git a/Session/Meta/Translations/de.lproj/Localizable.strings b/Session/Meta/Translations/de.lproj/Localizable.strings index b92b9d79a..519a42b20 100644 --- a/Session/Meta/Translations/de.lproj/Localizable.strings +++ b/Session/Meta/Translations/de.lproj/Localizable.strings @@ -613,5 +613,5 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; -"GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index 4b00d63ca..d2bb87bd7 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -613,5 +613,5 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; -"GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/es.lproj/Localizable.strings b/Session/Meta/Translations/es.lproj/Localizable.strings index a960a8ef9..91484888d 100644 --- a/Session/Meta/Translations/es.lproj/Localizable.strings +++ b/Session/Meta/Translations/es.lproj/Localizable.strings @@ -613,5 +613,5 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; -"GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/fa.lproj/Localizable.strings b/Session/Meta/Translations/fa.lproj/Localizable.strings index c909428cf..d764eb276 100644 --- a/Session/Meta/Translations/fa.lproj/Localizable.strings +++ b/Session/Meta/Translations/fa.lproj/Localizable.strings @@ -613,5 +613,5 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; -"GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/fi.lproj/Localizable.strings b/Session/Meta/Translations/fi.lproj/Localizable.strings index 6be88f937..84c99f71c 100644 --- a/Session/Meta/Translations/fi.lproj/Localizable.strings +++ b/Session/Meta/Translations/fi.lproj/Localizable.strings @@ -613,5 +613,5 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; -"GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/fr.lproj/Localizable.strings b/Session/Meta/Translations/fr.lproj/Localizable.strings index 200b528f8..67c8dda6a 100644 --- a/Session/Meta/Translations/fr.lproj/Localizable.strings +++ b/Session/Meta/Translations/fr.lproj/Localizable.strings @@ -613,5 +613,5 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; -"GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/hi.lproj/Localizable.strings b/Session/Meta/Translations/hi.lproj/Localizable.strings index d02a780fc..cf6d01b55 100644 --- a/Session/Meta/Translations/hi.lproj/Localizable.strings +++ b/Session/Meta/Translations/hi.lproj/Localizable.strings @@ -613,5 +613,5 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; -"GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/hr.lproj/Localizable.strings b/Session/Meta/Translations/hr.lproj/Localizable.strings index dc559901c..505787d9e 100644 --- a/Session/Meta/Translations/hr.lproj/Localizable.strings +++ b/Session/Meta/Translations/hr.lproj/Localizable.strings @@ -613,5 +613,5 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; -"GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/id-ID.lproj/Localizable.strings b/Session/Meta/Translations/id-ID.lproj/Localizable.strings index 01696a223..e6f6ff426 100644 --- a/Session/Meta/Translations/id-ID.lproj/Localizable.strings +++ b/Session/Meta/Translations/id-ID.lproj/Localizable.strings @@ -613,5 +613,5 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; -"GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/it.lproj/Localizable.strings b/Session/Meta/Translations/it.lproj/Localizable.strings index baf144a01..36c15a86f 100644 --- a/Session/Meta/Translations/it.lproj/Localizable.strings +++ b/Session/Meta/Translations/it.lproj/Localizable.strings @@ -613,5 +613,5 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; -"GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/ja.lproj/Localizable.strings b/Session/Meta/Translations/ja.lproj/Localizable.strings index 5100121f5..92fdf76b8 100644 --- a/Session/Meta/Translations/ja.lproj/Localizable.strings +++ b/Session/Meta/Translations/ja.lproj/Localizable.strings @@ -613,5 +613,5 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; -"GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/nl.lproj/Localizable.strings b/Session/Meta/Translations/nl.lproj/Localizable.strings index b487ef47f..885a4f8e6 100644 --- a/Session/Meta/Translations/nl.lproj/Localizable.strings +++ b/Session/Meta/Translations/nl.lproj/Localizable.strings @@ -613,5 +613,5 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; -"GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/pl.lproj/Localizable.strings b/Session/Meta/Translations/pl.lproj/Localizable.strings index f6a87bc54..3282638c4 100644 --- a/Session/Meta/Translations/pl.lproj/Localizable.strings +++ b/Session/Meta/Translations/pl.lproj/Localizable.strings @@ -613,5 +613,5 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; -"GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings index 0840cdd50..ea550a3bd 100644 --- a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings +++ b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings @@ -613,5 +613,5 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; -"GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/ru.lproj/Localizable.strings b/Session/Meta/Translations/ru.lproj/Localizable.strings index 52f2b7a6d..53e0b8003 100644 --- a/Session/Meta/Translations/ru.lproj/Localizable.strings +++ b/Session/Meta/Translations/ru.lproj/Localizable.strings @@ -613,5 +613,5 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; -"GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/si.lproj/Localizable.strings b/Session/Meta/Translations/si.lproj/Localizable.strings index 041426c6c..4d5cdb2c3 100644 --- a/Session/Meta/Translations/si.lproj/Localizable.strings +++ b/Session/Meta/Translations/si.lproj/Localizable.strings @@ -613,5 +613,5 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; -"GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/sk.lproj/Localizable.strings b/Session/Meta/Translations/sk.lproj/Localizable.strings index 2ffedda83..6db1a875d 100644 --- a/Session/Meta/Translations/sk.lproj/Localizable.strings +++ b/Session/Meta/Translations/sk.lproj/Localizable.strings @@ -613,5 +613,5 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; -"GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/sv.lproj/Localizable.strings b/Session/Meta/Translations/sv.lproj/Localizable.strings index c7385b194..d8bf60419 100644 --- a/Session/Meta/Translations/sv.lproj/Localizable.strings +++ b/Session/Meta/Translations/sv.lproj/Localizable.strings @@ -613,5 +613,5 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; -"GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/th.lproj/Localizable.strings b/Session/Meta/Translations/th.lproj/Localizable.strings index 5c4427c05..f9056d0ec 100644 --- a/Session/Meta/Translations/th.lproj/Localizable.strings +++ b/Session/Meta/Translations/th.lproj/Localizable.strings @@ -613,5 +613,5 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; -"GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings index 0b89fd2f2..1d7092db3 100644 --- a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings +++ b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings @@ -613,5 +613,5 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; -"GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings index 30cfd9650..d25216d30 100644 --- a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings @@ -613,5 +613,5 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; -"GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings index da56b17ec..bfae91d61 100644 --- a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings @@ -613,5 +613,5 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; -"GROUP_CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; diff --git a/SessionMessagingKit/Database/Migrations/_012_SessionUtilChanges.swift b/SessionMessagingKit/Database/Migrations/_012_SessionUtilChanges.swift index a4331fdf9..6a1285a38 100644 --- a/SessionMessagingKit/Database/Migrations/_012_SessionUtilChanges.swift +++ b/SessionMessagingKit/Database/Migrations/_012_SessionUtilChanges.swift @@ -160,6 +160,18 @@ enum _012_SessionUtilChanges: Migration { SessionThread.Columns.pinnedPriority.set(to: 1) ) + // There seems to have been an issue where the interaction FTS table might have been dropped + // so if it has we should recreate it + if try db.tableExists(Interaction.fullTextSearchTableName) == false { + try db.create(virtualTable: Interaction.fullTextSearchTableName, using: FTS5()) { t in + t.synchronize(withTable: Interaction.databaseTableName) + t.tokenizer = _001_InitialSetupMigration.fullTextSearchTokenizer + + t.column(Interaction.Columns.body.name) + t.column(Interaction.Columns.threadId.name) + } + } + // If we don't have an ed25519 key then no need to create cached dump data let userPublicKey: String = getUserHexEncodedPublicKey(db) From 8f39fe697225abe44dd633aee0b4286c8ec2d662 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 9 Mar 2023 17:51:26 +1100 Subject: [PATCH 040/135] Fixed a couple of minor bugs Fixed a bug where the volatile info would remain after removing the conversation Fixed a bug where sending a message wasn't correctly jumping to the bottom of the conversation --- Session/Conversations/ConversationVC.swift | 15 ++++-- Session/Notifications/SyncPushTokensJob.swift | 5 +- .../SessionUtil+Contacts.swift | 2 + .../SessionUtil+ConvoInfoVolatile.swift | 46 +++++++++++++++++++ .../SessionUtil+UserGroups.swift | 16 +++++++ 5 files changed, 75 insertions(+), 9 deletions(-) diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index e6c0c3ee9..a8fce8ea9 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -826,11 +826,16 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl self.viewModel.updateInteractionData(updatedData) self.tableView.reloadData() - // Note: The scroll button alpha won't get set correctly in this case so we forcibly set it to - // have an alpha of 0 to stop it appearing buggy - self.scrollToBottom(isAnimated: false) - self.scrollButton.alpha = 0 - self.unreadCountView.alpha = scrollButton.alpha + // We need to dispatch to the next run loop because it seems trying to scroll immediately after + // triggering a 'reloadData' doesn't work + DispatchQueue.main.async { [weak self] in + self?.scrollToBottom(isAnimated: false) + + // Note: The scroll button alpha won't get set correctly in this case so we forcibly set it to + // have an alpha of 0 to stop it appearing buggy + self?.scrollButton.alpha = 0 + self?.unreadCountView.alpha = 0 + } return } diff --git a/Session/Notifications/SyncPushTokensJob.swift b/Session/Notifications/SyncPushTokensJob.swift index b9d3cdf3a..9235690a5 100644 --- a/Session/Notifications/SyncPushTokensJob.swift +++ b/Session/Notifications/SyncPushTokensJob.swift @@ -72,10 +72,7 @@ public enum SyncPushTokensJob: JobExecutor { PushRegistrationManager.shared.requestPushTokens() .subscribe(on: queue) .flatMap { (pushToken: String, voipToken: String) -> AnyPublisher in - let lastPushToken: String? = Storage.shared[.lastRecordedPushToken] - let lastVoipToken: String? = Storage.shared[.lastRecordedVoipToken] - - return Deferred { + Deferred { Future { resolver in SyncPushTokensJob.registerForPushNotifications( pushToken: pushToken, diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift index c4191fd26..3b5812119 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift @@ -223,6 +223,8 @@ internal extension SessionUtil { db, Profile.Columns.nickname.set(to: nil) ) + + try SessionUtil.remove(db, volatileContactIds: contactIdsToRemove) } } diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift index 0ce09f050..45854e1b3 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift @@ -288,6 +288,52 @@ internal extension SessionUtil { ) } } + + static func remove(_ db: Database, volatileContactIds: [String]) throws { + try SessionUtil.performAndPushChange( + db, + for: .convoInfoVolatile, + publicKey: getUserHexEncodedPublicKey(db) + ) { conf in + volatileContactIds.forEach { contactId in + var cSessionId: [CChar] = contactId.cArray + + // Don't care if the data doesn't exist + convo_info_volatile_erase_1to1(conf, &cSessionId) + } + } + } + + static func remove(_ db: Database, volatileLegacyGroupIds: [String]) throws { + try SessionUtil.performAndPushChange( + db, + for: .convoInfoVolatile, + publicKey: getUserHexEncodedPublicKey(db) + ) { conf in + volatileLegacyGroupIds.forEach { legacyGroupId in + var cLegacyGroupId: [CChar] = legacyGroupId.cArray + + // Don't care if the data doesn't exist + convo_info_volatile_erase_legacy_group(conf, &cLegacyGroupId) + } + } + } + + static func remove(_ db: Database, volatileCommunityInfo: [OpenGroupUrlInfo]) throws { + try SessionUtil.performAndPushChange( + db, + for: .convoInfoVolatile, + publicKey: getUserHexEncodedPublicKey(db) + ) { conf in + volatileCommunityInfo.forEach { urlInfo in + var cBaseUrl: [CChar] = urlInfo.server.cArray + var cRoom: [CChar] = urlInfo.roomToken.cArray + + // Don't care if the data doesn't exist + convo_info_volatile_erase_community(conf, &cBaseUrl, &cRoom) + } + } + } } // MARK: - External Outgoing Changes diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift index 578902573..19dde7ac6 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift @@ -516,6 +516,19 @@ public extension SessionUtil { // Don't care if the community doesn't exist user_groups_erase_community(conf, &cBaseUrl, &cRoom) } + + // Remove the volatile info as well + try SessionUtil.remove( + db, + volatileCommunityInfo: [ + OpenGroupUrlInfo( + threadId: OpenGroup.idFor(roomToken: roomToken, server: server), + server: server, + roomToken: roomToken, + publicKey: "" + ) + ] + ) } // MARK: -- Legacy Group Changes @@ -634,6 +647,9 @@ public extension SessionUtil { user_groups_erase_legacy_group(conf, &cGroupId) } } + + // Remove the volatile info as well + try SessionUtil.remove(db, volatileLegacyGroupIds: legacyGroupIds) } // MARK: -- Group Changes From 1334a64031b7f246ff1ea1959e869f3efea68d10 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 10 Mar 2023 09:42:36 +1100 Subject: [PATCH 041/135] Removed usages of Box.KeyPair, removed a TODO --- Session.xcodeproj/project.pbxproj | 8 ++--- .../Database/LegacyDatabase/SMKLegacy.swift | 2 +- ...haredConfigDump.swift => ConfigDump.swift} | 0 .../Database/Models/Interaction.swift | 4 +-- .../Database/Models/SessionThread.swift | 6 ++-- .../Jobs/Types/FailedMessageSendsJob.swift | 1 - .../SessionUtil+UserGroups.swift | 2 +- .../ClosedGroupControlMessage.swift | 8 ++--- .../Open Groups/OpenGroupAPI.swift | 6 ++-- .../Open Groups/OpenGroupManager.swift | 6 ++-- .../Open Groups/Types/SodiumProtocols.swift | 9 ++--- .../MessageReceiver+ClosedGroups.swift | 4 +-- ...essageReceiver+ConfigurationMessages.swift | 2 +- .../MessageReceiver+VisibleMessages.swift | 4 +-- .../MessageSender+ClosedGroups.swift | 4 +-- .../MessageReceiver+Decryption.swift | 4 +-- .../Sending & Receiving/MessageReceiver.swift | 6 ++-- .../MessageSender+Encryption.swift | 4 +-- .../Sending & Receiving/MessageSender.swift | 4 +-- .../Utilities/Sodium+Utilities.swift | 13 ++----- .../Open Groups/OpenGroupAPISpec.swift | 2 +- .../Open Groups/OpenGroupManagerSpec.swift | 12 +++---- .../MessageReceiverDecryptionSpec.swift | 36 +++++++++---------- .../MessageSenderEncryptionSpec.swift | 2 +- .../Utilities/SodiumUtilitiesSpec.swift | 9 ++--- .../_TestUtilities/MockEd25519.swift | 3 +- .../_TestUtilities/MockSodium.swift | 5 +-- SessionSnodeKit/Networking/SnodeAPI.swift | 4 +-- SessionUtilitiesKit/Crypto/KeyPair.swift | 2 +- .../Database/Models/Identity.swift | 8 ++--- SessionUtilitiesKit/General/Features.swift | 2 +- .../Profile Pictures/ProfilePictureView.swift | 2 +- .../CommonMockedExtensions.swift | 5 +-- 33 files changed, 92 insertions(+), 97 deletions(-) rename SessionMessagingKit/Database/Models/{SharedConfigDump.swift => ConfigDump.swift} (100%) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 8a712c9d3..16204f357 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -739,7 +739,7 @@ FD8ECF7929340F7200C0D1BB /* libsession-util.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FD8ECF7829340F7100C0D1BB /* libsession-util.xcframework */; }; FD8ECF7B29340FFD00C0D1BB /* SessionUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7A29340FFD00C0D1BB /* SessionUtil.swift */; }; FD8ECF7D2934293A00C0D1BB /* _012_SessionUtilChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7C2934293A00C0D1BB /* _012_SessionUtilChanges.swift */; }; - FD8ECF7F2934298100C0D1BB /* SharedConfigDump.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7E2934298100C0D1BB /* SharedConfigDump.swift */; }; + FD8ECF7F2934298100C0D1BB /* ConfigDump.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7E2934298100C0D1BB /* ConfigDump.swift */; }; FD8ECF822934387A00C0D1BB /* ConfigUserProfileSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF812934387A00C0D1BB /* ConfigUserProfileSpec.swift */; }; FD8ECF892935AB7200C0D1BB /* SessionUtilError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF882935AB7200C0D1BB /* SessionUtilError.swift */; }; FD8ECF8B2935DB4B00C0D1BB /* SharedConfigMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF8A2935DB4B00C0D1BB /* SharedConfigMessage.swift */; }; @@ -1870,7 +1870,7 @@ FD8ECF7829340F7100C0D1BB /* libsession-util.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = "libsession-util.xcframework"; sourceTree = ""; }; FD8ECF7A29340FFD00C0D1BB /* SessionUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionUtil.swift; sourceTree = ""; }; FD8ECF7C2934293A00C0D1BB /* _012_SessionUtilChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _012_SessionUtilChanges.swift; sourceTree = ""; }; - FD8ECF7E2934298100C0D1BB /* SharedConfigDump.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedConfigDump.swift; sourceTree = ""; }; + FD8ECF7E2934298100C0D1BB /* ConfigDump.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigDump.swift; sourceTree = ""; }; FD8ECF812934387A00C0D1BB /* ConfigUserProfileSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigUserProfileSpec.swift; sourceTree = ""; }; FD8ECF882935AB7200C0D1BB /* SessionUtilError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionUtilError.swift; sourceTree = ""; }; FD8ECF8A2935DB4B00C0D1BB /* SharedConfigMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedConfigMessage.swift; sourceTree = ""; }; @@ -3612,7 +3612,7 @@ FDE77F6A280FEB28002CFC5D /* ControlMessageProcessRecord.swift */, FD5C7308285007920029977D /* BlindedIdLookup.swift */, FD09B7E6288670FD00ED0B66 /* Reaction.swift */, - FD8ECF7E2934298100C0D1BB /* SharedConfigDump.swift */, + FD8ECF7E2934298100C0D1BB /* ConfigDump.swift */, FD432433299C6985008A0213 /* PendingReadReceipt.swift */, ); path = Models; @@ -5800,7 +5800,7 @@ FD778B6429B189FF001BAC6B /* _013_GenerateInitialUserConfigDumps.swift in Sources */, C32C598A256D0664003C73A2 /* SNProtoEnvelope+Conversion.swift in Sources */, FDC438CB27BB7DB100C60D73 /* UpdateMessageRequest.swift in Sources */, - FD8ECF7F2934298100C0D1BB /* SharedConfigDump.swift in Sources */, + FD8ECF7F2934298100C0D1BB /* ConfigDump.swift in Sources */, FDA1E83B29A5F2D500C5C3BD /* SessionUtil+Shared.swift in Sources */, C352A2FF25574B6300338F3E /* MessageSendJob.swift in Sources */, FDC438C327BB512200C60D73 /* SodiumProtocols.swift in Sources */, diff --git a/SessionMessagingKit/Database/LegacyDatabase/SMKLegacy.swift b/SessionMessagingKit/Database/LegacyDatabase/SMKLegacy.swift index 989272ed0..7967974ac 100644 --- a/SessionMessagingKit/Database/LegacyDatabase/SMKLegacy.swift +++ b/SessionMessagingKit/Database/LegacyDatabase/SMKLegacy.swift @@ -501,7 +501,7 @@ public enum SMKLegacy { return .new( publicKey: publicKey, name: name, - encryptionKeyPair: Box.KeyPair( + encryptionKeyPair: KeyPair( publicKey: encryptionKeyPair.publicKey.bytes, secretKey: encryptionKeyPair.privateKey.bytes ), diff --git a/SessionMessagingKit/Database/Models/SharedConfigDump.swift b/SessionMessagingKit/Database/Models/ConfigDump.swift similarity index 100% rename from SessionMessagingKit/Database/Models/SharedConfigDump.swift rename to SessionMessagingKit/Database/Models/ConfigDump.swift diff --git a/SessionMessagingKit/Database/Models/Interaction.swift b/SessionMessagingKit/Database/Models/Interaction.swift index f7bff4f29..9c4a55cf5 100644 --- a/SessionMessagingKit/Database/Models/Interaction.swift +++ b/SessionMessagingKit/Database/Models/Interaction.swift @@ -782,8 +782,8 @@ public extension Interaction { let sodium: Sodium = Sodium() if - let userEd25519KeyPair: Box.KeyPair = Identity.fetchUserEd25519KeyPair(db), - let blindedKeyPair: Box.KeyPair = sodium.blindedKeyPair( + let userEd25519KeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db), + let blindedKeyPair: KeyPair = sodium.blindedKeyPair( serverPublicKey: openGroup.publicKey, edKeyPair: userEd25519KeyPair, genericHash: sodium.genericHash diff --git a/SessionMessagingKit/Database/Models/SessionThread.swift b/SessionMessagingKit/Database/Models/SessionThread.swift index c78f7a426..42c3d4a3a 100644 --- a/SessionMessagingKit/Database/Models/SessionThread.swift +++ b/SessionMessagingKit/Database/Models/SessionThread.swift @@ -383,7 +383,7 @@ public extension SessionThread { ) -> String? { guard threadVariant == .community, - let blindingInfo: (edkeyPair: Box.KeyPair?, publicKey: String?, capabilities: Set) = Storage.shared.read({ db in + let blindingInfo: (edkeyPair: KeyPair?, publicKey: String?, capabilities: Set) = Storage.shared.read({ db in struct OpenGroupInfo: Decodable, FetchableRecord { let publicKey: String? let server: String? @@ -405,14 +405,14 @@ public extension SessionThread { .defaulting(to: []) ) }), - let userEdKeyPair: Box.KeyPair = blindingInfo.edkeyPair, + let userEdKeyPair: KeyPair = blindingInfo.edkeyPair, let publicKey: String = blindingInfo.publicKey, blindingInfo.capabilities.isEmpty || blindingInfo.capabilities.contains(.blind) else { return nil } let sodium: Sodium = Sodium() - let blindedKeyPair: Box.KeyPair? = sodium.blindedKeyPair( + let blindedKeyPair: KeyPair? = sodium.blindedKeyPair( serverPublicKey: publicKey, edKeyPair: userEdKeyPair, genericHash: sodium.getGenericHash() diff --git a/SessionMessagingKit/Jobs/Types/FailedMessageSendsJob.swift b/SessionMessagingKit/Jobs/Types/FailedMessageSendsJob.swift index 9e92d257e..48298d73c 100644 --- a/SessionMessagingKit/Jobs/Types/FailedMessageSendsJob.swift +++ b/SessionMessagingKit/Jobs/Types/FailedMessageSendsJob.swift @@ -2,7 +2,6 @@ import Foundation import GRDB -//import SignalCoreKit import SessionUtilitiesKit public enum FailedMessageSendsJob: JobExecutor { diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift index 19dde7ac6..69cd956f8 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift @@ -205,7 +205,7 @@ internal extension SessionUtil { db, groupPublicKey: group.id, name: name, - encryptionKeyPair: Box.KeyPair( + encryptionKeyPair: KeyPair( publicKey: lastKeyPair.publicKey.bytes, secretKey: lastKeyPair.secretKey.bytes ), diff --git a/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift b/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift index b0c22ea07..f9017965b 100644 --- a/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift +++ b/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift @@ -36,7 +36,7 @@ public final class ClosedGroupControlMessage: ControlMessage { case wrappers } - case new(publicKey: Data, name: String, encryptionKeyPair: Box.KeyPair, members: [Data], admins: [Data], expirationTimer: UInt32) + case new(publicKey: Data, name: String, encryptionKeyPair: KeyPair, members: [Data], admins: [Data], expirationTimer: UInt32) /// An encryption key pair encrypted for each member individually. /// @@ -68,7 +68,7 @@ public final class ClosedGroupControlMessage: ControlMessage { let newDescription: String = Kind.new( publicKey: Data(), name: "", - encryptionKeyPair: Box.KeyPair(publicKey: [], secretKey: []), + encryptionKeyPair: KeyPair(publicKey: [], secretKey: []), members: [], admins: [], expirationTimer: 0 @@ -79,7 +79,7 @@ public final class ClosedGroupControlMessage: ControlMessage { self = .new( publicKey: try container.decode(Data.self, forKey: .publicKey), name: try container.decode(String.self, forKey: .name), - encryptionKeyPair: Box.KeyPair( + encryptionKeyPair: KeyPair( publicKey: try container.decode([UInt8].self, forKey: .encryptionPublicKey), secretKey: try container.decode([UInt8].self, forKey: .encryptionSecretKey) ), @@ -252,7 +252,7 @@ public final class ClosedGroupControlMessage: ControlMessage { kind: .new( publicKey: publicKey, name: name, - encryptionKeyPair: Box.KeyPair( + encryptionKeyPair: KeyPair( publicKey: encryptionKeyPairAsProto.publicKey.removingIdPrefixIfNeeded().bytes, secretKey: encryptionKeyPairAsProto.privateKey.bytes ), diff --git a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift index ead83d7ed..1f48f5726 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift @@ -1349,7 +1349,7 @@ public enum OpenGroupAPI { ) ) -> (publicKey: String, signature: Bytes)? { guard - let userEdKeyPair: Box.KeyPair = Identity.fetchUserEd25519KeyPair(db), + let userEdKeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db), let serverPublicKey: String = try? OpenGroup .select(.publicKey) .filter(OpenGroup.Columns.server == serverName.lowercased()) @@ -1366,7 +1366,7 @@ public enum OpenGroupAPI { // If we have no capabilities or if the server supports blinded keys then sign using the blinded key if forceBlinded || capabilities.isEmpty || capabilities.contains(.blind) { - guard let blindedKeyPair: Box.KeyPair = dependencies.sodium.blindedKeyPair(serverPublicKey: serverPublicKey, edKeyPair: userEdKeyPair, genericHash: dependencies.genericHash) else { + guard let blindedKeyPair: KeyPair = dependencies.sodium.blindedKeyPair(serverPublicKey: serverPublicKey, edKeyPair: userEdKeyPair, genericHash: dependencies.genericHash) else { return nil } @@ -1394,7 +1394,7 @@ public enum OpenGroupAPI { // Default to using the 'standard' key default: - guard let userKeyPair: Box.KeyPair = Identity.fetchUserKeyPair(db) else { return nil } + guard let userKeyPair: KeyPair = Identity.fetchUserKeyPair(db) else { return nil } guard let signatureResult: Bytes = try? dependencies.ed25519.sign(data: messageBytes, keyPair: userKeyPair) else { return nil } diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index a490e219d..17c2dcb35 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -945,7 +945,7 @@ public final class OpenGroupManager { fallthrough case .unblinded: - guard let userEdKeyPair: Box.KeyPair = Identity.fetchUserEd25519KeyPair(db) else { + guard let userEdKeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db) else { return false } guard sessionId.prefix != .unblinded || publicKey == SessionId(.unblinded, publicKey: userEdKeyPair.publicKey).hexString else { @@ -955,13 +955,13 @@ public final class OpenGroupManager { case .blinded: guard - let userEdKeyPair: Box.KeyPair = Identity.fetchUserEd25519KeyPair(db), + let userEdKeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db), let openGroupPublicKey: String = try? OpenGroup .select(.publicKey) .filter(id: groupId) .asRequest(of: String.self) .fetchOne(db), - let blindedKeyPair: Box.KeyPair = dependencies.sodium.blindedKeyPair( + let blindedKeyPair: KeyPair = dependencies.sodium.blindedKeyPair( serverPublicKey: openGroupPublicKey, edKeyPair: userEdKeyPair, genericHash: dependencies.genericHash diff --git a/SessionMessagingKit/Open Groups/Types/SodiumProtocols.swift b/SessionMessagingKit/Open Groups/Types/SodiumProtocols.swift index 3e3842f4d..223a42e44 100644 --- a/SessionMessagingKit/Open Groups/Types/SodiumProtocols.swift +++ b/SessionMessagingKit/Open Groups/Types/SodiumProtocols.swift @@ -3,6 +3,7 @@ import Foundation import Sodium import Curve25519Kit +import SessionUtilitiesKit public protocol SodiumType { func getBox() -> BoxType @@ -11,7 +12,7 @@ public protocol SodiumType { func getAeadXChaCha20Poly1305Ietf() -> AeadXChaCha20Poly1305IetfType func generateBlindingFactor(serverPublicKey: String, genericHash: GenericHashType) -> Bytes? - func blindedKeyPair(serverPublicKey: String, edKeyPair: Box.KeyPair, genericHash: GenericHashType) -> Box.KeyPair? + func blindedKeyPair(serverPublicKey: String, edKeyPair: KeyPair, genericHash: GenericHashType) -> KeyPair? func sogsSignature(message: Bytes, secretKey: Bytes, blindedSecretKey ka: Bytes, blindedPublicKey kA: Bytes) -> Bytes? func combineKeys(lhsKeyBytes: Bytes, rhsKeyBytes: Bytes) -> Bytes? @@ -29,7 +30,7 @@ public protocol AeadXChaCha20Poly1305IetfType { } public protocol Ed25519Type { - func sign(data: Bytes, keyPair: Box.KeyPair) throws -> Bytes? + func sign(data: Bytes, keyPair: KeyPair) throws -> Bytes? func verifySignature(_ signature: Data, publicKey: Data, data: Data) throws -> Bool } @@ -81,7 +82,7 @@ extension Sodium: SodiumType { public func getSign() -> SignType { return sign } public func getAeadXChaCha20Poly1305Ietf() -> AeadXChaCha20Poly1305IetfType { return aead.xchacha20poly1305ietf } - public func blindedKeyPair(serverPublicKey: String, edKeyPair: Box.KeyPair) -> Box.KeyPair? { + public func blindedKeyPair(serverPublicKey: String, edKeyPair: KeyPair) -> KeyPair? { return blindedKeyPair(serverPublicKey: serverPublicKey, edKeyPair: edKeyPair, genericHash: getGenericHash()) } } @@ -92,7 +93,7 @@ extension Sign: SignType {} extension Aead.XChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType {} struct Ed25519Wrapper: Ed25519Type { - func sign(data: Bytes, keyPair: Box.KeyPair) throws -> Bytes? { + func sign(data: Bytes, keyPair: KeyPair) throws -> Bytes? { let ecKeyPair: ECKeyPair = try ECKeyPair( publicKeyData: Data(keyPair.publicKey), privateKeyData: Data(keyPair.secretKey) diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift index 5cd353d33..d3cec5194 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift @@ -88,7 +88,7 @@ extension MessageReceiver { _ db: Database, groupPublicKey: String, name: String, - encryptionKeyPair: Box.KeyPair, + encryptionKeyPair: KeyPair, members: [String], admins: [String], expirationTimer: UInt32, @@ -210,7 +210,7 @@ extension MessageReceiver { let groupPublicKey: String = (explicitGroupPublicKey?.toHexString() ?? threadId) - guard let userKeyPair: Box.KeyPair = Identity.fetchUserKeyPair(db) else { + guard let userKeyPair: KeyPair = Identity.fetchUserKeyPair(db) else { return SNLog("Couldn't find user X25519 key pair.") } guard let closedGroup: ClosedGroup = try? ClosedGroup.fetchOne(db, id: groupPublicKey) else { diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift index f679385c2..02200d27a 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift @@ -177,7 +177,7 @@ extension MessageReceiver { try message.closedGroups.forEach { closedGroup in guard !existingClosedGroupsIds.contains(closedGroup.publicKey) else { return } - let keyPair: Box.KeyPair = Box.KeyPair( + let keyPair: KeyPair = KeyPair( publicKey: closedGroup.encryptionKeyPublicKey.bytes, secretKey: closedGroup.encryptionKeySecretKey.bytes ) diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift index c2e5ba2c4..0dc6acc39 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift @@ -88,8 +88,8 @@ extension MessageReceiver { let sodium: Sodium = Sodium() guard - let userEdKeyPair: Box.KeyPair = Identity.fetchUserEd25519KeyPair(db), - let blindedKeyPair: Box.KeyPair = sodium.blindedKeyPair( + let userEdKeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db), + let blindedKeyPair: KeyPair = sodium.blindedKeyPair( serverPublicKey: openGroup.publicKey, edKeyPair: userEdKeyPair, genericHash: sodium.genericHash diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift index b686bdb5e..a8cfb64bf 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift @@ -93,7 +93,7 @@ extension MessageSender { kind: .new( publicKey: Data(hex: groupPublicKey), name: name, - encryptionKeyPair: Box.KeyPair( + encryptionKeyPair: KeyPair( publicKey: encryptionKeyPair.publicKey.bytes, secretKey: encryptionKeyPair.privateKey.bytes ), @@ -442,7 +442,7 @@ extension MessageSender { kind: .new( publicKey: Data(hex: closedGroup.id), name: closedGroup.name, - encryptionKeyPair: Box.KeyPair( + encryptionKeyPair: KeyPair( publicKey: encryptionKeyPair.publicKey.bytes, secretKey: encryptionKeyPair.secretKey.bytes ), diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Decryption.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Decryption.swift index 0f2f7b2d5..06780bcaf 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Decryption.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Decryption.swift @@ -6,7 +6,7 @@ import Sodium import SessionUtilitiesKit extension MessageReceiver { - internal static func decryptWithSessionProtocol(ciphertext: Data, using x25519KeyPair: Box.KeyPair, dependencies: SMKDependencies = SMKDependencies()) throws -> (plaintext: Data, senderX25519PublicKey: String) { + internal static func decryptWithSessionProtocol(ciphertext: Data, using x25519KeyPair: KeyPair, dependencies: SMKDependencies = SMKDependencies()) throws -> (plaintext: Data, senderX25519PublicKey: String) { let recipientX25519PrivateKey = x25519KeyPair.secretKey let recipientX25519PublicKey = x25519KeyPair.publicKey let signatureSize = dependencies.sign.Bytes @@ -44,7 +44,7 @@ extension MessageReceiver { return (Data(plaintext), SessionId(.standard, publicKey: senderX25519PublicKey).hexString) } - internal static func decryptWithSessionBlindingProtocol(data: Data, isOutgoing: Bool, otherBlindedPublicKey: String, with openGroupPublicKey: String, userEd25519KeyPair: Box.KeyPair, using dependencies: SMKDependencies = SMKDependencies()) throws -> (plaintext: Data, senderX25519PublicKey: String) { + internal static func decryptWithSessionBlindingProtocol(data: Data, isOutgoing: Bool, otherBlindedPublicKey: String, with openGroupPublicKey: String, userEd25519KeyPair: KeyPair, using dependencies: SMKDependencies = SMKDependencies()) throws -> (plaintext: Data, senderX25519PublicKey: String) { /// Ensure the data is at least long enough to have the required components guard data.count > (dependencies.nonceGenerator24.NonceBytes + 2), diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index c94e99a95..68674434e 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -39,7 +39,7 @@ public enum MessageReceiver { // Default to 'standard' as the old code didn't seem to require an `envelope.source` switch (SessionId.Prefix(from: envelope.source) ?? .standard) { case .standard, .unblinded: - guard let userX25519KeyPair: Box.KeyPair = Identity.fetchUserKeyPair(db) else { + guard let userX25519KeyPair: KeyPair = Identity.fetchUserKeyPair(db) else { throw MessageReceiverError.noUserX25519KeyPair } @@ -52,7 +52,7 @@ public enum MessageReceiver { guard let openGroupServerPublicKey: String = openGroupServerPublicKey else { throw MessageReceiverError.invalidGroupPublicKey } - guard let userEd25519KeyPair: Box.KeyPair = Identity.fetchUserEd25519KeyPair(db) else { + guard let userEd25519KeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db) else { throw MessageReceiverError.noUserED25519KeyPair } @@ -93,7 +93,7 @@ public enum MessageReceiver { do { return try decryptWithSessionProtocol( ciphertext: ciphertext, - using: Box.KeyPair( + using: KeyPair( publicKey: keyPair.publicKey.bytes, secretKey: keyPair.secretKey.bytes ) diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender+Encryption.swift b/SessionMessagingKit/Sending & Receiving/MessageSender+Encryption.swift index 0517705ab..57d8ae287 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender+Encryption.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender+Encryption.swift @@ -12,7 +12,7 @@ extension MessageSender { for recipientHexEncodedX25519PublicKey: String, using dependencies: SMKDependencies = SMKDependencies() ) throws -> Data { - guard let userEd25519KeyPair: Box.KeyPair = Identity.fetchUserEd25519KeyPair(db) else { + guard let userEd25519KeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db) else { throw MessageSenderError.noUserED25519KeyPair } @@ -41,7 +41,7 @@ extension MessageSender { guard SessionId.Prefix(from: recipientBlindedId) == .blinded else { throw MessageSenderError.signingFailed } - guard let userEd25519KeyPair: Box.KeyPair = Identity.fetchUserEd25519KeyPair(db) else { + guard let userEd25519KeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db) else { throw MessageSenderError.noUserED25519KeyPair } guard let blindedKeyPair = dependencies.sodium.blindedKeyPair(serverPublicKey: openGroupPublicKey, edKeyPair: userEd25519KeyPair, genericHash: dependencies.genericHash) else { diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index cf9adf92b..8cd7f46aa 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -388,7 +388,7 @@ public final class MessageSender { // error in a non-retryable way guard let openGroup: OpenGroup = try? OpenGroup.fetchOne(db, id: threadId), - let userEdKeyPair: Box.KeyPair = Identity.fetchUserEd25519KeyPair(db), + let userEdKeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db), case .openGroup(_, let server, _, _, _) = destination else { throw MessageSenderError.invalidMessage @@ -407,7 +407,7 @@ public final class MessageSender { guard capabilities.isEmpty || capabilities.contains(.blind) else { return SessionId(.unblinded, publicKey: userEdKeyPair.publicKey).hexString } - guard let blindedKeyPair: Box.KeyPair = dependencies.sodium.blindedKeyPair(serverPublicKey: openGroup.publicKey, edKeyPair: userEdKeyPair, genericHash: dependencies.genericHash) else { + guard let blindedKeyPair: KeyPair = dependencies.sodium.blindedKeyPair(serverPublicKey: openGroup.publicKey, edKeyPair: userEdKeyPair, genericHash: dependencies.genericHash) else { preconditionFailure() } diff --git a/SessionMessagingKit/Utilities/Sodium+Utilities.swift b/SessionMessagingKit/Utilities/Sodium+Utilities.swift index 4e113c2e7..9166c392d 100644 --- a/SessionMessagingKit/Utilities/Sodium+Utilities.swift +++ b/SessionMessagingKit/Utilities/Sodium+Utilities.swift @@ -68,7 +68,7 @@ extension Sodium { } /// Constructs a "blinded" key pair (`ka, kA`) based on an open group server `publicKey` and an ed25519 `keyPair` - public func blindedKeyPair(serverPublicKey: String, edKeyPair: Box.KeyPair, genericHash: GenericHashType) -> Box.KeyPair? { + public func blindedKeyPair(serverPublicKey: String, edKeyPair: KeyPair, genericHash: GenericHashType) -> KeyPair? { guard edKeyPair.publicKey.count == Sodium.publicKeyLength && edKeyPair.secretKey.count == Sodium.secretKeyLength else { return nil } @@ -97,7 +97,7 @@ extension Sodium { guard crypto_scalarmult_ed25519_base_noclamp(kAPtr, kaPtr) == 0 else { return nil } - return Box.KeyPair( + return KeyPair( publicKey: Data(bytes: kAPtr, count: Sodium.publicKeyLength).bytes, secretKey: Data(bytes: kaPtr, count: Sodium.secretKeyLength).bytes ) @@ -277,12 +277,3 @@ extension AeadXChaCha20Poly1305IetfType { return authenticatedCipherText } } - -extension Box.KeyPair: Equatable { - public static func == (lhs: Box.KeyPair, rhs: Box.KeyPair) -> Bool { - return ( - lhs.publicKey == rhs.publicKey && - lhs.secretKey == rhs.secretKey - ) - } -} diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift b/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift index 98c0c6f5b..77629645d 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift +++ b/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift @@ -89,7 +89,7 @@ class OpenGroupAPISpec: QuickSpec { mockSodium .when { $0.blindedKeyPair(serverPublicKey: any(), edKeyPair: any(), genericHash: mockGenericHash) } .thenReturn( - Box.KeyPair( + KeyPair( publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes, secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes ) diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift index 775a2a579..e995baee8 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift +++ b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift @@ -245,7 +245,7 @@ class OpenGroupManagerSpec: QuickSpec { ) } .thenReturn( - Box.KeyPair( + KeyPair( publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes, secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes ) @@ -2931,7 +2931,7 @@ class OpenGroupManagerSpec: QuickSpec { ) } .thenReturn( - Box.KeyPair( + KeyPair( publicKey: Data.data(fromHex: otherKey)!.bytes, secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes ) @@ -3029,7 +3029,7 @@ class OpenGroupManagerSpec: QuickSpec { ) } .thenReturn( - Box.KeyPair( + KeyPair( publicKey: Data.data(fromHex: otherKey)!.bytes, secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes ) @@ -3108,7 +3108,7 @@ class OpenGroupManagerSpec: QuickSpec { ) } .thenReturn( - Box.KeyPair( + KeyPair( publicKey: Data.data(fromHex: otherKey)!.bytes, secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes ) @@ -3136,7 +3136,7 @@ class OpenGroupManagerSpec: QuickSpec { ) } .thenReturn( - Box.KeyPair( + KeyPair( publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes, secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes ) @@ -3175,7 +3175,7 @@ class OpenGroupManagerSpec: QuickSpec { ) } .thenReturn( - Box.KeyPair( + KeyPair( publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes, secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes ) diff --git a/SessionMessagingKitTests/Sending & Receiving/MessageReceiverDecryptionSpec.swift b/SessionMessagingKitTests/Sending & Receiving/MessageReceiverDecryptionSpec.swift index 2f9e0e3f7..b9066ba26 100644 --- a/SessionMessagingKitTests/Sending & Receiving/MessageReceiverDecryptionSpec.swift +++ b/SessionMessagingKitTests/Sending & Receiving/MessageReceiverDecryptionSpec.swift @@ -69,7 +69,7 @@ class MessageReceiverDecryptionSpec: QuickSpec { mockSodium .when { $0.blindedKeyPair(serverPublicKey: any(), edKeyPair: any(), genericHash: mockGenericHash) } .thenReturn( - Box.KeyPair( + KeyPair( publicKey: Data(hex: TestConstants.blindedPublicKey).bytes, secretKey: Data(hex: TestConstants.edSecretKey).bytes ) @@ -113,7 +113,7 @@ class MessageReceiverDecryptionSpec: QuickSpec { "sFMhE5G4PbRtQFey1hsxLl221Qivc3ayaX2Mm/X89Dl8e45BC+Lb/KU9EdesxIK4pVgYXs9XrMtX3v8" + "dt0eBaXneOBfr7qB8pHwwMZjtkOu1ED07T9nszgbWabBphUfWXe2U9K3PTRisSCI=" )!, - using: Box.KeyPair( + using: KeyPair( publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes, secretKey: Data.data(fromHex: TestConstants.privateKey)!.bytes ), @@ -139,7 +139,7 @@ class MessageReceiverDecryptionSpec: QuickSpec { expect { try MessageReceiver.decryptWithSessionProtocol( ciphertext: "TestMessage".data(using: .utf8)!, - using: Box.KeyPair( + using: KeyPair( publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes, secretKey: Data.data(fromHex: TestConstants.privateKey)!.bytes ), @@ -163,7 +163,7 @@ class MessageReceiverDecryptionSpec: QuickSpec { expect { try MessageReceiver.decryptWithSessionProtocol( ciphertext: "TestMessage".data(using: .utf8)!, - using: Box.KeyPair( + using: KeyPair( publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes, secretKey: Data.data(fromHex: TestConstants.privateKey)!.bytes ), @@ -181,7 +181,7 @@ class MessageReceiverDecryptionSpec: QuickSpec { expect { try MessageReceiver.decryptWithSessionProtocol( ciphertext: "TestMessage".data(using: .utf8)!, - using: Box.KeyPair( + using: KeyPair( publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes, secretKey: Data.data(fromHex: TestConstants.privateKey)!.bytes ), @@ -197,7 +197,7 @@ class MessageReceiverDecryptionSpec: QuickSpec { expect { try MessageReceiver.decryptWithSessionProtocol( ciphertext: "TestMessage".data(using: .utf8)!, - using: Box.KeyPair( + using: KeyPair( publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes, secretKey: Data.data(fromHex: TestConstants.privateKey)!.bytes ), @@ -219,7 +219,7 @@ class MessageReceiverDecryptionSpec: QuickSpec { isOutgoing: true, otherBlindedPublicKey: "15\(TestConstants.blindedPublicKey)", with: TestConstants.serverPublicKey, - userEd25519KeyPair: Box.KeyPair( + userEd25519KeyPair: KeyPair( publicKey: Data.data(fromHex: TestConstants.edPublicKey)!.bytes, secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes ), @@ -241,7 +241,7 @@ class MessageReceiverDecryptionSpec: QuickSpec { isOutgoing: false, otherBlindedPublicKey: "15\(TestConstants.blindedPublicKey)", with: TestConstants.serverPublicKey, - userEd25519KeyPair: Box.KeyPair( + userEd25519KeyPair: KeyPair( publicKey: Data.data(fromHex: TestConstants.edPublicKey)!.bytes, secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes ), @@ -260,7 +260,7 @@ class MessageReceiverDecryptionSpec: QuickSpec { isOutgoing: true, otherBlindedPublicKey: "15\(TestConstants.blindedPublicKey)", with: TestConstants.serverPublicKey, - userEd25519KeyPair: Box.KeyPair( + userEd25519KeyPair: KeyPair( publicKey: Data.data(fromHex: TestConstants.edPublicKey)!.bytes, secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes ), @@ -285,7 +285,7 @@ class MessageReceiverDecryptionSpec: QuickSpec { isOutgoing: true, otherBlindedPublicKey: "15\(TestConstants.blindedPublicKey)", with: TestConstants.serverPublicKey, - userEd25519KeyPair: Box.KeyPair( + userEd25519KeyPair: KeyPair( publicKey: Data.data(fromHex: TestConstants.edPublicKey)!.bytes, secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes ), @@ -318,7 +318,7 @@ class MessageReceiverDecryptionSpec: QuickSpec { isOutgoing: true, otherBlindedPublicKey: "15\(TestConstants.blindedPublicKey)", with: TestConstants.serverPublicKey, - userEd25519KeyPair: Box.KeyPair( + userEd25519KeyPair: KeyPair( publicKey: Data.data(fromHex: TestConstants.edPublicKey)!.bytes, secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes ), @@ -339,7 +339,7 @@ class MessageReceiverDecryptionSpec: QuickSpec { isOutgoing: true, otherBlindedPublicKey: "15\(TestConstants.blindedPublicKey)", with: TestConstants.serverPublicKey, - userEd25519KeyPair: Box.KeyPair( + userEd25519KeyPair: KeyPair( publicKey: Data.data(fromHex: TestConstants.edPublicKey)!.bytes, secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes ), @@ -364,7 +364,7 @@ class MessageReceiverDecryptionSpec: QuickSpec { isOutgoing: true, otherBlindedPublicKey: "15\(TestConstants.blindedPublicKey)", with: TestConstants.serverPublicKey, - userEd25519KeyPair: Box.KeyPair( + userEd25519KeyPair: KeyPair( publicKey: Data.data(fromHex: TestConstants.edPublicKey)!.bytes, secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes ), @@ -389,7 +389,7 @@ class MessageReceiverDecryptionSpec: QuickSpec { isOutgoing: true, otherBlindedPublicKey: "15\(TestConstants.blindedPublicKey)", with: TestConstants.serverPublicKey, - userEd25519KeyPair: Box.KeyPair( + userEd25519KeyPair: KeyPair( publicKey: Data.data(fromHex: TestConstants.edPublicKey)!.bytes, secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes ), @@ -414,7 +414,7 @@ class MessageReceiverDecryptionSpec: QuickSpec { isOutgoing: true, otherBlindedPublicKey: "15\(TestConstants.blindedPublicKey)", with: TestConstants.serverPublicKey, - userEd25519KeyPair: Box.KeyPair( + userEd25519KeyPair: KeyPair( publicKey: Data.data(fromHex: TestConstants.edPublicKey)!.bytes, secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes ), @@ -439,7 +439,7 @@ class MessageReceiverDecryptionSpec: QuickSpec { isOutgoing: true, otherBlindedPublicKey: "15\(TestConstants.blindedPublicKey)", with: TestConstants.serverPublicKey, - userEd25519KeyPair: Box.KeyPair( + userEd25519KeyPair: KeyPair( publicKey: Data.data(fromHex: TestConstants.edPublicKey)!.bytes, secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes ), @@ -464,7 +464,7 @@ class MessageReceiverDecryptionSpec: QuickSpec { isOutgoing: true, otherBlindedPublicKey: "15\(TestConstants.blindedPublicKey)", with: TestConstants.serverPublicKey, - userEd25519KeyPair: Box.KeyPair( + userEd25519KeyPair: KeyPair( publicKey: Data.data(fromHex: TestConstants.edPublicKey)!.bytes, secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes ), @@ -489,7 +489,7 @@ class MessageReceiverDecryptionSpec: QuickSpec { isOutgoing: true, otherBlindedPublicKey: "15\(TestConstants.blindedPublicKey)", with: TestConstants.serverPublicKey, - userEd25519KeyPair: Box.KeyPair( + userEd25519KeyPair: KeyPair( publicKey: Data.data(fromHex: TestConstants.edPublicKey)!.bytes, secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes ), diff --git a/SessionMessagingKitTests/Sending & Receiving/MessageSenderEncryptionSpec.swift b/SessionMessagingKitTests/Sending & Receiving/MessageSenderEncryptionSpec.swift index 514bb6af9..6334229a1 100644 --- a/SessionMessagingKitTests/Sending & Receiving/MessageSenderEncryptionSpec.swift +++ b/SessionMessagingKitTests/Sending & Receiving/MessageSenderEncryptionSpec.swift @@ -264,7 +264,7 @@ class MessageSenderEncryptionSpec: QuickSpec { ) } .thenReturn( - Box.KeyPair( + KeyPair( publicKey: Data(hex: TestConstants.edPublicKey).bytes, secretKey: Data(hex: TestConstants.edSecretKey).bytes ) diff --git a/SessionMessagingKitTests/Utilities/SodiumUtilitiesSpec.swift b/SessionMessagingKitTests/Utilities/SodiumUtilitiesSpec.swift index 5b7ef3fdb..99668e499 100644 --- a/SessionMessagingKitTests/Utilities/SodiumUtilitiesSpec.swift +++ b/SessionMessagingKitTests/Utilities/SodiumUtilitiesSpec.swift @@ -2,6 +2,7 @@ import Foundation import Sodium +import SessionUtilitiesKit import Quick import Nimble @@ -86,7 +87,7 @@ class SodiumUtilitiesSpec: QuickSpec { it("successfully generates a blinded key pair") { let result = sodium.blindedKeyPair( serverPublicKey: TestConstants.serverPublicKey, - edKeyPair: Box.KeyPair( + edKeyPair: KeyPair( publicKey: Data(hex: TestConstants.edPublicKey).bytes, secretKey: Data(hex: TestConstants.edSecretKey).bytes ), @@ -102,7 +103,7 @@ class SodiumUtilitiesSpec: QuickSpec { it("fails if the edKeyPair public key length wrong") { let result = sodium.blindedKeyPair( serverPublicKey: TestConstants.serverPublicKey, - edKeyPair: Box.KeyPair( + edKeyPair: KeyPair( publicKey: Data(hex: String(TestConstants.edPublicKey.prefix(4))).bytes, secretKey: Data(hex: TestConstants.edSecretKey).bytes ), @@ -115,7 +116,7 @@ class SodiumUtilitiesSpec: QuickSpec { it("fails if the edKeyPair secret key length wrong") { let result = sodium.blindedKeyPair( serverPublicKey: TestConstants.serverPublicKey, - edKeyPair: Box.KeyPair( + edKeyPair: KeyPair( publicKey: Data(hex: TestConstants.edPublicKey).bytes, secretKey: Data(hex: String(TestConstants.edSecretKey.prefix(4))).bytes ), @@ -128,7 +129,7 @@ class SodiumUtilitiesSpec: QuickSpec { it("fails if it cannot generate a blinding factor") { let result = sodium.blindedKeyPair( serverPublicKey: "Test", - edKeyPair: Box.KeyPair( + edKeyPair: KeyPair( publicKey: Data(hex: TestConstants.edPublicKey).bytes, secretKey: Data(hex: TestConstants.edSecretKey).bytes ), diff --git a/SessionMessagingKitTests/_TestUtilities/MockEd25519.swift b/SessionMessagingKitTests/_TestUtilities/MockEd25519.swift index ee23e7250..259a18bfd 100644 --- a/SessionMessagingKitTests/_TestUtilities/MockEd25519.swift +++ b/SessionMessagingKitTests/_TestUtilities/MockEd25519.swift @@ -2,11 +2,12 @@ import Foundation import Sodium +import SessionUtilitiesKit @testable import SessionMessagingKit class MockEd25519: Mock, Ed25519Type { - func sign(data: Bytes, keyPair: Box.KeyPair) throws -> Bytes? { + func sign(data: Bytes, keyPair: KeyPair) throws -> Bytes? { return accept(args: [data, keyPair]) as? Bytes } diff --git a/SessionMessagingKitTests/_TestUtilities/MockSodium.swift b/SessionMessagingKitTests/_TestUtilities/MockSodium.swift index 80f1877d1..a679462e0 100644 --- a/SessionMessagingKitTests/_TestUtilities/MockSodium.swift +++ b/SessionMessagingKitTests/_TestUtilities/MockSodium.swift @@ -2,6 +2,7 @@ import Foundation import Sodium +import SessionUtilitiesKit @testable import SessionMessagingKit @@ -15,8 +16,8 @@ class MockSodium: Mock, SodiumType { return accept(args: [serverPublicKey, genericHash]) as? Bytes } - func blindedKeyPair(serverPublicKey: String, edKeyPair: Box.KeyPair, genericHash: GenericHashType) -> Box.KeyPair? { - return accept(args: [serverPublicKey, edKeyPair, genericHash]) as? Box.KeyPair + func blindedKeyPair(serverPublicKey: String, edKeyPair: KeyPair, genericHash: GenericHashType) -> KeyPair? { + return accept(args: [serverPublicKey, edKeyPair, genericHash]) as? KeyPair } func sogsSignature(message: Bytes, secretKey: Bytes, blindedSecretKey ka: Bytes, blindedPublicKey kA: Bytes) -> Bytes? { diff --git a/SessionSnodeKit/Networking/SnodeAPI.swift b/SessionSnodeKit/Networking/SnodeAPI.swift index 794366691..82590eb42 100644 --- a/SessionSnodeKit/Networking/SnodeAPI.swift +++ b/SessionSnodeKit/Networking/SnodeAPI.swift @@ -494,7 +494,7 @@ public final class SnodeAPI { .eraseToAnyPublisher() } - guard let userED25519KeyPair: Box.KeyPair = Storage.shared.read({ db in Identity.fetchUserEd25519KeyPair(db) }) else { + guard let userED25519KeyPair: KeyPair = Storage.shared.read({ db in Identity.fetchUserEd25519KeyPair(db) }) else { throw SnodeAPIError.noKeyPair } @@ -573,7 +573,7 @@ public final class SnodeAPI { .eraseToAnyPublisher() } - guard let userED25519KeyPair: Box.KeyPair = Storage.shared.read({ db in Identity.fetchUserEd25519KeyPair(db) }) else { + guard let userED25519KeyPair: KeyPair = Storage.shared.read({ db in Identity.fetchUserEd25519KeyPair(db) }) else { throw SnodeAPIError.noKeyPair } diff --git a/SessionUtilitiesKit/Crypto/KeyPair.swift b/SessionUtilitiesKit/Crypto/KeyPair.swift index 4c7ec0a74..14ea83ca0 100644 --- a/SessionUtilitiesKit/Crypto/KeyPair.swift +++ b/SessionUtilitiesKit/Crypto/KeyPair.swift @@ -2,7 +2,7 @@ import Foundation -public struct KeyPair { +public struct KeyPair: Equatable { public let publicKey: [UInt8] public let secretKey: [UInt8] diff --git a/SessionUtilitiesKit/Database/Models/Identity.swift b/SessionUtilitiesKit/Database/Models/Identity.swift index b3cd36958..d7462facb 100644 --- a/SessionUtilitiesKit/Database/Models/Identity.swift +++ b/SessionUtilitiesKit/Database/Models/Identity.swift @@ -94,7 +94,7 @@ public extension Identity { return try? Identity.fetchOne(db, id: .x25519PrivateKey)?.data } - static func fetchUserKeyPair(_ db: Database? = nil) -> Box.KeyPair? { + static func fetchUserKeyPair(_ db: Database? = nil) -> KeyPair? { guard let db: Database = db else { return Storage.shared.read { db in fetchUserKeyPair(db) } } @@ -103,13 +103,13 @@ public extension Identity { let privateKey: Data = fetchUserPrivateKey(db) else { return nil } - return Box.KeyPair( + return KeyPair( publicKey: publicKey.bytes, secretKey: privateKey.bytes ) } - static func fetchUserEd25519KeyPair(_ db: Database? = nil) -> Box.KeyPair? { + static func fetchUserEd25519KeyPair(_ db: Database? = nil) -> KeyPair? { guard let db: Database = db else { return Storage.shared.read { db in fetchUserEd25519KeyPair(db) } } @@ -118,7 +118,7 @@ public extension Identity { let secretKey: Data = try? Identity.fetchOne(db, id: .ed25519SecretKey)?.data else { return nil } - return Box.KeyPair( + return KeyPair( publicKey: publicKey.bytes, secretKey: secretKey.bytes ) diff --git a/SessionUtilitiesKit/General/Features.swift b/SessionUtilitiesKit/General/Features.swift index 0584e131c..3cc3d8e83 100644 --- a/SessionUtilitiesKit/General/Features.swift +++ b/SessionUtilitiesKit/General/Features.swift @@ -6,5 +6,5 @@ public final class Features { public static let useOnionRequests: Bool = true public static let useTestnet: Bool = false - public static let useSharedUtilForUserConfig: Bool = true + public static let useSharedUtilForUserConfig: Bool = true // TODO: Base this off a timestamp } diff --git a/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift b/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift index 56f3b9fc4..6d0d5e195 100644 --- a/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift +++ b/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift @@ -132,7 +132,7 @@ public final class ProfilePictureView: UIView { additionalProfilePlaceholderImageView.pin(.right, to: .right, of: additionalImageContainerView) additionalProfilePlaceholderImageView.pin(.bottom, to: .bottom, of: additionalImageContainerView, withInset: 5) } -// TODO: Update this to be more explicit? (or add a helper method? current code requires duplicate logic around deciding what properties should be set in what cases) + private func prepareForReuse() { imageView.contentMode = .scaleAspectFill imageView.isHidden = true diff --git a/_SharedTestUtilities/CommonMockedExtensions.swift b/_SharedTestUtilities/CommonMockedExtensions.swift index 052931525..97eeee5c9 100644 --- a/_SharedTestUtilities/CommonMockedExtensions.swift +++ b/_SharedTestUtilities/CommonMockedExtensions.swift @@ -3,9 +3,10 @@ import Foundation import Sodium import Curve25519Kit +import SessionUtilitiesKit -extension Box.KeyPair: Mocked { - static var mockValue: Box.KeyPair = Box.KeyPair( +extension KeyPair: Mocked { + static var mockValue: KeyPair = KeyPair( publicKey: Data(hex: TestConstants.publicKey).bytes, secretKey: Data(hex: TestConstants.edSecretKey).bytes ) From a8c4c3eb7604d32f1f6ee483097013e8ea777c6f Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 15 Mar 2023 15:27:01 +1100 Subject: [PATCH 042/135] Updated migration numbers as a result from merge and bug fixes Updated the CallVC to support an animated profile picture Fixed a button layout issue with the landing screen Fixed a bug where the input view could appear above the call UI when the app returns to the foreground Fixed a bug where the push notification registration could incorrectly get triggered during onboarding when recovering from an onboarding crase --- Session.xcodeproj/project.pbxproj | 16 ++++++------ .../Calls/Call Management/SessionCall.swift | 14 +++++++++-- Session/Calls/CallVC.swift | 25 ++++++++++++++++--- Session/Conversations/ConversationVC.swift | 2 +- Session/Notifications/SyncPushTokensJob.swift | 7 +++++- SessionMessagingKit/Configuration.swift | 4 +-- ...es.swift => _013_SessionUtilChanges.swift} | 14 +---------- ..._014_GenerateInitialUserConfigDumps.swift} | 2 +- .../Sending & Receiving/MessageSender.swift | 2 ++ SessionUIKit/Style Guide/Values.swift | 2 +- 10 files changed, 55 insertions(+), 33 deletions(-) rename SessionMessagingKit/Database/Migrations/{_012_SessionUtilChanges.swift => _013_SessionUtilChanges.swift} (92%) rename SessionMessagingKit/Database/Migrations/{_013_GenerateInitialUserConfigDumps.swift => _014_GenerateInitialUserConfigDumps.swift} (99%) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 1e63e6b39..095fd6a76 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -710,7 +710,7 @@ FD7728982849E8110018502F /* UITableView+ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7728972849E8110018502F /* UITableView+ReusableView.swift */; }; FD77289A284AF1BD0018502F /* Sodium+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD772899284AF1BD0018502F /* Sodium+Utilities.swift */; }; FD77289E284EF1C50018502F /* Sodium+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD77289D284EF1C50018502F /* Sodium+Utilities.swift */; }; - FD778B6429B189FF001BAC6B /* _013_GenerateInitialUserConfigDumps.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD778B6329B189FF001BAC6B /* _013_GenerateInitialUserConfigDumps.swift */; }; + FD778B6429B189FF001BAC6B /* _014_GenerateInitialUserConfigDumps.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD778B6329B189FF001BAC6B /* _014_GenerateInitialUserConfigDumps.swift */; }; FD83B9B327CF200A005E1583 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; platformFilter = ios; }; FD83B9BB27CF20AF005E1583 /* SessionIdSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9BA27CF20AF005E1583 /* SessionIdSpec.swift */; }; FD83B9BF27CF2294005E1583 /* TestConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9BD27CF2243005E1583 /* TestConstants.swift */; }; @@ -739,7 +739,7 @@ FD87DD0428B8727D00AF0F98 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD87DD0328B8727D00AF0F98 /* Configuration.swift */; }; FD8ECF7929340F7200C0D1BB /* libsession-util.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FD8ECF7829340F7100C0D1BB /* libsession-util.xcframework */; }; FD8ECF7B29340FFD00C0D1BB /* SessionUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7A29340FFD00C0D1BB /* SessionUtil.swift */; }; - FD8ECF7D2934293A00C0D1BB /* _012_SessionUtilChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7C2934293A00C0D1BB /* _012_SessionUtilChanges.swift */; }; + FD8ECF7D2934293A00C0D1BB /* _013_SessionUtilChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7C2934293A00C0D1BB /* _013_SessionUtilChanges.swift */; }; FD8ECF7F2934298100C0D1BB /* ConfigDump.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7E2934298100C0D1BB /* ConfigDump.swift */; }; FD8ECF822934387A00C0D1BB /* ConfigUserProfileSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF812934387A00C0D1BB /* ConfigUserProfileSpec.swift */; }; FD8ECF892935AB7200C0D1BB /* SessionUtilError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF882935AB7200C0D1BB /* SessionUtilError.swift */; }; @@ -1840,7 +1840,7 @@ FD7728972849E8110018502F /* UITableView+ReusableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITableView+ReusableView.swift"; sourceTree = ""; }; FD772899284AF1BD0018502F /* Sodium+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Sodium+Utilities.swift"; sourceTree = ""; }; FD77289D284EF1C50018502F /* Sodium+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Sodium+Utilities.swift"; sourceTree = ""; }; - FD778B6329B189FF001BAC6B /* _013_GenerateInitialUserConfigDumps.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _013_GenerateInitialUserConfigDumps.swift; sourceTree = ""; }; + FD778B6329B189FF001BAC6B /* _014_GenerateInitialUserConfigDumps.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _014_GenerateInitialUserConfigDumps.swift; sourceTree = ""; }; FD83B9AF27CF200A005E1583 /* SessionUtilitiesKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SessionUtilitiesKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; FD83B9BA27CF20AF005E1583 /* SessionIdSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionIdSpec.swift; sourceTree = ""; }; FD83B9BD27CF2243005E1583 /* TestConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestConstants.swift; sourceTree = ""; }; @@ -1871,7 +1871,7 @@ FD87DD0328B8727D00AF0F98 /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; FD8ECF7829340F7100C0D1BB /* libsession-util.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = "libsession-util.xcframework"; sourceTree = ""; }; FD8ECF7A29340FFD00C0D1BB /* SessionUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionUtil.swift; sourceTree = ""; }; - FD8ECF7C2934293A00C0D1BB /* _012_SessionUtilChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _012_SessionUtilChanges.swift; sourceTree = ""; }; + FD8ECF7C2934293A00C0D1BB /* _013_SessionUtilChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _013_SessionUtilChanges.swift; sourceTree = ""; }; FD8ECF7E2934298100C0D1BB /* ConfigDump.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigDump.swift; sourceTree = ""; }; FD8ECF812934387A00C0D1BB /* ConfigUserProfileSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigUserProfileSpec.swift; sourceTree = ""; }; FD8ECF882935AB7200C0D1BB /* SessionUtilError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionUtilError.swift; sourceTree = ""; }; @@ -3634,9 +3634,9 @@ 7BAA7B6528D2DE4700AE1489 /* _009_OpenGroupPermission.swift */, FD7115F128C6CB3900B47552 /* _010_AddThreadIdToFTS.swift */, FD432431299C6933008A0213 /* _011_AddPendingReadReceipts.swift */, - FD8ECF7C2934293A00C0D1BB /* _012_SessionUtilChanges.swift */, - FD778B6329B189FF001BAC6B /* _013_GenerateInitialUserConfigDumps.swift */, 7B521E0729BFEAFF00C3C36A /* _012_AddFTSIfNeeded.swift */, + FD8ECF7C2934293A00C0D1BB /* _013_SessionUtilChanges.swift */, + FD778B6329B189FF001BAC6B /* _014_GenerateInitialUserConfigDumps.swift */, ); path = Migrations; sourceTree = ""; @@ -5756,7 +5756,7 @@ FD43EE9F297E2EE0009C87C5 /* SessionUtil+ConvoInfoVolatile.swift in Sources */, B8EB20EE2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift in Sources */, FDF8487F29405994007DCAE5 /* HTTPHeader+OpenGroup.swift in Sources */, - FD8ECF7D2934293A00C0D1BB /* _012_SessionUtilChanges.swift in Sources */, + FD8ECF7D2934293A00C0D1BB /* _013_SessionUtilChanges.swift in Sources */, FD17D7A227F40F0500122BE0 /* _001_InitialSetupMigration.swift in Sources */, FD245C5D2850660F00B966DD /* OWSAudioPlayer.m in Sources */, FDF0B7582807F368004C14C5 /* MessageReceiverError.swift in Sources */, @@ -5801,7 +5801,7 @@ FD09796E27FA6D0000936362 /* Contact.swift in Sources */, C38D5E8D2575011E00B6A65C /* MessageSender+ClosedGroups.swift in Sources */, FD5C72F7284F0E560029977D /* MessageReceiver+ReadReceipts.swift in Sources */, - FD778B6429B189FF001BAC6B /* _013_GenerateInitialUserConfigDumps.swift in Sources */, + FD778B6429B189FF001BAC6B /* _014_GenerateInitialUserConfigDumps.swift in Sources */, C32C598A256D0664003C73A2 /* SNProtoEnvelope+Conversion.swift in Sources */, FDC438CB27BB7DB100C60D73 /* UpdateMessageRequest.swift in Sources */, FD8ECF7F2934298100C0D1BB /* ConfigDump.swift in Sources */, diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index d708302f9..b180ece4e 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. -import Foundation +import UIKit +import YYImage import Combine import CallKit import GRDB @@ -25,6 +26,7 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate { let contactName: String let profilePicture: UIImage + let animatedProfilePicture: YYImage? // MARK: - Control @@ -151,10 +153,18 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate { self.webRTCSession = WebRTCSession.current ?? WebRTCSession(for: sessionId, with: uuid) self.isOutgoing = outgoing + let avatarData: Data? = ProfileManager.profileAvatar(db, id: sessionId) self.contactName = Profile.displayName(db, id: sessionId, threadVariant: .contact) - self.profilePicture = ProfileManager.profileAvatar(db, id: sessionId) + self.profilePicture = avatarData .map { UIImage(data: $0) } .defaulting(to: Identicon.generatePlaceholderIcon(seed: sessionId, text: self.contactName, size: 300)) + self.animatedProfilePicture = avatarData + .map { data in + switch data.guessedImageFormat { + case .gif, .webp: return YYImage(data: data) + default: return nil + } + } WebRTCSession.current = self.webRTCSession self.webRTCSession.delegate = self diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index a568f4088..89de47cf6 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit +import YYImage import MediaPlayer import WebRTC import SessionUIKit @@ -8,6 +9,8 @@ import SessionMessagingKit import SessionUtilitiesKit final class CallVC: UIViewController, VideoPreviewDelegate { + private static let avatarRadius: CGFloat = (isIPhone6OrSmaller ? 100 : 120) + let call: SessionCall var latestKnownAudioOutputDeviceName: String? var durationTimer: Timer? @@ -63,17 +66,29 @@ final class CallVC: UIViewController, VideoPreviewDelegate { private lazy var profilePictureView: UIImageView = { let result = UIImageView() - let radius: CGFloat = isIPhone6OrSmaller ? 100 : 120 result.image = self.call.profilePicture - result.set(.width, to: radius * 2) - result.set(.height, to: radius * 2) - result.layer.cornerRadius = radius + result.set(.width, to: CallVC.avatarRadius * 2) + result.set(.height, to: CallVC.avatarRadius * 2) + result.layer.cornerRadius = CallVC.avatarRadius result.layer.masksToBounds = true result.contentMode = .scaleAspectFill return result }() + private lazy var animatedImageView: YYAnimatedImageView = { + let result: YYAnimatedImageView = YYAnimatedImageView() + result.image = self.call.animatedProfilePicture + result.set(.width, to: CallVC.avatarRadius * 2) + result.set(.height, to: CallVC.avatarRadius * 2) + result.layer.cornerRadius = CallVC.avatarRadius + result.layer.masksToBounds = true + result.contentMode = .scaleAspectFill + result.isHidden = (self.call.animatedProfilePicture == nil) + + return result + }() + private lazy var minimizeButton: UIButton = { let result = UIButton(type: .custom) result.setImage( @@ -416,7 +431,9 @@ final class CallVC: UIViewController, VideoPreviewDelegate { profilePictureContainer.pin(.bottom, to: .top, of: operationPanel) profilePictureContainer.pin([ UIView.HorizontalEdge.left, UIView.HorizontalEdge.right ], to: view) profilePictureContainer.addSubview(profilePictureView) + profilePictureContainer.addSubview(animatedImageView) profilePictureView.center(in: profilePictureContainer) + animatedImageView.center(in: profilePictureContainer) // Call info label let callInfoLabelContainer = UIView() diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index a8fce8ea9..e94794001 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -551,7 +551,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl startObservingChanges(didReturnFromBackground: true) recoverInputView() - if !isShowingSearchUI { + if !isShowingSearchUI && self.presentedViewController == nil { if !self.isFirstResponder { self.becomeFirstResponder() } diff --git a/Session/Notifications/SyncPushTokensJob.swift b/Session/Notifications/SyncPushTokensJob.swift index 9235690a5..bd83701f8 100644 --- a/Session/Notifications/SyncPushTokensJob.swift +++ b/Session/Notifications/SyncPushTokensJob.swift @@ -24,7 +24,12 @@ public enum SyncPushTokensJob: JobExecutor { // Don't run when inactive or not in main app or if the user doesn't exist yet guard (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false), - Identity.userExists() + Identity.userExists(), + // If we have no display name then the user will be asked to enter one (this + // can happen if the app crashed during onboarding which would leave the user + // in an invalid state with no display name - the user is likely going to be + // taken to the PN registration screen next which will re-trigger this job) + !Profile.fetchOrCreateCurrentUser().name.isEmpty else { deferred(job) // Don't need to do anything if it's not the main app return diff --git a/SessionMessagingKit/Configuration.swift b/SessionMessagingKit/Configuration.swift index 7b5e9e054..f46762a32 100644 --- a/SessionMessagingKit/Configuration.swift +++ b/SessionMessagingKit/Configuration.swift @@ -29,11 +29,11 @@ public enum SNMessagingKit { // Just to make the external API nice [ _011_AddPendingReadReceipts.self, _012_AddFTSIfNeeded.self, - _012_SessionUtilChanges.self, + _013_SessionUtilChanges.self, // Wait until the feature is turned on before doing the migration that generates // the config dump data (Features.useSharedUtilForUserConfig ? - _013_GenerateInitialUserConfigDumps.self : + _014_GenerateInitialUserConfigDumps.self : (nil as Migration.Type?) ) ].compactMap { $0 } diff --git a/SessionMessagingKit/Database/Migrations/_012_SessionUtilChanges.swift b/SessionMessagingKit/Database/Migrations/_013_SessionUtilChanges.swift similarity index 92% rename from SessionMessagingKit/Database/Migrations/_012_SessionUtilChanges.swift rename to SessionMessagingKit/Database/Migrations/_013_SessionUtilChanges.swift index 6a1285a38..06783e900 100644 --- a/SessionMessagingKit/Database/Migrations/_012_SessionUtilChanges.swift +++ b/SessionMessagingKit/Database/Migrations/_013_SessionUtilChanges.swift @@ -7,7 +7,7 @@ import SessionUtil import SessionUtilitiesKit /// This migration makes the neccessary changes to support the updated user config syncing system -enum _012_SessionUtilChanges: Migration { +enum _013_SessionUtilChanges: Migration { static let target: TargetMigrations.Identifier = .messagingKit static let identifier: String = "SessionUtilChanges" static let needsConfigSync: Bool = true @@ -160,18 +160,6 @@ enum _012_SessionUtilChanges: Migration { SessionThread.Columns.pinnedPriority.set(to: 1) ) - // There seems to have been an issue where the interaction FTS table might have been dropped - // so if it has we should recreate it - if try db.tableExists(Interaction.fullTextSearchTableName) == false { - try db.create(virtualTable: Interaction.fullTextSearchTableName, using: FTS5()) { t in - t.synchronize(withTable: Interaction.databaseTableName) - t.tokenizer = _001_InitialSetupMigration.fullTextSearchTokenizer - - t.column(Interaction.Columns.body.name) - t.column(Interaction.Columns.threadId.name) - } - } - // If we don't have an ed25519 key then no need to create cached dump data let userPublicKey: String = getUserHexEncodedPublicKey(db) diff --git a/SessionMessagingKit/Database/Migrations/_013_GenerateInitialUserConfigDumps.swift b/SessionMessagingKit/Database/Migrations/_014_GenerateInitialUserConfigDumps.swift similarity index 99% rename from SessionMessagingKit/Database/Migrations/_013_GenerateInitialUserConfigDumps.swift rename to SessionMessagingKit/Database/Migrations/_014_GenerateInitialUserConfigDumps.swift index a84910b4e..00fcbdf33 100644 --- a/SessionMessagingKit/Database/Migrations/_013_GenerateInitialUserConfigDumps.swift +++ b/SessionMessagingKit/Database/Migrations/_014_GenerateInitialUserConfigDumps.swift @@ -8,7 +8,7 @@ import SessionUtilitiesKit /// This migration goes through the current state of the database and generates config dumps for the user config types /// /// **Note:** This migration won't be run until the `useSharedUtilForUserConfig` feature flag is enabled -enum _013_GenerateInitialUserConfigDumps: Migration { +enum _014_GenerateInitialUserConfigDumps: Migration { static let target: TargetMigrations.Identifier = .messagingKit static let identifier: String = "GenerateInitialUserConfigDumps" static let needsConfigSync: Bool = true diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index 8cd7f46aa..72b4d05d5 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -651,6 +651,8 @@ public final class MessageSender { switch updatedMessage { case is VisibleMessage, is UnsendRequest: return !isSyncMessage case let callMessage as CallMessage: + // Note: Other 'CallMessage' types are too big to send as push notifications + // so only send the 'preOffer' message as a notification switch callMessage.kind { case .preOffer: return true default: return false diff --git a/SessionUIKit/Style Guide/Values.swift b/SessionUIKit/Style Guide/Values.swift index fd12c70c9..c47a20173 100644 --- a/SessionUIKit/Style Guide/Values.swift +++ b/SessionUIKit/Style Guide/Values.swift @@ -54,7 +54,7 @@ public final class Values : NSObject { // MARK: - iPad Sizes @objc public static let iPadModalWidth = UIScreen.main.bounds.width / 2 - @objc public static let iPadButtonWidth = CGFloat(196) + @objc public static let iPadButtonWidth = CGFloat(240) @objc public static let iPadButtonSpacing = CGFloat(32) @objc public static let iPadUserSessionIdContainerWidth = iPadButtonWidth * 2 + iPadButtonSpacing } From 4012f917773b9a2f599f7b8c23ba76308b1fa6d2 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 15 Mar 2023 16:45:19 +1100 Subject: [PATCH 043/135] Attempt to fix a QA accessibility element issue --- Session/Shared/Views/SessionCell+AccessoryView.swift | 1 + Session/Shared/Views/SessionCell.swift | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Session/Shared/Views/SessionCell+AccessoryView.swift b/Session/Shared/Views/SessionCell+AccessoryView.swift index c909e6041..18cee967b 100644 --- a/Session/Shared/Views/SessionCell+AccessoryView.swift +++ b/Session/Shared/Views/SessionCell+AccessoryView.swift @@ -196,6 +196,7 @@ extension SessionCell { result.themeBackgroundColor = .clear result.searchTextField.themeBackgroundColor = .backgroundSecondary result.delegate = self + result.isHidden = true return result }() diff --git a/Session/Shared/Views/SessionCell.swift b/Session/Shared/Views/SessionCell.swift index d390fb1a2..5ad87cb99 100644 --- a/Session/Shared/Views/SessionCell.swift +++ b/Session/Shared/Views/SessionCell.swift @@ -275,6 +275,7 @@ public class SessionCell: UITableViewCell { shouldHighlightTitle = true accessibilityIdentifier = nil accessibilityLabel = nil + isAccessibilityElement = false originalInputValue = nil titleExtraView?.removeFromSuperview() titleExtraView = nil @@ -319,6 +320,7 @@ public class SessionCell: UITableViewCell { subtitleExtraView = info.subtitle?.extraViewGenerator?() accessibilityIdentifier = info.accessibility?.identifier accessibilityLabel = info.accessibility?.label + isAccessibilityElement = true originalInputValue = info.title?.text // Convenience Flags From 15a0eccaf22e4a8b27dc4577086ecff91c2b832d Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 16 Mar 2023 11:57:13 +1100 Subject: [PATCH 044/135] Added more accessibility info to the name field --- Session/Settings/SettingsViewModel.swift | 10 ++++++++++ Session/Shared/Views/SessionCell+AccessoryView.swift | 1 + 2 files changed, 11 insertions(+) diff --git a/Session/Settings/SettingsViewModel.swift b/Session/Settings/SettingsViewModel.swift index 290e2085a..4fcbb9245 100644 --- a/Session/Settings/SettingsViewModel.swift +++ b/Session/Settings/SettingsViewModel.swift @@ -254,6 +254,9 @@ class SettingsViewModel: SessionTableViewModel Date: Thu, 16 Mar 2023 14:50:14 +1100 Subject: [PATCH 045/135] Added logic to update cached expiry after config TTL extension Updated the UpdateExpiryResponse signature verification logic Another couple of accessibility label/id tweaks --- .../Settings/ThreadSettingsViewModel.swift | 4 ++ Session/Settings/SettingsViewModel.swift | 3 +- .../Models/UpdateExpiryResponse.swift | 41 ++++++++++++------- SessionSnodeKit/Networking/SnodeAPI.swift | 40 ++++++++++++++++-- 4 files changed, 69 insertions(+), 19 deletions(-) diff --git a/Session/Conversations/Settings/ThreadSettingsViewModel.swift b/Session/Conversations/Settings/ThreadSettingsViewModel.swift index 264ee7a7e..cd0948d44 100644 --- a/Session/Conversations/Settings/ThreadSettingsViewModel.swift +++ b/Session/Conversations/Settings/ThreadSettingsViewModel.swift @@ -306,6 +306,10 @@ class ThreadSettingsViewModel: SessionTableViewModel = try decoder.container(keyedBy: CodingKeys.self) updated = ((try? container.decode([String].self, forKey: .updated)) ?? []) + unchanged = ((try? container.decode([String: UInt64].self, forKey: .unchanged)) ?? [:]) expiry = try? container.decode(UInt64.self, forKey: .expiry) try super.init(from: decoder) @@ -35,7 +38,7 @@ public extension UpdateExpiryResponse { extension UpdateExpiryResponse: ValidatableResponse { typealias ValidationData = [String] - typealias ValidationResponse = (hashes: [String], expiry: UInt64) + typealias ValidationResponse = [(hash: String, expiry: UInt64)] /// All responses in the swarm must be valid internal static var requiredSuccessfulResponses: Int { -1 } @@ -44,14 +47,15 @@ extension UpdateExpiryResponse: ValidatableResponse { sodium: Sodium, userX25519PublicKey: String, validationData: [String] - ) throws -> [String: (hashes: [String], expiry: UInt64)] { - let validationMap: [String: (hashes: [String], expiry: UInt64)] = try swarm.reduce(into: [:]) { result, next in + ) throws -> [String: [(hash: String, expiry: UInt64)]] { + let validationMap: [String: [(hash: String, expiry: UInt64)]] = try swarm.reduce(into: [:]) { result, next in guard !next.value.failed, + let appliedExpiry: UInt64 = next.value.expiry, let signatureBase64: String = next.value.signatureBase64, let encodedSignature: Data = Data(base64Encoded: signatureBase64) else { - result[next.key] = ([], 0) + result[next.key] = [] if let reason: String = next.value.reason, let statusCode: Int = next.value.code { SNLog("Couldn't update expiry from: \(next.key) due to error: \(reason) (\(statusCode)).") @@ -63,13 +67,24 @@ extension UpdateExpiryResponse: ValidatableResponse { } /// Signature of - /// `( PUBKEY_HEX || EXPIRY || RMSG[0] || ... || RMSG[N] || UMSG[0] || ... || UMSG[M] )` - /// where RMSG are the requested expiry hashes and UMSG are the actual updated hashes. The signature uses - /// the node's ed25519 pubkey. + /// `( PUBKEY_HEX || EXPIRY || RMSGs... || UMSGs... || CMSG_EXPs... )` + /// where RMSGs are the requested expiry hashes, UMSGs are the actual updated hashes, and + /// CMSG_EXPs are (HASH || EXPIRY) values, ascii-sorted by hash, for the unchanged message + /// hashes included in the "unchanged" field. The signature uses the node's ed25519 pubkey. + /// + /// **Note:** If `updated` is empty then the `expiry` value will match the value that was + /// included in the original request let verificationBytes: [UInt8] = userX25519PublicKey.bytes - .appending(contentsOf: "\(String(describing: next.value.expiry))".data(using: .ascii)?.bytes) + .appending(contentsOf: "\(appliedExpiry)".data(using: .ascii)?.bytes) .appending(contentsOf: validationData.joined().bytes) - .appending(contentsOf: next.value.updated.joined().bytes) + .appending(contentsOf: next.value.updated.sorted().joined().bytes) + .appending(contentsOf: next.value.unchanged + .sorted(by: { lhs, rhs in lhs.key < rhs.key }) + .reduce(into: [UInt8]()) { result, nextUnchanged in + result.append(contentsOf: nextUnchanged.key.bytes) + result.append(contentsOf: "\(nextUnchanged.value)".data(using: .ascii)?.bytes ?? []) + } + ) let isValid: Bool = sodium.sign.verify( message: verificationBytes, publicKey: Data(hex: next.key).bytes, @@ -79,11 +94,9 @@ extension UpdateExpiryResponse: ValidatableResponse { // If the update signature is invalid then we want to fail here guard isValid else { throw SnodeAPIError.signatureVerificationFailed } - // If we didn't get an `expiry` value from the snode then don't bother adding it to the result - // as it's not valid data - guard let expiry: UInt64 = next.value.expiry else { return } - - result[next.key] = (hashes: next.value.updated, expiry: expiry) + result[next.key] = next.value.updated + .map { ($0, appliedExpiry) } + .appending(contentsOf: next.value.unchanged.map { ($0.key, $0.value) }) } return try Self.validated(map: validationMap, totalResponseCount: swarm.count) diff --git a/SessionSnodeKit/Networking/SnodeAPI.swift b/SessionSnodeKit/Networking/SnodeAPI.swift index 82590eb42..ee578452a 100644 --- a/SessionSnodeKit/Networking/SnodeAPI.swift +++ b/SessionSnodeKit/Networking/SnodeAPI.swift @@ -409,10 +409,42 @@ public final class SnodeAPI { using: dependencies ) .decoded(as: responseTypes, using: dependencies) - .map { batchResponse -> [SnodeAPI.Namespace: (info: ResponseInfoType, data: (messages: [SnodeReceivedMessage], lastHash: String?)?)] in + .map { (batchResponse: HTTP.BatchResponse) -> [SnodeAPI.Namespace: (info: ResponseInfoType, data: (messages: [SnodeReceivedMessage], lastHash: String?)?)] in let messageResponses: [HTTP.BatchSubResponse] = batchResponse.responses .compactMap { $0 as? HTTP.BatchSubResponse } + /// Since we have extended the TTL for a number of messages we need to make sure we update the local + /// `SnodeReceivedMessageInfo.expirationDateMs` values so we don't end up deleting them + /// incorrectly before they actually expire on the swarm + if + !refreshingConfigHashes.isEmpty, + let refreshTTLSubReponse: HTTP.BatchSubResponse = batchResponse + .responses + .first(where: { $0 is HTTP.BatchSubResponse }) + .asType(HTTP.BatchSubResponse.self), + let refreshTTLResponse: UpdateExpiryResponse = refreshTTLSubReponse.body, + let validResults: [String: [(hash: String, expiry: UInt64)]] = try? refreshTTLResponse.validResultMap( + sodium: sodium.wrappedValue, + userX25519PublicKey: getUserHexEncodedPublicKey(), + validationData: refreshingConfigHashes + ), + let groupedExpiryResult: [UInt64: [String]] = validResults[snode.ed25519PublicKey]? + .grouped(by: \.expiry) + .mapValues({ groupedResults in groupedResults.map { $0.hash } }) + { + Storage.shared.writeAsync { db in + try groupedExpiryResult.forEach { updatedExpiry, hashes in + try SnodeReceivedMessageInfo + .filter(hashes.contains(SnodeReceivedMessageInfo.Columns.hash)) + .updateAll( + db, + SnodeReceivedMessageInfo.Columns.expirationDateMs + .set(to: updatedExpiry) + ) + } + } + } + return zip(namespaces, messageResponses) .reduce(into: [:]) { result, next in guard let messageResponse: GetMessagesResponse = next.1.body else { return } @@ -720,7 +752,7 @@ public final class SnodeAPI { serverHashes: [String], updatedExpiryMs: UInt64, using dependencies: SSKDependencies = SSKDependencies() - ) -> AnyPublisher<[String: (hashes: [String], expiry: UInt64)], Error> { + ) -> AnyPublisher<[String: [(hash: String, expiry: UInt64)]], Error> { guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else { return Fail(error: SnodeAPIError.noKeyPair) .eraseToAnyPublisher() @@ -728,7 +760,7 @@ public final class SnodeAPI { return getSwarm(for: publicKey) .subscribe(on: Threading.workQueue) - .tryFlatMap { swarm -> AnyPublisher<[String: (hashes: [String], expiry: UInt64)], Error> in + .tryFlatMap { swarm -> AnyPublisher<[String: [(hash: String, expiry: UInt64)]], Error> in guard let snode: Snode = swarm.randomElement() else { throw SnodeAPIError.generic } return SnodeAPI @@ -749,7 +781,7 @@ public final class SnodeAPI { using: dependencies ) .decoded(as: UpdateExpiryResponse.self, using: dependencies) - .tryMap { _, response -> [String: (hashes: [String], expiry: UInt64)] in + .tryMap { _, response -> [String: [(hash: String, expiry: UInt64)]] in try response.validResultMap( sodium: sodium.wrappedValue, userX25519PublicKey: getUserHexEncodedPublicKey(), From 5fdfd6df3b3054b478df3532fb60833346f8097e Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 17 Mar 2023 15:12:35 +1100 Subject: [PATCH 046/135] Fixed issues raised during QA Fixed a bug where the legacy group invitation was getting sent to the wrong location Fixed a bug where outgoing typing indicators would be sent to blocked contacts Fixed a bug where the call button was visible for blocked contacts Fixed a bug where read receipts could be sent to blocked contacts Fixed a bug where the conversation nav buttons wouldn't get updated correctly in some cases Fixed a bug where we could incorrectly include the current user in the contacts syncing Fixed a bug where the initial state of the Note to Self conversation wasn't getting synced Fixed a bug where the Note to Self conversation could get removed Fixed a bug with where the conversation title would be misaligned in some cases Fixed a bug where link previews and quotes with images weren't getting sent correctly Fixed a crash when removing a user from a legacy group Added some missing accessibility info Updated the code to ensure the user is kicked from the conversation if it's deletion gets synced while it's open Updated the conversation empty state copy --- Session.xcodeproj/project.pbxproj | 4 + .../ConversationVC+Interaction.swift | 3 + Session/Conversations/ConversationVC.swift | 83 ++++++++++++++++--- .../Conversations/ConversationViewModel.swift | 19 +++++ .../Conversations/Input View/InputView.swift | 1 - .../Input View/MentionSelectionView.swift | 5 ++ .../ConversationTitleView.swift | 7 +- Session/Home/HomeVC.swift | 6 +- .../MessageRequestsViewController.swift | 17 +--- .../MessageRequestsViewModel.swift | 36 +++++++- .../ImagePickerController.swift | 3 +- .../Translations/de.lproj/Localizable.strings | 3 + .../Translations/en.lproj/Localizable.strings | 3 + .../Translations/es.lproj/Localizable.strings | 3 + .../Translations/fa.lproj/Localizable.strings | 3 + .../Translations/fi.lproj/Localizable.strings | 3 + .../Translations/fr.lproj/Localizable.strings | 3 + .../Translations/hi.lproj/Localizable.strings | 3 + .../Translations/hr.lproj/Localizable.strings | 3 + .../id-ID.lproj/Localizable.strings | 3 + .../Translations/it.lproj/Localizable.strings | 3 + .../Translations/ja.lproj/Localizable.strings | 3 + .../Translations/nl.lproj/Localizable.strings | 3 + .../Translations/pl.lproj/Localizable.strings | 3 + .../pt_BR.lproj/Localizable.strings | 3 + .../Translations/ru.lproj/Localizable.strings | 3 + .../Translations/si.lproj/Localizable.strings | 3 + .../Translations/sk.lproj/Localizable.strings | 3 + .../Translations/sv.lproj/Localizable.strings | 3 + .../Translations/th.lproj/Localizable.strings | 3 + .../vi-VN.lproj/Localizable.strings | 3 + .../zh-Hant.lproj/Localizable.strings | 3 + .../zh_CN.lproj/Localizable.strings | 3 + Session/Notifications/AppNotifications.swift | 48 +++++++---- Session/Settings/SettingsViewModel.swift | 2 +- .../_014_GenerateInitialUserConfigDumps.swift | 9 +- .../Database/Models/SessionThread.swift | 42 ++++++++++ .../SessionUtil+Contacts.swift | 9 ++ .../Config Handling/SessionUtil+Shared.swift | 55 +++++++++++- .../SessionUtil+UserGroups.swift | 18 ++-- .../MessageReceiver+TypingIndicators.swift | 24 +++++- .../MessageReceiver+VisibleMessages.swift | 8 +- .../MessageSender+ClosedGroups.swift | 21 +++-- .../Sending & Receiving/MessageSender.swift | 8 +- .../Typing Indicators/TypingIndicators.swift | 12 ++- .../SessionThreadViewModel.swift | 17 +++- .../Utilities/FetchRequest+Utilities.swift | 11 +++ .../Utilities/UIViewController+OWS.swift | 10 +++ 48 files changed, 463 insertions(+), 81 deletions(-) create mode 100644 SessionUtilitiesKit/Database/Utilities/FetchRequest+Utilities.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 095fd6a76..ef352545d 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -818,6 +818,7 @@ FDD250702837199200198BDA /* GarbageCollectionJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD2506F2837199200198BDA /* GarbageCollectionJob.swift */; }; FDD250722837234B00198BDA /* MediaGalleryNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD250712837234B00198BDA /* MediaGalleryNavigationController.swift */; }; FDDC08F229A300E800BF9681 /* LibSessionTypeConversionUtilitiesSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDDC08F129A300E800BF9681 /* LibSessionTypeConversionUtilitiesSpec.swift */; }; + FDDF074429C3E3D000E5E8B5 /* FetchRequest+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDDF074329C3E3D000E5E8B5 /* FetchRequest+Utilities.swift */; }; FDE658A129418C7900A33BC1 /* CryptoKit+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE658A029418C7900A33BC1 /* CryptoKit+Utilities.swift */; }; FDE658A329418E2F00A33BC1 /* KeyPair.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE658A229418E2F00A33BC1 /* KeyPair.swift */; }; FDE77F6B280FEB28002CFC5D /* ControlMessageProcessRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE77F6A280FEB28002CFC5D /* ControlMessageProcessRecord.swift */; }; @@ -1946,6 +1947,7 @@ FDD2506F2837199200198BDA /* GarbageCollectionJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GarbageCollectionJob.swift; sourceTree = ""; }; FDD250712837234B00198BDA /* MediaGalleryNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaGalleryNavigationController.swift; sourceTree = ""; }; FDDC08F129A300E800BF9681 /* LibSessionTypeConversionUtilitiesSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibSessionTypeConversionUtilitiesSpec.swift; sourceTree = ""; }; + FDDF074329C3E3D000E5E8B5 /* FetchRequest+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FetchRequest+Utilities.swift"; sourceTree = ""; }; FDE658A029418C7900A33BC1 /* CryptoKit+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CryptoKit+Utilities.swift"; sourceTree = ""; }; FDE658A229418E2F00A33BC1 /* KeyPair.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyPair.swift; sourceTree = ""; }; FDE7214F287E50D50093DF33 /* ProtoWrappers.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = ProtoWrappers.py; sourceTree = ""; }; @@ -3718,6 +3720,7 @@ FD17D7C227F5204C00122BE0 /* Database+Utilities.swift */, FD17D7C627F5207C00122BE0 /* DatabaseMigrator+Utilities.swift */, FDF22210281B5E0B000A4995 /* TableRecord+Utilities.swift */, + FDDF074329C3E3D000E5E8B5 /* FetchRequest+Utilities.swift */, FDF2220E281B55E6000A4995 /* QueryInterfaceRequest+Utilities.swift */, FD1A94FA2900D1C2000D73D3 /* PersistableRecord+Utilities.swift */, ); @@ -5642,6 +5645,7 @@ FD17D7E527F6A09900122BE0 /* Identity.swift in Sources */, FD9004142818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift in Sources */, FDF8487A29405906007DCAE5 /* HTTPError.swift in Sources */, + FDDF074429C3E3D000E5E8B5 /* FetchRequest+Utilities.swift in Sources */, B87EF18126377A1D00124B3C /* Features.swift in Sources */, FD09797727FAB7A600936362 /* Data+Image.swift in Sources */, C300A60D2554B31900555489 /* Logging.swift in Sources */, diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 28f27bfa0..dac18e737 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -64,6 +64,7 @@ extension ConversationVC: @objc func startCall(_ sender: Any?) { guard SessionCall.isEnabled else { return } + guard viewModel.threadData.threadIsBlocked == false else { return } guard Storage.shared[.areCallsEnabled] else { let confirmationModal: ConfirmationModal = ConfirmationModal( info: ConfirmationModal.Info( @@ -678,9 +679,11 @@ extension ConversationVC: let threadId: String = self.viewModel.threadData.threadId let threadVariant: SessionThread.Variant = self.viewModel.threadData.threadVariant let threadIsMessageRequest: Bool = (self.viewModel.threadData.threadIsMessageRequest == true) + let threadIsBlocked: Bool = (self.viewModel.threadData.threadIsBlocked == true) let needsToStartTypingIndicator: Bool = TypingIndicators.didStartTypingNeedsToStart( threadId: threadId, threadVariant: threadVariant, + threadIsBlocked: threadIsBlocked, threadIsMessageRequest: threadIsMessageRequest, direction: .outgoing, timestampMs: SnodeAPI.currentOffsetTimestampMs() diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index e94794001..35cf9ea39 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -9,7 +9,7 @@ import SessionMessagingKit import SessionUtilitiesKit import SignalUtilitiesKit -final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITableViewDataSource, UITableViewDelegate { +final class ConversationVC: BaseVC, SessionUtilRespondingViewController, ConversationSearchControllerDelegate, UITableViewDataSource, UITableViewDelegate { private static let loadingHeaderHeight: CGFloat = 40 internal let viewModel: ConversationViewModel @@ -211,8 +211,14 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl private lazy var emptyStateLabel: UILabel = { let text: String = String( - format: "CONVERSATION_EMPTY_STATE".localized(), - self.viewModel.threadData.displayName + format: { + switch (viewModel.threadData.threadIsNoteToSelf, viewModel.threadData.canWrite) { + case (true, _): return "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF".localized() + case (_, false): return "CONVERSATION_EMPTY_STATE_READ_ONLY".localized() + default: return "CONVERSATION_EMPTY_STATE".localized() + } + }(), + viewModel.threadData.displayName ) let result: UILabel = UILabel() @@ -385,8 +391,16 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl // nav will be offset incorrectly during the push animation (unfortunately the profile icon still // doesn't appear until after the animation, I assume it's taking a snapshot or something, but // there isn't much we can do about that unfortunately) - updateNavBarButtons(threadData: nil, initialVariant: self.viewModel.initialThreadVariant) - titleView.initialSetup(with: self.viewModel.initialThreadVariant) + updateNavBarButtons( + threadData: nil, + initialVariant: self.viewModel.initialThreadVariant, + initialIsNoteToSelf: self.viewModel.threadData.threadIsNoteToSelf, + initialIsBlocked: (self.viewModel.threadData.threadIsBlocked == true) + ) + titleView.initialSetup( + with: self.viewModel.initialThreadVariant, + isNoteToSelf: self.viewModel.threadData.threadIsNoteToSelf + ) // Constraints view.addSubview(tableView) @@ -533,6 +547,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl let threadId: String = viewModel.threadData.threadId if + viewModel.threadData.threadIsNoteToSelf == false && viewModel.threadData.threadShouldBeVisible == false && !SessionUtil.conversationExistsInConfig( threadId: threadId, @@ -674,9 +689,16 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl // Update the empty state let text: String = String( - format: "CONVERSATION_EMPTY_STATE".localized(), + format: { + switch (updatedThreadData.threadIsNoteToSelf, updatedThreadData.canWrite) { + case (true, _): return "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF".localized() + case (_, false): return "CONVERSATION_EMPTY_STATE_READ_ONLY".localized() + default: return "CONVERSATION_EMPTY_STATE".localized() + } + }(), updatedThreadData.displayName ) + emptyStateLabel.attributedText = NSAttributedString(string: text) .adding( attributes: [.font: UIFont.boldSystemFont(ofSize: Values.verySmallFontSize)], @@ -689,11 +711,17 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl if initialLoad || viewModel.threadData.threadVariant != updatedThreadData.threadVariant || + viewModel.threadData.threadIsBlocked != updatedThreadData.threadIsBlocked || viewModel.threadData.threadRequiresApproval != updatedThreadData.threadRequiresApproval || viewModel.threadData.threadIsMessageRequest != updatedThreadData.threadIsMessageRequest || viewModel.threadData.profile != updatedThreadData.profile { - updateNavBarButtons(threadData: updatedThreadData, initialVariant: viewModel.initialThreadVariant) + updateNavBarButtons( + threadData: updatedThreadData, + initialVariant: viewModel.initialThreadVariant, + initialIsNoteToSelf: viewModel.threadData.threadIsNoteToSelf, + initialIsBlocked: (viewModel.threadData.threadIsBlocked == true) + ) let messageRequestsViewWasVisible: Bool = ( messageRequestStackView.isHidden == false @@ -1139,7 +1167,12 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl } } - func updateNavBarButtons(threadData: SessionThreadViewModel?, initialVariant: SessionThread.Variant) { + func updateNavBarButtons( + threadData: SessionThreadViewModel?, + initialVariant: SessionThread.Variant, + initialIsNoteToSelf: Bool, + initialIsBlocked: Bool + ) { navigationItem.hidesBackButton = isShowingSearchUI if isShowingSearchUI { @@ -1147,6 +1180,13 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl navigationItem.rightBarButtonItems = [] } else { + let shouldHaveCallButton: Bool = ( + SessionCall.isEnabled && + (threadData?.threadVariant ?? initialVariant) == .contact && + (threadData?.threadIsNoteToSelf ?? initialIsNoteToSelf) == false && + (threadData?.threadIsBlocked ?? initialIsBlocked) == false + ) + guard let threadData: SessionThreadViewModel = threadData, ( @@ -1169,7 +1209,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl ) ) ), - (initialVariant == .contact ? + (shouldHaveCallButton ? UIBarButtonItem(customView: UIView(frame: CGRect(x: 0, y: 0, width: 44, height: 44))) : nil ) @@ -1199,7 +1239,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl settingsButtonItem.accessibilityLabel = "More options" settingsButtonItem.isAccessibilityElement = true - if SessionCall.isEnabled && !threadData.threadIsNoteToSelf { + if shouldHaveCallButton { let callButton = UIBarButtonItem( image: UIImage(named: "Phone"), style: .plain, @@ -1207,11 +1247,12 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl action: #selector(startCall) ) callButton.accessibilityLabel = "Call button" + callButton.isAccessibilityElement = true navigationItem.rightBarButtonItems = [settingsButtonItem, callButton] } else { - navigationItem.rightBarButtonItem = settingsButtonItem + navigationItem.rightBarButtonItems = [settingsButtonItem] } default: @@ -1640,7 +1681,12 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl } // Nav bar buttons - updateNavBarButtons(threadData: self.viewModel.threadData, initialVariant: viewModel.initialThreadVariant) + updateNavBarButtons( + threadData: viewModel.threadData, + initialVariant: viewModel.initialThreadVariant, + initialIsNoteToSelf: viewModel.threadData.threadIsNoteToSelf, + initialIsBlocked: (viewModel.threadData.threadIsBlocked == true) + ) // Hack so that the ResultsBar stays on the screen when dismissing the search field // keyboard. @@ -1675,7 +1721,12 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl @objc func hideSearchUI() { isShowingSearchUI = false navigationItem.titleView = titleView - updateNavBarButtons(threadData: self.viewModel.threadData, initialVariant: viewModel.initialThreadVariant) + updateNavBarButtons( + threadData: viewModel.threadData, + initialVariant: viewModel.initialThreadVariant, + initialIsNoteToSelf: viewModel.threadData.threadIsNoteToSelf, + initialIsBlocked: (viewModel.threadData.threadIsBlocked == true) + ) searchController.uiSearchController.stubbableSearchBar.stubbedNextResponder = nil becomeFirstResponder() @@ -1800,4 +1851,10 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl .highlight() } } + + // MARK: - SessionUtilRespondingViewController + + func isConversation(in threadIds: [String]) -> Bool { + return threadIds.contains(self.viewModel.threadData.threadId) + } } diff --git a/Session/Conversations/ConversationViewModel.swift b/Session/Conversations/ConversationViewModel.swift index b146b256a..f64664135 100644 --- a/Session/Conversations/ConversationViewModel.swift +++ b/Session/Conversations/ConversationViewModel.swift @@ -111,6 +111,16 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { threadId: self.threadId, threadVariant: self.initialThreadVariant, threadIsNoteToSelf: (self.threadId == getUserHexEncodedPublicKey()), + threadIsBlocked: (self.initialThreadVariant != .contact ? false : + Storage.shared.read { db in + try Contact + .filter(id: self.threadId) + .select(.isBlocked) + .asRequest(of: Bool.self) + .fetchOne(db) + .defaulting(to: false) + } + ), currentUserIsClosedGroupMember: ((self.initialThreadVariant != .legacyGroup && self.initialThreadVariant != .group) ? nil : Storage.shared.read { db in @@ -120,6 +130,15 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { .filter(GroupMember.Columns.role == GroupMember.Role.standard) .isNotEmpty(db) } + ), + openGroupPermissions: (self.initialThreadVariant != .community ? nil : + Storage.shared.read { db in + try OpenGroup + .filter(id: threadId) + .select(.permissions) + .asRequest(of: OpenGroup.Permissions.self) + .fetchOne(db) + } ) ) .populatingCurrentUserBlindedKey() diff --git a/Session/Conversations/Input View/InputView.swift b/Session/Conversations/Input View/InputView.swift index d0fc58d77..eeb0632cf 100644 --- a/Session/Conversations/Input View/InputView.swift +++ b/Session/Conversations/Input View/InputView.swift @@ -93,7 +93,6 @@ final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, M let result: UIView = UIView() result.accessibilityLabel = "Mentions list" result.accessibilityIdentifier = "Mentions list" - result.isAccessibilityElement = true result.alpha = 0 let backgroundView = UIView() diff --git a/Session/Conversations/Input View/MentionSelectionView.swift b/Session/Conversations/Input View/MentionSelectionView.swift index b89cd884f..3ad8be5ba 100644 --- a/Session/Conversations/Input View/MentionSelectionView.swift +++ b/Session/Conversations/Input View/MentionSelectionView.swift @@ -92,6 +92,11 @@ final class MentionSelectionView: UIView, UITableViewDataSource, UITableViewDele ), isLast: (indexPath.row == (candidates.count - 1)) ) + cell.accessibilityIdentifier = "Contact" + cell.accessibilityLabel = candidates[indexPath.row].profile.displayName( + for: candidates[indexPath.row].threadVariant + ) + cell.isAccessibilityElement = true return cell } diff --git a/Session/Conversations/Views & Modals/ConversationTitleView.swift b/Session/Conversations/Views & Modals/ConversationTitleView.swift index 84a24c3fe..69c695b0b 100644 --- a/Session/Conversations/Views & Modals/ConversationTitleView.swift +++ b/Session/Conversations/Views & Modals/ConversationTitleView.swift @@ -71,10 +71,13 @@ final class ConversationTitleView: UIView { // MARK: - Content - public func initialSetup(with threadVariant: SessionThread.Variant) { + public func initialSetup( + with threadVariant: SessionThread.Variant, + isNoteToSelf: Bool + ) { self.update( with: " ", - isNoteToSelf: false, + isNoteToSelf: isNoteToSelf, threadVariant: threadVariant, mutedUntilTimestamp: nil, onlyNotifyForMentions: false, diff --git a/Session/Home/HomeVC.swift b/Session/Home/HomeVC.swift index ae619d872..aed1ff06e 100644 --- a/Session/Home/HomeVC.swift +++ b/Session/Home/HomeVC.swift @@ -8,7 +8,7 @@ import SessionMessagingKit import SessionUtilitiesKit import SignalUtilitiesKit -final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, SeedReminderViewDelegate { +final class HomeVC: BaseVC, SessionUtilRespondingViewController, UITableViewDataSource, UITableViewDelegate, SeedReminderViewDelegate { private static let loadingHeaderHeight: CGFloat = 40 public static let newConversationButtonSize: CGFloat = 60 @@ -20,6 +20,10 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, SeedRemi private var isAutoLoadingNextPage: Bool = false private var viewHasAppeared: Bool = false + // MARK: - SessionUtilRespondingViewController + + let isConversationList: Bool = true + // MARK: - Intialization init() { diff --git a/Session/Home/Message Requests/MessageRequestsViewController.swift b/Session/Home/Message Requests/MessageRequestsViewController.swift index 268878979..b9be340a0 100644 --- a/Session/Home/Message Requests/MessageRequestsViewController.swift +++ b/Session/Home/Message Requests/MessageRequestsViewController.swift @@ -481,19 +481,10 @@ class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDat title: "MESSAGE_REQUESTS_CLEAR_ALL_CONFIRMATION_ACTON".localized(), style: .destructive ) { _ in - // Clear the requests - Storage.shared.write { db in - _ = try SessionThread - .filter(ids: contactThreadIds) - .deleteAll(db) - - try ClosedGroup.removeKeysAndUnsubscribe( - db, - threadIds: closedGroupThreadIds, - removeGroupData: true, - calledFromConfigHandling: false - ) - } + MessageRequestsViewModel.clearAllRequests( + contactThreadIds: contactThreadIds, + closedGroupThreadIds: closedGroupThreadIds + ) }) alertVC.addAction(UIAlertAction(title: "TXT_CANCEL_TITLE".localized(), style: .cancel, handler: nil)) diff --git a/Session/Home/Message Requests/MessageRequestsViewModel.swift b/Session/Home/Message Requests/MessageRequestsViewModel.swift index 85c31dbda..9a79a13e4 100644 --- a/Session/Home/Message Requests/MessageRequestsViewModel.swift +++ b/Session/Home/Message Requests/MessageRequestsViewModel.swift @@ -186,7 +186,10 @@ public class MessageRequestsViewModel { ) { _ in Storage.shared.write { db in switch threadVariant { - case .contact, .community: + case .contact: + try SessionUtil + .hide(db, contactIds: [threadId]) + _ = try SessionThread .filter(id: threadId) .deleteAll(db) @@ -201,6 +204,8 @@ public class MessageRequestsViewModel { // Trigger a config sync ConfigurationSyncJob.enqueue(db, publicKey: getUserHexEncodedPublicKey(db)) + + default: break } } @@ -245,6 +250,10 @@ public class MessageRequestsViewModel { Contact.Columns.didApproveMe.set(to: true) ) + // Sync the removal of the thread from other devices + try SessionUtil + .hide(db, contactIds: [threadId]) + // Remove the thread _ = try SessionThread .filter(id: threadId) @@ -257,4 +266,29 @@ public class MessageRequestsViewModel { viewController?.present(modal, animated: true, completion: nil) } + + static func clearAllRequests( + contactThreadIds: [String], + closedGroupThreadIds: [String] + ) { + // Clear the requests + Storage.shared.write { db in + // Sync the removal of the thread from other devices + try SessionUtil + .hide(db, contactIds: contactThreadIds) + + // Remove the threads + _ = try SessionThread + .filter(ids: contactThreadIds) + .deleteAll(db) + + // Remove the groups + try ClosedGroup.removeKeysAndUnsubscribe( + db, + threadIds: closedGroupThreadIds, + removeGroupData: true, + calledFromConfigHandling: false + ) + } + } } diff --git a/Session/Media Viewing & Editing/ImagePickerController.swift b/Session/Media Viewing & Editing/ImagePickerController.swift index bb3bd5d42..504465c31 100644 --- a/Session/Media Viewing & Editing/ImagePickerController.swift +++ b/Session/Media Viewing & Editing/ImagePickerController.swift @@ -533,7 +533,8 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat let cell: PhotoGridViewCell = collectionView.dequeue(type: PhotoGridViewCell.self, for: indexPath) let assetItem = photoCollectionContents.assetItem(at: indexPath.item, photoMediaSize: photoMediaSize) cell.configure(item: assetItem) - + cell.isAccessibilityElement = true + cell.accessibilityIdentifier = "\(assetItem.asset.modificationDate.map { "\($0)" } ?? "Unknown Date")" cell.isSelected = delegate.imagePicker(self, isAssetSelected: assetItem.asset) return cell diff --git a/Session/Meta/Translations/de.lproj/Localizable.strings b/Session/Meta/Translations/de.lproj/Localizable.strings index 519a42b20..31fa91ebb 100644 --- a/Session/Meta/Translations/de.lproj/Localizable.strings +++ b/Session/Meta/Translations/de.lproj/Localizable.strings @@ -614,4 +614,7 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; +"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"UPDATE_PROFILE_TITLE" = "Update Profile Picture"; diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index d2bb87bd7..55a7dffc6 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -614,4 +614,7 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; +"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"UPDATE_PROFILE_TITLE" = "Update Profile Picture"; diff --git a/Session/Meta/Translations/es.lproj/Localizable.strings b/Session/Meta/Translations/es.lproj/Localizable.strings index 91484888d..50e0d517e 100644 --- a/Session/Meta/Translations/es.lproj/Localizable.strings +++ b/Session/Meta/Translations/es.lproj/Localizable.strings @@ -614,4 +614,7 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; +"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"UPDATE_PROFILE_TITLE" = "Update Profile Picture"; diff --git a/Session/Meta/Translations/fa.lproj/Localizable.strings b/Session/Meta/Translations/fa.lproj/Localizable.strings index d764eb276..43eece40a 100644 --- a/Session/Meta/Translations/fa.lproj/Localizable.strings +++ b/Session/Meta/Translations/fa.lproj/Localizable.strings @@ -614,4 +614,7 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; +"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"UPDATE_PROFILE_TITLE" = "Update Profile Picture"; diff --git a/Session/Meta/Translations/fi.lproj/Localizable.strings b/Session/Meta/Translations/fi.lproj/Localizable.strings index 84c99f71c..2f924fbee 100644 --- a/Session/Meta/Translations/fi.lproj/Localizable.strings +++ b/Session/Meta/Translations/fi.lproj/Localizable.strings @@ -614,4 +614,7 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; +"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"UPDATE_PROFILE_TITLE" = "Update Profile Picture"; diff --git a/Session/Meta/Translations/fr.lproj/Localizable.strings b/Session/Meta/Translations/fr.lproj/Localizable.strings index 67c8dda6a..8b8ecd21c 100644 --- a/Session/Meta/Translations/fr.lproj/Localizable.strings +++ b/Session/Meta/Translations/fr.lproj/Localizable.strings @@ -614,4 +614,7 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; +"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"UPDATE_PROFILE_TITLE" = "Update Profile Picture"; diff --git a/Session/Meta/Translations/hi.lproj/Localizable.strings b/Session/Meta/Translations/hi.lproj/Localizable.strings index cf6d01b55..0640cdf3e 100644 --- a/Session/Meta/Translations/hi.lproj/Localizable.strings +++ b/Session/Meta/Translations/hi.lproj/Localizable.strings @@ -614,4 +614,7 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; +"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"UPDATE_PROFILE_TITLE" = "Update Profile Picture"; diff --git a/Session/Meta/Translations/hr.lproj/Localizable.strings b/Session/Meta/Translations/hr.lproj/Localizable.strings index 505787d9e..c7f523d91 100644 --- a/Session/Meta/Translations/hr.lproj/Localizable.strings +++ b/Session/Meta/Translations/hr.lproj/Localizable.strings @@ -614,4 +614,7 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; +"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"UPDATE_PROFILE_TITLE" = "Update Profile Picture"; diff --git a/Session/Meta/Translations/id-ID.lproj/Localizable.strings b/Session/Meta/Translations/id-ID.lproj/Localizable.strings index e6f6ff426..9e46d0bd8 100644 --- a/Session/Meta/Translations/id-ID.lproj/Localizable.strings +++ b/Session/Meta/Translations/id-ID.lproj/Localizable.strings @@ -614,4 +614,7 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; +"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"UPDATE_PROFILE_TITLE" = "Update Profile Picture"; diff --git a/Session/Meta/Translations/it.lproj/Localizable.strings b/Session/Meta/Translations/it.lproj/Localizable.strings index 36c15a86f..cdbe55283 100644 --- a/Session/Meta/Translations/it.lproj/Localizable.strings +++ b/Session/Meta/Translations/it.lproj/Localizable.strings @@ -614,4 +614,7 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; +"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"UPDATE_PROFILE_TITLE" = "Update Profile Picture"; diff --git a/Session/Meta/Translations/ja.lproj/Localizable.strings b/Session/Meta/Translations/ja.lproj/Localizable.strings index 92fdf76b8..3c6f81e33 100644 --- a/Session/Meta/Translations/ja.lproj/Localizable.strings +++ b/Session/Meta/Translations/ja.lproj/Localizable.strings @@ -614,4 +614,7 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; +"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"UPDATE_PROFILE_TITLE" = "Update Profile Picture"; diff --git a/Session/Meta/Translations/nl.lproj/Localizable.strings b/Session/Meta/Translations/nl.lproj/Localizable.strings index 885a4f8e6..7e2504992 100644 --- a/Session/Meta/Translations/nl.lproj/Localizable.strings +++ b/Session/Meta/Translations/nl.lproj/Localizable.strings @@ -614,4 +614,7 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; +"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"UPDATE_PROFILE_TITLE" = "Update Profile Picture"; diff --git a/Session/Meta/Translations/pl.lproj/Localizable.strings b/Session/Meta/Translations/pl.lproj/Localizable.strings index 3282638c4..8dcefccaa 100644 --- a/Session/Meta/Translations/pl.lproj/Localizable.strings +++ b/Session/Meta/Translations/pl.lproj/Localizable.strings @@ -614,4 +614,7 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; +"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"UPDATE_PROFILE_TITLE" = "Update Profile Picture"; diff --git a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings index ea550a3bd..a7f2d55de 100644 --- a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings +++ b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings @@ -614,4 +614,7 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; +"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"UPDATE_PROFILE_TITLE" = "Update Profile Picture"; diff --git a/Session/Meta/Translations/ru.lproj/Localizable.strings b/Session/Meta/Translations/ru.lproj/Localizable.strings index 53e0b8003..2145e6d83 100644 --- a/Session/Meta/Translations/ru.lproj/Localizable.strings +++ b/Session/Meta/Translations/ru.lproj/Localizable.strings @@ -614,4 +614,7 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; +"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"UPDATE_PROFILE_TITLE" = "Update Profile Picture"; diff --git a/Session/Meta/Translations/si.lproj/Localizable.strings b/Session/Meta/Translations/si.lproj/Localizable.strings index 4d5cdb2c3..e0a48dadd 100644 --- a/Session/Meta/Translations/si.lproj/Localizable.strings +++ b/Session/Meta/Translations/si.lproj/Localizable.strings @@ -614,4 +614,7 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; +"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"UPDATE_PROFILE_TITLE" = "Update Profile Picture"; diff --git a/Session/Meta/Translations/sk.lproj/Localizable.strings b/Session/Meta/Translations/sk.lproj/Localizable.strings index 6db1a875d..e295451a2 100644 --- a/Session/Meta/Translations/sk.lproj/Localizable.strings +++ b/Session/Meta/Translations/sk.lproj/Localizable.strings @@ -614,4 +614,7 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; +"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"UPDATE_PROFILE_TITLE" = "Update Profile Picture"; diff --git a/Session/Meta/Translations/sv.lproj/Localizable.strings b/Session/Meta/Translations/sv.lproj/Localizable.strings index d8bf60419..0f4f5d75e 100644 --- a/Session/Meta/Translations/sv.lproj/Localizable.strings +++ b/Session/Meta/Translations/sv.lproj/Localizable.strings @@ -614,4 +614,7 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; +"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"UPDATE_PROFILE_TITLE" = "Update Profile Picture"; diff --git a/Session/Meta/Translations/th.lproj/Localizable.strings b/Session/Meta/Translations/th.lproj/Localizable.strings index f9056d0ec..0772e09f5 100644 --- a/Session/Meta/Translations/th.lproj/Localizable.strings +++ b/Session/Meta/Translations/th.lproj/Localizable.strings @@ -614,4 +614,7 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; +"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"UPDATE_PROFILE_TITLE" = "Update Profile Picture"; diff --git a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings index 1d7092db3..8ee7eabca 100644 --- a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings +++ b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings @@ -614,4 +614,7 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; +"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"UPDATE_PROFILE_TITLE" = "Update Profile Picture"; diff --git a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings index d25216d30..9ad9427bb 100644 --- a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings @@ -614,4 +614,7 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; +"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"UPDATE_PROFILE_TITLE" = "Update Profile Picture"; diff --git a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings index bfae91d61..c594cfcfe 100644 --- a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings @@ -614,4 +614,7 @@ "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; +"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; +"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; "USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated."; +"UPDATE_PROFILE_TITLE" = "Update Profile Picture"; diff --git a/Session/Notifications/AppNotifications.swift b/Session/Notifications/AppNotifications.swift index 0e374700f..181598896 100644 --- a/Session/Notifications/AppNotifications.swift +++ b/Session/Notifications/AppNotifications.swift @@ -517,13 +517,13 @@ class NotificationActionHandler { return Fail(error: NotificationError.failDebug("threadId was unexpectedly nil")) .eraseToAnyPublisher() } - - guard let thread: SessionThread = Storage.shared.read({ db in try SessionThread.fetchOne(db, id: threadId) }) else { + + guard Storage.shared.read({ db in try SessionThread.exists(db, id: threadId) }) == true else { return Fail(error: NotificationError.failDebug("unable to find thread with id: \(threadId)")) .eraseToAnyPublisher() } - return markAsRead(thread: thread) + return markAsRead(threadId: threadId) } func reply(userInfo: [AnyHashable: Any], replyText: String) -> AnyPublisher { @@ -540,7 +540,7 @@ class NotificationActionHandler { return Storage.shared .writePublisher(receiveOn: DispatchQueue.main) { db in let interaction: Interaction = try Interaction( - threadId: thread.id, + threadId: threadId, authorId: getUserHexEncodedPublicKey(db), variant: .standardOutgoing, body: replyText, @@ -557,16 +557,20 @@ class NotificationActionHandler { try Interaction.markAsRead( db, interactionId: interaction.id, - threadId: thread.id, + threadId: threadId, threadVariant: thread.variant, includingOlder: true, - trySendReadReceipt: true + trySendReadReceipt: try SessionThread.canSendReadReceipt( + db, + threadId: threadId, + threadVariant: thread.variant + ) ) return try MessageSender.preparedSendData( db, interaction: interaction, - threadId: thread.id, + threadId: threadId, threadVariant: thread.variant ) } @@ -605,20 +609,34 @@ class NotificationActionHandler { .eraseToAnyPublisher() } - private func markAsRead(thread: SessionThread) -> AnyPublisher { + private func markAsRead(threadId: String) -> AnyPublisher { return Storage.shared .writePublisher(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db in - try Interaction.markAsRead( - db, - interactionId: try thread.interactions + guard + let threadVariant: SessionThread.Variant = try SessionThread + .filter(id: threadId) + .select(.variant) + .asRequest(of: SessionThread.Variant.self) + .fetchOne(db), + let lastInteractionId: Int64 = try Interaction .select(.id) + .filter(Interaction.Columns.threadId == threadId) .order(Interaction.Columns.timestampMs.desc) .asRequest(of: Int64.self) - .fetchOne(db), - threadId: thread.id, - threadVariant: thread.variant, + .fetchOne(db) + else { throw NotificationError.failDebug("unable to required thread info: \(threadId)") } + + try Interaction.markAsRead( + db, + interactionId: lastInteractionId, + threadId: threadId, + threadVariant: threadVariant, includingOlder: true, - trySendReadReceipt: true + trySendReadReceipt: try SessionThread.canSendReadReceipt( + db, + threadId: threadId, + threadVariant: threadVariant + ) ) } .eraseToAnyPublisher() diff --git a/Session/Settings/SettingsViewModel.swift b/Session/Settings/SettingsViewModel.swift index 9ceaea9b6..06c882916 100644 --- a/Session/Settings/SettingsViewModel.swift +++ b/Session/Settings/SettingsViewModel.swift @@ -487,7 +487,7 @@ class SettingsViewModel: SessionTableViewModel Bool { + let threadVariant: SessionThread.Variant = try { + try maybeThreadVariant ?? + SessionThread + .filter(id: threadId) + .select(.variant) + .asRequest(of: SessionThread.Variant.self) + .fetchOne(db, orThrow: StorageError.objectNotFound) + }() + let threadIsBlocked: Bool = try { + try maybeIsBlocked ?? + ( + threadVariant == .contact && + Contact + .filter(id: threadId) + .select(.isBlocked) + .asRequest(of: Bool.self) + .fetchOne(db, orThrow: StorageError.objectNotFound) + ) + }() + let threadIsMessageRequest: Bool = SessionThread + .filter(id: threadId) + .filter( + SessionThread.isMessageRequest( + userPublicKey: getUserHexEncodedPublicKey(db), + includeNonVisible: true + ) + ) + .isNotEmpty(db) + + return ( + !threadIsBlocked && + !threadIsMessageRequest + ) + } + @available(*, unavailable, message: "should not be used until pin re-ordering is built") static func refreshPinnedPriorities(_ db: Database, adding threadId: String) throws { struct PinnedPriority: TableRecord, ColumnExpressible { diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift index 3b5812119..a48c82259 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift @@ -168,6 +168,8 @@ internal extension SessionUtil { switch (data.shouldBeVisible, threadExists) { case (false, true): + SessionUtil.kickFromConversationUIIfNeeded(removedThreadIds: [contact.id]) + try SessionThread .filter(id: contact.id) .deleteAll(db) @@ -212,6 +214,8 @@ internal extension SessionUtil { .fetchAll(db) if !contactIdsToRemove.isEmpty { + SessionUtil.kickFromConversationUIIfNeeded(removedThreadIds: contactIdsToRemove) + try Contact .filter(ids: contactIdsToRemove) .deleteAll(db) @@ -224,6 +228,11 @@ internal extension SessionUtil { Profile.Columns.nickname.set(to: nil) ) + // Delete the one-to-one conversations associated to the contact + try SessionThread + .filter(ids: contactIdsToRemove) + .deleteAll(db) + try SessionUtil.remove(db, volatileContactIds: contactIdsToRemove) } } diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Shared.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Shared.swift index e82fc39a2..5f0bae6ea 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Shared.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Shared.swift @@ -1,7 +1,8 @@ // Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. -import Foundation +import UIKit import GRDB +import SessionUIKit import SessionUtil import SessionUtilitiesKit @@ -191,6 +192,44 @@ internal extension SessionUtil { return updated } + + static func kickFromConversationUIIfNeeded(removedThreadIds: [String]) { + guard !removedThreadIds.isEmpty else { return } + + // If the user is currently navigating somewhere within the view hierarchy of a conversation + // we just deleted then return to the home screen + DispatchQueue.main.async { + guard + let rootViewController: UIViewController = CurrentAppContext().mainWindow?.rootViewController, + let topBannerController: TopBannerController = (rootViewController as? TopBannerController), + !topBannerController.children.isEmpty, + let navController: UINavigationController = topBannerController.children[0] as? UINavigationController + else { return } + + // Extract the ones which will respond to SessionUtil changes + let targetViewControllers: [any SessionUtilRespondingViewController] = navController + .viewControllers + .compactMap({ $0 as? SessionUtilRespondingViewController }) + + // Make sure we have a conversation list and that one of the removed conversations are + // in the nav hierarchy + guard + targetViewControllers.count > 1, + targetViewControllers.contains(where: { $0.isConversationList }), + targetViewControllers.contains(where: { $0.isConversation(in: removedThreadIds) }) + else { return } + + // Return to the root view controller as the removed conversation will be invalid + if navController.presentedViewController != nil { + navController.dismiss(animated: false) { + navController.popToRootViewController(animated: true) + } + } + else { + navController.popToRootViewController(animated: true) + } + } + } } // MARK: - External Outgoing Changes @@ -285,3 +324,17 @@ extension SessionUtil { let shouldBeVisible: Bool } } + +// MARK: - SessionUtilRespondingViewController + +public protocol SessionUtilRespondingViewController { + var isConversationList: Bool { get } + + func isConversation(in threadIds: [String]) -> Bool +} + +public extension SessionUtilRespondingViewController { + var isConversationList: Bool { false } + + func isConversation(in threadIds: [String]) -> Bool { return false } +} diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift index 69cd956f8..bda053c0e 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift @@ -168,12 +168,16 @@ internal extension SessionUtil { .keys) .subtracting(communities.map { $0.data.threadId }) - communityIdsToRemove.forEach { threadId in - OpenGroupManager.shared.delete( - db, - openGroupId: threadId, - calledFromConfigHandling: true - ) + if !communityIdsToRemove.isEmpty { + SessionUtil.kickFromConversationUIIfNeeded(removedThreadIds: Array(communityIdsToRemove)) + + communityIdsToRemove.forEach { threadId in + OpenGroupManager.shared.delete( + db, + openGroupId: threadId, + calledFromConfigHandling: true + ) + } } // MARK: -- Handle Legacy Group Changes @@ -320,6 +324,8 @@ internal extension SessionUtil { .subtracting(legacyGroups.map { $0.id }) if !legacyGroupIdsToRemove.isEmpty { + SessionUtil.kickFromConversationUIIfNeeded(removedThreadIds: Array(legacyGroupIdsToRemove)) + try ClosedGroup.removeKeysAndUnsubscribe( db, threadIds: Array(legacyGroupIdsToRemove), diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+TypingIndicators.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+TypingIndicators.swift index 4d4c39329..91a00bd41 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+TypingIndicators.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+TypingIndicators.swift @@ -11,24 +11,40 @@ extension MessageReceiver { threadVariant: SessionThread.Variant, message: TypingIndicator ) throws { - guard let thread: SessionThread = try SessionThread.fetchOne(db, id: threadId) else { return } + guard try SessionThread.exists(db, id: threadId) else { return } switch message.kind { case .started: + let userPublicKey: String = getUserHexEncodedPublicKey(db) + let threadIsBlocked: Bool = ( + threadVariant == .contact && + (try? Contact + .filter(id: threadId) + .select(.isBlocked) + .asRequest(of: Bool.self) + .fetchOne(db)) + .defaulting(to: false) + ) + let threadIsMessageRequest: Bool = (try? SessionThread + .filter(id: threadId) + .filter(SessionThread.isMessageRequest(userPublicKey: userPublicKey, includeNonVisible: true)) + .isEmpty(db)) + .defaulting(to: false) let needsToStartTypingIndicator: Bool = TypingIndicators.didStartTypingNeedsToStart( threadId: threadId, threadVariant: threadVariant, - threadIsMessageRequest: thread.isMessageRequest(db), + threadIsBlocked: threadIsBlocked, + threadIsMessageRequest: threadIsMessageRequest, direction: .incoming, timestampMs: message.sentTimestamp.map { Int64($0) } ) if needsToStartTypingIndicator { - TypingIndicators.start(db, threadId: thread.id, direction: .incoming) + TypingIndicators.start(db, threadId: threadId, direction: .incoming) } case .stopped: - TypingIndicators.didStopTyping(db, threadId: thread.id, direction: .incoming) + TypingIndicators.didStopTyping(db, threadId: threadId, direction: .incoming) default: SNLog("Unknown TypingIndicator Kind ignored") diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift index 0dc6acc39..a891bb3ba 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift @@ -196,7 +196,7 @@ extension MessageReceiver { // If we receive an outgoing message that already exists in the database // then we still need up update the recipient and read states for the // message (even if we don't need to do anything else) - try updateRecipientAndReadStates( + try updateRecipientAndReadStatesForOutgoingInteraction( db, thread: thread, interactionId: existingInteractionId, @@ -214,7 +214,7 @@ extension MessageReceiver { guard let interactionId: Int64 = interaction.id else { throw StorageError.failedToSave } // Update and recipient and read states as needed - try updateRecipientAndReadStates( + try updateRecipientAndReadStatesForOutgoingInteraction( db, thread: thread, interactionId: interactionId, @@ -398,7 +398,7 @@ extension MessageReceiver { return interactionId } - private static func updateRecipientAndReadStates( + private static func updateRecipientAndReadStatesForOutgoingInteraction( _ db: Database, thread: SessionThread, interactionId: Int64, @@ -454,7 +454,7 @@ extension MessageReceiver { threadId: thread.id, threadVariant: thread.variant, includingOlder: true, - trySendReadReceipt: true + trySendReadReceipt: false ) // Process any PendingReadReceipt values diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift index a8cfb64bf..da4f59e7a 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift @@ -455,8 +455,8 @@ extension MessageSender { ) ), interactionId: nil, - threadId: closedGroup.threadId, - threadVariant: .legacyGroup + threadId: member, + threadVariant: .contact ) // Add the users to the group @@ -548,13 +548,16 @@ extension MessageSender { ) ) .flatMap { _ -> AnyPublisher in - generateAndSendNewEncryptionKeyPair( - db, - targetMembers: members, - userPublicKey: userPublicKey, - allGroupMembers: allGroupMembers, - closedGroup: closedGroup - ) + Storage.shared + .writePublisherFlatMap(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db in + generateAndSendNewEncryptionKeyPair( + db, + targetMembers: members, + userPublicKey: userPublicKey, + allGroupMembers: allGroupMembers, + closedGroup: closedGroup + ) + } } .eraseToAnyPublisher() } diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index 72b4d05d5..f861fad72 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -599,7 +599,13 @@ public final class MessageSender { // `MessageSender.performUploadsIfNeeded(queue:preparedSendData:)` before calling this function switch preparedSendData.message { case let visibleMessage as VisibleMessage: - guard visibleMessage.attachmentIds.count == preparedSendData.totalAttachmentsUploaded else { + let expectedAttachmentUploadCount: Int = ( + visibleMessage.attachmentIds.count + + (visibleMessage.linkPreview?.attachmentId != nil ? 1 : 0) + + (visibleMessage.quote?.attachmentId != nil ? 1 : 0) + ) + + guard expectedAttachmentUploadCount == preparedSendData.totalAttachmentsUploaded else { return Fail(error: MessageSenderError.attachmentsNotUploaded) .eraseToAnyPublisher() } diff --git a/SessionMessagingKit/Sending & Receiving/Typing Indicators/TypingIndicators.swift b/SessionMessagingKit/Sending & Receiving/Typing Indicators/TypingIndicators.swift index d3891d087..b8c4d3b49 100644 --- a/SessionMessagingKit/Sending & Receiving/Typing Indicators/TypingIndicators.swift +++ b/SessionMessagingKit/Sending & Receiving/Typing Indicators/TypingIndicators.swift @@ -25,6 +25,7 @@ public class TypingIndicators { init?( threadId: String, threadVariant: SessionThread.Variant, + threadIsBlocked: Bool, threadIsMessageRequest: Bool, direction: Direction, timestampMs: Int64? @@ -34,9 +35,11 @@ public class TypingIndicators { // or show typing indicators for other users // // We also don't want to show/send typing indicators for message requests - guard Storage.shared[.typingIndicatorsEnabled] && !threadIsMessageRequest else { - return nil - } + guard + Storage.shared[.typingIndicatorsEnabled] && + !threadIsBlocked && + !threadIsMessageRequest + else { return nil } // Don't send typing indicators in group threads guard @@ -143,6 +146,7 @@ public class TypingIndicators { public static func didStartTypingNeedsToStart( threadId: String, threadVariant: SessionThread.Variant, + threadIsBlocked: Bool, threadIsMessageRequest: Bool, direction: Direction, timestampMs: Int64? @@ -159,6 +163,7 @@ public class TypingIndicators { let newIndicator: Indicator? = Indicator( threadId: threadId, threadVariant: threadVariant, + threadIsBlocked: threadIsBlocked, threadIsMessageRequest: threadIsMessageRequest, direction: direction, timestampMs: timestampMs @@ -179,6 +184,7 @@ public class TypingIndicators { let newIndicator: Indicator? = Indicator( threadId: threadId, threadVariant: threadVariant, + threadIsBlocked: threadIsBlocked, threadIsMessageRequest: threadIsMessageRequest, direction: direction, timestampMs: timestampMs diff --git a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift index 94f9bdd89..a10542eaf 100644 --- a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift +++ b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift @@ -279,7 +279,8 @@ public struct SessionThreadViewModel: FetchableRecordWithRowId, Decodable, Equat let threadId: String = self.threadId let threadVariant: SessionThread.Variant = self.threadVariant - let trySendReadReceipt: Bool = (self.threadIsMessageRequest == false) + let threadIsBlocked: Bool? = self.threadIsBlocked + let threadIsMessageRequest: Bool? = self.threadIsMessageRequest Storage.shared.writeAsync { db in // Only make this change if needed (want to avoid triggering a thread update @@ -299,7 +300,13 @@ public struct SessionThreadViewModel: FetchableRecordWithRowId, Decodable, Equat threadId: threadId, threadVariant: threadVariant, includingOlder: true, - trySendReadReceipt: trySendReadReceipt + trySendReadReceipt: try SessionThread.canSendReadReceipt( + db, + threadId: threadId, + threadVariant: threadVariant, + isBlocked: threadIsBlocked, + isMessageRequest: threadIsMessageRequest + ) ) } } @@ -332,8 +339,10 @@ public extension SessionThreadViewModel { threadId: String? = nil, threadVariant: SessionThread.Variant? = nil, threadIsNoteToSelf: Bool = false, + threadIsBlocked: Bool? = nil, contactProfile: Profile? = nil, currentUserIsClosedGroupMember: Bool? = nil, + openGroupPermissions: OpenGroup.Permissions? = nil, unreadCount: UInt = 0 ) { self.rowId = -1 @@ -347,7 +356,7 @@ public extension SessionThreadViewModel { self.threadRequiresApproval = false self.threadShouldBeVisible = false self.threadPinnedPriority = 0 - self.threadIsBlocked = nil + self.threadIsBlocked = threadIsBlocked self.threadMutedUntilTimestamp = nil self.threadOnlyNotifyForMentions = nil self.threadMessageDraft = nil @@ -373,7 +382,7 @@ public extension SessionThreadViewModel { self.openGroupPublicKey = nil self.openGroupProfilePictureData = nil self.openGroupUserCount = nil - self.openGroupPermissions = nil + self.openGroupPermissions = openGroupPermissions // Interaction display info diff --git a/SessionUtilitiesKit/Database/Utilities/FetchRequest+Utilities.swift b/SessionUtilitiesKit/Database/Utilities/FetchRequest+Utilities.swift new file mode 100644 index 000000000..a3bccd855 --- /dev/null +++ b/SessionUtilitiesKit/Database/Utilities/FetchRequest+Utilities.swift @@ -0,0 +1,11 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import GRDB + +public extension FetchRequest where RowDecoder: DatabaseValueConvertible { + func fetchOne(_ db: Database, orThrow error: Error) throws -> RowDecoder { + guard let result: RowDecoder = try fetchOne(db) else { throw error } + + return result + } +} diff --git a/SignalUtilitiesKit/Utilities/UIViewController+OWS.swift b/SignalUtilitiesKit/Utilities/UIViewController+OWS.swift index c9762da43..6791a15e1 100644 --- a/SignalUtilitiesKit/Utilities/UIViewController+OWS.swift +++ b/SignalUtilitiesKit/Utilities/UIViewController+OWS.swift @@ -14,6 +14,16 @@ public extension UIViewController { var nextViewController: UIViewController? = viewController.presentedViewController + if + let topBannerController: TopBannerController = nextViewController as? TopBannerController, + !topBannerController.children.isEmpty + { + nextViewController = ( + topBannerController.children[0].presentedViewController ?? + topBannerController.children[0] + ) + } + if let nextViewController: UIViewController = nextViewController { if !ignoringAlerts || !(nextViewController is UIAlertController) { if visitedViewControllers.contains(nextViewController) { From c80b6c720eac03daf56ca757277bc6f56ad6df79 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 31 Mar 2023 12:09:04 +1100 Subject: [PATCH 047/135] Fixed the QA issues and a few other bugs Updated the convoInfoVolatile to only ever set `last_read` to the maximum between the current and updated values Fixed an issue where deleting the Note to Self and One-to-one conversations wouldn't reset the 'pinnedPriority' value Fixed an issue with updating legacy group members and losing admin status Fixed an issue where receiving a 'NEW' legacy group control message could revert legacy group changes Fixed a bug where the open group suggestion grid could have broken positioning depending on the number of items Fixed a bug where the UI wouldn't update correctly when network access was lost Fixed a fun bug where one-to-one conversations could reappear after deletion because a new snode was polled and the latest (locally deleted) message was received again Fixed some incorrect accessibility values --- Session/Conversations/ConversationVC.swift | 18 +++- .../Message Cells/VisibleMessageCell.swift | 8 +- .../Settings/ThreadSettingsViewModel.swift | 4 + .../GlobalSearchViewController.swift | 15 +++- Session/Home/HomeViewModel.swift | 45 ++-------- .../MessageRequestsViewController.swift | 10 ++- .../MessageRequestsViewModel.swift | 73 ++++++--------- .../GIFs/GifPickerViewController.swift | 2 +- .../Open Groups/OpenGroupSuggestionGrid.swift | 48 ++++++---- Session/Path/PathStatusView.swift | 17 ++-- Session/Path/PathVC.swift | 16 ++-- Session/Shared/Views/SessionCell.swift | 13 ++- .../Database/Models/ClosedGroup.swift | 2 +- .../Models/ControlMessageProcessRecord.swift | 14 ++- .../Database/Models/GroupMember.swift | 2 +- .../Database/Models/SessionThread.swift | 83 +++++++++++++++++ .../SessionUtil+Contacts.swift | 26 ++++-- .../SessionUtil+ConvoInfoVolatile.swift | 6 +- .../Config Handling/SessionUtil+Shared.swift | 88 +++++++++++++++---- .../SessionUtil+UserGroups.swift | 67 +++++++++----- .../SessionUtil+UserProfile.swift | 14 +++ .../MessageReceiver+MessageRequests.swift | 9 +- .../Utilities/SSKReachabilityManager.swift | 2 + 23 files changed, 398 insertions(+), 184 deletions(-) diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index 35cf9ea39..f25d8022f 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -555,7 +555,7 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers ) { Storage.shared.writeAsync { db in - _ = try SessionThread + _ = try SessionThread // Intentionally use `deleteAll` here instead of `deleteOrLeave` .filter(id: threadId) .deleteAll(db) } @@ -601,8 +601,18 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers }), let unblindedId: String = blindedLookup.sessionId else { - // If we don't have an unblinded id then something has gone very wrong so pop to the HomeVC - self?.navigationController?.popToRootViewController(animated: true) + // If we don't have an unblinded id then something has gone very wrong so pop to the + // nearest conversation list + let maybeTargetViewController: UIViewController? = self?.navigationController? + .viewControllers + .last(where: { ($0 as? SessionUtilRespondingViewController)?.isConversationList == true }) + + if let targetViewController: UIViewController = maybeTargetViewController { + self?.navigationController?.popToViewController(targetViewController, animated: true) + } + else { + self?.navigationController?.popToRootViewController(animated: true) + } return } @@ -1246,7 +1256,7 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers target: self, action: #selector(startCall) ) - callButton.accessibilityLabel = "Call button" + callButton.accessibilityLabel = "Call" callButton.isAccessibilityElement = true navigationItem.rightBarButtonItems = [settingsButtonItem, callButton] diff --git a/Session/Conversations/Message Cells/VisibleMessageCell.swift b/Session/Conversations/Message Cells/VisibleMessageCell.swift index 8b21a3d4d..8def1187d 100644 --- a/Session/Conversations/Message Cells/VisibleMessageCell.swift +++ b/Session/Conversations/Message Cells/VisibleMessageCell.swift @@ -301,9 +301,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { lastSearchText: String? ) { self.viewModel = cellViewModel - self.bubbleView.accessibilityIdentifier = "Message Body" - self.bubbleView.isAccessibilityElement = true - self.bubbleView.accessibilityLabel = cellViewModel.body + // We want to add spacing between "clusters" of messages to indicate that time has // passed (even if there wasn't enough time to warrant showing a date header) let shouldAddTopInset: Bool = ( @@ -362,6 +360,10 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { lastSearchText: lastSearchText ) + bubbleView.accessibilityIdentifier = "Message Body" + bubbleView.accessibilityLabel = bodyTappableLabel?.attributedText?.string + bubbleView.isAccessibilityElement = true + // Author label authorLabelTopConstraint.constant = (shouldAddTopInset ? Values.mediumSpacing : 0) authorLabel.isHidden = (cellViewModel.senderName == nil) diff --git a/Session/Conversations/Settings/ThreadSettingsViewModel.swift b/Session/Conversations/Settings/ThreadSettingsViewModel.swift index cd0948d44..8452a7940 100644 --- a/Session/Conversations/Settings/ThreadSettingsViewModel.swift +++ b/Session/Conversations/Settings/ThreadSettingsViewModel.swift @@ -285,6 +285,10 @@ class ThreadSettingsViewModel: SessionTableViewModel // MARK: - SearchSection @@ -20,6 +20,15 @@ class GlobalSearchViewController: BaseVC, UITableViewDelegate, UITableViewDataSo case messages } + // MARK: - SessionUtilRespondingViewController + + let isConversationList: Bool = true + + func forceRefreshIfNeeded() { + // Need to do this as the 'GlobalSearchViewController' doesn't observe database changes + updateSearchResults(searchText: searchText, force: true) + } + // MARK: - Variables private lazy var defaultSearchResults: [SectionModel] = { @@ -152,7 +161,7 @@ class GlobalSearchViewController: BaseVC, UITableViewDelegate, UITableViewDataSo } } - private func updateSearchResults(searchText rawSearchText: String) { + private func updateSearchResults(searchText rawSearchText: String, force: Bool = false) { let searchText = rawSearchText.stripped guard searchText.count > 0 else { @@ -161,7 +170,7 @@ class GlobalSearchViewController: BaseVC, UITableViewDelegate, UITableViewDataSo reloadTableData() return } - guard lastSearchText != searchText else { return } + guard force || lastSearchText != searchText else { return } lastSearchText = searchText diff --git a/Session/Home/HomeViewModel.swift b/Session/Home/HomeViewModel.swift index 5f0158af5..3069bd9ed 100644 --- a/Session/Home/HomeViewModel.swift +++ b/Session/Home/HomeViewModel.swift @@ -368,44 +368,13 @@ public class HomeViewModel { public func deleteOrLeave(threadId: String, threadVariant: SessionThread.Variant) { Storage.shared.writeAsync { db in - switch threadVariant { - case .contact: - // We need to custom handle the 'Note to Self' conversation (it should just be - // hidden rather than deleted - guard threadId != getUserHexEncodedPublicKey(db) else { - _ = try Interaction - .filter(Interaction.Columns.threadId == threadId) - .deleteAll(db) - - _ = try SessionThread - .filter(id: threadId) - .updateAllAndConfig( - db, - SessionThread.Columns.shouldBeVisible.set(to: false) - ) - - return - } - - try SessionUtil - .hide(db, contactIds: [threadId]) - - case .legacyGroup, .group: - MessageSender - .leave(db, groupPublicKey: threadId) - .sinkUntilComplete() - - case .community: - OpenGroupManager.shared.delete( - db, - openGroupId: threadId, - calledFromConfigHandling: false - ) - } - - _ = try SessionThread - .filter(id: threadId) - .deleteAll(db) + try SessionThread.deleteOrLeave( + db, + threadId: threadId, + threadVariant: threadVariant, + shouldSendLeaveMessageForGroups: true, + calledFromConfigHandling: false + ) } } } diff --git a/Session/Home/Message Requests/MessageRequestsViewController.swift b/Session/Home/Message Requests/MessageRequestsViewController.swift index b9be340a0..b5dc1cb6a 100644 --- a/Session/Home/Message Requests/MessageRequestsViewController.swift +++ b/Session/Home/Message Requests/MessageRequestsViewController.swift @@ -7,7 +7,7 @@ import SessionUIKit import SessionMessagingKit import SignalUtilitiesKit -class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDataSource { +class MessageRequestsViewController: BaseVC, SessionUtilRespondingViewController, UITableViewDelegate, UITableViewDataSource { private static let loadingHeaderHeight: CGFloat = 40 private let viewModel: MessageRequestsViewModel = MessageRequestsViewModel() @@ -17,6 +17,10 @@ class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDat private var isAutoLoadingNextPage: Bool = false private var viewHasAppeared: Bool = false + // MARK: - SessionUtilRespondingViewController + + let isConversationList: Bool = true + // MARK: - Intialization init() { @@ -466,7 +470,7 @@ class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDat .filter { $0.threadVariant == .contact } .map { $0.threadId }) .defaulting(to: []) - let closedGroupThreadIds: [String] = (viewModel.threadData + let groupThreadIds: [String] = (viewModel.threadData .first { $0.model == .threads }? .elements .filter { $0.threadVariant == .legacyGroup || $0.threadVariant == .group } @@ -483,7 +487,7 @@ class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDat ) { _ in MessageRequestsViewModel.clearAllRequests( contactThreadIds: contactThreadIds, - closedGroupThreadIds: closedGroupThreadIds + groupThreadIds: groupThreadIds ) }) alertVC.addAction(UIAlertAction(title: "TXT_CANCEL_TITLE".localized(), style: .cancel, handler: nil)) diff --git a/Session/Home/Message Requests/MessageRequestsViewModel.swift b/Session/Home/Message Requests/MessageRequestsViewModel.swift index 9a79a13e4..28e8dc00f 100644 --- a/Session/Home/Message Requests/MessageRequestsViewModel.swift +++ b/Session/Home/Message Requests/MessageRequestsViewModel.swift @@ -185,28 +185,13 @@ public class MessageRequestsViewModel { cancelStyle: .alert_text ) { _ in Storage.shared.write { db in - switch threadVariant { - case .contact: - try SessionUtil - .hide(db, contactIds: [threadId]) - - _ = try SessionThread - .filter(id: threadId) - .deleteAll(db) - - case .legacyGroup, .group: - try ClosedGroup.removeKeysAndUnsubscribe( - db, - threadId: threadId, - removeGroupData: true, - calledFromConfigHandling: false - ) - - // Trigger a config sync - ConfigurationSyncJob.enqueue(db, publicKey: getUserHexEncodedPublicKey(db)) - - default: break - } + try SessionThread.deleteOrLeave( + db, + threadId: threadId, + threadVariant: threadVariant, + shouldSendLeaveMessageForGroups: false, + calledFromConfigHandling: false + ) } completion?() @@ -250,14 +235,13 @@ public class MessageRequestsViewModel { Contact.Columns.didApproveMe.set(to: true) ) - // Sync the removal of the thread from other devices - try SessionUtil - .hide(db, contactIds: [threadId]) - - // Remove the thread - _ = try SessionThread - .filter(id: threadId) - .deleteAll(db) + try SessionThread.deleteOrLeave( + db, + threadId: threadId, + threadVariant: .contact, + shouldSendLeaveMessageForGroups: false, + calledFromConfigHandling: false + ) }, completion: { _, _ in completion?() } ) @@ -269,24 +253,25 @@ public class MessageRequestsViewModel { static func clearAllRequests( contactThreadIds: [String], - closedGroupThreadIds: [String] + groupThreadIds: [String] ) { // Clear the requests Storage.shared.write { db in - // Sync the removal of the thread from other devices - try SessionUtil - .hide(db, contactIds: contactThreadIds) - - // Remove the threads - _ = try SessionThread - .filter(ids: contactThreadIds) - .deleteAll(db) - - // Remove the groups - try ClosedGroup.removeKeysAndUnsubscribe( + // Remove the one-to-one requests + try SessionThread.deleteOrLeave( db, - threadIds: closedGroupThreadIds, - removeGroupData: true, + threadIds: contactThreadIds, + threadVariant: .contact, + shouldSendLeaveMessageForGroups: false, + calledFromConfigHandling: false + ) + + // Remove the group requests + try SessionThread.deleteOrLeave( + db, + threadIds: groupThreadIds, + threadVariant: .group, + shouldSendLeaveMessageForGroups: false, calledFromConfigHandling: false ) } diff --git a/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift b/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift index 244ba9fb7..6ee71df52 100644 --- a/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift +++ b/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift @@ -115,7 +115,7 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect createViews() - reachability = Reachability.forInternetConnection() + reachability = Environment.shared?.reachabilityManager.reachability NotificationCenter.default.addObserver( self, selector: #selector(reachabilityChanged), diff --git a/Session/Open Groups/OpenGroupSuggestionGrid.swift b/Session/Open Groups/OpenGroupSuggestionGrid.swift index 706df79f4..80b38ae0a 100644 --- a/Session/Open Groups/OpenGroupSuggestionGrid.swift +++ b/Session/Open Groups/OpenGroupSuggestionGrid.swift @@ -18,7 +18,7 @@ final class OpenGroupSuggestionGrid: UIView, UICollectionViewDataSource, UIColle private static let cellHeight: CGFloat = 40 private static let separatorWidth = Values.separatorThickness - private static let numHorizontalCells: CGFloat = (UIDevice.current.isIPad ? 4 : 2) + fileprivate static let numHorizontalCells: Int = (UIDevice.current.isIPad ? 4 : 2) private lazy var layout: LastRowCenteredLayout = { let result = LastRowCenteredLayout() @@ -157,7 +157,7 @@ final class OpenGroupSuggestionGrid: UIView, UICollectionViewDataSource, UIColle spinner.isHidden = true let roomCount: CGFloat = CGFloat(min(rooms.count, 8)) // Cap to a maximum of 8 (4 rows of 2) - let numRows: CGFloat = ceil(roomCount / OpenGroupSuggestionGrid.numHorizontalCells) + let numRows: CGFloat = ceil(roomCount / CGFloat(OpenGroupSuggestionGrid.numHorizontalCells)) let height: CGFloat = ((OpenGroupSuggestionGrid.cellHeight * numRows) + ((numRows - 1) * layout.minimumLineSpacing)) heightConstraint.constant = height collectionView.reloadData() @@ -172,18 +172,18 @@ final class OpenGroupSuggestionGrid: UIView, UICollectionViewDataSource, UIColle // MARK: - Layout func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { - guard - indexPath.item == (collectionView.numberOfItems(inSection: indexPath.section) - 1) && - indexPath.item % 2 == 0 - else { - let cellWidth: CGFloat = ((maxWidth / OpenGroupSuggestionGrid.numHorizontalCells) - ((OpenGroupSuggestionGrid.numHorizontalCells - 1) * layout.minimumInteritemSpacing)) + let totalItems: Int = collectionView.numberOfItems(inSection: indexPath.section) + let itemsInFinalRow: Int = (totalItems % OpenGroupSuggestionGrid.numHorizontalCells) + + guard indexPath.item >= (totalItems - itemsInFinalRow) && itemsInFinalRow != 0 else { + let cellWidth: CGFloat = ((maxWidth / CGFloat(OpenGroupSuggestionGrid.numHorizontalCells)) - ((CGFloat(OpenGroupSuggestionGrid.numHorizontalCells) - 1) * layout.minimumInteritemSpacing)) return CGSize(width: cellWidth, height: OpenGroupSuggestionGrid.cellHeight) } - // If the last item is by itself then we want to make it wider + // If there isn't an even number of items then we want to calculate proper sizing return CGSize( - width: (Cell.calculatedWith(for: rooms[indexPath.item].name)), + width: Cell.calculatedWith(for: rooms[indexPath.item].name), height: OpenGroupSuggestionGrid.cellHeight ) } @@ -380,16 +380,30 @@ class LastRowCenteredLayout: UICollectionViewFlowLayout { }() guard - (elementAttributes?.count ?? 0) % 2 == 1, - let lastItemAttributes: UICollectionViewLayoutAttributes = elementAttributes?.last + let remainingItems: Int = elementAttributes.map({ $0.count % OpenGroupSuggestionGrid.numHorizontalCells }), + remainingItems != 0, + let lastItems: [UICollectionViewLayoutAttributes] = elementAttributes?.suffix(remainingItems), + !lastItems.isEmpty else { return elementAttributes } - lastItemAttributes.frame = CGRect( - x: ((targetViewWidth - lastItemAttributes.frame.size.width) / 2), - y: lastItemAttributes.frame.origin.y, - width: lastItemAttributes.frame.size.width, - height: lastItemAttributes.frame.size.height - ) + let totalItemWidth: CGFloat = lastItems + .map { $0.frame.size.width } + .reduce(0, +) + let lastRowWidth: CGFloat = (totalItemWidth + (CGFloat(lastItems.count - 1) * minimumInteritemSpacing)) + + // Offset the start width by half of the remaining space + var itemXPos: CGFloat = ((targetViewWidth - lastRowWidth) / 2) + + lastItems.forEach { item in + item.frame = CGRect( + x: itemXPos, + y: item.frame.origin.y, + width: item.frame.size.width, + height: item.frame.size.height + ) + + itemXPos += (item.frame.size.width + minimumInteritemSpacing) + } return elementAttributes } diff --git a/Session/Path/PathStatusView.swift b/Session/Path/PathStatusView.swift index c606b3268..a0fdb805d 100644 --- a/Session/Path/PathStatusView.swift +++ b/Session/Path/PathStatusView.swift @@ -4,6 +4,7 @@ import UIKit import Reachability import SessionUIKit import SessionSnodeKit +import SessionMessagingKit final class PathStatusView: UIView { enum Size { @@ -44,7 +45,7 @@ final class PathStatusView: UIView { // MARK: - Initialization private let size: Size - private let reachability: Reachability = Reachability.forInternetConnection() + private let reachability: Reachability? = Environment.shared?.reachabilityManager.reachability init(size: Size = .small) { self.size = size @@ -76,10 +77,10 @@ final class PathStatusView: UIView { self.set(.width, to: self.size.pointSize) self.set(.height, to: self.size.pointSize) - switch (reachability.isReachable(), OnionRequestAPI.paths.isEmpty) { - case (false, _): setStatus(to: .error) - case (true, true): setStatus(to: .connecting) - case (true, false): setStatus(to: .connected) + switch (reachability?.isReachable(), OnionRequestAPI.paths.isEmpty) { + case (.some(false), _), (nil, _): setStatus(to: .error) + case (.some(true), true): setStatus(to: .connecting) + case (.some(true), false): setStatus(to: .connected) } } @@ -124,7 +125,7 @@ final class PathStatusView: UIView { } @objc private func handleBuildingPathsNotification() { - guard reachability.isReachable() else { + guard reachability?.isReachable() == true else { setStatus(to: .error) return } @@ -133,7 +134,7 @@ final class PathStatusView: UIView { } @objc private func handlePathsBuiltNotification() { - guard reachability.isReachable() else { + guard reachability?.isReachable() == true else { setStatus(to: .error) return } @@ -147,7 +148,7 @@ final class PathStatusView: UIView { return } - guard reachability.isReachable() else { + guard reachability?.isReachable() == true else { setStatus(to: .error) return } diff --git a/Session/Path/PathVC.swift b/Session/Path/PathVC.swift index 1b641f43f..aa6c52fff 100644 --- a/Session/Path/PathVC.swift +++ b/Session/Path/PathVC.swift @@ -241,7 +241,7 @@ private final class LineView: UIView { private var dotViewWidthConstraint: NSLayoutConstraint! private var dotViewHeightConstraint: NSLayoutConstraint! private var dotViewAnimationTimer: Timer! - private let reachability: Reachability = Reachability.forInternetConnection() + private let reachability: Reachability? = Environment.shared?.reachabilityManager.reachability enum Location { case top, middle, bottom @@ -326,10 +326,10 @@ private final class LineView: UIView { } } - switch (reachability.isReachable(), OnionRequestAPI.paths.isEmpty) { - case (false, _): setStatus(to: .error) - case (true, true): setStatus(to: .connecting) - case (true, false): setStatus(to: .connected) + switch (reachability?.isReachable(), OnionRequestAPI.paths.isEmpty) { + case (.some(false), _), (nil, _): setStatus(to: .error) + case (.some(true), true): setStatus(to: .connecting) + case (.some(true), false): setStatus(to: .connected) } } @@ -380,7 +380,7 @@ private final class LineView: UIView { } @objc private func handleBuildingPathsNotification() { - guard reachability.isReachable() else { + guard reachability?.isReachable() == true else { setStatus(to: .error) return } @@ -389,7 +389,7 @@ private final class LineView: UIView { } @objc private func handlePathsBuiltNotification() { - guard reachability.isReachable() else { + guard reachability?.isReachable() == true else { setStatus(to: .error) return } @@ -403,7 +403,7 @@ private final class LineView: UIView { return } - guard reachability.isReachable() else { + guard reachability?.isReachable() == true else { setStatus(to: .error) return } diff --git a/Session/Shared/Views/SessionCell.swift b/Session/Shared/Views/SessionCell.swift index 5ad87cb99..90407d4c6 100644 --- a/Session/Shared/Views/SessionCell.swift +++ b/Session/Shared/Views/SessionCell.swift @@ -95,7 +95,7 @@ public class SessionCell: UITableViewCell { return result }() - private let titleLabel: SRCopyableLabel = { + fileprivate let titleLabel: SRCopyableLabel = { let result: SRCopyableLabel = SRCopyableLabel() result.translatesAutoresizingMaskIntoConstraints = false result.isUserInteractionEnabled = false @@ -586,7 +586,16 @@ public class SessionCell: UITableViewCell { extension CombineCompatible where Self: SessionCell { var textPublisher: AnyPublisher { - return self.titleTextField.publisher(for: .editingChanged) + return self.titleTextField.publisher(for: [.editingChanged, .editingDidEnd]) + .handleEvents( + receiveOutput: { [weak self] textField in + // When editing the text update the 'accessibilityLabel' of the cell to match + // the text + let targetText: String? = (textField.isEditing ? textField.text : self?.titleLabel.text) + self?.accessibilityLabel = (targetText ?? self?.accessibilityLabel) + } + ) + .filter { $0.isEditing } // Don't bother sending events for 'editingDidEnd' .map { textField -> String in (textField.text ?? "") } .eraseToAnyPublisher() } diff --git a/SessionMessagingKit/Database/Models/ClosedGroup.swift b/SessionMessagingKit/Database/Models/ClosedGroup.swift index e24dff75b..f2e4964ba 100644 --- a/SessionMessagingKit/Database/Models/ClosedGroup.swift +++ b/SessionMessagingKit/Database/Models/ClosedGroup.swift @@ -164,7 +164,7 @@ public extension ClosedGroup { // Remove the remaining group data if desired if removeGroupData { - try SessionThread + try SessionThread // Intentionally use `deleteAll` here as this gets triggered via `deleteOrLeave` .filter(ids: threadIds) .deleteAll(db) diff --git a/SessionMessagingKit/Database/Models/ControlMessageProcessRecord.swift b/SessionMessagingKit/Database/Models/ControlMessageProcessRecord.swift index 3890df232..be48d773a 100644 --- a/SessionMessagingKit/Database/Models/ControlMessageProcessRecord.swift +++ b/SessionMessagingKit/Database/Models/ControlMessageProcessRecord.swift @@ -41,6 +41,15 @@ public struct ControlMessageProcessRecord: Codable, FetchableRecord, Persistable case unsendRequest = 7 case messageRequestResponse = 8 case call = 9 + + /// Since we retrieve messages from all snodes in a swarm there is a fun issue where a user can delete a + /// one-to-one conversation (which removes all associated interactions) and then the poller checks a + /// different service node, if a previously processed message hadn't been processed yet for that specific + /// service node it results in the conversation re-appearing + /// + /// This `Variant` allows us to create a record which survives thread deletion to prevent a duplicate + /// message from being reprocessed + case visibleMessageDedupe = 10 } /// The id for the thread the control message is associated to @@ -68,10 +77,6 @@ public struct ControlMessageProcessRecord: Codable, FetchableRecord, Persistable message: Message, serverExpirationTimestamp: TimeInterval? ) { - // All `VisibleMessage` values will have an associated `Interaction` so just let - // the unique constraints on that table prevent duplicate messages - if message is VisibleMessage { return nil } - // Allow duplicates for UnsendRequest messages, if a user received an UnsendRequest // as a push notification the it wouldn't include a serverHash and, as a result, // wouldn't get deleted from the server - since the logic only runs if we find a @@ -113,6 +118,7 @@ public struct ControlMessageProcessRecord: Codable, FetchableRecord, Persistable case is UnsendRequest: return .unsendRequest case is MessageRequestResponse: return .messageRequestResponse case is CallMessage: return .call + case is VisibleMessage: return .visibleMessageDedupe default: preconditionFailure("[ControlMessageProcessRecord] Unsupported message type") } }() diff --git a/SessionMessagingKit/Database/Models/GroupMember.swift b/SessionMessagingKit/Database/Models/GroupMember.swift index 2ccd56d68..75fb605f0 100644 --- a/SessionMessagingKit/Database/Models/GroupMember.swift +++ b/SessionMessagingKit/Database/Models/GroupMember.swift @@ -4,7 +4,7 @@ import Foundation import GRDB import SessionUtilitiesKit -public struct GroupMember: Codable, Equatable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { +public struct GroupMember: Codable, Equatable, Hashable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { public static var databaseTableName: String { "groupMember" } internal static let openGroupForeignKey = ForeignKey([Columns.groupId], to: [OpenGroup.Columns.threadId]) internal static let closedGroupForeignKey = ForeignKey([Columns.groupId], to: [ClosedGroup.Columns.threadId]) diff --git a/SessionMessagingKit/Database/Models/SessionThread.swift b/SessionMessagingKit/Database/Models/SessionThread.swift index 507e96814..bedd17894 100644 --- a/SessionMessagingKit/Database/Models/SessionThread.swift +++ b/SessionMessagingKit/Database/Models/SessionThread.swift @@ -285,6 +285,89 @@ public extension SessionThread { ) """) } + + static func deleteOrLeave( + _ db: Database, + threadId: String, + threadVariant: Variant, + shouldSendLeaveMessageForGroups: Bool, + calledFromConfigHandling: Bool + ) throws { + try deleteOrLeave( + db, + threadIds: [threadId], + threadVariant: threadVariant, + shouldSendLeaveMessageForGroups: shouldSendLeaveMessageForGroups, + calledFromConfigHandling: calledFromConfigHandling + ) + } + + static func deleteOrLeave( + _ db: Database, + threadIds: [String], + threadVariant: Variant, + shouldSendLeaveMessageForGroups: Bool, + calledFromConfigHandling: Bool + ) throws { + let currentUserPublicKey: String = getUserHexEncodedPublicKey(db) + let remainingThreadIds: [String] = threadIds.filter { $0 != currentUserPublicKey } + + switch threadVariant { + case .contact: + // We need to custom handle the 'Note to Self' conversation (it should just be + // hidden rather than deleted + if threadIds.contains(currentUserPublicKey) { + _ = try Interaction + .filter(Interaction.Columns.threadId == currentUserPublicKey) + .deleteAll(db) + + _ = try SessionThread + .filter(id: currentUserPublicKey) + .updateAllAndConfig( + db, + SessionThread.Columns.pinnedPriority.set(to: 0), + SessionThread.Columns.shouldBeVisible.set(to: false) + ) + return + } + + // If this wasn't called from config handling then we need to hide the conversation + if !calledFromConfigHandling { + try SessionUtil + .hide(db, contactIds: threadIds) + } + + case .legacyGroup, .group: + if shouldSendLeaveMessageForGroups { + threadIds.forEach { threadId in + MessageSender + .leave(db, groupPublicKey: threadId) + .sinkUntilComplete() + } + } + else { + try ClosedGroup.removeKeysAndUnsubscribe( + db, + threadIds: threadIds, + removeGroupData: true, + calledFromConfigHandling: calledFromConfigHandling + ) + } + + case .community: + threadIds.forEach { threadId in + OpenGroupManager.shared.delete( + db, + openGroupId: threadId, + calledFromConfigHandling: calledFromConfigHandling + ) + } + } + + _ = try SessionThread + .filter(ids: remainingThreadIds) + .deleteAll(db) + } } // MARK: - Convenience diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift index a48c82259..2700d1425 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift @@ -171,8 +171,13 @@ internal extension SessionUtil { SessionUtil.kickFromConversationUIIfNeeded(removedThreadIds: [contact.id]) try SessionThread - .filter(id: contact.id) - .deleteAll(db) + .deleteOrLeave( + db, + threadId: contact.id, + threadVariant: .contact, + shouldSendLeaveMessageForGroups: false, + calledFromConfigHandling: true + ) case (true, false): try SessionThread( @@ -230,8 +235,13 @@ internal extension SessionUtil { // Delete the one-to-one conversations associated to the contact try SessionThread - .filter(ids: contactIdsToRemove) - .deleteAll(db) + .deleteOrLeave( + db, + threadIds: contactIdsToRemove, + threadVariant: .contact, + shouldSendLeaveMessageForGroups: false, + calledFromConfigHandling: true + ) try SessionUtil.remove(db, volatileContactIds: contactIdsToRemove) } @@ -441,7 +451,13 @@ public extension SessionUtil { // Mark the contacts as hidden try SessionUtil.upsert( contactData: contactIds - .map { SyncedContactInfo(id: $0, hidden: true) }, + .map { + SyncedContactInfo( + id: $0, + hidden: true, + priority: 0 + ) + }, in: conf ) } diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift index 45854e1b3..41f0497af 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift @@ -199,7 +199,7 @@ internal extension SessionUtil { threadInfo.changes.forEach { change in switch change { case .lastReadTimestampMs(let lastReadMs): - oneToOne.last_read = lastReadMs + oneToOne.last_read = max(oneToOne.last_read, lastReadMs) case .markedAsUnread(let unread): oneToOne.unread = unread @@ -218,7 +218,7 @@ internal extension SessionUtil { threadInfo.changes.forEach { change in switch change { case .lastReadTimestampMs(let lastReadMs): - legacyGroup.last_read = lastReadMs + legacyGroup.last_read = max(legacyGroup.last_read, lastReadMs) case .markedAsUnread(let unread): legacyGroup.unread = unread @@ -246,7 +246,7 @@ internal extension SessionUtil { threadInfo.changes.forEach { change in switch change { case .lastReadTimestampMs(let lastReadMs): - community.last_read = lastReadMs + community.last_read = max(community.last_read, lastReadMs) case .markedAsUnread(let unread): community.unread = unread diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Shared.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Shared.swift index 5f0bae6ea..c6775b7d1 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Shared.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Shared.swift @@ -209,24 +209,74 @@ internal extension SessionUtil { // Extract the ones which will respond to SessionUtil changes let targetViewControllers: [any SessionUtilRespondingViewController] = navController .viewControllers - .compactMap({ $0 as? SessionUtilRespondingViewController }) + .compactMap { $0 as? SessionUtilRespondingViewController } + let presentedNavController: UINavigationController? = (navController.presentedViewController as? UINavigationController) + let presentedTargetViewControllers: [any SessionUtilRespondingViewController] = (presentedNavController? + .viewControllers + .compactMap { $0 as? SessionUtilRespondingViewController }) + .defaulting(to: []) // Make sure we have a conversation list and that one of the removed conversations are // in the nav hierarchy - guard - targetViewControllers.count > 1, - targetViewControllers.contains(where: { $0.isConversationList }), + let rootNavControllerNeedsPop: Bool = ( + targetViewControllers.count > 1 && + targetViewControllers.contains(where: { $0.isConversationList }) && targetViewControllers.contains(where: { $0.isConversation(in: removedThreadIds) }) - else { return } + ) + let presentedNavControllerNeedsPop: Bool = ( + presentedTargetViewControllers.count > 1 && + presentedTargetViewControllers.contains(where: { $0.isConversationList }) && + presentedTargetViewControllers.contains(where: { $0.isConversation(in: removedThreadIds) }) + ) - // Return to the root view controller as the removed conversation will be invalid - if navController.presentedViewController != nil { - navController.dismiss(animated: false) { - navController.popToRootViewController(animated: true) - } - } - else { - navController.popToRootViewController(animated: true) + // Force the UI to refresh if needed (most screens should do this automatically via database + // observation, but a couple of screens don't so need to be done manually) + targetViewControllers + .appending(contentsOf: presentedTargetViewControllers) + .filter { $0.isConversationList } + .forEach { $0.forceRefreshIfNeeded() } + + switch (rootNavControllerNeedsPop, presentedNavControllerNeedsPop) { + case (true, false): + // Return to the conversation list as the removed conversation will be invalid + guard + let targetViewController: UIViewController = navController.viewControllers + .last(where: { viewController in + ((viewController as? SessionUtilRespondingViewController)?.isConversationList) + .defaulting(to: false) + }) + else { return } + + if navController.presentedViewController != nil { + navController.dismiss(animated: false) { + navController.popToViewController(targetViewController, animated: true) + } + } + else { + navController.popToViewController(targetViewController, animated: true) + } + + case (false, true): + // Return to the conversation list as the removed conversation will be invalid + guard + let targetViewController: UIViewController = presentedNavController? + .viewControllers + .last(where: { viewController in + ((viewController as? SessionUtilRespondingViewController)?.isConversationList) + .defaulting(to: false) + }) + else { return } + + if presentedNavController?.presentedViewController != nil { + presentedNavController?.dismiss(animated: false) { + presentedNavController?.popToViewController(targetViewController, animated: true) + } + } + else { + presentedNavController?.popToViewController(targetViewController, animated: true) + } + + default: break } } } @@ -256,7 +306,12 @@ public extension SessionUtil { var cThreadId: [CChar] = threadId.cArray switch threadVariant { - case .contact: return contacts_get(conf, nil, &cThreadId) + case .contact: + var contact: contacts_contact = contacts_contact() + + guard contacts_get(conf, &contact, &cThreadId) else { return false } + + return !contact.hidden case .community: let maybeUrlInfo: OpenGroupUrlInfo? = Storage.shared @@ -267,8 +322,9 @@ public extension SessionUtil { var cBaseUrl: [CChar] = urlInfo.server.cArray var cRoom: [CChar] = urlInfo.roomToken.cArray + var community: ugroups_community_info = ugroups_community_info() - return user_groups_get_community(conf, nil, &cBaseUrl, &cRoom) + return user_groups_get_community(conf, &community, &cBaseUrl, &cRoom) case .legacyGroup: let groupInfo: UnsafeMutablePointer? = user_groups_get_legacy_group(conf, &cThreadId) @@ -331,10 +387,12 @@ public protocol SessionUtilRespondingViewController { var isConversationList: Bool { get } func isConversation(in threadIds: [String]) -> Bool + func forceRefreshIfNeeded() } public extension SessionUtilRespondingViewController { var isConversationList: Bool { false } func isConversation(in threadIds: [String]) -> Bool { return false } + func forceRefreshIfNeeded() {} } diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift index bda053c0e..d62de5de6 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift @@ -88,7 +88,7 @@ internal extension SessionUtil { ), groupMembers: members .filter { _, isAdmin in !isAdmin } - .map { memberId, admin in + .map { memberId, _ in GroupMember( groupId: groupId, profileId: memberId, @@ -98,7 +98,7 @@ internal extension SessionUtil { }, groupAdmins: members .filter { _, isAdmin in isAdmin } - .map { memberId, admin in + .map { memberId, _ in GroupMember( groupId: groupId, profileId: memberId, @@ -171,13 +171,14 @@ internal extension SessionUtil { if !communityIdsToRemove.isEmpty { SessionUtil.kickFromConversationUIIfNeeded(removedThreadIds: Array(communityIdsToRemove)) - communityIdsToRemove.forEach { threadId in - OpenGroupManager.shared.delete( + try SessionThread + .deleteOrLeave( db, - openGroupId: threadId, + threadIds: Array(communityIdsToRemove), + threadVariant: .community, + shouldSendLeaveMessageForGroups: false, calledFromConfigHandling: true ) - } } // MARK: -- Handle Legacy Group Changes @@ -200,7 +201,7 @@ internal extension SessionUtil { let name: String = group.name, let lastKeyPair: ClosedGroupKeyPair = group.lastKeyPair, let members: [GroupMember] = group.groupMembers, - let updatedAdmins: [GroupMember] = group.groupAdmins + let updatedAdmins: Set = group.groupAdmins?.asSet() else { return } if !existingLegacyGroupIds.contains(group.id) { @@ -214,7 +215,8 @@ internal extension SessionUtil { secretKey: lastKeyPair.secretKey.bytes ), members: members - .appending(contentsOf: updatedAdmins) // Admins should also have 'standard' member entries + .asSet() + .inserting(contentsOf: updatedAdmins) // Admins should also have 'standard' member entries .map { $0.profileId }, admins: updatedAdmins.map { $0.profileId }, expirationTimer: UInt32(group.disappearingConfig?.durationSeconds ?? 0), @@ -253,7 +255,7 @@ internal extension SessionUtil { .saved(db) // Update the members - let updatedMembers: [GroupMember] = members + let updatedMembers: Set = members .appending( contentsOf: updatedAdmins.map { admin in GroupMember( @@ -264,10 +266,12 @@ internal extension SessionUtil { ) } ) + .asSet() if - let existingMembers: [GroupMember] = existingLegacyGroupMembers[group.id]? - .filter({ $0.role == .standard || $0.role == .zombie }), + let existingMembers: Set = existingLegacyGroupMembers[group.id]? + .filter({ $0.role == .standard || $0.role == .zombie }) + .asSet(), existingMembers != updatedMembers { // Add in any new members and remove any removed members @@ -288,8 +292,9 @@ internal extension SessionUtil { } if - let existingAdmins: [GroupMember] = existingLegacyGroupMembers[group.id]? - .filter({ $0.role == .admin }), + let existingAdmins: Set = existingLegacyGroupMembers[group.id]? + .filter({ $0.role == .admin }) + .asSet(), existingAdmins != updatedAdmins { // Add in any new admins and remove any removed admins @@ -326,12 +331,14 @@ internal extension SessionUtil { if !legacyGroupIdsToRemove.isEmpty { SessionUtil.kickFromConversationUIIfNeeded(removedThreadIds: Array(legacyGroupIdsToRemove)) - try ClosedGroup.removeKeysAndUnsubscribe( - db, - threadIds: Array(legacyGroupIdsToRemove), - removeGroupData: true, - calledFromConfigHandling: true - ) + try SessionThread + .deleteOrLeave( + db, + threadIds: Array(legacyGroupIdsToRemove), + threadVariant: .legacyGroup, + shouldSendLeaveMessageForGroups: false, + calledFromConfigHandling: true + ) } // MARK: -- Handle Group Changes @@ -364,8 +371,6 @@ internal extension SessionUtil { guard conf != nil else { throw SessionUtilError.nilConfigObject } guard !legacyGroups.isEmpty else { return } - // Since we are doing direct memory manipulation we are using an `Atomic` type which has - // blocking access in it's `mutate` closure legacyGroups .forEach { legacyGroup in var cGroupId: [CChar] = legacyGroup.id.cArray @@ -405,7 +410,12 @@ internal extension SessionUtil { }() if let groupMembers: [GroupMember] = legacyGroup.groupMembers { - let memberIds: Set = groupMembers.map { $0.profileId }.asSet() + // Need to make sure we remove any admins before adding them here otherwise we will + // overwrite the admin permission to be a standard user permission + let memberIds: Set = groupMembers + .map { $0.profileId } + .asSet() + .subtracting(legacyGroup.groupAdmins.defaulting(to: []).map { $0.profileId }.asSet()) let existingMemberIds: Set = Array(existingMembers .filter { _, isAdmin in !isAdmin } .keys) @@ -555,6 +565,19 @@ public extension SessionUtil { for: .userGroups, publicKey: getUserHexEncodedPublicKey(db) ) { conf in + guard conf != nil else { throw SessionUtilError.nilConfigObject } + + var cGroupId: [CChar] = groupPublicKey.cArray + let userGroup: UnsafeMutablePointer? = user_groups_get_legacy_group(conf, &cGroupId) + + // Need to make sure the group doesn't already exist (otherwise we will end up overriding the + // content which could revert newer changes since this can be triggered from other 'NEW' messages + // coming in from the legacy group swarm) + guard userGroup == nil else { + ugroups_legacy_group_free(userGroup) + return + } + try SessionUtil.upsert( legacyGroups: [ LegacyGroupInfo( diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift index 938fb9a5b..9576b9509 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift @@ -98,6 +98,20 @@ internal extension SessionUtil { db, SessionThread.Columns.pinnedPriority.set(to: targetPriority) ) + + // If the 'Note to Self' conversation is hidden then we should trigger the proper + // `deleteOrLeave` behaviour (for 'Note to Self' this will leave the conversation + // but remove the associated interactions) + if targetHiddenState { + try SessionThread + .deleteOrLeave( + db, + threadId: userPublicKey, + threadVariant: .contact, + shouldSendLeaveMessageForGroups: false, + calledFromConfigHandling: true + ) + } } // Create a contact for the current user if needed (also force-approve the current user diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift index 7cf96a268..7cd9330af 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift @@ -93,8 +93,13 @@ extension MessageReceiver { .updateAll(db, Interaction.Columns.threadId.set(to: unblindedThread.id)) _ = try SessionThread - .filter(id: blindedIdLookup.blindedId) - .deleteAll(db) + .deleteOrLeave( + db, + threadId: blindedIdLookup.blindedId, + threadVariant: .contact, + shouldSendLeaveMessageForGroups: false, + calledFromConfigHandling: false + ) } // Update the `didApproveMe` state of the sender diff --git a/SessionMessagingKit/Utilities/SSKReachabilityManager.swift b/SessionMessagingKit/Utilities/SSKReachabilityManager.swift index 9f6bea814..a68fa58ba 100644 --- a/SessionMessagingKit/Utilities/SSKReachabilityManager.swift +++ b/SessionMessagingKit/Utilities/SSKReachabilityManager.swift @@ -7,6 +7,8 @@ public enum ReachabilityType: Int { @objc public protocol SSKReachabilityManager { + var reachability: Reachability { get } + var observationContext: AnyObject { get } func setup() From 8c8453d922e307e91e367768e2fbffd6de8a781d Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 14 Apr 2023 12:39:18 +1000 Subject: [PATCH 048/135] Updated to the latest libSession, fixed remaining items Updated to the latest libSession version Updated the 'hidden' logic to be based on a negative 'priority' value Added an index on the Quote table to speed up conversation query Fixed an odd behaviour with GRDB and Combine (simplified the interface as well) Fixed an issue where migrations could fail --- .../Calls/Call Management/SessionCall.swift | 25 ++- Session/Closed Groups/EditClosedGroupVC.swift | 3 +- Session/Closed Groups/NewClosedGroupVC.swift | 3 +- .../ConversationVC+Interaction.swift | 28 ++- Session/Conversations/ConversationVC.swift | 3 +- .../Conversations/ConversationViewModel.swift | 3 +- Session/Home/HomeViewModel.swift | 10 +- .../MediaInfoVC+MediaInfoView.swift | 3 +- Session/Notifications/AppNotifications.swift | 6 +- Session/Onboarding/Onboarding.swift | 2 +- Session/Open Groups/JoinOpenGroupVC.swift | 3 +- .../Open Groups/OpenGroupSuggestionGrid.swift | 3 +- Session/Shared/FullConversationCell.swift | 13 +- .../UIContextualAction+Utilities.swift | 13 +- SessionMessagingKit/Calls/WebRTCSession.swift | 58 +++--- .../Migrations/_013_SessionUtilChanges.swift | 47 ++++- .../_014_GenerateInitialUserConfigDumps.swift | 29 ++- .../Database/Models/Attachment.swift | 10 +- .../File Server/FileServerAPI.swift | 2 +- .../Jobs/Types/AttachmentDownloadJob.swift | 5 +- .../Jobs/Types/AttachmentUploadJob.swift | 62 +++--- .../Jobs/Types/ConfigurationSyncJob.swift | 5 +- .../Jobs/Types/GroupLeavingJob.swift | 2 +- .../Jobs/Types/MessageSendJob.swift | 2 +- .../Jobs/Types/SendReadReceiptsJob.swift | 2 +- .../SessionUtil+Contacts.swift | 66 ++++--- .../SessionUtil+ConvoInfoVolatile.swift | 42 ++-- .../Config Handling/SessionUtil+Shared.swift | 48 +++-- .../SessionUtil+UserGroups.swift | 180 +++++++++++++----- .../SessionUtil+UserProfile.swift | 22 +-- .../LibSessionUtil/SessionUtil.swift | 29 ++- .../LibSessionUtil/SessionUtilError.swift | 1 + .../ios-arm64/libsession-util.a | Bin 2083088 -> 2089392 bytes .../libsession-util.a | Bin 4588424 -> 4601496 bytes .../module.modulemap | 1 + .../session/config/base.h | 5 +- .../session/config/contacts.h | 10 +- .../session/config/contacts.hpp | 25 ++- .../session/config/convo_info_volatile.h | 27 ++- .../session/config/notify.h | 8 + .../session/config/notify.hpp | 12 ++ .../session/config/user_groups.h | 36 ++-- .../session/config/user_groups.hpp | 51 +++-- .../session/config/user_profile.h | 12 +- .../session/config/user_profile.hpp | 17 +- .../Open Groups/OpenGroupManager.swift | 6 +- .../MessageReceiver+ClosedGroups.swift | 6 +- ...essageReceiver+ConfigurationMessages.swift | 2 +- .../MessageSender+ClosedGroups.swift | 2 +- .../MessageSender+Convenience.swift | 17 +- .../Sending & Receiving/MessageSender.swift | 27 ++- .../Notifications/PushNotificationAPI.swift | 3 +- .../Pollers/OpenGroupPoller.swift | 6 +- .../Shared Models/MessageViewModel.swift | 2 +- .../SessionThreadViewModel.swift | 6 +- .../Configs/ConfigContactsSpec.swift | 29 ++- .../Configs/ConfigConvoInfoVolatileSpec.swift | 20 +- .../Configs/ConfigUserGroupsSpec.swift | 51 +++-- .../Configs/ConfigUserProfileSpec.swift | 10 +- ...ibSessionTypeConversionUtilitiesSpec.swift | 4 +- .../Open Groups/OpenGroupAPISpec.swift | 144 +++++++------- .../Open Groups/OpenGroupManagerSpec.swift | 26 +-- SessionShareExtension/ThreadPickerVC.swift | 3 +- SessionSnodeKit/Jobs/GetSnodePoolJob.swift | 5 +- SessionSnodeKit/Networking/SnodeAPI.swift | 7 +- SessionUtilitiesKit/Database/Storage.swift | 61 ++++-- .../OWSViewController.m | 3 +- _SharedTestUtilities/SynchronousStorage.swift | 10 +- 68 files changed, 841 insertions(+), 543 deletions(-) create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/notify.h create mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/notify.hpp diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index e7fc23d77..8d8f206b4 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -216,6 +216,7 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate { let thread: SessionThread = try? SessionThread.fetchOne(db, id: sessionId) else { return } + let webRTCSession: WebRTCSession = self.webRTCSession let timestampMs: Int64 = SnodeAPI.currentOffsetTimestampMs() let message: CallMessage = CallMessage( uuid: self.uuid, @@ -235,26 +236,21 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate { self.callInteractionId = interaction?.id - try? self.webRTCSession + try? webRTCSession .sendPreOffer( db, message: message, interactionId: interaction?.id, in: thread ) - .sinkUntilComplete( - receiveCompletion: { [weak self] result in - switch result { - case .failure: break - case .finished: - Storage.shared.writeAsync { db in - self?.webRTCSession.sendOffer(db, to: sessionId) - } - - self?.setupTimeoutTimer() - } + // Start the timeout timer for the call + .handleEvents(receiveOutput: { [weak self] _ in self?.setupTimeoutTimer() }) + .flatMap { _ in + Storage.shared.writePublisherFlatMap { db -> AnyPublisher in + webRTCSession.sendOffer(db, to: sessionId) } - ) + } + .sinkUntilComplete() } func answerSessionCall() { @@ -435,9 +431,10 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate { let webRTCSession: WebRTCSession = self.webRTCSession Storage.shared - .readPublisherFlatMap(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db in + .readPublisherFlatMap { db in webRTCSession.sendOffer(db, to: sessionId, isRestartingICEConnection: true) } + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .sinkUntilComplete() } diff --git a/Session/Closed Groups/EditClosedGroupVC.swift b/Session/Closed Groups/EditClosedGroupVC.swift index 754585d8b..cfdca2267 100644 --- a/Session/Closed Groups/EditClosedGroupVC.swift +++ b/Session/Closed Groups/EditClosedGroupVC.swift @@ -465,7 +465,7 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat ModalActivityIndicatorViewController.present(fromViewController: navigationController) { _ in Storage.shared - .writePublisherFlatMap(receiveOn: DispatchQueue.main) { db -> AnyPublisher in + .writePublisherFlatMap { db -> AnyPublisher in if !updatedMemberIds.contains(userPublicKey) { try MessageSender.leave( db, @@ -485,6 +485,7 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat name: updatedName ) } + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .receive(on: DispatchQueue.main) .sinkUntilComplete( receiveCompletion: { [weak self] result in diff --git a/Session/Closed Groups/NewClosedGroupVC.swift b/Session/Closed Groups/NewClosedGroupVC.swift index a55a1c16a..f7c943b41 100644 --- a/Session/Closed Groups/NewClosedGroupVC.swift +++ b/Session/Closed Groups/NewClosedGroupVC.swift @@ -333,9 +333,10 @@ final class NewClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegate let message: String? = (selectedContacts.count > 20 ? "GROUP_CREATION_PLEASE_WAIT".localized() : nil) ModalActivityIndicatorViewController.present(fromViewController: navigationController!, message: message) { [weak self] _ in Storage.shared - .writePublisherFlatMap(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db in + .writePublisherFlatMap { db in try MessageSender.createClosedGroup(db, name: name, members: selectedContacts) } + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .receive(on: DispatchQueue.main) .sinkUntilComplete( receiveCompletion: { result in diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 473d002b6..462018a08 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -448,7 +448,7 @@ extension ConversationVC: // Send the message Storage.shared - .writePublisher(receiveOn: DispatchQueue.global(qos: .userInitiated)) { [weak self] db in + .writePublisher { [weak self] db in // Let the viewModel know we are about to send a message self?.viewModel.sentMessageBeforeUpdate = true @@ -517,6 +517,7 @@ extension ConversationVC: threadVariant: threadVariant ) } + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .sinkUntilComplete( receiveCompletion: { [weak self] _ in self?.handleMessageSent() @@ -579,7 +580,7 @@ extension ConversationVC: // Send the message Storage.shared - .writePublisher(receiveOn: DispatchQueue.global(qos: .userInitiated)) { [weak self] db in + .writePublisher { [weak self] db in // Let the viewModel know we are about to send a message self?.viewModel.sentMessageBeforeUpdate = true @@ -625,6 +626,7 @@ extension ConversationVC: threadVariant: threadVariant ) } + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .sinkUntilComplete( receiveCompletion: { [weak self] _ in self?.handleMessageSent() @@ -1222,7 +1224,7 @@ extension ConversationVC: guard cellViewModel.threadVariant == .community else { return } Storage.shared - .readPublisherFlatMap(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db -> AnyPublisher<(OpenGroupAPI.ReactionRemoveAllResponse, OpenGroupAPI.PendingChange), Error> in + .readPublisherFlatMap { db -> AnyPublisher<(OpenGroupAPI.ReactionRemoveAllResponse, OpenGroupAPI.PendingChange), Error> in guard let openGroup: OpenGroup = try? OpenGroup .fetchOne(db, id: cellViewModel.threadId), @@ -1253,6 +1255,7 @@ extension ConversationVC: .map { _, response in (response, pendingChange) } .eraseToAnyPublisher() } + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .handleEvents( receiveOutput: { response, pendingChange in OpenGroupManager @@ -1310,7 +1313,7 @@ extension ConversationVC: // Perform the sending logic Storage.shared - .writePublisherFlatMap(receiveOn: DispatchQueue.global(qos: .userInitiated)) { [weak self] db -> AnyPublisher in + .writePublisherFlatMap { [weak self] db -> AnyPublisher in // Update the thread to be visible (if it isn't already) if self?.viewModel.threadData.threadShouldBeVisible == false { _ = try SessionThread @@ -1467,6 +1470,7 @@ extension ConversationVC: .map { _ in nil } .eraseToAnyPublisher() } + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .flatMap { maybeSendData -> AnyPublisher in guard let sendData: MessageSender.PreparedSendData = maybeSendData else { return Just(()) @@ -1598,7 +1602,7 @@ extension ConversationVC: } Storage.shared - .writePublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .writePublisherFlatMap { db in OpenGroupManager.shared.add( db, roomToken: room, @@ -1607,6 +1611,7 @@ extension ConversationVC: calledFromConfigHandling: false ) } + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .receive(on: DispatchQueue.main) .sinkUntilComplete( receiveCompletion: { result in @@ -1787,6 +1792,7 @@ extension ConversationVC: } } .flatMap { _ in request } + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .receive(on: DispatchQueue.main) .sinkUntilComplete( receiveCompletion: { [weak self] result in @@ -1893,7 +1899,7 @@ extension ConversationVC: // Delete the message from the open group deleteRemotely( from: self, - request: Storage.shared.readPublisherFlatMap(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db in + request: Storage.shared.readPublisherFlatMap { db in OpenGroupAPI.messageDelete( db, id: openGroupServerMessageId, @@ -2091,7 +2097,7 @@ extension ConversationVC: cancelStyle: .alert_text, onConfirm: { [weak self] _ in Storage.shared - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db -> AnyPublisher in + .readPublisherFlatMap { db -> AnyPublisher in guard let openGroup: OpenGroup = try OpenGroup.fetchOne(db, id: threadId) else { throw StorageError.objectNotFound } @@ -2106,6 +2112,7 @@ extension ConversationVC: .map { _ in () } .eraseToAnyPublisher() } + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .receive(on: DispatchQueue.main) .sinkUntilComplete( receiveCompletion: { result in @@ -2147,7 +2154,7 @@ extension ConversationVC: cancelStyle: .alert_text, onConfirm: { [weak self] _ in Storage.shared - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db -> AnyPublisher in + .readPublisherFlatMap { db -> AnyPublisher in guard let openGroup: OpenGroup = try OpenGroup.fetchOne(db, id: threadId) else { throw StorageError.objectNotFound } @@ -2162,6 +2169,7 @@ extension ConversationVC: .map { _ in () } .eraseToAnyPublisher() } + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .receive(on: DispatchQueue.main) .sinkUntilComplete( receiveCompletion: { result in @@ -2407,7 +2415,7 @@ extension ConversationVC { else { return } Storage.shared - .writePublisher(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db in + .writePublisher { db in // If we aren't creating a new thread (ie. sending a message request) then send a // messageRequestResponse back to the sender (this allows the sender to know that // they have been approved and can now use this contact in closed groups) @@ -2435,6 +2443,8 @@ extension ConversationVC { .set(to: contact.didApproveMe || !isNewThread) ) } + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) + .receive(on: DispatchQueue.main) .sinkUntilComplete( receiveCompletion: { _ in // Update the UI diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index c2bfb9a7a..7ee477e53 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -549,7 +549,7 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers if viewModel.threadData.threadIsNoteToSelf == false && viewModel.threadData.threadShouldBeVisible == false && - !SessionUtil.conversationExistsInConfig( + !SessionUtil.conversationVisibleInConfig( threadId: threadId, threadVariant: viewModel.threadData.threadVariant ) @@ -1399,6 +1399,7 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers // after the app goes into background and goes back in foreground. DispatchQueue.main.async { self.snInputView.text = self.snInputView.text + completion?() } } diff --git a/Session/Conversations/ConversationViewModel.swift b/Session/Conversations/ConversationViewModel.swift index dedc99137..a538ad736 100644 --- a/Session/Conversations/ConversationViewModel.swift +++ b/Session/Conversations/ConversationViewModel.swift @@ -121,8 +121,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { .defaulting(to: false) } ), - currentUserIsClosedGroupMember: ((self.initialThreadVariant != .legacyGroup && self.initialThreadVariant != .group) ? - nil : + currentUserIsClosedGroupMember: (![.legacyGroup, .group].contains(self.initialThreadVariant) ? nil : Storage.shared.read { db in GroupMember .filter(GroupMember.Columns.groupId == self.threadId) diff --git a/Session/Home/HomeViewModel.swift b/Session/Home/HomeViewModel.swift index 3666372df..8abcec895 100644 --- a/Session/Home/HomeViewModel.swift +++ b/Session/Home/HomeViewModel.swift @@ -102,8 +102,8 @@ public class HomeViewModel { return SQL(""" JOIN \(Profile.self) ON ( ( -- Contact profile change - \(SQL("\(thread[.variant]) = \(SessionThread.Variant.contact)")) AND - \(profile[.id]) = \(thread[.id]) + \(profile[.id]) = \(thread[.id]) AND + \(SQL("\(thread[.variant]) = \(SessionThread.Variant.contact)")) ) OR ( -- Closed group profile change \(SQL("\(thread[.variant]) IN \(threadVariants)")) AND ( profile.id = ( -- Front profile @@ -111,8 +111,8 @@ public class HomeViewModel { FROM \(GroupMember.self) JOIN \(Profile.self) ON \(profile[.id]) = \(groupMember[.profileId]) WHERE ( - \(SQL("\(groupMember[.role]) = \(targetRole)")) AND \(groupMember[.groupId]) = \(thread[.id]) AND + \(SQL("\(groupMember[.role]) = \(targetRole)")) AND \(groupMember[.profileId]) != \(userPublicKey) ) ) OR @@ -121,8 +121,8 @@ public class HomeViewModel { FROM \(GroupMember.self) JOIN \(Profile.self) ON \(profile[.id]) = \(groupMember[.profileId]) WHERE ( - \(SQL("\(groupMember[.role]) = \(targetRole)")) AND \(groupMember[.groupId]) = \(thread[.id]) AND + \(SQL("\(groupMember[.role]) = \(targetRole)")) AND \(groupMember[.profileId]) != \(userPublicKey) ) ) OR ( -- Fallback profile @@ -132,8 +132,8 @@ public class HomeViewModel { FROM \(GroupMember.self) JOIN \(Profile.self) ON \(profile[.id]) = \(groupMember[.profileId]) WHERE ( - \(SQL("\(groupMember[.role]) = \(targetRole)")) AND \(groupMember[.groupId]) = \(thread[.id]) AND + \(SQL("\(groupMember[.role]) = \(targetRole)")) AND \(groupMember[.profileId]) != \(userPublicKey) ) ) = 1 diff --git a/Session/Media Viewing & Editing/MediaInfoVC+MediaInfoView.swift b/Session/Media Viewing & Editing/MediaInfoVC+MediaInfoView.swift index 25c0f5774..565623603 100644 --- a/Session/Media Viewing & Editing/MediaInfoVC+MediaInfoView.swift +++ b/Session/Media Viewing & Editing/MediaInfoVC+MediaInfoView.swift @@ -170,6 +170,7 @@ extension MediaInfoVC { } // MARK: - Interaction + public func update(attachment: Attachment?) { guard let attachment: Attachment = attachment else { return } @@ -177,7 +178,7 @@ extension MediaInfoVC { fileIdLabel.text = attachment.serverId fileTypeLabel.text = attachment.contentType - fileSizeLabel.text = OWSFormat.formatFileSize(attachment.byteCount) + fileSizeLabel.text = Format.fileSize(attachment.byteCount) resolutionLabel.text = { guard let width = attachment.width, let height = attachment.height else { return "N/A" } return "\(width)×\(height)" diff --git a/Session/Notifications/AppNotifications.swift b/Session/Notifications/AppNotifications.swift index db964ba0e..dfd62083b 100644 --- a/Session/Notifications/AppNotifications.swift +++ b/Session/Notifications/AppNotifications.swift @@ -539,7 +539,7 @@ class NotificationActionHandler { } return Storage.shared - .writePublisher(receiveOn: DispatchQueue.main) { db in + .writePublisher { db in let interaction: Interaction = try Interaction( threadId: threadId, authorId: getUserHexEncodedPublicKey(db), @@ -575,7 +575,9 @@ class NotificationActionHandler { threadVariant: thread.variant ) } + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .flatMap { MessageSender.sendImmediate(preparedSendData: $0) } + .receive(on: DispatchQueue.main) .handleEvents( receiveCompletion: { result in switch result { @@ -612,7 +614,7 @@ class NotificationActionHandler { private func markAsRead(threadId: String) -> AnyPublisher { return Storage.shared - .writePublisher(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db in + .writePublisher { db in guard let threadVariant: SessionThread.Variant = try SessionThread .filter(id: threadId) diff --git a/Session/Onboarding/Onboarding.swift b/Session/Onboarding/Onboarding.swift index d2c6a2ca7..93047b95d 100644 --- a/Session/Onboarding/Onboarding.swift +++ b/Session/Onboarding/Onboarding.swift @@ -94,7 +94,7 @@ enum Onboarding { } } .flatMap { _ -> AnyPublisher in - Storage.shared.readPublisher(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db in + Storage.shared.readPublisher { db in try Profile .filter(id: userPublicKey) .select(.name) diff --git a/Session/Open Groups/JoinOpenGroupVC.swift b/Session/Open Groups/JoinOpenGroupVC.swift index f762e2930..4cc4bf69f 100644 --- a/Session/Open Groups/JoinOpenGroupVC.swift +++ b/Session/Open Groups/JoinOpenGroupVC.swift @@ -169,7 +169,7 @@ final class JoinOpenGroupVC: BaseVC, UIPageViewControllerDataSource, UIPageViewC ModalActivityIndicatorViewController.present(fromViewController: navigationController, canCancel: false) { [weak self] _ in Storage.shared - .writePublisherFlatMap(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db in + .writePublisherFlatMap { db in OpenGroupManager.shared.add( db, roomToken: roomToken, @@ -178,6 +178,7 @@ final class JoinOpenGroupVC: BaseVC, UIPageViewControllerDataSource, UIPageViewC calledFromConfigHandling: false ) } + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .receive(on: DispatchQueue.main) .sinkUntilComplete( receiveCompletion: { result in diff --git a/Session/Open Groups/OpenGroupSuggestionGrid.swift b/Session/Open Groups/OpenGroupSuggestionGrid.swift index 80b38ae0a..128cb6259 100644 --- a/Session/Open Groups/OpenGroupSuggestionGrid.swift +++ b/Session/Open Groups/OpenGroupSuggestionGrid.swift @@ -322,7 +322,7 @@ extension OpenGroupSuggestionGrid { Publishers .MergeMany( Storage.shared - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupManager .roomImage(db, fileId: imageId, for: room.token, on: OpenGroupAPI.defaultServer) } @@ -335,6 +335,7 @@ extension OpenGroupSuggestionGrid { .delay(for: .milliseconds(10), scheduler: DispatchQueue.main) .eraseToAnyPublisher() ) + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .receiveOnMain(immediately: true) .sinkUntilComplete( receiveValue: { [weak self] imageData, hasData in diff --git a/Session/Shared/FullConversationCell.swift b/Session/Shared/FullConversationCell.swift index ac6a4c5dc..ecdf6b34e 100644 --- a/Session/Shared/FullConversationCell.swift +++ b/Session/Shared/FullConversationCell.swift @@ -430,6 +430,13 @@ public final class FullConversationCell: UITableViewCell, SwipeActionOptimisticC typingIndicatorView.startAnimation() } else { + displayNameLabel.themeTextColor = { + guard cellViewModel.interactionVariant != .infoClosedGroupCurrentUserLeaving else { + return .textSecondary + } + + return .textPrimary + }() typingIndicatorView.isHidden = true typingIndicatorView.stopAnimation() @@ -437,8 +444,6 @@ public final class FullConversationCell: UITableViewCell, SwipeActionOptimisticC if cellViewModel.interactionVariant == .infoClosedGroupCurrentUserLeaving { guard let textColor: UIColor = theme.color(for: .textSecondary) else { return } - self?.displayNameLabel.themeTextColor = .textSecondary - snippetLabel?.attributedText = self?.getSnippet( cellViewModel: cellViewModel, textColor: textColor @@ -446,8 +451,6 @@ public final class FullConversationCell: UITableViewCell, SwipeActionOptimisticC } else if cellViewModel.interactionVariant == .infoClosedGroupCurrentUserErrorLeaving { guard let textColor: UIColor = theme.color(for: .danger) else { return } - self?.displayNameLabel.themeTextColor = .textPrimary - snippetLabel?.attributedText = self?.getSnippet( cellViewModel: cellViewModel, textColor: textColor @@ -455,8 +458,6 @@ public final class FullConversationCell: UITableViewCell, SwipeActionOptimisticC } else { guard let textColor: UIColor = theme.color(for: .textPrimary) else { return } - self?.displayNameLabel.themeTextColor = .textPrimary - snippetLabel?.attributedText = self?.getSnippet( cellViewModel: cellViewModel, textColor: textColor diff --git a/Session/Utilities/UIContextualAction+Utilities.swift b/Session/Utilities/UIContextualAction+Utilities.swift index cc1bc4c78..934b595ea 100644 --- a/Session/Utilities/UIContextualAction+Utilities.swift +++ b/Session/Utilities/UIContextualAction+Utilities.swift @@ -286,12 +286,12 @@ public extension UIContextualAction { let threadIsMessageRequest: Bool = (threadViewModel.threadIsMessageRequest == true) let contactChanges: [ConfigColumnAssignment] = [ Contact.Columns.isBlocked.set(to: !threadIsBlocked), - (!threadIsMessageRequest ? nil : Contact.Columns.isApproved.set(to: false)), - // Note: We set this to true so the current user will be able to send a - // message to the person who originally sent them the message request in - // the future if they unblock them - (!threadIsMessageRequest ? nil : Contact.Columns.didApproveMe.set(to: true)) + /// **Note:** We set `didApproveMe` to `true` so the current user will be able to send a + /// message to the person who originally sent them the message request in the future if they + /// unblock them + (!threadIsMessageRequest ? nil : Contact.Columns.didApproveMe.set(to: true)), + (!threadIsMessageRequest ? nil : Contact.Columns.isApproved.set(to: false)) ].compactMap { $0 } let performBlock: (UIViewController?) -> () = { viewController in @@ -305,7 +305,7 @@ public extension UIContextualAction { // Delay the change to give the cell "unswipe" animation some time to complete DispatchQueue.global(qos: .default).asyncAfter(deadline: .now() + unswipeAnimationDelay) { Storage.shared - .writePublisher(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db in + .writePublisher { db in // Create the contact if it doesn't exist try Contact .fetchOrCreate(db, id: threadViewModel.threadId) @@ -325,6 +325,7 @@ public extension UIContextualAction { ) } } + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .sinkUntilComplete() } } diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index 86c8c281d..d6816ae8f 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -141,14 +141,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { interactionId: interactionId ) ) - .handleEvents( - receiveCompletion: { result in - switch result { - case .failure: break - case .finished: SNLog("[Calls] Pre-offer message has been sent.") - } - } - ) + .handleEvents(receiveOutput: { _ in SNLog("[Calls] Pre-offer message has been sent.") }) .eraseToAnyPublisher() } @@ -186,7 +179,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { } Storage.shared - .writePublisher(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db in + .writePublisher { db in try MessageSender .preparedSendData( db, @@ -225,14 +218,12 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { let mediaConstraints: RTCMediaConstraints = mediaConstraints(false) return Storage.shared - .readPublisherFlatMap(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db -> AnyPublisher in + .readPublisher { db -> SessionThread in guard let thread: SessionThread = try? SessionThread.fetchOne(db, id: sessionId) else { throw WebRTCSessionError.noThread } - return Just(thread) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() + return thread } .flatMap { [weak self] thread in Future { resolver in @@ -254,7 +245,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { } Storage.shared - .writePublisher(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db in + .writePublisher { db in try MessageSender .preparedSendData( db, @@ -305,36 +296,33 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { self.queuedICECandidates.removeAll() Storage.shared - .writePublisherFlatMap(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db in + .writePublisher { db in guard let thread: SessionThread = try SessionThread.fetchOne(db, id: contactSessionId) else { throw WebRTCSessionError.noThread } SNLog("[Calls] Batch sending \(candidates.count) ICE candidates.") - return Just( - try MessageSender - .preparedSendData( - db, - message: CallMessage( - uuid: uuid, - kind: .iceCandidates( - sdpMLineIndexes: candidates.map { UInt32($0.sdpMLineIndex) }, - sdpMids: candidates.map { $0.sdpMid! } - ), - sdps: candidates.map { $0.sdp } + return try MessageSender + .preparedSendData( + db, + message: CallMessage( + uuid: uuid, + kind: .iceCandidates( + sdpMLineIndexes: candidates.map { UInt32($0.sdpMLineIndex) }, + sdpMids: candidates.map { $0.sdpMid! } ), - to: try Message.Destination - .from(db, threadId: thread.id, threadVariant: thread.variant), - namespace: try Message.Destination - .from(db, threadId: thread.id, threadVariant: thread.variant) - .defaultNamespace, - interactionId: nil - ) + sdps: candidates.map { $0.sdp } + ), + to: try Message.Destination + .from(db, threadId: thread.id, threadVariant: thread.variant), + namespace: try Message.Destination + .from(db, threadId: thread.id, threadVariant: thread.variant) + .defaultNamespace, + interactionId: nil ) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() } + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .flatMap { MessageSender.sendImmediate(preparedSendData: $0) } .sinkUntilComplete() } diff --git a/SessionMessagingKit/Database/Migrations/_013_SessionUtilChanges.swift b/SessionMessagingKit/Database/Migrations/_013_SessionUtilChanges.swift index 06783e900..b537933ca 100644 --- a/SessionMessagingKit/Database/Migrations/_013_SessionUtilChanges.swift +++ b/SessionMessagingKit/Database/Migrations/_013_SessionUtilChanges.swift @@ -11,7 +11,7 @@ enum _013_SessionUtilChanges: Migration { static let target: TargetMigrations.Identifier = .messagingKit static let identifier: String = "SessionUtilChanges" static let needsConfigSync: Bool = true - static let minExpectedRunDuration: TimeInterval = 0.1 + static let minExpectedRunDuration: TimeInterval = 0.4 static func migrate(_ db: Database) throws { // Add `markedAsUnread` to the thread table @@ -139,6 +139,12 @@ enum _013_SessionUtilChanges: Migration { columns: [.threadId, .threadKeyPairHash] ) + // Add an index for the 'Quote' table to speed up queries + try db.createIndex( + on: Quote.self, + columns: [.timestampMs] + ) + // New table for storing the latest config dump for each type try db.create(table: ConfigDump.self) { t in t.column(.variant, .text) @@ -163,13 +169,46 @@ enum _013_SessionUtilChanges: Migration { // If we don't have an ed25519 key then no need to create cached dump data let userPublicKey: String = getUserHexEncodedPublicKey(db) - // Remove any hidden threads to avoid syncing them (they are basically shadow threads created - // by starting a conversation but not sending a message so can just be cleared out) - try SessionThread + /// Remove any hidden threads to avoid syncing them (they are basically shadow threads created by starting a conversation + /// but not sending a message so can just be cleared out) + /// + /// **Note:** Our settings defer foreign key checks to the end of the migration, unfortunately the `PRAGMA foreign_keys` + /// setting is also a no-on during transactions so we can't enable it for the delete action, as a result we need to manually clean + /// up any data associated with the threads we want to delete, at the time of this migration the following tables should cascade + /// delete when a thread is deleted: + /// - DisappearingMessagesConfiguration + /// - ClosedGroup + /// - GroupMember + /// - Interaction + /// - ThreadTypingIndicator + /// - PendingReadReceipt + let threadIdsToDelete: [String] = try SessionThread .filter( SessionThread.Columns.shouldBeVisible == false && SessionThread.Columns.id != userPublicKey ) + .select(.id) + .asRequest(of: String.self) + .fetchAll(db) + try SessionThread + .deleteAll(db, ids: threadIdsToDelete) + try DisappearingMessagesConfiguration + .filter(threadIdsToDelete.contains(DisappearingMessagesConfiguration.Columns.threadId)) + .deleteAll(db) + try ClosedGroup + .filter(threadIdsToDelete.contains(ClosedGroup.Columns.threadId)) + .deleteAll(db) + try GroupMember + .filter(threadIdsToDelete.contains(GroupMember.Columns.groupId)) + .deleteAll(db) + try Interaction + .filter(threadIdsToDelete.contains(Interaction.Columns.threadId)) + .deleteAll(db) + try ThreadTypingIndicator + .filter(threadIdsToDelete.contains(ThreadTypingIndicator.Columns.threadId)) + .deleteAll(db) + try PendingReadReceipt + .filter(threadIdsToDelete.contains(PendingReadReceipt.Columns.threadId)) .deleteAll(db) /// There was previously a bug which allowed users to fully delete the 'Note to Self' conversation but we don't want that, so diff --git a/SessionMessagingKit/Database/Migrations/_014_GenerateInitialUserConfigDumps.swift b/SessionMessagingKit/Database/Migrations/_014_GenerateInitialUserConfigDumps.swift index 552939684..0478650e8 100644 --- a/SessionMessagingKit/Database/Migrations/_014_GenerateInitialUserConfigDumps.swift +++ b/SessionMessagingKit/Database/Migrations/_014_GenerateInitialUserConfigDumps.swift @@ -12,7 +12,7 @@ enum _014_GenerateInitialUserConfigDumps: Migration { static let target: TargetMigrations.Identifier = .messagingKit static let identifier: String = "GenerateInitialUserConfigDumps" static let needsConfigSync: Bool = true - static let minExpectedRunDuration: TimeInterval = 0.1 // TODO: Need to test this + static let minExpectedRunDuration: TimeInterval = 4.0 static func migrate(_ db: Database) throws { // If we have no ed25519 key then there is no need to create cached dump data @@ -40,8 +40,11 @@ enum _014_GenerateInitialUserConfigDumps: Migration { ) try SessionUtil.updateNoteToSelf( - hidden: (allThreads[userPublicKey]?.shouldBeVisible == true), - priority: Int32(allThreads[userPublicKey]?.pinnedPriority ?? 0), + priority: { + guard allThreads[userPublicKey]?.shouldBeVisible == true else { return SessionUtil.hiddenPriority } + + return Int32(allThreads[userPublicKey]?.pinnedPriority ?? 0) + }(), in: conf ) @@ -78,16 +81,32 @@ enum _014_GenerateInitialUserConfigDumps: Migration { .including(optional: Contact.profile) .asRequest(of: ContactInfo.self) .fetchAll(db) + let threadIdsNeedingContacts: [String] = validContactIds + .filter { contactId in !contactsData.contains(where: { $0.contact.id == contactId }) } try SessionUtil.upsert( contactData: contactsData + .appending( + contentsOf: threadIdsNeedingContacts + .map { contactId in + ContactInfo( + contact: Contact.fetchOrCreate(db, id: contactId), + profile: nil + ) + } + ) .map { data in SessionUtil.SyncedContactInfo( id: data.contact.id, contact: data.contact, profile: data.profile, - hidden: (allThreads[data.contact.id]?.shouldBeVisible == true), - priority: Int32(allThreads[data.contact.id]?.pinnedPriority ?? 0) + priority: { + guard allThreads[data.contact.id]?.shouldBeVisible == true else { + return SessionUtil.hiddenPriority + } + + return Int32(allThreads[data.contact.id]?.pinnedPriority ?? 0) + }() ) }, in: conf diff --git a/SessionMessagingKit/Database/Models/Attachment.swift b/SessionMessagingKit/Database/Models/Attachment.swift index b758b65c3..aed812c51 100644 --- a/SessionMessagingKit/Database/Models/Attachment.swift +++ b/SessionMessagingKit/Database/Models/Attachment.swift @@ -1022,10 +1022,7 @@ extension Attachment { } } - internal func upload( - to destination: Attachment.Destination, - queue: DispatchQueue - ) -> AnyPublisher { + internal func upload(to destination: Attachment.Destination) -> AnyPublisher { // This can occur if an AttachmnetUploadJob was explicitly created for a message // dependant on the attachment being uploaded (in this case the attachment has // already been uploaded so just succeed) @@ -1045,7 +1042,7 @@ extension Attachment { let attachmentId: String = self.id return Storage.shared - .writePublisherFlatMap(receiveOn: queue) { db -> AnyPublisher<(String?, Data?, Data?), Error> in + .writePublisherFlatMap { db -> AnyPublisher<(String?, Data?, Data?), Error> in // If the attachment is a downloaded attachment, check if it came from // the server and if so just succeed immediately (no use re-uploading // an attachment that is already present on the server) - or if we want @@ -1136,14 +1133,13 @@ extension Attachment { .eraseToAnyPublisher() } } - .receive(on: queue) .flatMap { fileId, encryptionKey, digest -> AnyPublisher in /// Save the final upload info /// /// **Note:** We **MUST** use the `.with` function here to ensure the `isValid` flag is /// updated correctly Storage.shared - .writePublisher(receiveOn: queue) { db in + .writePublisher { db in try self .with( serverId: fileId, diff --git a/SessionMessagingKit/File Server/FileServerAPI.swift b/SessionMessagingKit/File Server/FileServerAPI.swift index 21a5b04cc..cc2bc3636 100644 --- a/SessionMessagingKit/File Server/FileServerAPI.swift +++ b/SessionMessagingKit/File Server/FileServerAPI.swift @@ -59,7 +59,7 @@ public enum FileServerAPI { ] ) - return send(request, serverPublicKey: serverPublicKey, timeout: HTTP.timeout) + return send(request, serverPublicKey: serverPublicKey, timeout: HTTP.defaultTimeout) .decoded(as: VersionResponse.self) .map { response in response.version } .eraseToAnyPublisher() diff --git a/SessionMessagingKit/Jobs/Types/AttachmentDownloadJob.swift b/SessionMessagingKit/Jobs/Types/AttachmentDownloadJob.swift index 1738afbd4..e529b5453 100644 --- a/SessionMessagingKit/Jobs/Types/AttachmentDownloadJob.swift +++ b/SessionMessagingKit/Jobs/Types/AttachmentDownloadJob.swift @@ -94,7 +94,7 @@ public enum AttachmentDownloadJob: JobExecutor { else { throw AttachmentDownloadError.invalidUrl } return Storage.shared - .readPublisher(receiveOn: queue) { db in try OpenGroup.fetchOne(db, id: threadId) } + .readPublisher { db in try OpenGroup.fetchOne(db, id: threadId) } .flatMap { maybeOpenGroup -> AnyPublisher in guard let openGroup: OpenGroup = maybeOpenGroup else { return FileServerAPI @@ -106,7 +106,7 @@ public enum AttachmentDownloadJob: JobExecutor { } return Storage.shared - .readPublisherFlatMap(receiveOn: queue) { db in + .readPublisherFlatMap { db in OpenGroupAPI .downloadFile( db, @@ -120,6 +120,7 @@ public enum AttachmentDownloadJob: JobExecutor { } .eraseToAnyPublisher() } + .subscribe(on: queue) .receive(on: queue) .tryMap { data -> Void in // Store the encrypted data temporarily diff --git a/SessionMessagingKit/Jobs/Types/AttachmentUploadJob.swift b/SessionMessagingKit/Jobs/Types/AttachmentUploadJob.swift index ac9b1ee25..47981542d 100644 --- a/SessionMessagingKit/Jobs/Types/AttachmentUploadJob.swift +++ b/SessionMessagingKit/Jobs/Types/AttachmentUploadJob.swift @@ -70,40 +70,40 @@ public enum AttachmentUploadJob: JobExecutor { // Note: In the AttachmentUploadJob we intentionally don't provide our own db instance to prevent // reentrancy issues when the success/failure closures get called before the upload as the JobRunner // will attempt to update the state of the job immediately - attachment.upload( - to: (openGroup.map { .openGroup($0) } ?? .fileServer), - queue: queue - ) - .sinkUntilComplete( - receiveCompletion: { result in - switch result { - case .failure(let error): - // If this upload is related to sending a message then trigger the - // 'handleFailedMessageSend' logic as we want to ensure the message - // has the correct delivery status - Storage.shared.read { db in - guard - let sendJob: Job = try Job.fetchOne(db, id: details.messageSendJobId), - let sendJobDetails: Data = sendJob.details, - let details: MessageSendJob.Details = try? JSONDecoder() - .decode(MessageSendJob.Details.self, from: sendJobDetails) - else { return } + attachment + .upload(to: (openGroup.map { .openGroup($0) } ?? .fileServer)) + .subscribe(on: queue) + .receive(on: queue) + .sinkUntilComplete( + receiveCompletion: { result in + switch result { + case .failure(let error): + // If this upload is related to sending a message then trigger the + // 'handleFailedMessageSend' logic as we want to ensure the message + // has the correct delivery status + Storage.shared.read { db in + guard + let sendJob: Job = try Job.fetchOne(db, id: details.messageSendJobId), + let sendJobDetails: Data = sendJob.details, + let details: MessageSendJob.Details = try? JSONDecoder() + .decode(MessageSendJob.Details.self, from: sendJobDetails) + else { return } + + MessageSender.handleFailedMessageSend( + db, + message: details.message, + with: .other(error), + interactionId: interactionId, + isSyncMessage: details.isSyncMessage + ) + } - MessageSender.handleFailedMessageSend( - db, - message: details.message, - with: .other(error), - interactionId: interactionId, - isSyncMessage: details.isSyncMessage - ) - } + failure(job, error, false) - failure(job, error, false) - - case .finished: success(job, false) + case .finished: success(job, false) + } } - } - ) + ) } } diff --git a/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift b/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift index 277e85294..40e36ec74 100644 --- a/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift +++ b/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift @@ -64,7 +64,7 @@ public enum ConfigurationSyncJob: JobExecutor { .asSet() Storage.shared - .readPublisher(receiveOn: queue) { db in + .readPublisher { db in try pendingConfigChanges.map { change -> MessageSender.PreparedSendData in try MessageSender.preparedSendData( db, @@ -89,6 +89,7 @@ public enum ConfigurationSyncJob: JobExecutor { allObsoleteHashes: Array(allObsoleteHashes) ) } + .subscribe(on: queue) .receive(on: queue) .map { (response: HTTP.BatchResponse) -> [ConfigDump] in /// The number of responses returned might not match the number of changes sent but they will be returned @@ -233,7 +234,7 @@ public extension ConfigurationSyncJob { // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent guard Features.useSharedUtilForUserConfig else { return Storage.shared - .writePublisher(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db -> MessageSender.PreparedSendData in + .writePublisher { db -> MessageSender.PreparedSendData in // If we don't have a userKeyPair yet then there is no need to sync the configuration // as the user doesn't exist yet (this will get triggered on the first launch of a // fresh install due to the migrations getting run) diff --git a/SessionMessagingKit/Jobs/Types/GroupLeavingJob.swift b/SessionMessagingKit/Jobs/Types/GroupLeavingJob.swift index 51d6d741d..af9918392 100644 --- a/SessionMessagingKit/Jobs/Types/GroupLeavingJob.swift +++ b/SessionMessagingKit/Jobs/Types/GroupLeavingJob.swift @@ -32,7 +32,7 @@ public enum GroupLeavingJob: JobExecutor { let destination: Message.Destination = .closedGroup(groupPublicKey: threadId) Storage.shared - .writePublisher(receiveOn: queue) { db in + .writePublisher { db in guard (try? SessionThread.exists(db, id: threadId)) == true else { SNLog("Can't update nonexistent closed group.") throw MessageSenderError.noThread diff --git a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift index ce550d019..8a9e3e218 100644 --- a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift +++ b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift @@ -163,7 +163,7 @@ public enum MessageSendJob: JobExecutor { /// **Note:** No need to upload attachments as part of this process as the above logic splits that out into it's own job /// so we shouldn't get here until attachments have already been uploaded Storage.shared - .writePublisher(receiveOn: queue) { db in + .writePublisher { db in try MessageSender.preparedSendData( db, message: details.message, diff --git a/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift b/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift index 016dbf578..3ac03bdc3 100644 --- a/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift +++ b/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift @@ -36,7 +36,7 @@ public enum SendReadReceiptsJob: JobExecutor { } Storage.shared - .writePublisher(receiveOn: queue) { db in + .writePublisher { db in try MessageSender.preparedSendData( db, message: ReadReceipt( diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift index 720e21cae..283afe901 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift @@ -37,8 +37,8 @@ internal extension SessionUtil { String: ( contact: Contact, profile: Profile, - shouldBeVisible: Bool, - priority: Int32 + priority: Int32, + created: TimeInterval ) ] @@ -77,8 +77,8 @@ internal extension SessionUtil { contactData[contactId] = ( contactResult, profileResult, - (contact.hidden == false), - contact.priority + contact.priority, + TimeInterval(contact.created) ) contacts_iterator_advance(contactIterator) } @@ -165,8 +165,9 @@ internal extension SessionUtil { .asRequest(of: PriorityVisibilityInfo.self) .fetchOne(db) let threadExists: Bool = (threadInfo != nil) - - switch (data.shouldBeVisible, threadExists) { + let updatedShouldBeVisible: Bool = SessionUtil.shouldBeVisible(priority: data.priority) + + switch (updatedShouldBeVisible, threadExists) { case (false, true): SessionUtil.kickFromConversationUIIfNeeded(removedThreadIds: [contact.id]) @@ -183,14 +184,15 @@ internal extension SessionUtil { try SessionThread( id: contact.id, variant: .contact, + creationDateTimestamp: data.created, shouldBeVisible: true, pinnedPriority: data.priority ).save(db) case (true, true): let changes: [ConfigColumnAssignment] = [ - (threadInfo?.shouldBeVisible == data.shouldBeVisible ? nil : - SessionThread.Columns.shouldBeVisible.set(to: data.shouldBeVisible) + (threadInfo?.shouldBeVisible == updatedShouldBeVisible ? nil : + SessionThread.Columns.shouldBeVisible.set(to: updatedShouldBeVisible) ), (threadInfo?.pinnedPriority == data.priority ? nil : SessionThread.Columns.pinnedPriority.set(to: data.priority) @@ -208,7 +210,7 @@ internal extension SessionUtil { } } - // Delete any contact records which have been removed + // Delete any contact/thread records which aren't in the config message let syncedContactIds: [String] = targetContactData .map { $0.key } .appending(userPublicKey) @@ -217,17 +219,25 @@ internal extension SessionUtil { .select(.id) .asRequest(of: String.self) .fetchAll(db) + let threadIdsToRemove: [String] = try SessionThread + .filter(!syncedContactIds.contains(SessionThread.Columns.id)) + .filter(SessionThread.Columns.variant == SessionThread.Variant.contact) + .filter(!SessionThread.Columns.id.like("\(SessionId.Prefix.blinded.rawValue)%")) + .select(.id) + .asRequest(of: String.self) + .fetchAll(db) + let combinedIds: [String] = contactIdsToRemove.appending(contentsOf: threadIdsToRemove) - if !contactIdsToRemove.isEmpty { - SessionUtil.kickFromConversationUIIfNeeded(removedThreadIds: contactIdsToRemove) + if !combinedIds.isEmpty { + SessionUtil.kickFromConversationUIIfNeeded(removedThreadIds: combinedIds) try Contact - .filter(ids: contactIdsToRemove) + .filter(ids: combinedIds) .deleteAll(db) // Also need to remove any 'nickname' values since they are associated to contact data try Profile - .filter(ids: contactIdsToRemove) + .filter(ids: combinedIds) .updateAll( db, Profile.Columns.nickname.set(to: nil) @@ -237,13 +247,13 @@ internal extension SessionUtil { try SessionThread .deleteOrLeave( db, - threadIds: contactIdsToRemove, + threadIds: combinedIds, threadVariant: .contact, groupLeaveType: .forced, calledFromConfigHandling: true ) - try SessionUtil.remove(db, volatileContactIds: contactIdsToRemove) + try SessionUtil.remove(db, volatileContactIds: combinedIds) } } @@ -268,13 +278,15 @@ internal extension SessionUtil { guard !targetContacts.isEmpty else { return } // Update the name - targetContacts + try targetContacts .forEach { info in - var sessionId: [CChar] = info.id.cArray + var sessionId: [CChar] = info.id.cArray.nullTerminated() var contact: contacts_contact = contacts_contact() guard contacts_get_or_construct(conf, &contact, &sessionId) else { - SNLog("Unable to upsert contact from Config Message") - return + /// It looks like there are some situations where this object might not get created correctly (and + /// will throw due to the implicit unwrapping) as a result we put it in a guard and throw instead + SNLog("Unable to upsert contact to SessionUtil: \(SessionUtil.lastError(conf))") + throw SessionUtilError.getOrConstructFailedUnexpectedly } // Assign all properties to match the updated contact (if there is one) @@ -304,7 +316,10 @@ internal extension SessionUtil { // Download the profile picture if needed (this can be triggered within // database reads/writes so dispatch the download to a separate queue to // prevent blocking) - if oldAvatarUrl != updatedProfile.profilePictureUrl || oldAvatarKey != updatedProfile.profileEncryptionKey { + if + oldAvatarUrl != (updatedProfile.profilePictureUrl ?? "") || + oldAvatarKey != (updatedProfile.profileEncryptionKey ?? Data(repeating: 0, count: ProfileManager.avatarAES256KeyByteLength)) + { DispatchQueue.global(qos: .background).async { ProfileManager.downloadAvatar(for: updatedProfile) } @@ -315,7 +330,6 @@ internal extension SessionUtil { } // Store the updated contact (can't be sure if we made any changes above) - contact.hidden = (info.hidden ?? contact.hidden) contact.priority = (info.priority ?? contact.priority) contacts_set(conf, &contact) } @@ -350,7 +364,7 @@ internal extension SessionUtil { // contacts are new/invalid, and if so, fetch any profile data we have for them let newContactIds: [String] = targetContacts .compactMap { contactData -> String? in - var cContactId: [CChar] = contactData.id.cArray + var cContactId: [CChar] = contactData.id.cArray.nullTerminated() var contact: contacts_contact = contacts_contact() guard @@ -454,8 +468,7 @@ public extension SessionUtil { .map { SyncedContactInfo( id: $0, - hidden: true, - priority: 0 + priority: SessionUtil.hiddenPriority ) }, in: conf @@ -472,7 +485,7 @@ public extension SessionUtil { publicKey: getUserHexEncodedPublicKey(db) ) { conf in contactIds.forEach { sessionId in - var cSessionId: [CChar] = sessionId.cArray + var cSessionId: [CChar] = sessionId.cArray.nullTerminated() // Don't care if the contact doesn't exist contacts_erase(conf, &cSessionId) @@ -488,20 +501,17 @@ extension SessionUtil { let id: String let contact: Contact? let profile: Profile? - let hidden: Bool? let priority: Int32? init( id: String, contact: Contact? = nil, profile: Profile? = nil, - hidden: Bool? = nil, priority: Int32? = nil ) { self.id = id self.contact = contact self.profile = profile - self.hidden = hidden self.priority = priority } } diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift index 41f0497af..1a834a168 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift @@ -184,16 +184,18 @@ internal extension SessionUtil { } } - validChanges.forEach { threadInfo in - var cThreadId: [CChar] = threadInfo.threadId.cArray + try validChanges.forEach { threadInfo in + var cThreadId: [CChar] = threadInfo.threadId.cArray.nullTerminated() switch threadInfo.variant { case .contact: var oneToOne: convo_info_volatile_1to1 = convo_info_volatile_1to1() guard convo_info_volatile_get_or_construct_1to1(conf, &oneToOne, &cThreadId) else { - SNLog("Unable to create contact conversation when updating last read timestamp") - return + /// It looks like there are some situations where this object might not get created correctly (and + /// will throw due to the implicit unwrapping) as a result we put it in a guard and throw instead + SNLog("Unable to upsert contact volatile info to SessionUtil: \(SessionUtil.lastError(conf))") + throw SessionUtilError.getOrConstructFailedUnexpectedly } threadInfo.changes.forEach { change in @@ -211,8 +213,10 @@ internal extension SessionUtil { var legacyGroup: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() guard convo_info_volatile_get_or_construct_legacy_group(conf, &legacyGroup, &cThreadId) else { - SNLog("Unable to create legacy group conversation when updating last read timestamp") - return + /// It looks like there are some situations where this object might not get created correctly (and + /// will throw due to the implicit unwrapping) as a result we put it in a guard and throw instead + SNLog("Unable to upsert legacy group volatile info to SessionUtil: \(SessionUtil.lastError(conf))") + throw SessionUtilError.getOrConstructFailedUnexpectedly } threadInfo.changes.forEach { change in @@ -228,8 +232,8 @@ internal extension SessionUtil { case .community: guard - var cBaseUrl: [CChar] = threadInfo.openGroupUrlInfo?.server.cArray, - var cRoomToken: [CChar] = threadInfo.openGroupUrlInfo?.roomToken.cArray, + var cBaseUrl: [CChar] = threadInfo.openGroupUrlInfo?.server.cArray.nullTerminated(), + var cRoomToken: [CChar] = threadInfo.openGroupUrlInfo?.roomToken.cArray.nullTerminated(), var cPubkey: [UInt8] = threadInfo.openGroupUrlInfo?.publicKey.bytes else { SNLog("Unable to create community conversation when updating last read timestamp due to missing URL info") @@ -239,8 +243,10 @@ internal extension SessionUtil { var community: convo_info_volatile_community = convo_info_volatile_community() guard convo_info_volatile_get_or_construct_community(conf, &community, &cBaseUrl, &cRoomToken, &cPubkey) else { - SNLog("Unable to create legacy group conversation when updating last read timestamp") - return + /// It looks like there are some situations where this object might not get created correctly (and + /// will throw due to the implicit unwrapping) as a result we put it in a guard and throw instead + SNLog("Unable to upsert community volatile info to SessionUtil: \(SessionUtil.lastError(conf))") + throw SessionUtilError.getOrConstructFailedUnexpectedly } threadInfo.changes.forEach { change in @@ -296,7 +302,7 @@ internal extension SessionUtil { publicKey: getUserHexEncodedPublicKey(db) ) { conf in volatileContactIds.forEach { contactId in - var cSessionId: [CChar] = contactId.cArray + var cSessionId: [CChar] = contactId.cArray.nullTerminated() // Don't care if the data doesn't exist convo_info_volatile_erase_1to1(conf, &cSessionId) @@ -311,7 +317,7 @@ internal extension SessionUtil { publicKey: getUserHexEncodedPublicKey(db) ) { conf in volatileLegacyGroupIds.forEach { legacyGroupId in - var cLegacyGroupId: [CChar] = legacyGroupId.cArray + var cLegacyGroupId: [CChar] = legacyGroupId.cArray.nullTerminated() // Don't care if the data doesn't exist convo_info_volatile_erase_legacy_group(conf, &cLegacyGroupId) @@ -326,8 +332,8 @@ internal extension SessionUtil { publicKey: getUserHexEncodedPublicKey(db) ) { conf in volatileCommunityInfo.forEach { urlInfo in - var cBaseUrl: [CChar] = urlInfo.server.cArray - var cRoom: [CChar] = urlInfo.roomToken.cArray + var cBaseUrl: [CChar] = urlInfo.server.cArray.nullTerminated() + var cRoom: [CChar] = urlInfo.roomToken.cArray.nullTerminated() // Don't care if the data doesn't exist convo_info_volatile_erase_community(conf, &cBaseUrl, &cRoom) @@ -382,7 +388,7 @@ public extension SessionUtil { .map { conf in switch threadVariant { case .contact: - var cThreadId: [CChar] = threadId.cArray + var cThreadId: [CChar] = threadId.cArray.nullTerminated() var oneToOne: convo_info_volatile_1to1 = convo_info_volatile_1to1() guard convo_info_volatile_get_1to1(conf, &oneToOne, &cThreadId) else { return false @@ -391,7 +397,7 @@ public extension SessionUtil { return (oneToOne.last_read > timestampMs) case .legacyGroup: - var cThreadId: [CChar] = threadId.cArray + var cThreadId: [CChar] = threadId.cArray.nullTerminated() var legacyGroup: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() guard convo_info_volatile_get_legacy_group(conf, &legacyGroup, &cThreadId) else { @@ -403,8 +409,8 @@ public extension SessionUtil { case .community: guard let openGroup: OpenGroup = openGroup else { return false } - var cBaseUrl: [CChar] = openGroup.server.cArray - var cRoomToken: [CChar] = openGroup.roomToken.cArray + var cBaseUrl: [CChar] = openGroup.server.cArray.nullTerminated() + var cRoomToken: [CChar] = openGroup.roomToken.cArray.nullTerminated() var convoCommunity: convo_info_volatile_community = convo_info_volatile_community() guard convo_info_volatile_get_community(conf, &convoCommunity, &cBaseUrl, &cRoomToken) else { diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Shared.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Shared.swift index c6775b7d1..d2d03a8e3 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Shared.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Shared.swift @@ -28,6 +28,13 @@ internal extension SessionUtil { return !allColumnsThatTriggerConfigUpdate.isDisjoint(with: targetColumns) } + /// A negative `priority` value indicates hidden + static let hiddenPriority: Int32 = -1 + + static func shouldBeVisible(priority: Int32) -> Bool { + return (priority >= 0) + } + static func performAndPushChange( _ db: Database, for variant: ConfigDump.Variant, @@ -37,6 +44,10 @@ internal extension SessionUtil { // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent guard Features.useSharedUtilForUserConfig else { return } + // If we haven't completed the required migrations then do nothing (assume that + // this is called from a migration change and we won't miss a change) + guard SessionUtil.requiredMigrationsCompleted(db) else { return } + // Since we are doing direct memory manipulation we are using an `Atomic` // type which has blocking access in it's `mutate` closure let needsPush: Bool @@ -109,10 +120,13 @@ internal extension SessionUtil { publicKey: userPublicKey ) { conf in try SessionUtil.updateNoteToSelf( - hidden: !noteToSelf.shouldBeVisible, - priority: noteToSelf.pinnedPriority - .map { Int32($0 == 0 ? 0 : max($0, 1)) } - .defaulting(to: 0), + priority: { + guard noteToSelf.shouldBeVisible else { return SessionUtil.hiddenPriority } + + return noteToSelf.pinnedPriority + .map { Int32($0 == 0 ? 0 : max($0, 1)) } + .defaulting(to: 0) + }(), in: conf ) } @@ -133,10 +147,13 @@ internal extension SessionUtil { .map { thread in SyncedContactInfo( id: thread.id, - hidden: !thread.shouldBeVisible, - priority: thread.pinnedPriority - .map { Int32($0 == 0 ? 0 : max($0, 1)) } - .defaulting(to: 0) + priority: { + guard thread.shouldBeVisible else { return SessionUtil.hiddenPriority } + + return thread.pinnedPriority + .map { Int32($0 == 0 ? 0 : max($0, 1)) } + .defaulting(to: 0) + }() ) }, in: conf @@ -285,7 +302,7 @@ internal extension SessionUtil { // MARK: - External Outgoing Changes public extension SessionUtil { - static func conversationExistsInConfig( + static func conversationVisibleInConfig( threadId: String, threadVariant: SessionThread.Variant ) -> Bool { @@ -303,7 +320,7 @@ public extension SessionUtil { .config(for: configVariant, publicKey: getUserHexEncodedPublicKey()) .wrappedValue .map { conf in - var cThreadId: [CChar] = threadId.cArray + var cThreadId: [CChar] = threadId.cArray.nullTerminated() switch threadVariant { case .contact: @@ -311,7 +328,10 @@ public extension SessionUtil { guard contacts_get(conf, &contact, &cThreadId) else { return false } - return !contact.hidden + /// If the user opens a conversation with an existing contact but doesn't send them a message + /// then the one-to-one conversation should remain hidden so we want to delete the `SessionThread` + /// when leaving the conversation + return SessionUtil.shouldBeVisible(priority: contact.priority) case .community: let maybeUrlInfo: OpenGroupUrlInfo? = Storage.shared @@ -320,15 +340,17 @@ public extension SessionUtil { guard let urlInfo: OpenGroupUrlInfo = maybeUrlInfo else { return false } - var cBaseUrl: [CChar] = urlInfo.server.cArray - var cRoom: [CChar] = urlInfo.roomToken.cArray + var cBaseUrl: [CChar] = urlInfo.server.cArray.nullTerminated() + var cRoom: [CChar] = urlInfo.roomToken.cArray.nullTerminated() var community: ugroups_community_info = ugroups_community_info() + /// Not handling the `hidden` behaviour for communities so just indicate the existence return user_groups_get_community(conf, &community, &cBaseUrl, &cRoom) case .legacyGroup: let groupInfo: UnsafeMutablePointer? = user_groups_get_legacy_group(conf, &cThreadId) + /// Not handling the `hidden` behaviour for legacy groups so just indicate the existence if groupInfo != nil { ugroups_legacy_group_free(groupInfo) return true diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift index e2b244763..edeb4a472 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift @@ -106,7 +106,8 @@ internal extension SessionUtil { isHidden: false ) }, - priority: legacyGroup.priority + priority: legacyGroup.priority, + joinedAt: legacyGroup.joined_at ) ) } @@ -201,7 +202,8 @@ internal extension SessionUtil { let name: String = group.name, let lastKeyPair: ClosedGroupKeyPair = group.lastKeyPair, let members: [GroupMember] = group.groupMembers, - let updatedAdmins: Set = group.groupAdmins?.asSet() + let updatedAdmins: Set = group.groupAdmins?.asSet(), + let joinedAt: Int64 = group.joinedAt else { return } if !existingLegacyGroupIds.contains(group.id) { @@ -220,18 +222,28 @@ internal extension SessionUtil { .map { $0.profileId }, admins: updatedAdmins.map { $0.profileId }, expirationTimer: UInt32(group.disappearingConfig?.durationSeconds ?? 0), - messageSentTimestamp: UInt64(latestConfigUpdateSentTimestamp * 1000), + formationTimestampMs: UInt64((group.joinedAt ?? Int64(latestConfigUpdateSentTimestamp)) * 1000), calledFromConfigHandling: true ) } else { // Otherwise update the existing group - if existingLegacyGroups[group.id]?.name != name { + let groupChanges: [ConfigColumnAssignment] = [ + (existingLegacyGroups[group.id]?.name == name ? nil : + ClosedGroup.Columns.name.set(to: name) + ), + (existingLegacyGroups[group.id]?.formationTimestamp == TimeInterval(joinedAt) ? nil : + ClosedGroup.Columns.formationTimestamp.set(to: TimeInterval(joinedAt)) + ) + ].compactMap { $0 } + + // Apply any group changes + if !groupChanges.isEmpty { _ = try? ClosedGroup .filter(id: group.id) .updateAll( // Handling a config update so don't use `updateAllAndConfig` db, - ClosedGroup.Columns.name.set(to: name) + groupChanges ) } @@ -371,10 +383,15 @@ internal extension SessionUtil { guard conf != nil else { throw SessionUtilError.nilConfigObject } guard !legacyGroups.isEmpty else { return } - legacyGroups + try legacyGroups .forEach { legacyGroup in - var cGroupId: [CChar] = legacyGroup.id.cArray - let userGroup: UnsafeMutablePointer = user_groups_get_or_construct_legacy_group(conf, &cGroupId) + var cGroupId: [CChar] = legacyGroup.id.cArray.nullTerminated() + guard let userGroup: UnsafeMutablePointer = user_groups_get_or_construct_legacy_group(conf, &cGroupId) else { + /// It looks like there are some situations where this object might not get created correctly (and + /// will throw due to the implicit unwrapping) as a result we put it in a guard and throw instead + SNLog("Unable to upsert legacy group conversation to SessionUtil: \(SessionUtil.lastError(conf))") + throw SessionUtilError.getOrConstructFailedUnexpectedly + } // Assign all properties to match the updated group (if there is one) if let updatedName: String = legacyGroup.name { @@ -424,12 +441,12 @@ internal extension SessionUtil { let membersIdsToRemove: Set = existingMemberIds.subtracting(memberIds) membersIdsToAdd.forEach { memberId in - var cProfileId: [CChar] = memberId.cArray + var cProfileId: [CChar] = memberId.cArray.nullTerminated() ugroups_legacy_member_add(userGroup, &cProfileId, false) } membersIdsToRemove.forEach { memberId in - var cProfileId: [CChar] = memberId.cArray + var cProfileId: [CChar] = memberId.cArray.nullTerminated() ugroups_legacy_member_remove(userGroup, &cProfileId) } } @@ -444,16 +461,20 @@ internal extension SessionUtil { let adminIdsToRemove: Set = existingAdminIds.subtracting(adminIds) adminIdsToAdd.forEach { adminId in - var cProfileId: [CChar] = adminId.cArray + var cProfileId: [CChar] = adminId.cArray.nullTerminated() ugroups_legacy_member_add(userGroup, &cProfileId, true) } adminIdsToRemove.forEach { adminId in - var cProfileId: [CChar] = adminId.cArray + var cProfileId: [CChar] = adminId.cArray.nullTerminated() ugroups_legacy_member_remove(userGroup, &cProfileId) } } + if let joinedAt: Int64 = legacyGroup.joinedAt { + userGroup.pointee.joined_at = joinedAt + } + // Store the updated group (can't be sure if we made any changes above) userGroup.pointee.priority = (legacyGroup.priority ?? userGroup.pointee.priority) @@ -469,16 +490,18 @@ internal extension SessionUtil { guard conf != nil else { throw SessionUtilError.nilConfigObject } guard !communities.isEmpty else { return } - communities + try communities .forEach { community in - var cBaseUrl: [CChar] = community.urlInfo.server.cArray - var cRoom: [CChar] = community.urlInfo.roomToken.cArray + var cBaseUrl: [CChar] = community.urlInfo.server.cArray.nullTerminated() + var cRoom: [CChar] = community.urlInfo.roomToken.cArray.nullTerminated() var cPubkey: [UInt8] = Data(hex: community.urlInfo.publicKey).cArray var userCommunity: ugroups_community_info = ugroups_community_info() guard user_groups_get_or_construct_community(conf, &userCommunity, &cBaseUrl, &cRoom, &cPubkey) else { - SNLog("Unable to upsert community conversation to Config Message") - return + /// It looks like there are some situations where this object might not get created correctly (and + /// will throw due to the implicit unwrapping) as a result we put it in a guard and throw instead + SNLog("Unable to upsert community conversation to SessionUtil: \(SessionUtil.lastError(conf))") + throw SessionUtilError.getOrConstructFailedUnexpectedly } userCommunity.priority = (community.priority ?? userCommunity.priority) @@ -526,8 +549,8 @@ public extension SessionUtil { for: .userGroups, publicKey: getUserHexEncodedPublicKey(db) ) { conf in - var cBaseUrl: [CChar] = server.cArray - var cRoom: [CChar] = roomToken.cArray + var cBaseUrl: [CChar] = server.cArray.nullTerminated() + var cRoom: [CChar] = roomToken.cArray.nullTerminated() // Don't care if the community doesn't exist user_groups_erase_community(conf, &cBaseUrl, &cRoom) @@ -567,7 +590,7 @@ public extension SessionUtil { ) { conf in guard conf != nil else { throw SessionUtilError.nilConfigObject } - var cGroupId: [CChar] = groupPublicKey.cArray + var cGroupId: [CChar] = groupPublicKey.cArray.nullTerminated() let userGroup: UnsafeMutablePointer? = user_groups_get_legacy_group(conf, &cGroupId) // Need to make sure the group doesn't already exist (otherwise we will end up overriding the @@ -670,7 +693,7 @@ public extension SessionUtil { publicKey: getUserHexEncodedPublicKey(db) ) { conf in legacyGroupIds.forEach { threadId in - var cGroupId: [CChar] = threadId.cArray + var cGroupId: [CChar] = threadId.cArray.nullTerminated() // Don't care if the group doesn't exist user_groups_erase_legacy_group(conf, &cGroupId) @@ -692,6 +715,13 @@ public extension SessionUtil { extension SessionUtil { struct LegacyGroupInfo: Decodable, FetchableRecord, ColumnExpressible { + private static let threadIdKey: SQL = SQL(stringLiteral: CodingKeys.threadId.stringValue) + private static let nameKey: SQL = SQL(stringLiteral: CodingKeys.name.stringValue) + private static let lastKeyPairKey: SQL = SQL(stringLiteral: CodingKeys.lastKeyPair.stringValue) + private static let disappearingConfigKey: SQL = SQL(stringLiteral: CodingKeys.disappearingConfig.stringValue) + private static let priorityKey: SQL = SQL(stringLiteral: CodingKeys.priority.stringValue) + private static let joinedAtKey: SQL = SQL(stringLiteral: CodingKeys.joinedAt.stringValue) + typealias Columns = CodingKeys enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable { case threadId @@ -701,6 +731,7 @@ extension SessionUtil { case groupMembers case groupAdmins case priority + case joinedAt = "formationTimestamp" } var id: String { threadId } @@ -712,6 +743,7 @@ extension SessionUtil { let groupMembers: [GroupMember]? let groupAdmins: [GroupMember]? let priority: Int32? + let joinedAt: Int64? init( id: String, @@ -720,7 +752,8 @@ extension SessionUtil { disappearingConfig: DisappearingMessagesConfiguration? = nil, groupMembers: [GroupMember]? = nil, groupAdmins: [GroupMember]? = nil, - priority: Int32? = nil + priority: Int32? = nil, + joinedAt: Int64? = nil ) { self.threadId = id self.name = name @@ -729,36 +762,87 @@ extension SessionUtil { self.groupMembers = groupMembers self.groupAdmins = groupAdmins self.priority = priority + self.joinedAt = joinedAt } static func fetchAll(_ db: Database) throws -> [LegacyGroupInfo] { - return try ClosedGroup - .filter(ClosedGroup.Columns.threadId.like("\(SessionId.Prefix.standard.rawValue)%")) - .including( - required: ClosedGroup.keyPairs - .order(ClosedGroupKeyPair.Columns.receivedTimestamp.desc) - .forKey(Columns.lastKeyPair.name) - ) - .including( - all: ClosedGroup.members - .filter([GroupMember.Role.standard, GroupMember.Role.zombie] - .contains(GroupMember.Columns.role)) - .forKey(Columns.groupMembers.name) - ) - .including( - all: ClosedGroup.members - .filter(GroupMember.Columns.role == GroupMember.Role.admin) - .forKey(Columns.groupAdmins.name) - ) - .joining( - optional: ClosedGroup.thread - .including( - optional: SessionThread.disappearingMessagesConfiguration - .forKey(Columns.disappearingConfig.name) - ) - ) - .asRequest(of: LegacyGroupInfo.self) + let closedGroup: TypedTableAlias = TypedTableAlias() + let thread: TypedTableAlias = TypedTableAlias() + let keyPair: TypedTableAlias = TypedTableAlias() + + let prefixLiteral: SQL = SQL(stringLiteral: "\(SessionId.Prefix.standard.rawValue)%") + let keyPairThreadIdColumnLiteral: SQL = SQL(stringLiteral: ClosedGroupKeyPair.Columns.threadId.name) + let receivedTimestampColumnLiteral: SQL = SQL(stringLiteral: ClosedGroupKeyPair.Columns.receivedTimestamp.name) + let threadIdColumnLiteral: SQL = SQL(stringLiteral: DisappearingMessagesConfiguration.Columns.threadId.name) + + /// **Note:** The `numColumnsBeforeTypes` value **MUST** match the number of fields before + /// the `LegacyGroupInfo.lastKeyPairKey` entry below otherwise the query will fail to + /// parse and might throw + /// + /// Explicitly set default values for the fields ignored for search results + let numColumnsBeforeTypes: Int = 4 + + let request: SQLRequest = """ + SELECT + \(closedGroup[.threadId]) AS \(LegacyGroupInfo.threadIdKey), + \(closedGroup[.name]) AS \(LegacyGroupInfo.nameKey), + \(closedGroup[.formationTimestamp]) AS \(LegacyGroupInfo.joinedAtKey), + \(thread[.pinnedPriority]) AS \(LegacyGroupInfo.priorityKey), + \(LegacyGroupInfo.lastKeyPairKey).*, + \(LegacyGroupInfo.disappearingConfigKey).* + + FROM \(ClosedGroup.self) + JOIN \(SessionThread.self) ON \(thread[.id]) = \(closedGroup[.threadId]) + LEFT JOIN ( + SELECT + \(keyPair[.threadId]), + \(keyPair[.publicKey]), + \(keyPair[.secretKey]), + MAX(\(keyPair[.receivedTimestamp])) AS \(receivedTimestampColumnLiteral), + \(keyPair[.threadKeyPairHash]) + FROM \(ClosedGroupKeyPair.self) + GROUP BY \(keyPair[.threadId]) + ) AS \(LegacyGroupInfo.lastKeyPairKey) ON \(LegacyGroupInfo.lastKeyPairKey).\(keyPairThreadIdColumnLiteral) = \(closedGroup[.threadId]) + LEFT JOIN \(DisappearingMessagesConfiguration.self) AS \(LegacyGroupInfo.disappearingConfigKey) ON \(LegacyGroupInfo.disappearingConfigKey).\(threadIdColumnLiteral) = \(closedGroup[.threadId]) + + WHERE \(SQL("\(closedGroup[.threadId]) LIKE '\(prefixLiteral)'")) + """ + + let legacyGroupInfoNoMembers: [LegacyGroupInfo] = try request + .adapted { db in + let adapters = try splittingRowAdapters(columnCounts: [ + numColumnsBeforeTypes, + ClosedGroupKeyPair.numberOfSelectedColumns(db), + DisappearingMessagesConfiguration.numberOfSelectedColumns(db) + ]) + + return ScopeAdapter([ + CodingKeys.lastKeyPair.stringValue: adapters[1], + CodingKeys.disappearingConfig.stringValue: adapters[2] + ]) + } .fetchAll(db) + let legacyGroupIds: [String] = legacyGroupInfoNoMembers.map { $0.threadId } + let allLegacyGroupMembers: [String: [GroupMember]] = try GroupMember + .filter(legacyGroupIds.contains(GroupMember.Columns.groupId)) + .fetchAll(db) + .grouped(by: \.groupId) + + return legacyGroupInfoNoMembers + .map { nonMemberGroup in + LegacyGroupInfo( + id: nonMemberGroup.id, + name: nonMemberGroup.name, + lastKeyPair: nonMemberGroup.lastKeyPair, + disappearingConfig: nonMemberGroup.disappearingConfig, + groupMembers: allLegacyGroupMembers[nonMemberGroup.id]? + .filter { $0.role == .standard || $0.role == .zombie }, + groupAdmins: allLegacyGroupMembers[nonMemberGroup.id]? + .filter { $0.role == .admin }, + priority: nonMemberGroup.priority, + joinedAt: nonMemberGroup.joinedAt + ) + } } } diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift index a829ec4e0..920eb3e47 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift @@ -61,13 +61,12 @@ internal extension SessionUtil { .asRequest(of: PriorityVisibilityInfo.self) .fetchOne(db) let targetPriority: Int32 = user_profile_get_nts_priority(conf) - let targetHiddenState: Bool = user_profile_get_nts_hidden(conf) // Create the 'Note to Self' thread if it doesn't exist if let threadInfo: PriorityVisibilityInfo = threadInfo { let threadChanges: [ConfigColumnAssignment] = [ - (threadInfo.shouldBeVisible == (targetHiddenState == false) ? nil : - SessionThread.Columns.shouldBeVisible.set(to: (targetHiddenState == false)) + ((threadInfo.shouldBeVisible == SessionUtil.shouldBeVisible(priority: targetPriority)) ? nil : + SessionThread.Columns.shouldBeVisible.set(to: SessionUtil.shouldBeVisible(priority: targetPriority)) ), (threadInfo.pinnedPriority == targetPriority ? nil : SessionThread.Columns.pinnedPriority.set(to: targetPriority) @@ -89,7 +88,7 @@ internal extension SessionUtil { db, id: userPublicKey, variant: .contact, - shouldBeVisible: (targetHiddenState == false) + shouldBeVisible: SessionUtil.shouldBeVisible(priority: targetPriority) ) try SessionThread @@ -102,7 +101,7 @@ internal extension SessionUtil { // If the 'Note to Self' conversation is hidden then we should trigger the proper // `deleteOrLeave` behaviour (for 'Note to Self' this will leave the conversation // but remove the associated interactions) - if targetHiddenState { + if !SessionUtil.shouldBeVisible(priority: targetPriority) { try SessionThread .deleteOrLeave( db, @@ -140,7 +139,7 @@ internal extension SessionUtil { guard conf != nil else { throw SessionUtilError.nilConfigObject } // Update the name - var updatedName: [CChar] = profile.name.cArray + var updatedName: [CChar] = profile.name.cArray.nullTerminated() user_profile_set_name(conf, &updatedName) // Either assign the updated profile pic, or sent a blank profile pic (to remove the current one) @@ -151,18 +150,11 @@ internal extension SessionUtil { } static func updateNoteToSelf( - hidden: Bool? = nil, - priority: Int32? = nil, + priority: Int32, in conf: UnsafeMutablePointer? ) throws { guard conf != nil else { throw SessionUtilError.nilConfigObject } - if let hidden: Bool = hidden { - user_profile_set_nts_hidden(conf, hidden) - } - - if let priority: Int32 = priority { - user_profile_set_nts_priority(conf, priority) - } + user_profile_set_nts_priority(conf, priority) } } diff --git a/SessionMessagingKit/LibSessionUtil/SessionUtil.swift b/SessionMessagingKit/LibSessionUtil/SessionUtil.swift index 03d5fd441..d20e50253 100644 --- a/SessionMessagingKit/LibSessionUtil/SessionUtil.swift +++ b/SessionMessagingKit/LibSessionUtil/SessionUtil.swift @@ -63,6 +63,22 @@ public enum SessionUtil { public static var libSessionVersion: String { String(cString: LIBSESSION_UTIL_VERSION_STR) } + private static var hasCompletedRequiredMigrations: Bool = false + + internal static func requiredMigrationsCompleted(_ db: Database) -> Bool { + guard !hasCompletedRequiredMigrations else { return true } + + return Storage.appliedMigrationIdentifiers(db) + .isSuperset(of: [ + _013_SessionUtilChanges.identifier, + _014_GenerateInitialUserConfigDumps.identifier + ]) + } + + internal static func lastError(_ conf: UnsafeMutablePointer?) -> String { + return (conf?.pointee.last_error.map { String(cString: $0) } ?? "Unknown") + } + // MARK: - Loading public static func loadState( @@ -268,7 +284,7 @@ public enum SessionUtil { guard conf != nil else { return nil } // Mark the config as pushed - var cHash: [CChar] = serverHash.cArray + var cHash: [CChar] = serverHash.cArray.nullTerminated() config_confirm_pushed(conf, message.seqNo, &cHash) // Update the result to indicate whether the config needs to be dumped @@ -349,10 +365,7 @@ public enum SessionUtil { .mutate { conf in // Merge the messages var mergeHashes: [UnsafePointer?] = next.value - .map { message in - (message.serverHash ?? "").cArray - .nullTerminated() - } + .map { message in (message.serverHash ?? "").cArray.nullTerminated() } .unsafeCopy() var mergeData: [UnsafePointer?] = next.value .map { message -> [UInt8] in message.data.bytes } @@ -441,7 +454,7 @@ fileprivate extension SessionUtil { public extension SessionUtil { static func parseCommunity(url: String) -> (room: String, server: String, publicKey: String)? { - var cFullUrl: [CChar] = url.cArray + var cFullUrl: [CChar] = url.cArray.nullTerminated() var cBaseUrl: [CChar] = [CChar](repeating: 0, count: COMMUNITY_BASE_URL_MAX_LENGTH) var cRoom: [CChar] = [CChar](repeating: 0, count: COMMUNITY_ROOM_MAX_LENGTH) var cPubkey: [UInt8] = [UInt8](repeating: 0, count: OpenGroup.pubkeyByteLength) @@ -464,8 +477,8 @@ public extension SessionUtil { } static func communityUrlFor(server: String, roomToken: String, publicKey: String) -> String { - var cBaseUrl: [CChar] = server.cArray - var cRoom: [CChar] = roomToken.cArray + var cBaseUrl: [CChar] = server.cArray.nullTerminated() + var cRoom: [CChar] = roomToken.cArray.nullTerminated() var cPubkey: [UInt8] = Data(hex: publicKey).cArray var cFullUrl: [CChar] = [CChar](repeating: 0, count: COMMUNITY_FULL_URL_MAX_LENGTH) community_make_full_url(&cBaseUrl, &cRoom, &cPubkey, &cFullUrl) diff --git a/SessionMessagingKit/LibSessionUtil/SessionUtilError.swift b/SessionMessagingKit/LibSessionUtil/SessionUtilError.swift index ff887336a..1c3cd4d9e 100644 --- a/SessionMessagingKit/LibSessionUtil/SessionUtilError.swift +++ b/SessionMessagingKit/LibSessionUtil/SessionUtilError.swift @@ -6,4 +6,5 @@ public enum SessionUtilError: Error { case unableToCreateConfigObject case nilConfigObject case userDoesNotExist + case getOrConstructFailedUnexpectedly } diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64/libsession-util.a b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64/libsession-util.a index fa8eefd96a94c016a40ab3f40e11c02de85a01be..a0903f5efd8aee2784a915f502eed2b5bb0a1bb0 100644 GIT binary patch delta 142064 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Ξ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%0DF>22oaMNHNd{{sw*=;0Y{e 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 delta 133348 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

    67Zri1st_l&e)P&bOEk6G- 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 IW<5Cde^tqQKmY&$ diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64_x86_64-simulator/libsession-util.a b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64_x86_64-simulator/libsession-util.a index c94e86d7db62e985834e5f1e725ff61582d958a1..9a1e7ea9ded501bb1e03ce137ed2461bc472353a 100644 GIT binary patch delta 286674 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

    *2}MQha7>a z`YKa%nTFjs?Jd!~AJ$V&n-_EBoBHU`M0-Bx+HRPbc-3j0m^k+Xt+rueVmQ1L6IXtu zjxiVxvRJ=8Z3f*M*v?+_u{!QjyJ6jX+WcTlXgj-;6Dc-0-Z*XkDvlyL8m2&9c-m|` zHnfx7Hzl-uKFMPJ;Iwy6%cv3~S>EgT@|4aFDsMHY`bta5 z`Pu9I_>|6zSqkUOgH4<-p3?b&;j8{ueR6X47JT@W*>wn}4uvoAJN3zl+3UOal((Bh z(qt?jQK!pXeFdPdO4i}LDsk(`GIB7PO4fG6rX zf|&dbI1lDV`u9T3;pfsQjC>0-XkCPk%zX0*&qTPF-_{(T7p*<0tgt$NA zwP6`x4s=WM9j7*I4cH6z$>43EI~yxQvC@Y89p4`$(GQ>`SWiv_Wee{FWean_7GN56 zClFr~l26C#(}|yhvc(U9Qf?Y3<;H?iZlG#+axbh%!Hd}3Mc{X!6qLVi%09l03?|QE z^TnPBO2RfEeu5>-70Cy{|Dl_t!e7Cs!6P7sN%BDuV?A;YI3KzST!`_Pyag*s=+d{q z+rjmq6j%dFf#skSXbnn57H|yWF5-*e4Cq==8gdvEzX?jYnY7E5#i_90K{~)|q5Z*9 zH&*_^ciRz2_$N3A_ipb8vtj?7tO84+w}KM)HYjl`K-tp8AijDh&jY0)@;3$H2(k@% z7T>XDm(ByF+~c4-0xQ$8A_eXsZzS!cJTfy4{&i70YRf@MupE2}e3qOCUI+akxBwgm z_64s6WdqW{5!e8^Wm`7jLTmK@5;(p@^Cj>Nat0_B6oTF@AtTA=!qW>kqU+4zm4stV?2K@&464(d26zmPYX4k}7pbXke_I^L1{=7c>=YGUJFWKJ5Vb6D-<8> z(7%JSkB@?~CEtJ&zY~<>`*rFSU=j2Ja1nSnC=E*hvFqK*f8%=hoiO|lltjORGN_J| z-+?lyJ_n`3H^F}3Dp1Bw87N)+6ewN%1Snm62PpY&1#!_NISZ5qb|#~|#PKip2#8}p zcpo?g#1*IHTyh{-3N6pH9kmt|}i9Z8MzLTJI`5y93@Db>F)c1gH*%!%J zk**8{rHlWtYJ#7@snDCiDd0*l4?XZaSO`4}l=1_~zTjf$1nNK0+AQd!p!C!?pyaCp zB_AFxa(ff5gFzBbW8g#v<}z>qC>5uJ5+_e1NkVxV>1j}&Mv^U-r;&v6G?MU5vI3k4 zT?{S+;cNt zNDqPXG}1&+9!8P|%1un7-PH0h(p>0~Vi1S?fDnT`jU<$(k%aOzlC)T!Mv@lG(@4@_ zc^c^&*c(5MBn^?LktAN8Mv{1W7)j#gVIJr7zQ zMv^==pyY4-Fp}6mhF$V|z;4JV4LUmiw!)G$Aq*djrOy8JGb zH1aT#q;LE%lGvBRo(jstNJ4oSNxDZKMv{KKV9{==A?0Bt=_dIVER=_lgz_+wa2hD( z@i3C%mUef?iu6k&DE%T2BT2tRfYR<`hIiIz@54ya;8JKA2v36ODeuEbG7yGS-vUZp ze^53c3Y38mj0U63E%FSJFjJm}k`;OOCjqzYNdr2APOv@b0OLU$=mf3c{h$FRgY`&^ z4);D3gboj_fyM@S9|{@;RzsJ7yFn>e1$G6ufY@L>6oh6PU{7k~r7 zV(?yYCRhOCcv_c~pJ`c1ZX4Qj%>G~+iGccRgJN%RUo-xd$3qo28yPay;+TZIbzrRJI zS^Q97u$VW(>oLjOIVMJZNKUz%a1dTyx?{WeqqS6m;t`4(QRbl z;vRJ0HMObqFU5$LXSXRe&bdq$Y%nXNI)vAqK;qz z-(~3v-xl=L3f;a2Q$NjozDs&T>CdPC2j(x9Su13j#TNT6J0D?zVixF6T~0lb@xH6C z%kfoP?zoQZqxn7r7yyS(aQe&6-gb~s_7yxZ_yXmz&MgjFag%jZn!yR14JTPXJv z@9wSs{|4zDQgv-qzhzZjO`Snqjxw_3Q`orZ(7ZOhmB6NU52ig#oki2?@t<`-UcEb4U^?5Zw1iUGFjrNeSbD8We@mnhK-=k^)>)U zLJqssfJ2ts@wH60!1qU-nMf?!$u52uT`RhZTD~KQuB2|u9xA84-di4tTS<0YKK91r zSG5B|j>MBpSjGau-U3xdG4(NTLR_y!Psnm_h*p@weY(@8T0VeenMqyUNp&GAmInI% zp3nvri!Se=^&3-hE)+veq84b6eJ48e8r5%jcM-NYLG@Fp*z0G!+;=SZ75e@Ln}>#r zuH|7VgH!Z6YMFdOZX3j(boETsLjqtp%Td08x|;gDHvt;J1TCly9@h(~Q|K@Bjv3sv z?mfvH#v9Ob6PuG`#)<6^T^?PaThtq4Kn%V+s{JqqMEm}BxReR<(e<*x<@ML#@RY^L z@--a9C5V?8-?F5=Ie3XRwtW%yePjEb*aeOCs5o=Q6UniDJ6o82!_#6@lbl!kJK`v7 z96uC8qp|+Nw5$kXdHGj+C1F9XRKw3IqTn+aqfa9ogzP+8i#jkqO+T{YD^P7ph^7h zCesgo@O&`kA~X9BAaA<)f4|{=)z{WKPkg3FAa?M z3~{85_A~Q0gmhXqBq3}`(Uw@Va8Y1_$M!_%G{0uJyTu%RD7CLS>}9Kc$=UbXm^CG# zT}_)Ac)Qv5AAg%?{oK%_eoN|pjqy~+1U=Zy^Gs>z)(CUJ-ysRjGK?H^(NA4bD5GNL_eEaA2*JrUozmBX_?D14}|0n36>Tc$01 zbhCzH@yo)ZJX`GH2d*R+lZmKnJO_G&r!)z5_6~od9+*j|yZiHJQY!(R#a4TIk1YI}H;C*_^+`y(34ZZAWBe6lOt7TRE1{W6B|xv#?~#rsU2@qdMn zzY>tOtrHp=V2}2Vir!|{bI9xnvA-YbE2E~m_V(+11BFq!z3gu{s()$kXixjW_T2%# zf}V3j?29`4l)1m3pTo>p78Y-wj!JKDUaAP{=W#x4Uvs6CR?ovxc>FQ@R^M3Y?SW}j zkS~SDve=%{&@G;6Scy0mJ@KK)nwxrSDmq@Hl0d1l^*QG=hT^R-5v&8<4h`@%u;yIofF{hzv=IRaBC{J-t#NiGN zRg5o+$hcDdYInq>;09ki)| z;kYP`Ot~^tEpl22TOB#Qp^r?QI&e05GqU!|wk~=%GSzpe;B4s~P&*>;_YJ6CruRc* z`mV^EeD(J-y`$>8|3t<&WcFANN6u>4b)GptVV<9c9$9@6r{L+QBO49z{eMJ$-VoqH zKy<^v@N93^I>R?y`8c-VY(uy&<%%r|-np^}e0mN1HfwZtK zR7TWR+m(nmp7DdDqGKByUMPrqz1rfu&Kg)`!7Q5N4|Op%jDlxOA# zQHL#zN--@V*zV~cM!n!0SuiceHeGWts@~UEFv%x+*3qc=lq&_rG$+&%=G$K>vcICu zmd?H{J=-E2%dXUcPC^~69a(J~N_)F|f2w1;Z}g&$ce~@mxUb`;P7P7xKfoq@KF)E$ zH)vB#9Pc=*-RJmjYD1J9!1E9N;P4#!A^N)U4Q6i@83Wo3y|NkJVYe>3&3a!iq0I?6 z`ilzM+~)f%Owog_rnt?XV-2~y2lnp#wx_?KDb?GT^Q*hZ;9ooZi^ab)8HEVH`{jPwJVDH{IG0 ziGw2Av!ZXO@;MDA9Awd+(c3yLPiioEKa#^vbvl>RQNOXJq&l6wQu;WZ&fdLx_3Y(L zO-bpUmgaP(_UzNMXOBhG+PG&9@V*qn*y8Xrnx?&mhq+QOmC) z(G{dz#;@(+XS{|V0>YyxFI;1zvavzjlPUGuiE3aWU{exCB=;+0XFDl`&yU zTrU#dPTqf$>Lt{+Y}KpNlm~85zB5Q!F;KbudgZOxDZ{Q&&d$Q`R;i$ErgB$*rCgvA z`*fG`1N>qZJ)oa*DRt7ds=M?hGnCHu%1xb>zv2h2lph|ie1Uq5n=AedT#R4J5?G6i zlfq*1Yg_{mokslz^&Ilkj_Uv2shmsxI!^T{>Q#(Orgoo>(|}oY$gLPs(GJ>Ik`r-J zLhNhEuSmJELF^xmQ!XJVkWc)V`JbqR@o23N*}Khj=+ zD`TRcV*E$AUL^V~F7pUK3s#15(Do&J$>b;!ux+&RS|)sx$M1t|`9#{EBVP*A__HmQ zG2~fXl8}6#;EX3M<9Vb5^V>(M-7-?yM&A1%fkW<4&b?h(jK7qL{oY~9uZAig%T@Lq zqTG0kGABnl?Plfl!OE97roLQ+8zXiF81o-gzDc^byr70x$;ISc@_uq0Ig}hg_9Q!y zt;tKvHSbyS5V@V)sB|08(=eBuN=B2PnwsblaugX(o?WK?o#e~pFw#o?^1Q~ClOw&v zACiwQRmT?cd2%v2ki4)&;|`IF$eYM77pwnuavnK`>_y(mUGYm9;@#LMho94e)z2zF ze@1B`SC^_DLO!-ob(g1=rxz&Y{c*C73+9n?mDiLg6P{F_dP4aU=#~N{bJWm*{JmK9 zYvgEhFpd_9m)rk@avOlK{xM|<*^fLwTkRF(a58{g{V0xqNpQ!b8hHK@rM#m{?BixB zBgkK7s{VkylT0N4cv$_PlFP_@$#in=LmJomA#Opld^L<4uk_rd44R-EbC2?siAwoX zuQV`;97%po){{F6G%jkA@;Y(``I?(6hsos08aRbqOl~DFk%?0@ZaCStQ1t@xH2L0C zwO>C?xrVgdr`ny*m93`| z6J?SU$oF2spYKk$FvgL8R;cdqsxpA~#2Lp^U$NrRp=Qm5+PrZed(x zK>cg#h-ScR<*L)jUG#^qRr`Te$|CY}Hn0`-qs-fhUs1(!Nj{*FrNOi?`XoWS#AsU zn=IFNllrft{foC%A5_2Fm_kFZZR&WIJpYF3Uwn4zAjZ#mOZ|u0FoT0Fk#P^O;&{gW z#KHVYrIx$ftKC-PZW?y5KoS!jv>~I;Tt(x{)ZDhBI~Qc#s7SP!FPBMSUMRkZeu=FNd^zEbV`vRXyaC@(1cwj2i*E zWlP@pRs+V8GP|PK7n9eM-+rz3S!D8Q)t`~?vV!k_ReKq=>x}Ad`;`}~m3N=P@h=sv zp!QYhSKy7lEH{!{`~dA;829m4T5c8Xt=N#=?*FLcNiu?bb)VYrX2HEI zc+GCL_ox5!1FD~*{f_^sK0%#A9Y$t-uKvG%SGxCeWe*)2sr~k7KpFMR2UO=%m(bsX z`T_czkwLWYV%)1gY564_1FcoNjrk|l@#Bxmf#fM370G+mevS!>*u~8mcar|k7{7-6 z<%E`dfE@OV>Z!+-Zf~4B(5SCb!<*z7(oP;brvBH+X=EUI_^A5Fk;i{feFu4z=Zh(1 z9nTZb{~@3MVL?V>JyyUIe<`<-Q_jUjSGHQbyNc-kLHNIXdKsg8VKh8W> z>M@9OOMtw#R@e`YErWuL>zi4Of}4Vj{(j1n7UhylxVI7Z9=OX=_~J#?zqC;O49ms* zqxOFETXCzD#1E(a8#3D;@7)#0YyKJ#{jU~0by<09fZCrUPlTwpF>x=(ZDWIqd<|oR z)A7uw&kv?xdfK5r6(__{*YWowhPkun;Acn_E9Y7VFP}>{wvCB`hYaO_oW#%_Q#!6Hme1|+o4xoPuX_9}E zx8p`xDYubyhlHu2ko<$(9Io~=VJ&FK0>Z1Ysi7^5$8t2H)I9u z(m`29eoFpLrgl_+0r?%7hgY0Q`GaJ3XVpdIYVs>GJwg4mLAMkMdYL0PzQVFHE6fAa0&yu(XoetS=0xHX@kz=#W)iG4D7YeP~#1- z3Qwf%Bu|hXM_7#VuAzo&v@(+Xd6eqyKgRg=Qe7sf=Pxd2kC#R5e!0LIS#t-CQWb|EX@9w2L)Od%Eo#Zm|P6T8x3N>bs zw~({$wiwItMw>yTjjVxR>`#+#k<%uq{Sdi^47~?-Hws)QE8qjC1hRr#kWpYU(jT@NQREoXARi@P1`{y_>t@g~$7Xy@{z*noQoAwP zVr(g~8OO=^$!;}VN5hlkc=8pp4-!bh9P(~*F1dqoYpHjUr^!1CErxT6&1gMEnL^&` z=1L(sk6cA=BYz%jWe zHY0W#2GfT&BZ=%!-bvn1K1)`T&B<2eF7n0u(4$}3+{VqiVib~H$WO^{Q2>EP2z4#! z_`zmais25UaJfQyDsbi=wGwvkqlSR9c@JBkDkx!Fr$#pEynfiMqtn3=`5yTf z;?`XkW(;{qnL>t>vmV6pF9FXma4or=JWNhzfw+fp?jIaxd`rGVE+p?Iv&c&*C-L8r z+sL`(-w)&XFCH3ZtelB&G0PbEAW;{7dIf^VI zZzB7U@npE0D*@zEuoklsbeoMc8fGWBgIrF|B-bEL{9DPRyazp6?LkjGu5?mgPp+f=fYNRJLqi1vJ|*Xnsq{A| zk36P{J>+8Y9`brJp1g>LNrO(1cQVhmIT+lZxx3)|4-Jo#Ysint!{py&J2oJT%p?Cs1^M5G8IC8F z9_l^h39=9T1xIk+0o{og!;BUs*!N%+48qCe8uBjqb1$QZ$m_|+$YtbPD*vT{ESi%H8o9A>s~%(+eHCWRt+&BYfVpfw2Wbi`O?a6-R9b^Id82KXkCb^TWAuo~c)}@*_nH)rp zB0b=tLE*+jdS_BHSn^7myE;N60;7+4J~$ zy(--Jgq%g*Lk=WU$?)gp_dg0)XgH5VqARI!v$~PJ9z8*R3%lskWB~0`z=F@ijdb#6 z@=o#*5|0WPx!>WiByTfu{EJ~84bLM`w1-?p`&RN4?S9m;7+78L?b-rkg8M$ysD6xsrUH{D9m`9wtwb7swW@IG((gyn~!bx_`$?ReiW|kbIAP znG9Tk@Bf&mY2u4ubGxyO+(jBI)!vhwM6M;jBkeC~+#GU+rtxq+Oz8i#d;-3TDFUx9YR@CyRO@e3X8#Q~kaM)@`Q8U1sqgUNJq z8U1fk|3ltHdnfW4$H`pMpA~12&yzdA^at!l*gAZ+ z%&{BipoJOK-%vlc&aIBobesYYmD-J+WChvkb+yk~kFHy4H=N`U@>=-CUO+A)KO%o6 zHzB^R+-@8o>)lwXdBtwr`vwk&*D;#OzT_fsW|wZcv`0?nylY9Qlde zxR2Z*cD&|mFL|C^NV;!(69vAs8&zZ!xstq}{DDj%{{feOYc~dMLiwNU#%1!$jjFRN zm20WTlO4ze^}CJjEO?yEV!%t(Q^`q8SWJH`?U$(+!k_3LVN`;}Z6b`cw=mdZB8-oq zg^|?N)c^Go6Zs|3;YVIi{=mQy(D{iG#v(AicZ6|~JW75+&L+2!Q^?oIE^ll3R^&p^ za1W0#9+wqxIysTdB{RtD$&k(XkvTfTsDKtOC;xs&^$+AB+Uv+>v`3S>K({35L&I3| zCbEE>M{Xupl1?_{6IO6Dbk%(k#`EM8WIDN?982D}8OOgA*hj+_a@f209`I0v@haJy z3?eU)ec%OMfN9?Jr=|E zN`&!0av3>?%qItuuamK4pqneTNGOT+k?)ePlFhcLKZ-PIpFUOeV9*yUEAM6=W58ggi&K*sl5Flx`!PhMUPT zksT`XNWmb{-_ zM6M%0Baf2xWZSJ;K1=9EqT6YhOfDc-lW&uI$lu9U+^0@5jqJ~cb|a(7mSjE0z<$sz zTe6mho0uSve1`T%s27oiv{z8SLw-x1C)<3WTbe6+6L%(WB*&4(pFn1i!^tA@S#k~eF`2?1c@cxEDyX%w zpM0CN@4!VUduyXFc?ovWr`=rXLdP($IH|R<6`Xl}Yomr-NEVXzojBKrMj2ztYsri7 zi`{P*zOZ~4WfZ|KEG0eU_oVwzu5|iT14oiikd@?D(90|7VlABPqrjGlOHo~8uhExpFr2(sl>#+%2%j^sE<-lqCP9yjRJSja2+}2Kls@_ zsGadRSq8i4Wz_GG$M>n-pZWl~m+_~ln|+~iVP7i$pgsz^CGkEQ%2}|I`UNtIiRM#J zAiL9k19f}y9p>r62KFO|lhetveK`K50c&a4LDn)s4l8I$`%!E`@%QbFp=3w$3-Ud( zo_vh#M}}5w`Q_A8zQpk_33ngBwb-BA8E=s*$tTIFYxirO{p4n{oV?|r z`g@Y|+;luhP9%qtE;9ZrEtpQ;Ms}kAA?ihBAnlu|&tuDF1AinxCEb-=SxG)b4kI(j zc=A&wJV^db8r-62^6js+!gBH%at3*a(rsKvLwC|awj}GBu!h`AZXy?x#pGRNb2jWW zcg;EE%O4zT1bw4y@1;A|NT*>0xr>3{lT%>N-V$rPOMXWFO#V%F`&Q!yldsS|mU=Pu zz0_{eZX}MRp_qJ?+ygGS9BX_+egoFt6K7l|A3TI#JZ79Rk6b}I$=Av2$SQIySpzP) zE33CzJS)NR&YfAidYB*G)iu)tiZ6Zd-OE{~a&^Sj?}=Dro!(#+i*7!Yvx{r8NY zVODld=xr_Ifm|?wxTAL|50y}sHbV)dT@0EA@eeCJF zC}C{N|NC3!63wWrpfJ<+Zb&Qh#myldJ@3Dm(5}5XRpV7m*g_$={nW=80%|?OKZS%EH&K^+@N; zLANA^b5<;vQZIGIG(iQ41$}gGh8`IvCbx0Se|=RV{&m5>Ydo*7>N;m^gF8O6l_MZ6 zC8bA-dF!RFqwOQ3@S_k*VlyucXtPz1h)A>8zjb2R2HZ<7i+KIOVl(icu6^42%pHTh zUgIEcQ5SdBP>Z>J(7;Hux;)VtR*eTyWU2E0AMbE6SO3#>Kv)?b6Ol#U6Di9U^Bl~P z$M9%_EEjN}xGXJjV}Ik9x_RH)b_lmE%TnyGG;qVUG<^3^i!q09ZR=unwi}5Xw!Mo( zQS4{&##P^LG5VU8;AFd*zb4TcT|N#M^8&T;5?WJWG5$b@qX6K<|2DicfUDjJ)hWvCaHI@1X==&;r~Mdv+|lGXLfdgfwyH8 z--efDZY}u4lJUQ5LmfZaN_Dq}ZowXhw9tspk@LcxX0n`@f2UVpq<#Cpl-E$dg`XO*9kvFc7E zV+Z2AUC}P{LgK~CoB!9$Sg-MRZ+rpjs%d7AU2jG5IK#+@8*60X{Z3H@Z89&!S&R&v zR4=@3KGroU%JQM-KV6fac`LH2ndQx9OD)do{>E;LZMVzV;+Ion-uFXNYjkvR3yqDq7h}`xzy+;BD6$WwvsR#!7qehD_t2-CBhV z5y9J#A;Q1HooSqp@L!&3ltzlkLtgR#1$gSDiev9E*m(zUn)vqMaIU!$_4h$9_iDl&}vj+b!Mo=*s_&on9$ zt>-fXs=L-Ts~=!gbuYoexiigwWrlGu&AL3pIGJW!kYQ};6>XTjtx?lE#xO4R_D9#3^zpChZ=6Z@Kc5~@mtKMc?0i;~wfy>kOIho@ryI*- za=MVEqo>`mr0#*9+}fn_)|L(Cxqp-5EJw_+%eeJ(d!{&JEdo(d0p$my_Q^EVvVGMk5*r#p6!vkW!c zTuzD&+%{CrR(a87RF|EU2b+}M$+7%-dJibS6lHuGz2zv<@_nx_5@P5e3Z$K0oWr*{k8{V3%W-K^1$$!GYUj1%k?1LIm)g(d0Nwvp3q?ET;;J=2ElSqH* zEc%;lKr#K79#Q`~pmF(fuitGL6_0AbN}zEIH#0%zZ1p$o&}#b6(4T@&&&KDHlk~?t zq2(7ezxu3E^b_n!_*b!j0YKvh)Jg!Bq^Fb|lj)QPu{Dj)LiG#}en!J%ykpYL2ue<{ z1bdGkD?P#96GmVTWwy$tlz8doV<`?NRY$a_aV-TuYxeWz)*yWF)GuOX&*OCM7qP^= z7?d1q*=$A!%cs%{>Ktxv4@!C7h0^D)9F0Vqfnzt(C*L{D@~G>rW4{OcTwTHLomj~?zh$H-Fs1iHI%tJJX79LZ=rcSB-!Dn!nO9RHf(`!!`{_s>6jLc z&Kl+P_w%Sf2M1B(j;p4B!Tai;&?J8y{awy{paI2A0@7;LUihI>$Gc^w`CS->^tR8K z7E^FypgYaza9B_7QERT(8kqJUl}pX|aOsvWRPI)9*?yHX&9@TKg$GoP^?vc2Ks8g; zO-HEm(Zt4Qz8p5OpM0moQjC8H87S+r6w<$q{(j!0vVHwG>Ryi6#^JLWUUo=7@0Kb9 zmmkvjbTd8@d!9;nu3s&K&nUap(Y+jH8Ykb${9Nup$mha8?=cdP5zt<8DJxh(HoM4Xsy{7{)kNn;< z&U)mktYay8QY~nZVL>>tQSp1$f(EDeJK0i7t9MBIV)@;wqQSZS^7Rfk)KQC(Tuv5| zBSD-RlG8w(|C62INqN^1h@qP74@$X{*b?zqgW}%?ihnaG{-tCt_#^C@;0Z7tJPx|g zqr1d$5){V~P#oo;G+;I;6%>F{K@KSKX=F!G%Ef?E&H+lrXRz~X@O};1rJ$=C{GV(n zcog=PO1G;7E2VJE76UkvJcE%V`XFe-hp8|R1*@rr(5}tYLh)Bp3&plpKY=SjxqrQIxvW@>yrm#69~YJ>^Gd-r2wVW-kk2Vmx@LnluulM+gXy4D zkOE2tPEaavfXG`Iq0F;^l0R6PX9e9-fj?HNQGua!)qkZGoB_pO3yS}w(shLXgW&(L z#p}Q<#Fc<&p$n+fK>SrVIf?oZw&HjAH-i#iegIRs;P2AO%W235&mkZed>i(D;9Fn{ zxCQj5y%y(6u^$1&z8S1Txg78|=p+yq5tD<#TyP&+|0|e6F8xyNMPK53qByKD;J+My zrE3As&0;@~Q?{@Glti`rR2P!x_o|*v#(+|BInMl2E|IMKTy-uec`A3S?&s#ppE*^5*)Aq#GrL2=VQft!q}!P z2Y*9AF(`*o?FV|io&;r-9|2E*)#N@9e=bhm1|A1DfSbXUApY!}Tu40u+zmYt%m5u; zKaT%P?;FMn7|w$`ad^}z^Ui=T!oCH32P^@l0kc7AZ~@pFgKh$u4`TNwr-A>K_^pfs zWkdQ=FWrJGdeAoT7>@tsbPtbTP!b1&lDO_Y)it0TF5AGfu&)E<=#UxIJ&gl%C50rKgr}#__)aD@9n@2$sL46)pvFOF(idbqP6>_I$7q zJvCODmrZ**C>xMMZBu_?urjZ{QvD}E{QW!mh`cDzgOzF+3XpK0GH*914X6SoVL2!j zu2kkN0H;BhDDx(Ok}wyPd;>wrm#NI_2THy)Q1V59lFwF&;~y(|mo{m_dS%`rPy%;@ zlDG<##G64$T&c|407~KtP~uBKNnEVVD*~mVh045aQ1WGhk}u7T6-k()%u50#VMkCB z`h$|tQ0CR)i@PK|qs%)5mLjeil*GG1NxTh|yjzrcn?cFD0hGLBL5X({$BGol2BpA2 zWnLyI1=2w&5CL`rt)P799C=e4c1W3b5c~x8ZJ;!8i!yH=h?_H#OF{g-rf`8WZ#amC zyOVRUA{C^A^6|M3KW)xHZvbWV&IZqe6R6X`>Cp9W;GuhP3n=9)$x_gTGvNZID+QD@ zx5+ui9MH1LRDHyDDSwZRAgRg6ayFqV7pu}xZy840A&`awK1BZA{ zz0$P-l=2l|4wwi^gX`DgC5~V*Bl!61v2oUF_9GlWr zU!mXSc7r&VTeK*_)P6&(K(xcn8ZcqS;0x;47b z-tHd#bYAV-3&k*D8_ItVwvpm~P7xsnM=3lA>V zfTiThMXE2A;U(Ab?*wHu`;*I`Q(gD0c6}`<4LkzMK0XNc1y_R7^||2l=%E~?%bkc7 z87vM^8gS_uj7m_hDR~D8DEstKDHD?WKuNp}{1L3Ay&RMV%q9!TY%-m+g7}i^PL}sQ z2Uv`pOAEbp)q^9T&nsQEpzM3$WY|wq3!z;#;3`zO1$-SW1~-CZK@6vykxExCh`pPW zqjU`fkIC_$jurVjodn`HYH}io@sZO}>58F00z`|GgF!zu#Hw`pgLgp7yLv&_rKdGd zJ&0kIb6)8>14=z6DX#7c~s~nVFxg3;TRtmbM-~y}&OBgU4%z-YXJsp(9NuV?!QRzA}U(3~! zHK4@pquxz!qrH?YB1g{0@h@GP3xjlNj?$G4N|z2)y3#=z1HwtLr%?-`T`8c90V}u; zl-C2}$jrGk&r4SgC}Uz9h$As)i_%p&56Axs7&gEFx+*}~H_JiUS7o4_xfX!6&?TTW zcs3{vo~d-r0Hwi2N>?E$V@YP!#vz|GLFpO`O5P;U+mrH6aICnTpmcfhTy@M=x?-pu z)JICxe@N-t47QR6fztIw^cT{f2)2a1=1I-J4Ge_N2T$SS*gaNP@{&Mriu3v0cEg_1z(p2fHJrYa5;MU62=thIu9;|u2Z^dLFvIGpp1cP&~RgAA685R z?pC_07_b?X!M6eQwj7i}SFUue1ZB`ISGtyhGU&>{Wyn{mbd`WoVJ;}UEJvAV1KqOE zgRvrm@Y3Vjg}Xtq?^L=rgEu2V2`DG3Vx?;!*aCJ3cm|Xy-DL~6fHFo#f;nIWC>7h3 zd9|~3jMU7=@h|)GAPlmF`#{+@yOpk;pzN9|rE4453~^gP91uC1m97n-Y+(r)2#y5* zkdIri6gmx*dOCtqPYhUr203P9%dwId0fSr`u_^O{!8Hi9g7^f^@mIPoJ*pMef#N>{ zmcd`Ebk)#b4obz#L8-VDl!{A0*}!6@YqlFJvV}91t{I?gVG)Q=b{wyVZA zmWYc6@_xT_?!lG*`L)mcyw~--&s@r9?sM*QpZols*~!c~iXJD(J~0X<7N8eA0>?uj z3gF8K;DlA>2U&uRAWPsCCJKA1Rd|zdqtFYoWQ8C{M}eXzALQuBRrI8TEEst;!p-IX z(f^uxf&fn{$dZkISB>idum^HG7y?^C8%h>Z^wfbrL%1K*5niL{sRo&@0%VEvK{}WN z(!p#+PdfNDA3fm-97qP~AxRIDL?-E=8>E9no0Nk+;BO%}gLJSF#4UrjLD5qK(!oj) zw*=meik=FP>DGcidYFq7UPO{WHiZjhQ#e3+K6<0-G%-cb2*^G&tmqj6*=eI7uDsqs zMb7|8`yp@=SOtE9wqFY}pW+*3{Bs$9W|Rw#gW()SPd3Pm(iJ^vATvq@IsQ`=JxL%l za)Gqt1XJJG;2iBQvqJW@n6mp zlz1)Z!uTi68WFif$(~}+tPzkUcYv52y_%vYcAchSitvsodWJxjJOZ-h{U9Cb1=++s zppTs`j1#tTx1y&DWEFLSm=wJoik>!*h5{h3@9Ch6Qz}S@(?llOgsI>|&`Sc%c?D!m zIY2rxdM)aoRT8__tQyY<$WAh>=otbze4-$Ruy;_=GXT<&K9E<#FgOZ!f}F@2Kn~lD zATurpnQ;lojJ+Uhpy*n3Vb(w)0$2kDik^IsHINHp#CUTQJ?S7bP6cTw8Ds&H6g_T` z1#l^P5IeHE*C>`&gI6NsN{}_N4&<-pVvq$a0$=9u|9tZVZ4J_J z4w#QN%vSWIgQbx9iW%rh2EUDrk`znbATw})G(38>ru`Hg2I+Vdq~m>{2KItipnyFj z`k$v8Cp6Fr(t&o6y|Ycx(+aY4h7>)`APqHvm%~t_qNg6D9St-cs8GXm807uLkjNzO zC!(N_RX%_ddfo>zVFMTitHJkC(kjK$B9RM49{#4LUBq-C=K-=54)uymLiTikw9~Fw z$_-q@CgcHOA4o^S;0&-+ zXjaMf{X}&j94cOe_h4bf(+-|x6GjZQpH-34%dL@ z#3kW{!W>~T$jE7%T=^!%=ukIRXy(aMFpBzk!XyjUWxAgEYY6 zkzFOKsyje7K@gk?M*>1Wm<@RZUNOE0WQI*3Ys4W8U8rif8T6s5OPg@=5ESY`wq+g2 z8sWDitdVMvHBzNmS_!g7HY%1@fUJ>n@O;!rnWASc$Qp5gEI``@>e>+kmq5Si0*wDQ zL^L4aTm%Hc63Bj#B`6nW3sZyx$cN7>1W0BE0E* zjDMc^5%38ds0Kd=*NU72vPP0X8gdCo&QtOb$oL+StAs@$YcNM-2Y3$b4x^M`1;gOk zV7m_|ML4Mgy+}|cTnDo1OF&-#ixo?~Ag}*Lilv1hum1($Sul~W=*a=!u~r*(ajlRK)j# zw9^67jvqXN@O9uASPaI%0+9A{z%}T9-fW)0;1GJmK|~CIROkg)Bfdw`Qvlur!)}ln zjG*ICzXuFL?gkm&1pW}L61fDVUM@(x>EJ5ZO#^*6@g(EqQ$!?+0()cu6h_gbKu-+( z4Dkct$1vOgy3krAhh4qMB!^uc$R-FVmX?9MXG{lKV=15q_LI-T_{WJS2?2G8aEU@M zdL9)!K_=`F)`3hIQ1n!T)GL;7uY~7Ic#?#>C43Y;k@|fg?ew03`lkb3?4pQh1DRo~ zuwE2{iXJ~m18XI`M8XRtJRPJRx5%UHsgR@K>8Qa7$UCbPkTsO-QzxZv(DV!)fef=l zAZ@pzCkWC|t;AP~oCvbbJJ3;5!6tSXupay>7y#L6YZX1^AnoUawC~GRC!REk7-ok+ z#E_zA0AzxYgf~lgorIT2c(H`%fNP-d0M7>d(Sgnc+dzi8gL?lYY@Jf{+ z3^G9j$OJVKK7!)#heVaI9Aph7f~-=fVrd6TLB}_Obzr&3CBj0J82>~0N^wBgB&-IR zuoC24UJSAgi$G3T1z;00%vUVU1@Za##vH}cZ16$orGuxTM$;5MDIh1TdKi1#hm&fa zfNpRpGGH!SAxDvlw`M)y-C!5E0BiuM9}rdx^TG2F-j}Cp!~wDZqq#~y0kzS@Lcq=6uox(ryz-y`XTNPa;Yrpir12Oa=o;KrfhZV2NS}crN4ykQoFeyh@k@ zX2LO-$h_Y&)5BXbEcAusq(bNw=72PuE^;VKB@7D7gPyO3)oyjbWK4kD3R5@85rlQan%K~A*|AUkysMge9pKRtVP$i-d{bxqBWh%=lJMogJi>)19>$KHf8obnLtGzUE=>%I_WqYO57ONb^zr&Ryl@n~C>egzf zt+Tod|F&0mR6Fc}n!1``%>-NTX1#B-KEc+!MGtKOhqma$WJj&uSqs*FUk`jA?EZls z{(g2Ef_f|2bDxgIXHk@Ty}cgvZ_{hHAxU?G9>z+j zJq-xMlBxCg>rPwq{d$OOykBp+AK~Hq^&ZLt_v;Z73zqgXeDr?Zza4ULyIxNQw(E7< z5#G67@1oqhUGF2q+w~rX_w3YrcY={-eXtqfBh7k@^bIxZ!#rvJi5~h16atUwb&o-A zd`xd5>mSn_9z%G;ZoP50uG{K%>o~QM!QF@t{z9++1sM2+UPq4X)?>R-xc1-c9lzH* ze~+ZSzt{VI&+_)_;aYpyiafKLrx8SdLvo;njUx!9PQKnWan#o*K4%%n%+(hysk%H2V?#EXg}Dx zPjA}?hWF_``+P_+uuqTd1N-*r{fwv`&;tWt!+_pM1_$(dvVB1BAj1QC57{-Kcawu} z>Cv~q*jxH2IsBGBLWbVfTi*sd_UoPd!JhqkFWJ3c50n0gUW-3p>9&#mI?iomj1fZ- zeK-ORM)WAz7t#C4zdWh_QPak*>>3iPOacU!b8QwUkHw}VAgZeNzIH*U- zzCpd8toxH5{1e#pC*9Y~lZHR(jpX28^ypu}*kANfa`-R$2w5N18=_z+s<)C&QN5WQ zeqSGXAFTaA4}1WQzOVbqrVsRHvi$?SgKYgkZzBgk)T1AQu@Ci8a`;1^KEjisA$@oV zZ1_lT{0MCOtKR-sF!Wcwm2CK{-bjxARrmjm;eXRx$*y6&dl>8();r12u--~Gj_6Gz zsF~o1j#C?1KZ5YUCwko{p#KxSmK^qt=p&36I;amHWW=X>?WbVupgu}D`l&udxi+Q; zV(23cF};xt#`O9a!rKq&9f!d1A-#v}I;3|WLI)3irq_R_H++U5|6#rMFxY)q50iCA z^xzSw^c>N9kNEVyBMckUgJWRRnBGh_jOmSJcuemhN5^!(-=^D!$8?<9$PrWjHe2&; zHt64GYa|1=+3Lu~Ew-jD;LsM^@D>|JV=X2@TW2kTLbuymZ%5F;?Y7A6Hs9dwR14UK z17QChwt+jqhC6MIcY>pL+WdEcp*mY@9XMELi;}TA+bB6)XB#0q?zVN_4fk5_=BbTr zyBp!7cia5;fIauvddcp4Y+&^0IA^jJ;wTeFbcN74sBW z*Jlg%fq~a;b+6kn4ZdlsdlT$<)7DA$ziAsFy9Y3BF#IiB=UbrvZCmZzpl|3c%ojLm zd)wB|0RMhl?S63RZQC&Aw*9tt%E5@OJ_5$}+eRsOMQq)a8{e@ty#v<1V+)Wy@7Q|D zx_52Ccfo;oZ4q+#6Whoqa5Q|Uz2{ClYW5*};34}2pKb6Fd-M?m_(S&EkR20#$lgHK zh3vr)6?3XVSQ~7#?Y6h?277ng`^d(}?M;t^(Z_xEp~vmh8_YjQYS!`f`H;uqrsJrMS6u0j@-j)m+{;m?-ejgOz zz?&j}dasfz4ygF$BL7=|E<`YEd^*5{R{p!MJa3z!T?H8Xso&_ zW1g?#EfqY2!SLgol5XGKO07=fA5DO)YudU-rGLf}C6`^J>w~BoIy~@BFBD=o>*@34%AE*o-5eMIt_(?&f z|1*(q`X1!*_{-m@(qBCZGJex)6;h=)O9nrc5RwedZPEfM7GR$|+#%~H%xwZJqI5D_ZGI)#`!eH}$mBB?~&{`nk zW;%#?Ymu146I2G)$NJAmle{f8a5)PIeQTMRiFadAunj*Kg@?sJiFB%ZRw)u_5#_*m zILj)`l`6bmnkegI75}N!K%wYgC4Jx|iGN@_(j&i~^W z=TRa}bfL(1N}F#Ld5@I%_ae__dNf(#n=1WA_Ik*g$ancTd=U_pDi0b;w!YK7NJ`WZ zQ3g(wHnf(vxqK`1QA4fosQ8bh#0h^_@+(VBds@SVN^Yiu(C-#~-*U;IUjoh+1Ixw0 zt*kO6h)KL(#&wDe+pSWiWul)U4q5BbluDnnJ|q5tI3TOc__Xzsfwh3lN=eWqRpb(b z)*3M%+yRHs2f{~Gi8e|T96q4r?a~CRMwEO%hcyb&{H~JkO@yrDr3|x=q5X%YibB%q zR$Emh3Eq<$Stka+BM!O-l)=B;r82OVPq|$(wAM!%y;Nmrt&{S-wMtIgi1f%`i+xQ6 zq%#1=ri+!FEi(T`MUSn;Q%bo4^8_% z%~~R+Tr#MX44#)NTzE)1I9cTDL_STbaEHhp(#GbJGDxyRn$TK7<}$JG7yF;FhQ^`& zPyS40aIrMO(_(P9Rb?XU(uUR=Go#nS0EV5l)=ciVm7E4UJgUB>~Gk zWM2QRHE7bVQVP~0Gz~KIS;`69nsrsN`#6R%KmiO6S^ zD!EMLTQ5_xwZhDI?nC>tms?BA6iAg@Ysu6 zt!dv{lI3~!DacKKQ3blh=Tr%-rBWt~La!*SkzqM3@{=+wttD1ANfQL6SKq}^fF`lz z3nYG<#4nd&SS6j_S0HU{Er{|q4PYEUDGE`_@Yq@qm159Z*X;*lz*<=CVv((d*v6rD=#aIrTd~+{MosdVute-vOi=-k z%uxY7#sxRxM{)9XD)?pz9>LVjBO^(v@vT7~Po1IUwNEH{+=WWEmOE=aOUZ+kD*mHF zB@dy6d0bwkWNSGyXND=;v}EJs8#A(e4$0?qztT&^K;-covwXORU8`j6B*x>?a*2|! zs#UVJ&gRO1N^h-kSdpmWBOawc$E;u2%Tw}2xKhBLv13!llBw2mm3;r7nP6R^O{gPl zRnAMyI>M?77pSxeW*uQ&36Z}tLnZ4#FXXZI2FMP-wqItXr6T`Ic)+xiKt$-PHX~{oP85}xM$=1?vN6Zp;Y1une z{JkRAH7a>Qy3%jnt>kaAm-t|?@o^RKr0O=BwLI7pG9Inf!+Pf_170!k)@&t@wyE^L z_^OhJo>uY^v&m2Z+`;fTRfa)<{6hN6EKD5c_>zF8TxB5VWtE^-GPIUQ^Gb%+QfgNV6RA>vSfCpD7h3a1^`RP?k9=S)! zhgK`uTBCkxp(TmC{H!8W~a~1!KD$2OLip-B)s6Q+hp~^dyoHC~LmwZp+ zN0sbr`J`@KVpd>r+%R1IywY7TjEziW{riU#`uNS zJjg*Qz-wlK(B>jGuk;5SwLUN}J6@pUC90F;NtKKwQ18t5~B?S&K-5D|rDF_0<-_DP0O$?!VN zY&2*sExcLelFMn${AS6FPZE2-k@U^tV2{{$eO=pg!z~%F@08X3bFp~hwsjqMu13S{vr-koRxp8fO9^w0p$xGcc&4vGGHlY{r zV5AKk-iF^X4rB7+Q1O1m0r=)xYOohlGBYy*Yf%K&*HYzrb>b9wpR8$-ZqBm z%6?7bdw!D;R;(;&_k#TYO8oyZ{x87t)DxENsqDzgpJ3FsX6~GKB=O|Er)h`wzN_ao zZPp%b+N?j?G}G_d`}c7jdvA5N?8$vJv&6nAX1DJ;WVh}5)UNM3h?VcP1$=iJ>+bV> zkYCRTZ`=4p@@Ap7U-X~%O>_Q6<)aArX8xfRcm<_JbZIe zi;=cFbJ2|1yO z_Myb--Z2~1>vnRdg3ZvVbT6G{-(GR1arqOOGn3H6Ox0(xKSJ4RTi(26?K@pKHw~qk zhVX*Vw`r)R;D_Usp+ESbDMtTLah`U_GJ2u$(36>0I#EdX1pL2kPiBhI{A6aj(Y8L_ z>0)A<$ z%e~ZTRE}GkWaK=RnN&E>sbd*^?Yd9w(|0x9b6ftCS87{7wa?fUnY<-`Z(Nr-gs_!U zoUz*`>s#4nnrt?7A??weUOu6BtM;* zXRKembh^9gVcRP^-_u`VgGbP5c0x9W-^;2phM&$%n7{IwydpDB#h{B)~a%3JhrhrVtuYW%F#Mh3DweMK1?Y2!aetF7L-$uJVkKVk4Jte4SnT^`l zjXYt)*=9ZTasnMEFdG+gskp-WXRe zz!2EdonJQD?-?6&K=+0G_fJQ-u`_+?^uqVg!YRgb*=$BnXc;>T?|0w!n|%GYaK7!f z7xS;&d`JHJ&4K)~%|FObW9LRGLkHf-t#=2i;PBjuTm;PYS$geY>*Jo|9zjE-BNi8GiC$=2E`M8$DYwTM; z!E~|h*UQq4U|P1@_bz)ftf?(CW|&nHeRy?V(ReL~xmeq7FswpH_3VEpFN&QsqP}PH z(y$Fjblx+073AKoybyUsS6&oMK{*cJd@geKwj8|VoR+aIcIEjp)jDSSBV)ehk&`3< zhN6ejd?$!ZgOsO;48s+av405L?O$cE(xF|?KJ+d|2=zD>{WC`xclhSXds~!}RzJ4J z@rsdh;psDdwq2iKIz3=_VvMVLjxJo27s3W7lp~Y6JZ9&BqwE}!EzhCl&?!(5c8bU> z#G@m;kCL)uyuT96q}~hAtIdxr?DD*S4#K2IXy^fD=m8pfLm7I3X1nb-#$C^5&Ys}Y z@(wQ4wi<1}Ugph>YOxiEZ!SiKo{tJWuO)WHxh-&6RqBKSOtJqMhoZTS%Iu{l8lDT5 z&T=0_%O60=IQxHs|M%X5-E;~TWv3dsi?iJ)u`9%oUljjy1X?2`>g&pTA31OyNZZ|D zy!u?`{8HxzZR`8{3-T6YO8aox>Xs`Ixqf$Gdtmn++y9v8zi{4G{o}sPf$cY90*YWV zdRMk@*?Ub+uJbvhUAao7eSFQA(wgav3%W7a?O2sJ6V^Gv_S=|sU^{wh`}U@H z0}QY+grAKZSKfy21MFH!!a{xP2XKGwZQvlyZWKP?WHAUvM9o_K7wEh0W|9skxUZSS$=$INCU$f<| z;A|A&c-Y^S=L+)#wdk~~fnkh3n|b^Oc*VYVkmWqGa(M*)QfA69JG#tcv=E?-`OJ)m zp(4s?63TzU^@p8)z1ivAyWT@>Gn^ft86_DTf15W)f6n;q`OJmBht+&sfhG#sr&b5P zQgEonenD*S*pimL_NlSZ*wU80W6N4vwzy-Qib4lwdM=W}*bCpxb?FL0W2H4M@XyfXhp)VK15ypRV? zyW@qtC}?K3e_UQT0hv7)m)RDT+4^VUfYz_ik2%^ z10d_AZ#i6@_k3POo>a#b@CNM>-J*X{_jXmU>v5-KWzVKom%D#x>exKp{oymX|A=4; zF&baYT)h5^c8^~1oN{-Aa@TBvFMavv$`1`9I3j>|XKNj6JKe!AL*a zt#;V*ideLTaoMbqY(DtUvl(ZW#`x`vnR8ZFjDx)c+80&zp03=!)&JCgP*tYe-;S%D zmEtxoL3F!nbHP)plj?a+*3fftS#42S?U<}xADFpgwth>&egA2eQiRW6LJ`vN6CiqD zoZW>|gfk1wQAI!gO=}-Juz8^Fd+2dNnbl+XQIq%?JEic4kFVo_NhKdi^W-JbC+H$kV-#`LuK)5hSHX)@3N-qC}zz-_t^3&#>X#Yo_t+=Z^d-_EG}$&m0?b+6_kIgWY>|gS&>J+$TST` z;x7#&&;6TBF@2^zXJvXtT&9R9dIp(1t_)9LCf9wDN%+MtXHx&-zsUp>UpV~FGC@T2 z1!VGoGMvLqv8)m^K#184u5DI`F&=3#@Ax;q0#s0>ZPWZ7dbew4WN6z zw)Ke(H?-`vB^i_6&N@D&>#ea8XIa5hfw#s^b>pYn6Sz7f^241+#!7evHii#pxmTnN zD6M%3+V#)8iSxBc@#fg6&J_72h_E+NQ&twA>^~n_%rdi>=ijFi7wwDBVwRZ&E_=q` z4rk5qMf$(U;WnNlhoXKfhnXL~^ZHoHGswZZDL!`4a4@}xvCJPq*6)b}Hy0e@O{4j< z3xg$k3NDuTO|njxu)PRd7+a2&JbX$C?GjiVTXC_epzgl^>x?i9hSHD`X2I3}u^pfK z1@tA4_hTwq1Vi|7f=__p;QyjSxU!_B!Va!1bN`hcJbkPGAFFW3vw6|^Fmz7Ap~Qb> z=s&T8D|Bcc?BELhnb|!0E+2pB{MS{@JBm3lggXj61u>ft1(Bx2O&BKT_bk~588@%y z7?zt@Hy%~+W-a81<*s-Git$qxo)<00V|mjU69tZZdEl4eSq1NIuXr_c#hGzKoAz+C z{FIe1&u8`dii<-l>`qaZ@V_Vot3OiqS^P_}CYB z<^d^GAMVV5hWKOe%;U=vKBg=q)6F~en0eb){%Yn@xl8zYU*=5TF&9N%FyF)N`u;gcj7h@k74nn_|?os<#82E zdrs8H0~lXcJ-|nnpLf74Kc8?!ktf?N4G*q(b*^%#t8-v{Y{jYcCGM`d9(m)g`FF2n zu2>EeU)(k0IR+n7#m)a|=3}fc?tDi+|Dqe58tC(P<>Rclsn$E?nKb7o=@Om&l-8yBR_GOt^(a7{wHo4<$0uk%Z#~iWiDJsr}+zKy2|{(6}FbB9cs%o{9gbyd&j~3c+MSH0Y?5anQ7*) zcH{j8OD7qDXEO7Rwb^(o>wY`)+Y=>2RF`pA=F&O;u_vfT(-WzN1N-gDlXJwA#~mF9 zSL*+TC)2<1#B4Y`b$i7qcr!C;HXkHe74zx>eXH3dtc6pI7v9EmfiZDKTCB;|<>|r` z(9pz!Lt#AR+KZib_u>I;e5J5ggr!$F*$YsdA`%rGkX{isd&NI_3>eoliqI3h@m<4t zOxFk>^YkO|rJnJR#*OS5*)x6U1h?p0-$l8tE^T#ZbkF#%zxBW|9p`}E_74U9ue<*` zKBkQ;<$u!utuD!DMgMd6X9>;juev)nIaY1{Kb3C6|Ax}BaBKgMEZwC4t4sF-?Wtpj z;D6mS|Icl^yZ(P&I?T1l{;lPJ-GNJz-F(_^&AuZWY~=e-A+mV!TjjM_h*ahreprq#TQQoGl` zD-Y)PUvp~irt$bYZ12z0Qwr3@$7ZHvYW`s+-pWi^!J<0mz1TbEA5zrcEq7eAJh#fG z9opmUSlVSX3S3!^Jq1& zdhL~jkN4*?3a=mV=Yl(GPuexlKI=n<@rR*vU00sbJ|inNk&pVF3DDB#{n3bAnAzp5 zbOdy*szEoJW@Z)pF48LSYOS+t9G2X0hHUm$-C1w5cj+#_&EBiG<0Xh%yEEp{>g;#{ zV!-Z-IJ6-TuMK8|NQa0)MnG_ID2)8N;EUrG@a*J7jZ5 zom$x8YISPE4p+#j`5MN#nw?tLI9HQX8yM$mbZT|uT@6kxG~R_jQ}m5@)j74`1Xs|h zHBIP;HyVu1((p?H|7Q=|u>F>E$cDwUX`XQ&vpk7C_So){Ug_3BQXciElY zAyV56sXfu5+O-Hb%`5{T8kJuvrS1MxB_R z7`AD(wldMJaMzBr*L30+6$`F4DBZBm)m1AK+f%ffM1P7Fwz*^Tv>}_TZyw&FNNk#? z_1j%_J_Om_O>;HBBXMw!*6DEd&C&WDuAVts>$rl(`C4qet7DE9ju(fFpDkKE$KH#R-!5A0naX9vd8Fe_4Ib6zX0RzD z;i-a#_e}~_96q`g?KWd;@6vfEw>wn?UtWafQ8g}F&ZCSkZsAb-(XX!+@mO`gsAo@9 z>U=#ZKG%AQZ~YhX%@UtxEO}+=ytC8!vJj3wbjW#eUbTywHd)12qrb#w!zJ8#R8LcE zHFl?F&6=O;R^kXsI3>;;mx||+WL%%Nc;3m$GnF_BlksY`mKy##$D^B@Gy4WkP*TY; z6&x1r{5dMV9Pwz2qct0mc=T8E=s+3c<3}WZ?R*s2|xNcJ6Tvm!l>oE?dN__HZ|2&>M1eh=Fg-=%s$JRiVWx%82hk$qo ztatgjWEhWQ-nvy;+9Dm=#La?wscM``C6>k~7Q(nNB=yZA26Z6oTpf*Hid2wG1U}T zcZwK*Qhf2+Bz58WDmA9*FU5CBeBuSh_M5WjIWMp>d4@Z5i&S9ft*0fWiL~|@6a8YM z_#&0ojg7jqlJtwzp2!u#jTaf8jmwyu;1?~~>o*{x>>*r#G?rUkJC91{roqkeA~T>t zTh~R#zVWBcvveKC$Jk#xO;m^BQhY;3#n(Rbvd!mftGGk6NV3iRh=^?L_{7eeRRQxa zR!lc`qTQS#xy=bIq2_BU(q&w5Qr4`K>n>4Z0F4qqJtm1Q_hk}qre58+PKh<5k`(qH8o-(BJu1< z@$uZBg-6Aein!Q+DZWwS`>#~n(+`NA?EgG`ROB?jz)`NO3>;&n)$*d;_-aPh+=S)| zrKeivYU@ju7((MG;x0+X^}l%>8{a4KP1mUSN|Y?Vdbv3b4=&wqQjM_`5mSt3K3SgR z6sg{Lg*&*5$JGI z-NAUtoN&0w3Xeo%_p7MGqejihqN9!UZB*D8)Gn|q@lmCU4m5v3^gM`JJe zEH}vqX(5)rD-?3W7s_eE6d^ZwVR#=ZVkX!Q-if!vgCH(v83B;-72s`D^*NPX__Lj7it`rOCmPTag?a03@6@PkZH1u{Ve$OL5|6L3EkW>5rDKOdxi zHc0(ckWJtKsW*yoN4;T?dV?VKxK9i9!lVx;+=ztZgO$PXXz85=h6LU@fSDmEl<$n^ZEmgy@%rY{DWz6fOcLXhcw z**KvAZtOw>sUQs`gEZg~e~#2@f8y1-iy-w7^)+yqiD08+04 zq+S_Fy>%e;d>Mr}q2YXxo^izmcD^)WlF%uP;abf2D9Fyq1yU&Y3Oj@$VS_Lr%olRu zm04K+G=nR&xbUSn`5rEXX(E%5J;~rKWZ;%?l6t(GU^Hjt=dPX$h1LBh4*)rcrj^dw98AX3vnMA0(Hbm-``e-xfAW3`TsZIBCWS6LKFFCJcZ~SRrx&$b=~%6Eb(EYpsy2u*+4+yFsS! z6gGg!%UiGL@q@pGo!$*;ng(&?Ry!h)VE=6g}kqM zr6_s^)+;-0Ak()hdII2UuwMiA(GhMx!;*)uP!80Ay^ssR>2NFuWJw1vR~|PB-5?!x z2@{1KrP9`lo<@)kG$?w?K{j;>h)VPp`_zdiNg^Walz{<7Pd`Y-K1EL#$hHoGthv6+ zu#Gw7X5m_pO;89vKnFn<&?(`gUsv%Fkm0^AoMhvq6U+kJM4?jHbE&EcZf`OJhRZ-^ zz}-h!LtcVb+9?N32Za6j!Nm9?kPdU}60+oCeCo*UkBZevsTbs4nnmC@QQ|^H zPcBGLv%xgPr-CPg$zTFj5l&Jpb%S#tyA(?k!EYd)13VG>!zJcURT+aI9UK5Bas2n< zgc*cEX3!1>A-93(sNPmZPcukEjUXMU2Y-lo?m_~3>cD=;+>HeE)Pl6bT}jX{y)}xS zDv)*}7r_Bu|9f%b!bCzwpvY}YD3g#qVURV`3DWa+a1vMtq9c0)ik=#YuL5JpxI*X! zS-?UN{W&8CcF_T1p}kP|@`!~wD(+?)mUjGm_o5 z&sFj5;7;fV!D)zJ2TlcZC7!#Gut3}#g!a0@pFqFChZ7gtnwxv@qQcERD3iRXaB~kj zRsqs*4mcHh+{^>?q>~8uo`a{MFjxq(#Q7jg%so6<;vA4om#tWu1hRlWZtn4QoN!YQ zde-z+#h|c6m=3b4yU$itUj@>@Mv&uD}#!$GZcG-t-?lOnXp)xCQK3z6iRwjN?JGP*(9X%Ys7{*^uzQ7Hjs!W57OU0@eD z{1s&|3SyvS^n*0mE^HRo3u}ekql0=mAnm1swC4cn;3&7B#)!!n0;w1la`O(#Az_0s zAS?oDFc+l3B#;JOAUb}=Xue9{C+rrs3!8<$dO6|d9yG|!I+(F096DWP&6QHszd)P%#QsrV4c0@R9J0Gc%enl%GrfMjSAKaxksG5!Z|LeIHt2tDsrEbRg5 zd04Tu6Qt)Iilyx!9p}0x)8TllqNf?8|CzG+d=9D!E7)9W`TY%9qDR7rY{CFVBf1)nj_(zr(*nb=4(1tUFDKM zD!LU*9U$YQIoNLvQT^cSU>Qigb&91qARS2unPIXpQP{gog?9X9Yr76ydeorK{I z5F;~V81+oK6U0Es;O-=pt3Y({j53kaLG-hXB#{R*v2+Dw?nXj=Un5S?tQi3b$N||* zDI#-s5?-~Mg(boi;b^)tG$b4lwh6iG2s82vtAyN41mV7n5(!8XI)$-Q#ISHc*duHb z`h}Ika$$+EP?#i)Emn4;COQ5kphws#Y!zZbL$d^B!eSwJBVox#Pgd*_wt+`jV_+C8 zm+%r{lF$J*qW@(?({KWY!R=s|uni1D4hq+T%&j0-P{GvP2{bM;A%Sgl*vG&}#;%Un}8N!g67nFbTw8kunlN>JOc$>sXqWxzfr>LgvG)FkTshtOa~u=>`X=dKZujgg~~va zunatm@Dk9C4Duy>WPy4T(hD-fHjwcRAe$y2^a}HZ1M_jO2mM}<6B^!5F>m!sd^q_S z5jh|xtBhojh7yINDM}s^4hU00K8op>r|1_ZfnBh}KOge0dT6ei^9Mmp_1=i0X8`1s z@9S45rHvpRsR!wRADj)jTI782Cy>)YW}FD}he`PyO?wXEx!`QbIUw)v(?QyEftcGe zqRGl`zc8%m%jl2*y#HfnycXPx1ce|CXM-$BGRUi!8)Qjbik?J}C2=a2_Mf0GMtvYA zy^KbX1*#KP3HwpHR9^qxIH9MBAQkaSk;x%po3Kt;0AljXNCuH4BbKD}3&EX`vqeq; zSz{W=8jH+QHP#F=pL&wl|4Ioc6XptsXR4{UOV|Li6V-w2L?z%D1 z%upN>4hVaM-Jp*pPsIslM{kOvCrK2B+{)uYa56IN1KAW|kvoMYASOd^v7)C4WZ!6= zu1cOQj7?K=Immq1O_T9ohyZ%*1bO2yI2EyI(}-eeKgjqlVI!zRt_RbQaZu4yCgE#A z%wgUVMNf`|r-PV_ylIM_q^Uk-$b|sRE#5>$Pt>KtBOvAq?|`DGN5Z>7ObOmDMNhMY zH-Z@Q-UdZajf7W$7}4HJMNf%@dwn>;Q1%undOD}5k_Lsj;Ag0ebPz+@o2KYV1rH%S z3B*YBx)nVxkTo?pS+NbIUNeZ1=xtK;G)la$Qcm(gDrSQiN#1ltPYTGUNLDNzJx;aV z2xw*`>=w3xgGdqr`Fp=n!fQc}mMW1qD*7_K5|9rvL9WQzAcu_`bb%v@O70W3gY0B} zke!mtUvlE{f^;kwj3L9=B(?eHAc#tCCf`K?Bc|-rnt6f%&j9!-5_Ezz+#oC$rh$(j zJ{9~JOa{#iCaMggATuDD!Jx<_?MFb`4}nbI1jf++GHP&g5ZnmT07(NCB9k;w4$?q6 zNCT-Le|jZ>`~l+v`2!|VvD69j2aH3pR0AQ78Pf7yDKQqQ_v}VR_ zAg4^cP-|w?Ea7-}){L(eRtd|6>p(i@21mi-3F;}CSJ9IXvX7*J)Jp+b02h|FF%1um zR}2d~gh7xc#Cx^ov))=ok00zuNoo{5l^~mrj39iY$RuP>1?YsG0ub+kX5@kxsoosp z&$T-y&VXy(NhCn{-Z3gLjBS_c%Als^3!qF_9D5W!VUQ*0R`hf^@ZwldGk*8P;+gB&Ce4Uo!bX+A+o0&FR|&j9MNcirqLVDT zUu2R+uK`)~5|B?Yvq@u)H{+yf9Ay!^DsLZ%>~DQivhRS*?AyS4m~B<`gupi-H!FG? zKxR+AgYbHhNywfc$m}aXW?x7eFI(A<+En&<_nO&f?pZw3$I>@KHc;I)!W59zp9He{ zouE1W>55&#Mq#zELWtgH76)m~Wl40J$V3-&-R27d<@fxia}z&F8c@J0~j^zj{&$uPn<$0sL&*MTh8wIIu3 zhd~Xz4{{6+qMauXgJ|E$5fBwK8UJfnFC!AyAfvyO$wt;MX0Rvljqo?^^t z&z@yFMK`W}VewqCnX)XMA-CR`FxMDJStiCJGiO&Au1yIJW8jHwCl%&yN?2s%KdFSb zf3tWd10TC~@?4`%0`J+BFw^LS0H+^Zuxy^nAo$%(aVvOfMlzy2d$OfGZDG^TZyycl zRRIqk4H!O}c=%GQbgmc8REMDiYsY;(YgRI=s{lh7y=Hta$O31Bsv4F4xgApU7Z=Yo z6Ldj~3HX9H6SUzUGeHmul<)qpXC$k$rjG37M8;B;sf_HKGTpNo(go49gMUmrAgbo_ zn=++}T`yVox^7Kli{5fs);!jc!mj|cE?-(am;BvJ7_4aa%Q7DI^-S)ajQ#7TwNAqh zl+(KKZ~L?k{5v#lcpA2in?5=nTdKK(ZtT?N4!E)PnR^KT4$g?qz~*wZ24`VQx#YHF z>`9v3l-!&=!PYdVc@FH&ZJmqF>E<@z-*8G#3bx!!8Nt5|^Bd<+uyxJv#=iseBlEG3 zTit@-0wh?_ihuhS^yA;)!uo{{dt~9@!stSqtv9VN&1P#_+`Jeux%Ii&t1h=L7nyqo zJ=mwtGvL9VfEAI7!3u}H{+fns8n3~|ZX0_y_HD%8aM#sdhc9!l9laKA-q3af_7}aO z5C4X5=)u1oH+0^BeLVX&4Q#?LoSXdL#V(+mVmKe%6y1b9`hq%kLd8yXLA{CFp>}N3 zJGVh@+orc~gWT4jw{r_r?0?$8txf$8>9r3bKK7tK`XJ=yW~`&)cC3AX6&Vs?y{!ca8+Pf9yTITsy`JpdrT6VZeD^LrOb$Gy zN1g)vp3?it-e2l{zXThe)*GJ&+n?4u$kyldw&%d6ZoRo13_Y*6J`dKvs5kgt#7X2e zeULkj_P?eNkbQsD`~L_wyskHL2h#S}^$s%Fuh(-Y(onzN+OPk)5R(gfqILWMH_YbF z0yQe6?<-3Ffym1!qx<6f74sNkdS6@s|F$q0W6E0cHu;P%%U9C?;#0(+AsH0kq6|LE z^tj63C-O(9D>>y>6+c7dO(OGe6Fw$*MiS)FARNMnHOfOVc$Z{|&!Wr>o)!7KFhG6l z`%&i4O{|v+#8zeu)`E{)))&dX!us@~2Yb)gq^eTqyFXJ}Kdu5|GO(gTW$^Ygi+Yzbf)G;*eM5 zQPv3J&lCAm+Cz<4-vLdh!{{^FQsrfI7;=HgzA4g1)|Ws(UkwH9q1L}qwc%sT5C*MJ zfb6(~W|c-uRQ#f8O19qauU)Cq7kyjBe@~jo`Y7n<9%WBH0`h71OM+%eu$xtc46Uz# zZd#=bTCaD1LIZHndcS+3$kq$q%a|VVqqy7QQN$X7T#UOQ9_O&Ykkwn>X#d+;#R!Or z!W5Pmaue@RaP*1n6?vajv3!Q4*+jO!I=W5@lz6>L|99!s)>lUlt`UbAkNmYOL?QJi zl^{_RTE)Nv(gfULK?s=oMnfcif^q-azT5qF2Bu!?0b+kv)Tkog;#;PGH;K_x`!O#sVf9-G5 z39ZkL-l9Wjf}tDe9`1~#hWObL53k79S4KTj1J(ycUzHMD9}uk(hppG(?~nqOf6pvG z-k1;%t*?UeBm?U^pMW%h^Fo+M=>{w1J=hff0uFZmJBwC1J(yH*Q6r-IPCN8H}f;=ncLhH zZ&vc3F@!1mu#mQS?35a?K8fKjGVBx9CooIrDTl02U>=*JWdC}VesQvr>qLG^n#B45 zW`)%WZ&v!(t3Kz`tS?+1pRWw`U#S$HOi{A+#mkf8ko7Igl~$98!JkTo)<-F+lA-kx z$~+bjrM5l`IG5@199pwJN0_K44y{VAehZi(5?CJ&oTECW7RApi9_O*j5pR7X(ZmcP zTVF7wvW+39i@hGvx4vQ6N{8_rS}PERBZVqKzhv-^IAne0&@K*?i2h5GL8{0*#o=U; z>!iePk?#J5$B~3`2~v_5D|c$W7O%_|2!NcJ6)ODJQ;L`FQamn&KQ%EV7}iMKu~esHOZ=L0q#_aCQZ{^@{6uEaM_P;!~X ztM7GDt>;LUTHgcjmkc{rsRVbMHGr!n`UVd^&Y^?WhnpD^pN!7OV_%kvw?1ucnWALt zL(-RKDB1inmCZWoBo$zN>bh`|l6wkh4!^WdRI>H4?hyI}9k9OX`+i znQh#qCCygyn;=UVDOd8#;$Xo>C7*-N$#}le&tp}hl2d#VP$gAteaCmN80@=3CE#CU zX~6o7^5!HZTVE$0#4{SkJH!FsNlK2aSLsV7-uejgd&jGIpY@sNsb(+j(yXsQ|HMu> z-hXsrim?+yc5PM$d)Nsf_ui)Dr*o9tCh>cjew_d4ME5e&Lw;Opyp`$4`F&c01ZA z*|$D;{)qNH$k6(RK0^{%Usr!9vbE;Ik07%E*71I}vF|I(=3aR!hF5&}Wy`)SpNP>B zA3q&4ZoK?c4BvS9boZC#yQdo;d_Q6Cw8Ms7I9VQ%pl zpWgeWcyyr$a0!W5;QPz*@(0Hl$UmlruEg{cum7`SAM7d zEZ&e0gC<@+3xg(J{u}yyyxfgG886oy9HP8qOrJO+1%CJj!Eb}Mou`U zFDD-(Up3iSeA4W>XKX*lq29tTJ7{3qig&pG7(3&buW`YPGfuGGGkMSKI}*J1ZQniV ztH!-mS?3u)Izyjo6z@)$XcUJN#*N$Xoo}qKGTIiMw0zI*x`dqxd%kyT)(ofNdMRPt zeZL*Kd{6NYvIeFYvzAYCnTe*YxZxXBS6q8t)zw#ByX z&jSaSOf(|9vVLT^UO*m=JC-dn9{X8Vx3TiXB`HQnYt~PGbpGV&M%khz`Nr*!X6-fV zE{5R;K1He+5Bb(2mIGe5{0ci*>0JjQD;B%EQ@dV%5Eot0{&>_}Ln z&ip$PR`YDbzpegBUsV4$jNx^Yl9@%>o)>p#-EG5yHqJ#xO?%c%<3~F)vyI1-Gp8?F zlC5pLBJ+l-?_}a^;gYOn%TvBMmMqU*lCv~-X`Zor`|>15-ilL~8*`q>`qs3xUNsk| z;g*m`T5sIWT<#VLM5GO;#joOx?Qv>nE-S7F1DQvDtJ3K>jK9rYkee$zb47NmXupEy zd_wKaRrU)NfYpu6Bk%-p%+6fX(in(iyeJiS7(3sy&sF<{?c4sLyN(I zWpVp*MLU#U0d9bJxH^?ya16iBu!~v7bi+S4cYZ$pMjn-N;?6{i;_r92z;p-xK-YT( zZgCM-%=9=Olm7$Ya_F3ObYHHtZ~b4j*_JVG-^s^M`*-`n%>H6)pt!wY9xljBa~O-$ zv)39kA31&IjDNcGh%xn%(@$XU!45lnTa4kCvr_(t&1V+;%gtx@Vkt~4GU{AoEY8SY z?7)=jHa2Etf8~G59v2&}-l&|Kot$(KEAV~#ulBxK9m9nIz0jS6twXr&4bvS$7?*A1 z`5?~GeXsyqlDWs(yY}O;$BK%JzPAcnO&EVj%l^av!`|C~M^&AB-+RwY$Rq?9VITn_ z2@oYfK87JcViOr4Z6iiaZLG9{1f}h0sYZ*68qW+6+YwV6Y;>iVO2D*DEVep_+ z*rql;jcsbv(^Lng&lx1Ou|`eWK;Hj%HE&2)})=b+3DU?zPwc zx_1_iHg10DhH2{IhdAnO6kmz5zR$8V=KCxvd=yh+aH+?ox?U^DNc{kQ0zX74NB&VM zD_A0ya<)~<6e|~gp)Fcsmhx}n<28&D9z$v0#8=tgLXG~2(oT{uvqb_v>oa6;|IpX6 z8sAY^bNG>cetcO+4ej)%ntt{463;qdbcyFnQ$lp^3Rk4vV7huYy_tW)X)JLE4{5~sH6~CSDQX4wpH28m1vF#d9sY;)b>rxfh zdS)Jx6U-5-4?TV4n!!chx6*fZhyPX(^^6uHGc zp*vbNDyfcZJ)RWtKMUXHw#Q45EX|y0c@jM0*z5_%{U3J`ID-Ama=Pbt4Vv~H&DOC4 zj`YGhJYXjJ-65O3sxunVEeR%9rG)JEI`fcqC^GM9wZdncN!4aXzun$!_DTeM^&ezX zH4%Px9$OhcZn})nk=H-(xzKh5KJU`w?Kr|}7k0(ipbNVj4$1L2dOB5w%urA)DD|&b z;+x~Od~W=J#AnDcI}A0bPNa`WyhClN$VpQvm7f0fV-n`WGM2w{Z8|y0;MXDXVQk0R zv^;nstYM1fn@DZA#Z$jFQxer-qUMiAaqOKaNWR3ouomX84Utfth%b3ud2jVx9HH9joZ=oiikAX_mKAg*m!to5P z5XLBoEoiC4cP-NKe6lFUfU!g3`IZ5HsYo9iUn}vBr8@rXhQN7Sn<%vBXa$xqwgw{- z-<_-Dn~*_ld=i#z{B84U@`<(B3S>%r?&$e=+{;QM3z`B8(35<*UTLOr-0W2N{BE83_k z)vN(fnP^`cTP5CknT`*h6Te;JLlV!0b8Px*iQjPfN2bSRB2nlU1wJ$u+Y-$ZfBXt< zpcwJ7E!iRQ6TaXu*fDM=D$pbG!K*(iy#p=r1NE!hJQpuX<@y!B;3m!bbNbvT@hR7< zInL}f_1W9;$WG`7>ekym4+Q!!fMRoP7v=shYh&r>)GGalI^1!yX5l&U`4Ug}GyHWT zep*be$|T;n)rt;SZc$?k;1>n@O5*R_0#r*poiy=xZUGu3zWjC_9|ll{iDqq+_`Ew9 zAHd~!#K!hZwT{yAXk2BmQFTyIL8F2QC<&fxmMN zWQaoB-BiQAYsV z9n>l5Er&n9<)v(K^u8sJ^D|=lv{nl4s@Cf8A4q>>B01FIWoWT861|g6Xj<;*M3N$v zh$s%n%FmA7I3dzEW7LpNYTH(Ac~VsA3Gq%dT^-xuNinOfBjw^INjHQt#*U{(DSX|% z(Zb)71P^o~i66x_5lhZV+$o92BtA)SEHs5FGUf*rX}r0@w(EG7DMRz*d4D| zO!eKb<110fX)%pFEb+x(*YO>Qk4>K{mj;Y)sLu^8Pjl94HLDR*&PeZO|%F`tzyy12iXBg_S% zN3=Kq#I{F=sBdl1E*>)*wO*3i^5^9#PLZ^Wzl35D(-kL)1zSZtw-0*7W_zP{KG`99 zZnfnexD6C(yE=L=d>9@ute;!ay} zsFPP)o)m5F$8>P$F&$i{=YamR#mpuhTdWQ*!q^sRgE~s5h7;13@vP*8)bE^SXxrIh z$Jt`iceS!dZ7D5Iaf+0s%iuo~NyGIMu~x2UM-HEfd>75|Jt?j=iEkAt!;(U0Nr$46 znomEfQOVLA7Af7DBr%^YNyF@7Axk6QLPBw~&Q4~sQW0ecY@CSY)TL8Yo~;s`P@z9X z6XO+mCDcuXTE1jO5+{qT6Or7vb%L&mFv~4tkBCx^fr(f;n01Qewp`W+@!GDYobYX)Eogfw3LHx^Z#(z|31R37|GQJMPzwB!K$M`Cc@jF1q zSAzJLU4j1?zX8PDl3fa7j>z_b4}+=Tx3NCSc4Gr0D%%BOcxNYp*dwwzK(PO0J3#DR z*+Wpmh|Ug!)EfY)*AG&!52Riv2*cU!ApT{y;XmrPg490*Qok8Ifb~&!9X4pV8l++n zq+%6F#T_6GZv|<1BZz<58}J|X%R%awfz&SrshYdXRe6AdWEE+d=A;gVf6c zab(IKM*B0o?_fYj)F6Nfsw83~$OL&H6QqEwfeT~}93X2jf{tbdj)OGV1yZjAq+Y9p z*GPD^gzu2>@_=mkgqcEza1i}L!#%=wVH?O-a%#YD;Mu{DW>JO2ZvZzVz6^9>{E>KT zrnFRK5^_<%hYc#`O9V;9JdsH%dcZ57SAlkB#w8#N<`brZJ5Z1m%_0YgV|jKt`r|Gz zNmz+yX8LWKfo#77xIr3lX%>y3{qKZ4tXb3vPC~*~@D79@(k$98;e{a6=W7<_>G)EQ zW>G3wfb@gtPZr1np5p$`4nBpxYD0&zMvdUJkhg)jTavvM#F8=F2Xbo81b+efI69tk zjj#lK8R6|108G~kh9K8~)Y}d+ok!$;43KLBh^WH`6FNX99Q&p?p#_<+6J)|Fku!v0 ztm&Ap47?tCrQmH~2{?|HDAp|Ufj9DvO3h6k@FvJvU;rDNGO=+B5~hG`0lovpJ{sJs z<$mx^80^z5ss$NeCGp#ZWgxqtRI|tlQf~muVW#ibENTOB`zpJ+7VV#ajj;!G&4)o2 zpdE|@E5VIm1&HN(whLt6^?Y5^2Qq^~VZLw>7fV=>h-OhQ$O80e7BzqzOEopv|L;IV z2mxSGnM9<3G~m`Oa_RWeB+a4`42S|4=mptj-5?9zA#9ZJAh>{C2hIm8CEg>f3GC4| zECe^fKsrc68}8HLNg!)pf{{c+86e{w!uqf2c%B2XWjepA=?1@yK6YttN&?Fe&oign zP%(#QQDEd=U4tQTI~0zC97F@)l`zn+xv3BQBII7pO+DaOP@pc5W2Y6o9Bcwvzy|Ot zuoh$sg+SWh4*q~G0p5b~SGrAa6m2D;un}Yd%E4BYv`n+86r6&IsYJ7=7^EQ|SP8vC z&7wSzb{eat0H6yqU@bTk>1#wLXX0t3q7XLNG*uu=z8z%AODV%J&z8ZWLW$1=E!jWBicWnZ1hkgS%4f}s-JvT7L)`6c#6W3~PYTv2XazWvt zpq>XdfGlXSun^=tF@(X)fZ2f-c`h)=#U!wL}jly1~4;&a>qW>}65aFY+@VDkuF zAPqV}8f^SGZ7>AVU=XCiD$SxDAnk3}+*AS5UYXDX(q1M=d!6@S|7QuC@6iSuK^kn( zEUE`-uugMR5TwCMAzu=rLB3c*gBc+0C4nrU1Ef7evuLbJ+Z)l`bi7Jx+J^udZU^6i zfmVAPdA>4$NryZfzh8(m+4R^n5@ZEb0ZBzDILYJ;?KhI*{iF)!@zGMqwZi z8$X5NOz;mNpS9-+h67{;RboUmO98DnZ)c zrn#vETnRlNcr*6@>{M$5HN}MgA`JF|Te08wXcl#W92<=wGpGldL7iq%EyxUNG&lJ{ zW>5iM2YVYp7ThCD7xG=8n|b^n;0D+OzJvs=Up? z_%`GXAPp9SG*}4IV7_Ki9!P_H6^8bPab%)Beuv<5NZ$+6UZ;@X6|n4oMP@b^0(jco zcdI7-`oD{WRUk926mA4r0KSF90?>^=SX2tK0CeZi0?=JQ3qbe$8(^msWQJ|R24QtT zHcCJifbUn4y*Q*rkdR*US%79Cz2>t3H6RNR(k!Y5S%9GCCVItZ0q6w(Gq6Kv_bdQ? z)BhRrR*-fBe1nVzOF$Yd)-3XYG+3y)iHE?g(Bm6u+%1PeW@Aj49T z=>neVGK6l>nomTg>vy*B;5NhfEcgE&Y%t)Eum9~kHHzxPY< zmIro#E-WxexbrQgi+0K+ zr501C0ZWst`(15!Q?mO}0oIUVG%7`{g0h0Vescna}D znDLk%UfFefH%UMhNClV3oEoXv^;yjZknw|wWyzgqvt~6MM04Cfn5q^=&MNSbqg(H};Xt+aoNZ26UAuJT82nVjx>GFgb0ofSA z3`K+V%1+YjI!Q0(ebFxC14$gUR3-7{!YuG7&`Sq@3pzmRjbT^d`Qs4C)58c@j^opD%}rqt zlj*hr%}xE_r`424uoLoZuoEJ)Zv$DMW{}xz16lrZVF}3WeBi~1PnU3qu=`T2Un#5* z`h;|ePJ73(gOGjeWhbncfDqWuj6wEnzHs0Y?QgReT#bYqK_{3G#)D4bV2Qr+&<`?w zvv4cOf^P(wajDQ%B1;^)I%hh`dO#*f2f1(16Ex+aV$C*Tldu>(4ZVDj`^*@|AN2=?t-?l-8CQeMFHkNU z9`FPLhSutY{lXrQP1gl}4Dn489uk%chu7$I$Aw+OHlbg*L71u;$WForOEl!uCF&4X zfUHqD$UYxhtqT$XS+jm&gRn}t9b|z^zyhStl<*OZSf-<6aq^HwEO8pJ@$XQqm589Q zP?#y~F46_+09l|?kQo(&H0S|mzz!XQTNxD^#u3QfAeT_hAdiIQAk&qC)+tUQHxNK~ z!{iX!nG6e?gmuDdAsv6y(AY&=AlD+1eCvUl>;aJBbgP|*8uLrle(%F`^$1et#%OXuGWGXENo$*Y7LgeAgsVgFLy0nNf7 z$a-%FIpzyN*56o)WgRO*uhvMLJp?j?;~>KaB)nU~YlJ%_zEa}T!S(9f2cCTK1+qc5g48Poxkk(Y*&vZLU7s+>_+DY1uuND2vVSu`6g4|F4Gn@kv)u^Tfe4o< z9-oK%L0Bw?H8*vGG+YL9kH`bLhaEz#sMoBysUBo}6*w8(2I8^U>|qRV>J4db>IPYm z&eVW5+=>8ZuwA%SST0Nv;*0f`Ub_(AtJeh+`h*VQ@i{uaQdl9(5V`}h;Sdhb)(N_W z&BA(NCU}}n2ePIva18QLicS|6HVMnYQ;4U3YH}=D>kWcT7wDFNc44iMeyZ6b6~eJu zI%E2&X2x|On~?sgskZ~aJ=9-$#5k$i5GP=z4X<_dIddUYU;o z8lqonvf8Z^cEYMPDS!-b7S;& zp69#(OFev^E_ENs0ag#vNJzLE>+{)`0A$AjsCD$7fFANgylZ0C|ln zJWXGt>YawE5MfQ&ur5;dfRssIr0NC>k$|qEX&?mBz*g{MU^z&Ak{7wkL?)TO6lD6L zsoe8_(F`Yx>(w8dJ&VjrHKhgf0BcM4&3{0t>p^Bx3$k`qAe(d}$W|%=*|(V>)3qmQ z{Z^3rH6Zm0B|J~UT@oI|G{p2J!hu8_FF3AWwc>qA)l~E7HR-6VSsjNe{z3Ii`ok z55v4CO3%XVT@o@p45KAK1w%drd-*WI8&_im!!tA)PUDub6Xpp_-SD4wiLuR?@ zSNKU}Prt&TX|(SC)Q{g_Q>Prt&* z-tsHV>>D6IjpnZsrh{x|`W0sLJ3%)8aZ|HX*dW9q(TevAJs_)-39>aaG%dfv?1ogx zY)x0JUtu;UUkzb>NH(VvWPQShVO$M%g4codAcm9WS9pW=E6m_h1hYOk2wKCb0i*6q zc%7NUm*lV{%Y<}zO*viY6gFa5F}xMoe-ka=3_b`pf_uSw@C$IgS_`_tkcms6+prNt z0NwVoK`sL;K@;?Yb`a+^6Ap_}dCO7p9vJi?{A*x7SPgo>-C!oTOZ{>7^0YuH)D4J- z5j=wWVR%?>i7_muh9RRpEw{we!CuHLPB+MOo!}3l-wvWbEVsneZ-k6{CvZ!Q`39^7 znJxq#hh7j|1$hTfe)%Ck%PsMx2=F7~BCrDdBf>X;{|hVw2f-5XPoNKc70d^J1$w{^ zFcW+WOb4gIjtjg2bb{xBfK`xm9MnJ{Gga?lPZC1^fKb~Vrh4Ed%tLpmDNlNGfubip z$+M{&#&Lq#^@AAllX}5B)R(o^x1aPZ(iU64lCUj-@ntwJAo)|CPtxeWzib)3=jv=L zkJDQG#Z#VDmT|iAr14mf$ah#Ckx_(ASLY@Jm%%@>&LenrE~|0RL_i~*D&L`=pa*4U z_7f|!KR`t2FVdG2Q=Yb@qDW${+Bko?6G5Av_M`##=$I!WiOK2^JSyu<8s{&k&0io= z2h{%5%H-5UK&uYOen#g?zsocVQ75RpJ;7}`VAi6C4wO;IiTE-Eh$bB{_tceZ4lm*sdD+llV_1XpqpgS za+7?=k>{WFC^G;DxE=I?TR&hn4A3d>fLS*Hx$AYa`*p}2ubZ8(Lk=A?YmPw<9y6tKfwv*|zHRos4S8(P4E_o7@Sr&|XxeOT ze=^(uWWrB!_#N~3JBa9i#~gSEigoXr_3uKidDpCc7m7Xan!WELeE2Ws$X^gX_!o2N zFOUa@&G0bfzG1U}81l&b=Ggm?hu$}b--q1yq1pc-Pg*~<&a&V7rXb%$h-)|eZAMCx~)<@RX*y?H!UtMDhk#+U9`g*XY-d0P7 zzGbWV7TEJGTQ3>;mTi#iKVTa;!0>~%-h*J@W48Xsz^*1+cc2M|JDO~re|5ic3VR`*w$%l?*w}~ZM|gZsIBHGW*Pqa zuEk`D$s_vfoDLaF3-KlrFur+}4p2BO@b}i0kdZj*t>iywAVz-fQ;@MZh`K4s+I+VB z%W^Fb;c&y>EwqOW2gQB~(?iaEim^C3-F$@(=&%CtpzGyYp58_Uto?ClV1i8&-^>h9 zpyC!Cf45}th-7f3WbiAI{Y(%2=&Rs6m>*>8)o$6mf5H?c=|FO zAAQ$*y408lQvOVlT_R7H45ROXuapdX`5oA1^68{bXQHEz3CD|}2!7=2lscgAQi z`f~X9v0S1YeHq*?a`ffz+c#PE92*X3d$+SfEYSD0ypm%Ma_RT2`s3P>B&d}H^QA`7 zm%Zz+)&{#J{vB!J=*!-`w@8E0cf0>46^Ope&CidcOC6~PbpbLZJ}GcW2mI<9tq^@* zYK_!5`ZD)UjCi{2^tWq$AJ!WjBdHH+xn5cz`fm5orHDs?qVIBFFD(##cl%-~P{93| zHn32dB>M6;Z^^U7(KofGN{b9ifqyS8ko1I3Un2&iuX07ChQ$)US$6Yb*{f#i_`t+V z+&NO?=u6w@V`atyWH#vx?v)x=i@`rg4WsX9mq`ty?__WLj84Bz((^m!Y?0_Y*?+SJ zDf&P9680iV&?O1Fqc!}2EgD(2Mc8$arephGkXKB&s``SNY z3!s30iJy!30GlNG!uF6T)QiGqDNyu%?e9ffNaA0SE~x&Y&ahA7hb6v9x+MC(b{=I6 zWZ#oo|5Q2le`~i90}WDu^zUf}J`2G3==<6W#Xy-DI4CU?eP8=YX_4sr*)K{LxLUQn z0cnxw>(_7dv0rB36NTSMhSArgzabe!UyI%^8OUqU##9*-TcyCim19G#*mKh!jvce4 z&-bzbkUu4R!B~JzjDUwE;Ny~EqZs%-ED=Jg5^SF43}6jH6d7uBBB|AdW z1*fHpwu-%X7D@U#ZLj&WTK|xxkN#gE3ARgu@P#@-^nKrN%0$#F8C))ds#^4aEET8| z`92O(R4Dr9ZUEiG7Vec6o+|lSZ{Z?;BQ-}dY}6%mWox-tWJi{kix24dPqK^90?{`% zC$WhkM_>KCgku15k}Obu#R5W(zLNQE77+4CvmF1QV2uz^i~#=TeM-v>BF_>7$3Y?bE7$sNkuPBlVXs}}3(B+{eT(#qQeeC?%K95dr(~Ee3HFGAe374$5(h;dltpFq zo!Pr2gDi=^L0TXr^3$BCP@qbYe<}qiZPxi4w@C|hiNbX>h<>Yh(kh>ED+`1@CFDLvS&jbGGi`dE?A`w+!Hw`I z(Cx{wHC3zi-JW^2N~LZDr$^r@Pe4{Q-*>;-$M}ruBhPhvE;pxViT%gLP$mq$;#q0C zzFb}T3JlIGSG!*EeAnv|y@o_>XsgK0PAzW~nNCcZQF*oc+|NDNPUnjt{C#DL4);~7 zSAOn!#(Yif?7^jk%&)3fdOY(2d>xCwOXrLE1}(QrvaO9;J|yPbMNT+R>lceWCe_Fj zc|2Xm^L;=5GIK@$>so#z)spe0tbID*^DDJN^zHh0^0XZFKG3s5%TaFwub;2wr~`%3 znOZLVnzq-?^pk=U{r<`2TIRDY{9S3qV^DP-(E-<9q7~Xj;UE>_f)fKNl?EW+^t9H` zwBm8ISLEIj9siwYbbOVSL8s9#_TTl=ey5Ql_D|DZ03)+R6xPxJ6oR7g{aKLFqa!zI zgU@{0x{hT;U7fI}a9zuYx;8mHL(7&c6U60+e$;tPhUiC~!SLIp0UC%pcPW(wj+=Fc zpOggR9me>KWDs@s@*>6n)ANyF{_c?UtwBrVJ8D+Ed#9G~lk`zPB1OrVRA``C68tA> zOnIZoFNqxWG%_by$45PyyoT1Jen*wg-~^^y%2Agp+oo$d>O#dWvRmd7*QfoG;C5{Q zX)OcV9Sz?5lM+Y0&+L@~wcV}tmDD(03b0QyC>I&G(=2yzmXWV(}=07f-qK4C#g|tJ~(~m4qSNrBK+oDCY3(i-Q7A(6~mD5SeS0|7pkGM$rq`u8Ov6x6N5`#wq+i*_Y2Dws(F7} zI#cy#EPF|v{&r4+8lDB`hpsn0bC0}n!Lm(u)%h|$e766p$%U%%G35WyVq{poXjzIn z)#pi=q&>2#or{*uRq4Nhhr72IFZ;F{T5dYjKKJA#mGqtE30l3=J$bf>UtDzlk=iB8 z3ga1*dX^1nO;N$($qv=|{pB0fT<@|uGnQ|Li`8{ymn;*mTDDo;TK-aq4`b>^X;>p(_gonSPMU;(P8oLaj4{Akp1 z%)|Wk{ky0vG4bKFH4AO!@|Z?{$1SZmshdd|m7L zvrOZ&+EHu8!OZj`T=F*hrM4;1bAHS;9)@jyxoLE6JKIw$=PCZx;SYBJa4U6<8Nd9< z(80{&_&^%oOHOkbqiHTM$ryD9jlJncXHljT?hI4#!ChytX!V%UvKpUYiNJGa+LVNm z55dR-&Jo-fxcPl}&1{_dNa1**F&egA{6yHc`iTz^)_wXv@2^u~u!id#4nRz~GkkzR|Ea74@G!THrTER#vR^jWR*ST66C) zI1GIg?m6FrF>NHpqH;x6%~FrPn7g2Kzs-2|^DE=_?w@3g?w@Wv8{B2=-9ObBJ%;?= zL~{Bfoh05p{kN`pHUxeDkA0)KvpMSD1sQ4lKY@%is^X>GEah={Q~$@!X0I}Q&t_Jd z&pI8x(aa8G)aim8gq#Gq3-Ul`0o!gT%I`$`vF&y)SPS>CnV!_d5E|pYgKG-*ys7@f z;axZBMTqlWRO2t@&Q!iQuV>M@U1}uq330UIbeP6BNDQ;HsAAVH^|d(fyqRwrdyh;t z;j&e`b4~LZhu6ld=iU-9YHf`Po9kgk4;+kjiB*U}Kdmy7tn-8unC^3z^9S6<52fk}V zrn4TmNq-!;&`!Ux>PwTnA9L!mI5(>wO!6k5_j+f6-!`%;EFNPGyLxSschPJHpB`W7 zK;zKWrXO1tNIcWL4SzR}Hm-zwdvUWoTzICFt*FNYW#;>_?;KhDXSr7CtU z#^gBJn>2aXe773n`d8VFHnzgCsh6nLPVeW;2h_#S<;;2b*_`unSgCvHAE+dF zm#^eF7%}uX2+BBc$~Xx9!*QU#mf)Q?>*Aw&jGzN2#>l~S>PHFQ1%XEqM^C^QJ83(O zcXz^{ZV0^Y1KPOObKy(ffcbQU$*f<=*~KJ2Z===f%{VBBsNJcJ(Rh&fAl zvgpng-8YPa@!f_VDX{Crh}*x`EZG03S-`ZJln+8aZ5H72bif^2f(|x~g8ezhsHFhc z&mqk4R@g;n>KCosxjpj%I{)a@OGdhFX(PX1V7wZ(&$W8+wbQFx-uQ@K<6)|Es?qk> zulhzd+tvS?;@vlUrV+d{)t-7k7KVAvQ;q33gt}+ieFe?xTT{I+Ob^No5xU$r>Wnk? zhAvk(PV<&8)6M!G3>uS-wmV>O+&1H6W1>->2ZLB`*1l#l_Hruv(4O!(;?$p~dGE1u zn`57I|DAuA+r2LDQnO7Rc6onpCaZhT^9CmsBTJu6O`h)km2H7dy)oT;6x60qaz}^ z1D!jfIZdldw>LR%A7^Tn;`XL{D{%aYY!)xrxlu3J5AC}SUa*;EjGUuI9Ur&1VCAv*7~rP2)@yo^NqbD zlYOJAaWFSEyU}KpJ1eZ?(2ALcx%ai}jJ;W#QR1+V>8+!u$^YkI(ZcH5|3Co!*)mDGd#^cOXYUt%nLy8 zgUMJap+$ZZXS9V;PA*m`zq!yix(hckj-_#NBMX<^U4_SXzg$=`$#^1Y!=X4aSQJXc zBsmAGmVYx^_H!k*>!reqUAqe-mm>T^?3V#&3);z=@6x8k<)>{i3QoMVwqP-irh)LR z&LYDM7M-}-H=2+@<;0fpD~!=h3{5|F!0$m*ALS8mo5LgM0)ej&g@ zm>5qng}~DYWF?9?*Y=hT%Yv1_&nbG;-G>lHy zua~E;!P$v5I=fMuGov$Ek4{)QYjiF!_9EAl6&QEch;|rxGu5jXcxNPjfFWrO&1ov9 zbNQ-dh#yQdtP%JSMjJ;ON1!U3oSV9s`9#J$3o^&XSH6xG=V}rc-mFDaXx@tj&5n_k zINNZ)uXbMOow1mZd@`Ug-1H&h~6C63!>on$qV)v&<;+)?EP)gMtA3R8&7u&7?=0*b&F{UI zAHw=DvKf;B_+hedG#S%o1j~vvGx_9vEHZgE^5I-Ppg-d;NR z<7sQQ+GQ(fe(!hr&Z$_=?B0MgexvTo7^AZ=+?hu=7MP8@ud>3KR|s+#(=qj!=hc53 zfBLmk<8Axb8i(JUsvhmgx!Sx~ebAA!G+?(Egm>eBgV9gzXl@bvx`O6OMqW66%@drw zxp?B_&BYTZ@9}`6CCrJP6E~(5J#ojij0YyQgwr~UBA5nGVE)8AN~0CCf<@zj_?E_9 zKY{*N3#|!0kkArYy1pfF!@>BLV+${Bx$&#c7C#R0*pJ%$Yi)Zooj3xZV9qtRz2kw& zEs?8vU=RL^ix|fHKOvJknudDDeA`}DAQE`708iblilkxECSO2vkn94G%~?UVH6{Yl+sOzh<3{V|bYq#Ng3;ari~#+0RvEu)a{!ju!n)W(9O z8|i)4L~mcG;(wg;C$+IZHzmNYDZFntt;Im(daW6GYi$7@RL%?UK8l0oi`Kz1yc-8a zw)Y}wZ!8SLc4_bB(%$2amhtg8^utdIBdcTDdK`UV@78TS9^VpKbP4p(yx)y4YKg?J zZ}Fq8v7#7dd;1+R?TrFi?LD5@68W@lZ>x~fwyJlb*B$WN(VF{lG$B`G1_MvseFa+c zavTva!x6ExW!$chh8%F$O)|>&Uu(SkCMMG1%|^ioc4IG|+kF}*A`?9sn@a?T+iuL? zZ?Q4uXl3Pxu<%mfbZb;IV?P71*nkh*7jsJDMj_c%&hK;Q;d4_t7sOc$F!jzaGiRaI z`LPxgr%#_8N!;WsI5{PFol`F-tYt;>d%w>QL4Ml)*^%Fv#?$lAE~uxj<~Ve%qqe$fnfLas2br0i z4*7A8a|4bnH@<1?U5&z*n4x_MI3v9eN08+>8|IOm$3ORFz80%sX!~wd94o^++1^&c z(HAUJ7&4FOZ^E{!=vrRkwbr>P6-(C)K84b~htkEaea3FS{LH$?uD-C`yLHZ)l~HW$ zduV(WUhJKzzI~;4p{E-~j8?l1hWlW6J*#ck)n;4?s?8ND_aT+O!s}MmdEPa)@uTXe zdER-6XBtucInO)iypL?KSw}o8yqB3`C1gTkzRvS95OhbB!g_#QT^YuHy1xwR+%pxvM5Ye1El}(zbhNe!{Z5 z+AJtxi+=?zPP=cNS?OUqI1?iu^X@L;xb*=a<|&(^ExkfI$?F4Slu0Cb^3p~ zv~iYIT!hb^m!8S-Iz!AYj4?M=7g8mCm|Ia~ee?%Cck1rT(3GXv*)K)++x5&DuY2yJ z;@Hf?%6vO_#tPQ>v6l>tVLjGZ`={)iMmSEb$r9fc%8W+~!`wmkQ9>%uLYec@REO7V2nen>y{q5X{C3eY1-9;tGwQLJ=?5R(>}(d+se(s8InP(c{CA-xC@4sL}`4P9=yfcEjT8(qP zu5+Eu$g?I|WVHlGiZ6LTu2z4{JL^-pXmkUzu)?1FM9XGOz*~_0uEY233rl_RBfvmvj||H~U7*IfY!WE*{KTwFTnv^@g7NU%+gWq?@}LWOENEp}Et#3PRIH zR(*i}{teEX#slt__xIzPg59=vm@*Dp!#HT&iR*?tuu@uWPdRxE+dium;lUm+hZH&H+^QaTpGZ*9P0#B-G?FDu8V((mA^Az>^#opBOCbyhejRYv# z4OqXPcnQ~TR(oepx{&>Lg}SQ<{f1&S|AqZ_*yqjjLh2qu*WIn_xf=&_wyhIcM6Nk| zTpdHD(LO5eV()dTWwm#1;Kag`mJ^TJTTWbxJQo?GCs@F@jEMrWdc1tV<3j}Dmen-9 zAdKKrT_%P`;9nPeCN8{s zFH_%nCuiEMV^|Z0QKpGqFJKfjJJo5QH`$KM!z$>*S%PPcchmGwLJ@sN#bb{#kwfiY z$EGg0b|w!)Ib2OIa}{4RL3nqJxOt0gT2z)DX!5i%@Ow&%*IZ! z?_n)7F~z=ty_E;Cn4Kyz3-W%QzRj|2JpN=%9x8J!4&5xtJWSA`8PJYLRbn(^`f#T5 z6np2Z-~BmfT3T#l?EI(de_H3Ao9>sq^MxGhnUhCWMZ;BfvG>4|b9N!_T5rkR$Mp*v z=si>>^1_SiijR9|O@R@vRkL6eWA)aLd#_KRT~7FStJgp7UGxunV&;*oPk5_MCvQ!0 zfpnyKEcdQBbM2AujpY8!rUo6JDQ2UZRObDX`r{ip=gl)7AD@g%dbnUSJ{O#U6PSg@ zUOZ`FUEWi*ah{o9TrDdI>`W|YtVyf@G{-+X-=gsno_}E!QvW|=&bzFG_eT`gYF^1Oyjwkb zr8iB@9?D5p8;{_*gUFTM#pb~y(?9J^PEwdOH4jNBidP;o!wj4~T2oK{h{R~D^3+~^o z!l66?Yk~2v8n+z70qX?Lz!#gog4HvPThdKK{b)0;%l%B)cLjDK;}@k6FPa5v=*_k1fvb)DmCsze7AJc~%dx)(em#!sWYN>nBRI&U;PhlR z{(fsqz8_1E+RxdIy*p5i%xU`O!0&9ng3N&9(Eh^f@s<_yKzg3-MI3#Ne}OP z!ij~62)7`T#sk%k-SP8dhK?FC191;GgJEHs0=3V9X52;zzX zR_o4;GglD|Hd3m4o<}Ag&_p-1XB!!{K;h99I!| zH6fu4`x+kOfBeMKOIs3J_Ov{Xi9HiXvU9H>I4f`vK?@|b8_#aX3LG~MTJo?EwV8g zc5Gs~+0DJ9`;CIXF68Ml=bPl^))bD*1dm7d<8%NQZR!`A zdvOcmPc34jf-6Y ztFq85;T&+#DDc1OD`@^;JTLI3QScP{cnmkjqB@wGSBxO+Hw%h!@kQGYO*Qf!!PvTe zx^MJ`Ilj?1YzZfQGmP?|p`2Lj63>KaYUG@gy15uBa25nO0|)|rQ^_+$2#v@r+%;#$%g&4M(;R*+Ve(2`aR{dSf3 zdGER<2Yt_WyNwn8&6r!!<~Fp)G2C^;v#0L2F*jX;_Fjsz1k73Lj?a7NU+4dDd@jXx{fBU#U&{nPg>VQWb!_WPKZ8|u@Snwm1%uH z*rh)By!X<=zl^WIlS*z55`4-wI!LUnsLC-CLRM+^Wb3vS@9;#&K$_b51@Bx}?3nvJ zx1D8RWE`f^2Cg#-ER7`0?d8KL1VgP5 z#{ulT%9G(q$Gugf&Xgu#y{uh!@{xUC^j>HC|Il})kC^K9Ph`#c=U-qB@Ifh#9UQg( z`(I#QxVFHVh`Y6R+#S8ud^Uoy=KnE{2Qwh=g8b_>E&iVvqr4W`jq!To4Id83#*DYI zh8nkFqIfdo*jIA+K9&1r@4SSy=H8LV))f5gP4$^CdlyZ@c>URRrrP~w?~(x0JCB-s zPhbVZi^V((b+(4~-H0c%_)v%sPl{ZE#Wp*(9XmF!V_Kbx10chA8IyzexA@~An!>@G zrsEW^`^h;x&d2}mYtc-vhSCdCQlb}O7{X6$lUA(fl8e*-^;e_=|6^Z~o-D16*~e;a z%sy6YFIsE0wjWd8+vC0hUUD6rt1r3EKOBnN7sB;c>r>N-{&nWw53ne{;VqSOvv=;I zg*YQUX6#K|W8%)Xam!+J&%XJXgc3}1uXBsJHxt)9c{EYIOFZ*8W4#+eLvq!913Tc} zm#jy1;L$p^$a`aBNpJiD9f9P-JrFHSGrcDUm1>Bc87S6 zK$hMA^!wM={%+I_U-4sHfHZLd(%9?65`_m-ca6HX(mQwZ12Iq6_6$TlG0a*5;bx8%j*;HY`zdHJuJ3tY zrGy;sv=@ZzYU+RH%$+PtmFquqX0M7_<5=tV*wdVt`zTgx^QykIDHEpzK0rh8 z)-;wS_`J8?<}4rGW>KAXV@32-g%3^O8JSzQVCBUkJF#4GCZPD{>es9WL|A5e@JbvM z-<`QLxbNY$(I(T6PBP6KH?GKk7|)VmdsE%Z=svcczH+G_&7{&ZyEnBzjjNY$O7~(v z{JZPS|C_yNj}}QkTY>7MSI_j`cJ!R}h)~RPVi7DXLs*6JIf`zOx8Lr=^A(Hn%pUKY z%}T(H3|tS2d=kg}dkpJ2lZm@#<1#O;@~J;3dg4@Nt~V~gZeDF&8eL^QGXhzk&i(!M zU)-r2wT2K18EcehaVOb)OJ(@b9(!CSIokD8##LK3X{`P9*bB@N7-m~?S!j(%)KY~L zG3wbax@TrtPrkxmBq1RrH(^BwhpmXP-QRWq^jN= zPa%f1@%SBH$HcnlIAlJ}`%xDVW?B~zW@3-#@Z>|m$!9&;8^J{fKG_RePxeM|@qthF z;)4Dt&*r1YkO-zn>)5l}m#uf@e{%65Q|3NDs5ZWwGdnqE+QTyqXLiwsl*K7?xm(59 z*IkLcF$<|*=6U9;-rKw@?C2=f`m=(REbBr7I*?t(2UDz>c0bPfnJY$!lOqo!gX>T= zWcRpL)zO@!ENi-r8Av58N6bKSQkH?lWcU~*uUJG!ii0CX_2zlf)zIzUY)lVl?Nhg5 zpIU))!E}rdH@cB)gV=q}ipRdx37fBBKcZb$pcy9#+@~U#b`rks8#!^Qem?WX3ddz?)HA#1lQi&*b@T6CcO@Le>mb zyUROs70yj~E`5w^=a^mDkFJP5N*lyMOdh4R4q(<1yZ2B|>XDIM-YmQ7y&&Iptxv9qc%L~kQHFbk$k^MP58#q+77$fdU@64-H^)6E(5Bm5~ z+-9s6{CEVEyHbTb$iv$+-KZj;%XVT6M<0{rWx9&!!##+@{T)2Bjfu;(a`BvP=Huyj z=5ca?f4Q!;Gjf+D$5d0gP7kHt z>A4sD!^oi@dplM83*K})p4wI~%*>q`a5fw*;JA#gfM#I@RA;S#^n-Lf#Gnmuf5DFB zr+!++I_P4o9$jyZRd*-GFm*ch*mig7{lLvf!x>(IDbb?D%lLF-C3e0n#nP;$ShO(F z%#^PacSkY?vYyWyHz$WoN0VvPm|Vq;kDCjPMw}csnohh7)NBT^-E2!8v>9O=Ln5{s zQ(bk`d*K(aG`bPjZ8~v8>@{)Of6R0TlZ+9&Yh<#~?r`Ce%5a>sHp!@)Jwg)Qp(LX{u@w`RmA}jAhuwbDfmPOkS*9+p@Lb@@+u*EA zG%Aw92}Y021xJO|_N3uNqr>j3aT-GoS5Jab9p?-?jheWmCZ|!aR=?=I`1}&QV(A`o z7~7Mo{;wV!PMw?2X5}Rk+_$>qzQ|H`*LYb@4_h zp24;8BdBh)>h#dpYEK%pqiVRpi_CK>owf1Cj-+5bE?T%763xmx25pL1-I(Pg?KlhB&Zwn@f-&DA)`7_vDBN zIh^o?SnKGps~^wJogKKn(iuudW_`2JP)R+rjA2_!gPYk5&oH{}Nu4u{I)@9cN1M>U z(~atQSH~=)G2YcS%jk{w;Uea6g0p9~QJv^)n{BjDcGb=@YNsUi&NL!ZU592G?bDJ5 z-A2F5)$cY2UCt)AG3@G3kjgK|H#PA#SyC|32$~t4PNU7{R5x{bFI1J+V`=krm)Dh; zhoeGL2hJ>;)GxcduLtVQq^3lp$!y29(Sf+)rb+h3N$7@9d=eg{uZur~)`aVwdv^SF ze0=TLE-(-pMYdrd6gF|3>;4x`3i277i_*kKIXGuUg6 zP#kLRL%TbuFy?Sn$HfH$aolH+fi-rq<+3uGX-=+6>9X7F%!n0X-ZO$L3*(Q>Hj}E& zjF7{Q-AYP^Gy_)Ld^0)S*=)Dhnyv;r6g+Kqd%uYhY1wZ-qcdjdMC|rP)3vbCZf`Za z#8w@Z#?A8#G!XuhaCXvdHXnKAC%CMoJU{cc&sYlYsy<`BaTDI#z0~+PJ_EQ#t#LT; z;@%U>9yC?>M$Z@2MX6J!sy0tT@sY|NZ@t})w;k2@FI;lIeE=Ijzi>&>;^H0_3a+upYX=sY5hc`&r<%5Td@mL6SX*uDol&9xkGHa zlGMHrSEc!qwAyw6niE+!iW+7V>vwK^tHh6~y{~%Ha9i;(kWf5TYqzUUErMUSG4-w$ ziqXhlP=C@~q(*)ReDT)vbR6D;Fz|rgME;#(zX#VH`D;>#6AO|PDtSc2uT~wq4a?=( zTIyEjUzVo?M8eyc20C)WRLU>3)-Y}%@yGXyW8kldiQlnQ$CqGa z#}1ky9Um|{vp-tFjkj$1!_6z}N2i~$?U66>S<7^M$Z>WB$|SyRg^qWgQ-C@vK7c)E zl~yQ*0lWl0(O}JDfCmizM(`I~g8_*jk@y~zF1CO}65r)x>c3B)`AY%iL z%GrafkP7%Yuh8`4zUNt8An}#uYWeGC4!pQBJF1_n3k_zmamMcXC5_<9{*jP$WB*(UL! ztvcSLj?TzVDG|vH)!5>6iFVHywKf(pXYI3LiEsLnj(4b|m|@>dM)*hcIV?M6;4_+fp1R{V&>M?EG~p3{k` zztZWdgPQ5*bW)yM|2K5J zTWy(~onn5&vXKy!P=}hIu$G~Jjmw^wP%oiQ-TsXtqQ%FvQk)Z!W=V03WbHy76OoqK zB_cBY>qQf~>1@$t+lVg1f-zAVLIGpjYD`K#SgW3UXL%Yw@XcS6m3nd1u}leqVpFC5 z+R`%0MI9ZB%o2hky423UU6hjfpjHZDti~Stvm|lK!#dur&Udz7{X%dl()7bjeij@(G z=lG3{ZUg($*Ml8Lq#oT-t&izQ&b6@x9g@W9+;#E8s|je`1$p1mB8wKQZuIGJ*LT(W?A&QA#on7ZVAnYqN*GxYuiNJe$L2n zOZJ(qT74VJ7TaUv?Yj%24%uY3DN_Lv_s1Ee0ol`LL9DQgG z+NVtd^VMF?wIY?Pqe$m`Q70&eLlU%B-C3e7@}k<4lbdF~q#oCJMdNLvOC{!7SOPe^ zqIN#jhrRe^Ep69Un?)>FrX52>q>cLgX2VakZkaV=nnm*IOGXN=sZwv6D+#*RxYW z%>UVL@P{D31Ih#=816`rJq%Jm0#bhfq<$~Rj5@)CkPm@O*96ueydHcCavk^(>;h+#m~-1hN7n*x#u? z1maMT-3LAn_JBVKV5314>O^6OC{$7b@ji*qm-tkPcT4m8f=piyGJPq?^m!oDXMs$g8Ndb&xIr3lfi%Da5e@MB7&I{Qk~RH-EKn=+B)gaRcg4m#eZ6FQ!K^mw4XD?gHCxJ}w0GWOahh(Nd4l?}!i1dN%er(V{7f1u0APuyGG|&ptKr=`K zwIB_IKo+1HWco^w>9>JQzZGQqjUdyPgG^rta&YH?-{t=A!NzwGkO4M>n=r_|rVnI%tAsa8c!PxR02`sVP2x9#oH5Hl&X^@&1NZ-YY)~-^WCp1q z6%meKs8-}4=!RYi2q%fzhJ=s2pzU;l0Uj)xuyG%_ z4g4mUDXjgmo&l>s#+QPuah@Qyagd6wAe+z+Qm;(MmzHW4Rbv5;6U5S>W|0qMiyg;0of$R=>1UXBszAnP z1+YPd2-f#j(}Ps#dqyi%gADfxx8t;ca-OiTP0I~9V$DSOR*>;cIAW3c!de`;N+E|p zR$v3jd;&c=37H_!-4AQuo-ID#aD6(D7gun-3Z8l=Z|+S>@? zTPoSD*f;%P6Zin^_^^+%03HxyFOZ#%4Q99;?_+1g#2mfDeG&$(`t9 zen|l=8Uc6XBZtG9oA^bBuR%VpxhV|pK{|S9XO|9RpAVTBe}mQrzGDeiq0(KNMXg{W zGXhzoc1#XrDkllh39`V$m@GJEhBS+6LF)Bz(g3@K)gbK$HH#`hR{#oS*ti=JrJ6-< zMj(9PfKJe_S=0wouUE6E1H1xy)r}|!4Qmz^g8x@{=N}bUb@lz32}vLsCL|#Q5|RuF zCM1Dm$V^~BMu{Ox6cv?PjL;Azii!r6T5K^hQL3m|MsWDGc%#L z3Of#A*=WeE;0eT30rJq6fnL@i3p;pNn-VV^ddg&41)D)dPd9iw((6ImSA(osnaCx= zERZ#HD|$wrlokNlLY<19CXfNuKZ)_b5IciUsEGPO7SIf`GZlgClzAZg$k;(S6vAYX zfg}kNg*}g}K)Mw@Z6E^(D0-?vws865Ue%;!Q2c;Dp(hDk%$YSAQS=OhNY5Km^z?#k z`WBGKdI-;Z*yJ6;3Xl~k0)K-TM{#zeT|gKO@(@_P*r6d$f8@Y^mCy;UN5&Ai4r~LN zUMcLUS1scMIm}8yrZ+yO^!XrLtPW=-`Y8jMo-6EoM5TMnv2!nmNtt4CDR=`6OTar3 zOOc`{A7sS2;6q>*$YJOLZ$f^DVsSF)gPf#ToCtmo0mOqGetaf(Jvipq1M;SfVCNPn z20<3k53+!6@MUD|0>6u-PDM`#NJniT1K{HJuOhug(bEhj;JnbJ=xGG$rva>lUA>~G z4t#*)e-x)TJpeF@7a4FZFES#NkUjk%TjnIlrtAipuNk}@j`_?J^wdjw9e4%OeZo?Z z6)Xa8ML;gFisOF>=S6-NYZMlOzk@+E_%QTVa1{aySM-E|d(p%^pMu4059vSCm(l>5 zVebZcSs$uXECqiLf7u`dcYrJ}8q~*s?}I9`9*_pr;QPo>1~PyWkO34adI~`XP@q_x z4c-sCM36O%2kEEl0p+I+q@Pxhep(bg%@1Jw(@_%?tVtb6M-?C)m4S4W2QnZxNJkEk zj*=BUNg(|sDi%k9^fP+D$@CTS4TH=#2r}P*qNo3UjDKeAgMtBcf+NV#0Ww3IGRzAo zdRjr+w$4U=+w>JbbUJX$QC;vJb2St3f_baDlO4BKU1E7Np(K4^_F|ZrSM+wt+0Dszy1^ z2k9^mq{A$*8-XT+bQBA+#!(<^Y*qB|3mMipOtHA<9_6PSq@Q;1x4i#v!VVqP3w9Aa}I0s}CW`Si0$OW>mB!av#F^N2Ux6=0udxZ61JjZ`6cHV}AN=Yc! z6L3)xMuC4r`tUx*K9B*P1Q}qDqNf{VfL)5kO&|ko0Ix%SKga-mAOkF-%<-Qm30cBq z;jm8`4uA}>7yL8W0WyF#kO2e~J*^-EXi+TofefGuEJeO@kO5>19iTq`O`;gYXrMta z_!cs>f+dhEK^~$~VLr$JazO@=qv**789@tA zK~}^Heg}TNBe=-ZU_jU+^a~3?)^Mmwu>m}dj1?dQDi!8~dY=IGKB4Hz2K7FnSUilI zfxGArL`!*72C&13LSPXbw}1?&QRowv3$s85)OUwsHTXI5m4FPWK$s0OAUDW>T#6nC z$bgb{dQHWHyt{y5Kgfc6gaKhA$eYZ1kjK1M^c5ga*(D+u3f;makoJip$Ab)j~bEPy^n!ya%@2rdTK(s8B7_2Qq+8@Y}Gf2OmWf`W1^SMXnIJ0K`17lpN9X z_a|UqT)0R1af7^Dj@^Ut&nD`-Mb)$u>_Y+V;11|p!0&*y;MJfVtfKyARdX^KHE$M~ zgzRYq^P~^d&0c1@p!9wUuM9v2}eB2;gAW`(u z!Z6{;jVk|uuovuyeFMn+wID0daD%EqgBLrjLA@wQ)}Rj5j~Vze0|6_MF$-LUSlx=A zMDTX#c?tXkc3s!2iiAKGR11C!`bv-$D*_83=Zfq-iA$4y>_Hkd2#bU+kWClZt zrNS(b>CL#pdnFevO0)(K05`NC{rw6J%l3cN*F1JYk5 zI0GycIa}xuj^O;OU;hKxVa8N=5Hda+Ynjh%f^R4Eo$g5@x*0562Eaz#%m$d6t{ARYHzZ8E)s zW7`e(fC2FLU?s=^%0X5jAG`$mERmBz{bF=A#(y*ttdcOcLuD8e_6fU%K9CtJKo(pA zehlV=JcBwwj+(x!Q~)PQq<4V~q)F%(mR{vm33;M$gRDt1xDy#haS~unhCw#r0LUin z1KEVVip3{EwqTE9aW}{o>;l<>or<0gQ2+1>vH~q$?6AN}kP#LM^FbDz3+5s{N%U4> z56(iguM(CCbA@i<(3Q%rPuL{%gTIDfZzXp0VJ95G*B(w{oggo2O zzVkB0Hjp)K75YI2T%;4@-;Et+NCG+R!bKjN#4*kAxd!ho;|Tn>G`&?kBA0y&K$yg4#v) z3;Bz!EYAh%_y2&M^tHk&VU{o%EQGxsRh4tNLe|HH8JFC>hj$1vlduvOS7 z%mO*=93W3j?dU-~(F8!|YXqa=r&{C!kOjIxUK^r8UK>K_V9eJJ>d*fhvEzYZt**ea z0<$1zi);nYhCF(%DyS38gd7mLMpz=u7e))iKz6>-bENNp47iKr_-_|QwXhsyh7yoX zmo0jS&@LQ6)O2(bqU2pfd8LYHt9&8L@>DdWEdigXlEFA1f>0%5E$T-cML9E5~5 z!b*^xt`ua&V!?kwwt_t6j=EL8L1C-gs}zl*s1ufe94-YQJ6)F00rH&Rj%fAH2eKxO zpx*gJE(bqG{vwbqmIboK;w9ZG9QCHDJDPfsj%q~i+pIqK2S66o0y4cpSS!pECJFmo zm@0*W+wqS9HG|AoFRT$(3cX3#;Vo5}Q+-k?0GS~NWDV>fYhVIdgU(GVy-nCC^a(43 zxk3}jt6xv5VhhL?Z3NMh-jq`8a6Bi0tf?JDo|Gt%febnndxc%Xdhj&tTwoFG!a(L9 z+oP;~HTJ$QtK@yj>3i8SvO@Wj`bg2+M_~AnmeOWBj8P zQru9W5-ARlPcD+h&;;^H$Jklw3B~}(z{)|M2l7FVimpUu*Qr?C1~RXa#w! ztAquj&jEREw~HJl9E?}_x`ge*0+0pgf-D#dc<3$I4_?9N{~g%53<<4Z6dtFMe4N%I zGRenj&EVy*D+hT9N0Wv?y{K+Dd%%21@|L_u(e-KpHf9@cm3H%ON1r~t# zB?uE5C9hP`QvhBAeIAHP=jAGT9HLJGQOUeSMUPeVVIa!RGbwt8jP zxS-%uthPn!=~h6|Qx9GXeI1AqlUJ+g$pcxS1DppAM61ye0HYySgY=sV(r@QNgie+cntBkfchI6AmeQW8Lto2kF4km zMNSrCr3gK}7i8Q$Ami><^mKs?yHn8<3Ny{XJ0{xOh-Vic?=>TV85>oGyaq*2y~>d1 zSM=0?tS`y>`a~vK-)fNcEdY5wbCBAyyp%Ny?uXbvL)G^rh~oFYB*nKwX7OBA;x)wG zs_1C}SE86^MNb3B;>mT;*NaR-_V_^-Uk0-H98!DBD1OYMiXR41{D$DV81Dlxd!*)88*fccMBEYSss*LA^V%7uJ|fcjXEb zh2g?sbRVYI3Iizaag)i`0-^|86NvkLTLXx?*!aHoVLkFXes<%Puv%zRns?b05NGcvT?XRDil0|F<^UwcHmS%B&w3 zJeu(Wsh0-oX4!jY$42q;sAo2`{@WvBwNNN5Oe^nP7p}@}TC^lKiWfv$20-RNaOK8I zm28h$;?R13z0QiT@A@|8;i9tN{93Q%o@bqQfW(L3+stRj@HjlC;`3hcs=| zZ`MV_&2w!I-Oby-(cSz9L}=f#JBi3&I2IQ$b3ZODbQ%)2gL;S{)0;%!AbPBrqw9U5 zuM&NQ=&?{+1mZ`erM`i`$Lz zd=JgHM^(XwW)=9Sn}v?s+y|~U21U>Qxl&@&uT$d@-QN?P>3Y9EFgJ#L{>^pE$+zFs zYxCO6j_16+(S6agEgdUED`#68RyN}QNWy5sY)fxKAO6=R`V(hchF6WOnr-P`)r0>n zt6K5DZj~SZ`x6I{+MU>g|9z|bSI@RIu4!5`+Y(ySiT}0h>(c;ra-kKgZqb}EJybIR9XKr}U z95$ot59aPan0x*JRr5#YmXDwc^_#t&{pPNI><{;wNBYg9-1VO_*Pk*soPuTd$L5}o z!L}iD`w*PB4w(Z(kUNLXUBi$&hRva2%3qkfzM%YtIrIhO+B4?5Gmw2}%r$42{*Afo z8>W9_4t)bTu+P%A&k}BF@$R#>LgGVewN08ok#L`cW9JP!d zwaoTfhK^c>v11v0(K7TR82GuR?dM?k&n-P<=c|^kSHboUOGgJd_?l(tHPCm=QgaOK zecjUcI@t5NS zvkbn+3}0Akz5u(wwDf!lj(%ks`-<{uOV4R=;Iw6sY&v6UK7$d6*D`Lxl?>z8*sj1; z$A&w=&`G6nl`8owk@?1i4LPxNlfHd$6RzWv74f@Zz;l3P2rr*1@1_H!H%a=BcR)sg zMei$ze%eDmB=Rx(gPi<(m7ZRr!#NURpG3I&8kPP}DR7rK*d+GP z<34~DFqWRmQ!NtK7*YkMen+JnOHbj&J-q?}@%J76*aDl-`mDi5)bi_8de$*z|6A4=0T~NQ{ha}zg1HiKU$K%e6xkarQi?oLe0jB!FA;gG z$oV3_AWeR`$N_2c0+H91s{B`q94;+bDDw7fq~re6+N%fbHGR88W!NDG)hqxtFqW*c zF&(n8?o_ihsj<$~KGq27#=279XN4de>qxar3!5I%3p72t6zObnUB>aB#Tp`mv69qI z3BXu6if=k0Ls+#m@pjf2_QuLlugD>hm7+{FatMrtp}vy*ZIb_=(r5IAp-{eQ&yA{p zAu)JFYG|wswL@yCR)WHfY?-ppKCWtXh1A$s213-=aBLBCvh_O;sp2+&jP{~o}v$E3hPY2sl?zl{N76qNo!+3%GW zj_T3tZ!+~?uL?94Nw`3o+}@)y{D&N(@nsXF$z@3?6JID|pDX#NWR*VX8RG>nG zzft}&B)!cm1w1U{IOl{i*eFfZB(hIxY%Cge4sICX&}1wb@~IRsCicbB3H!zWO;TfH znW)tgfY(^uX{R{I>QVu$k|u9@N6Gsny-LzAlmd*!oi5$2Y8a68LMhN#&Z$z;!`@K- zT&jh=CSyG(j}(w21}W0S#(GZc#6i8J?~*2$^_)yUk@Ox(e@kj;Ea$XPWMlcJD7}I> z{>FMu33AMhWtNsm0LHRPH_IU~)ycdO0?h-ks6f$)A@2T_=X#ryup!!WnhYw>F zAbvwi*;odsZljW$pH%suE|TlNUmX5PA~Y8Kxhz9v@I9q6@Y`-WtP#0dI%z=if4Lax z2)I`Qeog{37WLVhuJRj;`)o?X@u$OqBdUOUX~JP~_`C#UlIwL?xUx4^*!hbb<6Jq$ zH%oqFX`Sn&4;f48d=;Yt9Fzbnyb__Y5YAbfRRM_|Dxg@Gl8qH{uAQMA5^`-FB1Fz+@;d(O7@ywWRoF7o*4XrqX4q8GRl?`C1*+cR!MJ_ zfb%8bYLP=6hREM2a=TREq{vn&uU(gM{PQHjE-{!XH3*6P$xX_kvDQq21k@k_9b_j& zfrDZnlmLx&RJ2_xy-w_}6Z=ZaC|{rZLI)->;Cs3px5jPsdOE}~ZIE$1i#0$mZ<1mA zFIE88gTjyK3^%jq;!7vkhs3^MDZk?lnR*{q>9@>Nawp^sR8``+4Gs7>n%j9|FyvQq zuB2mivk&Vdmuo(kGu(T9zRF}Qb%x(h)14&Z1i`Igy^@V}xpvx>oUG}|ra_U7b-BKp ztI{h9RQi1ghCvxilSQD$lp7A~>0T3G;-jK7TV)vAu4KL&#cl4lm0T^=-k3rer%yh! z!?yeqB^%2{=A5VGj&zm&OGDnGe4;Scz5LoR_?F5Ltp|j&l@$2>IjR6- z!N)@@lx!^Uc&W(kIL&f3rqj-OH71 z5{I8jjf{0>Tdz>*{62?URJ4+t5Gc1NMK+eny#h^7|NJJ4+j){7EBvzlc%-^e8H|a+ zmEs^;WRo~BmhZh+Z$k9+oAm6awbDYyN`imXn>=LdkQO^MoBq)WMb6O=VaU|_uqxkl zi{1$_!Ec>%*p;jTGM1y=D}7)HW02d{i&eU@8tm`%0`N>2Cmn7!DS&fzxs{+I48T~v zuuSZ8yg2P~dshMocPM$;MXCT}J=?GJVThAKib{W00x*`F{hdDS@I8lgx{Z2)_;hot zvfr^$`7_oP9XMCX-ccE5d!&=N(RAFN%~l4+;>D2?v9a{<_by|4$Yd<&yV*E|^YrYd z_On#Fv0mYj*e9-3={{xeHRYfea(h>fA=h8v_UIzzz*rP76X!S<81)mCen6VkSUB#K zG&vXj;IgwSa3zLJe) z0Qcr7*;wxIfkY+OA652ENlG@>+w-qgvayceGYOErD9~8AuLVuR0P=sS9K3ptl8rU_ zp7n@>U#Rrg)+pInr0NjH`~yk{N8KMV9fstoSIZ4G7LBT6o0 z4QKhruTZD9D!JlAl|IB_iFAj^tF9DTu3(#(f2MEziuV%pL;lMjRQ@-%VEj|jC5pp1 zo2gP5k*f&W2 zYGh-6V^P{fzL!CTu?lUx7#NH6zB*r}8_VpbOS-X0?+23JM9he9fDVdxWx^SxeV|3uj>CfYwG+7Q?K2^SQnf9i~x!k+#vZ?w{!=}oi{HgLa)5_%( zj>+Ym!adDodF!^yntXIx%f(DP5{qzlo^WU~{bic{2Ag(f^^)b@c3glb+kJuoI$54Q zE&T>`@5$+IoZ=?SmK9UwV;iQ*b2d(qou;a31~1H+Vqhwq)@9r?wK;!2Yq|IFY5F@b zZYBp(hEv;Qd3W|yxdPpOa{9f~ws=WWcx%1CEXIdFMruleyY97M^vKuGi)tiCkg6C%ENE zWLMaM@2$B|d$@Arj$q&U&L1z)?2T)pwfsld6hG$pq&ZmfdgRBmwWz_!Ex~`?==^Dn zcE@$*_yqUuyDN8Iv+MTV+b+N8k}DiLQ#VuCdHrpBzFV>LhFh-NqlM02lce=J=k5yL z^ry%>V>QQ_$mp56m1cir&Ewj=Y1Rc={Xyqhnj*2S_|1zW3)Xj8)s{| ze_kJ>jo4Ecnp3r#TQ+8D*5eTiHT&zZTyY#<-5zh-7_EKtgma-*IJn-bEr{N*Q1dmy z)w(q?qdoUd6|rq0p2f4VV6D_9o3P}}%*WTw`4>0I&i{*&;t z_SuaqH2kiHRm&cWJWF#wK*t#+9|Kez^(j4~i zWtx8=a;>&=EOM51-(l4LKR#W*P&;whxq4BmYm@1Mty_2QuDW)2Mdi-QUDuTFc510d zoQt%>pRG^T97mjS+U=jMpFcNslMza)cI#)z|M_R@rCI$~n&uO)3x3<^unKAkvVU+8%M~!DMQ<`COmFNMn;;p%!=Z>4<%)36+hS( zJ|}60x}+zatJyY1$7ufNoNJ?TdgjJ73z&bN$@B~Dt>>JsrIKQmTmzew#V9VN+B-k7 zMQc4xN%jRjP;+xAm_|2i2QQ93H_EBxLOR8kq|Hy*aBg(rW+mRCVDh<@vv{rK?Fjp_ z9!ci=QQSBUh{Z?c<9#Y^%PPx4E%1k&1WwN3W)D|%WGXWU?jrVZLF9#6=mlp&7A}z` z@Wc>s#x;fG=HwZ$ssvf_*#ehn6CCT?7X0Vm+4$p~;CieiJ=+<~4>6gsG zwXZsz^Md`|&Vq$nd6A<~>*?4uE6n_CQn$qX``3 zXn{XDUHXreX>a_ObJbPOYNiASXS1%Sm(}+~bV9Xb_Vz+_!MU z3pi7XFr_MeomH_wJ2fjawoIfxD3M{@8^0Rh#+RkJ;gv!YVjEBABr9&&^A!1J(ByPZ z%Hmdm4*}fl`bzt2`FcDz4!_CuuNCt_++lFb(vIGQ;Bdddt#(@B4PxE8Sf%#^lY{S* z^g50%Y{^KRT-uPNx8weXo2=(QBw~)f0y`%~a8o~DV98mcegPv_F@kd_RPrt~W4K7N zYB(oeaIoKWS8}5|3 zsUNL~2#FZ29sVLMHhf&te^-KYUbv0oZ}Q=bm$PU8GCld<(jAiCyIlXtpoo6-L~ga} z=PJq)lvs`U&_Tv~O^#&aWF&4KNT1w?%O$-jQT<#+HlEaQlZDXp#>LCdxMRyi;xL@t z4mIMWRnqy1esVg$1>@#gqgVktj4HRNWz>LlWf|Bzkb#<~Ds8agrmq z*0uUCNLVUTv@@UMcSZ22j+-p~pNNkh+zQpY{MF;)WuIfEc#(><1pZ8oT>T2QecQ1j zzDUwOoRP>aTrI3Wp2&6LCrtI9P#L%6GW%XgPvCv)24M}D#rLnlv%xYj6D$QYz#WB{d#9tU_G?Csz>=;IYV0|`ps3Nn9-qNf?8U6Z1x4rF;nU^CcchkPw| zDzHN)f~-jtcrnchE?8Bc=D7y_BGNo39=WX3{}8QmcBwZ%)*3VW8S{9Rxj(m6fwelP&i ze+$?I)`RDP{-vk}4rg8+6riUDd=d%O;9;;5%t1Q8R{}ld;7yRr6g{ON9hHD*L0_ck z;rEL46I!AI34l?EmAn;pts=K_4j&Yr7VrUNXat`C>p|9_97I3LD^v6oNqPZz57Kjm z$>0@`mVdw3~4m0$E%y3fC(*rU?w_705nPAo_gKXM^`b?*Q3XqQFM54|g;_0lUDbKu%=59Q`lFhaI*+704E- zRPKXflO+vrW zC(IGLz&zwj7TUo}A@eK4OTbDri{8Y-c#tj8zCf)T*b1`3&7hYxZo&@eX#`p02F2oX zkTv5BNjfYLnU9w#yTEwRE^;`y6f!41GQ9~zf6HrB^wf*K667JtnUD6@9Z3QwC{hl9 zJh7C6kAc~ucL+!3DZ5sXc3I#7&<*m~I>1UW8Ds?#LH3^RZmW)#$_=xG62P_tq&zb0luJ`nvZr2=F*_~B=r zy+UuJ>=b}(%F#J0<1ok!{lbv26=c&kgKW75(fdF)X@$sT!d#&nq`gbzWDqUvO^L#e z9|^+|s>yqV0gz2!4YKJ=K{kCZ$iQ46&wwN0`oyS|eh}TGnY(Q4?X8-@~Xx;niqn6 zDW(p@?@_B7gACXQGO$w7bB-ou&e0?}N0a0nO$N?6nhczCG(mliCIZ&yXfp5+=V+d% z=4f8S060gJ85>mweU2tGa*if5a*if5PM)L58vCGUg-KSp8hj9bI7jmlkaIL2LB;b@ zRxe;5>7Su0%t@LkpOZB8@;OP9j^D4(-5Szpf5MEUwGP0F05xgF#zP0|kPT^8g4;4Dox@PJt{By1G&kpk1p zgbwg9Wq^(lp;~v+Y_`C*})woLCobc^X~l zB}WX&iEzrsaaBq(HP0hOa(!<@A~m+4p^3s2Gp#C!d4RfG$3ye0H2;nitNCfIcyH2T zU3=ip4bfI6^WiF*f0pKehJHTXk+NK6)Tg28E~4JV#5(2dY8-V~U2=7b9@$e@Zxm}E zY*50*2j(u;1LPDn7;=i5?)K`G=e)K-8z!608<>abL-YIRV>VM9!o>U!T-UuA^fjP_xjT@&uMGxR=D2UjQ@=}O*xnk zlrxC`zHK$zFjpvV3{%vGE*`!ZMP4#`3BtX6;Bp=|EX=hA?S-~7wnez6@%2V7*u?G1 z)k;nmIgB!zw*!5h+Z$dwn54MxEXXskEafI;aGL3p1I{{>Bdvn z45@(elyz8Ia9CQbNBpY?N4R`}#|0iYC*m!8UQe@-7 z5KhP}-^ABisThTV+YxC2_;Txh%CqtGFkO$&WJQznHD#aJ-#=HqbJc`TQ{0Z??9X?w>QY6KAqwL)W!}@$ zz<2^Z2T;blU?Mi7p)gD`+WFAj$-?u8+c*6V)BBqnxJ{(gvzN$Em(;;Q!%Z8`)0?_ji z7`HgR030{ej@t{kji9~pRZpF?!uS^kBJ-h%-ggJ*FazEeL;^S7s#7+;fcOQD0p%KL z=D*>XQmz(xskBI?$XoUN6JO16?GOfJd>b+#{`fRU@4Hq#L&#Jj4*sY+#Mne&+-5|o z0#7bga=|<$x2{!kxX4ZDK-`{BhHV7W65mCx+)TJMQ+x z!1%fiX}W>&l~jkMC&uXJCMRUt7~84@?ZU^xm!DUGV?9~_X~I}F>N|`Kw7ws}x z&YG5f>$Ga8Of7Gv=_4GF$qpW!CLh&K-Dz8~;C-AlCYx2pY9F41*Zm5nm46}n!DPF# zY0Z1fH2cJ9@>kR3In&}lXKMN;Q`fY>_`G0p0MD(ND#vT)AJ|ryuL~yp!1mw_yfBra zHPuBV1TT$s?XYMit&wLPxOC0@$12*FYvqqbtO-WlZM)H;9be|!p+#vC-wr;v+;zh8 z*zhOu!JhcJH&}uX)!AN-*3SKBs3)GuT^A07= zK9C6u|FZBXZF{CGQ@it!Git7L{OzJtZC|EqxAxB`H>7LleTDa@?l{}EPy6Ll8!?qgC*NsyDc%h)2`j@x@NQK4#x(^hSU@h`KHFA;P3^m zO=iiIrfqq6!Qx={cGr1}$5r#M-5M7>QsN5PW~o)K4yL^@W3tuaVEa>PcTZCl9!a~r3%-0N?VX7bwlu=#r{AQ# zY#Gm{A6uU}{lnOC6<#M=9IRTB{>n6~{AKB9;>NA^-H{m^ydgdPjrrrMgzR~-+MQ3% zPYB+&J>47j|Xe#eH4yAG!Rb)w1OEAE*akETy? zh0gz4dcs8O@4H}8T=31;(`zT{x$DPh{fhU~k571q73wpP{z%k#So7Q6aY4%$>4Rb8 zs&`JfV}lWA(hn~iSLs%7E_R1cv`xb5dFaXU?!Qh1Vg3;APp@*{Fwv8B)xHhxeG`3W zUn0iN%5-;0^tgNa(oUa@PS7&j7A^_)UF1G65l+H^`HO@8eD}K(2YT0&^U-iuxF?Os z!)H+TsY3Ui6J_hFJFj)$Ffsh7(%$GXZ{Y$h{=2TMV8zXD%f#{3Gd)t}o<9+= zt~%y(A2a-5H)kL-Lr|we| zD&5z<7u@$7C5qMBcK2zc71DX4iVbdg&3*qwiTfhY!&%~l+q^;=Qny+h4FA~urqRi8 zf_OPCF8Gfx-EQMJrE8CTlNKA?{$&5; z%FDNB?3xrr8+>I}Wem@h^MzLO>f*TI_PrUm8W#i9nyxE$jewi`e+jLcIaNn_v)5bB|tm$3i@e>(aCfwip2_il7 zZbtY-bLpojdtb&Y#kE`@ydgoVL$9w4}HI5QaZ=G)(zD?SxY+kIx zavr&MJfl8B%w;)m+&r$@bpnU)Z|~>WFBn(pR%@4S+j8BwNNmtvp H&?Wx|c*veq delta 271770 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%0Aws zn$=MD@h;We0cD1&Af6OGm8!W3N8F;IE=Bl1h4;Q2o|=Wq5-9nWJW>Livl$Y&AOtK7xA55Ql{) zNj2lI!PzM*h+zd6#9r_?RWk%idEGK3VGXo@7ld*Y=Ry-jIjR}LeAiN~R}ZDUPBkl` zyx~y}WkX7rV*am`Ekj}&Oo8%7LkN_Ea>o+GID{>!QnM?e94i$t7aea>%~B{G7DJht z0w^<+54WNHGBrC7HX_bdvva&yxEw3ag0iJ^p!~X=0%Z%6AU<@S7)RYuI*x&}u}@v9 z>4e#c!&EZ}UV{}GYIgTx?Y|vLzpZdP^fqBZM&1Bro$lxqO9% zjC?PYkr%=?m;;YsOBbryNsJR1H(hNQ=Sw>%>k+dtpc*^&8db7@gjortqY5?K34cbz z%B%DbBMYI#Z8%^C!$w*QWd>{D0_@6i*p9e>y5M(+JFd_TXrYzxJ1+`~WdX*+Dd?aE z2ZU^CHI$Ajpp+Ld&SRVboyf;Q89)q_b`CfL{j@F8xC%-?#ZV6F4A=<`=#>T?IUE_? z1f@X+ZNh;d@h&I>iNg-HR|vDPq7F=-^m714A+Do)p$w!N4uVyT3!v1;x0~tD|J@5U z(M;z+S)mKch+8nPGU6&I`Ftq(Ikfr`EnfyD-*K__S3#FS8E`JNpSYM$xESZZj3kDM zASg$7H#X}r91C4)b|?H8afh1S0{@G+S$`7-}@IoA9g{oNqFF?FfHJ3pd zsK<*1IUVLeIjHI{(u1uS%E1-~?Wu+`@-Qd^bU+zkkeY2k8DKZo3bVUl2By3do{trG zsAe0K^)*5HYoE6q3$h{)?0_jyrnmvamAD>CTm^rE#WWL22To|86`AS|C*Kj;1-mxfwF>?P-Y|#%8cYdnURHR zb{3Qw$yBp5pv;H|&clqP+v4><6$>&Y?eldDHbHrjDT9;IpafPUE`;YGE`XVc^Pp^@ zhq|bP)?sZjgEcS=PdwsMJi+Z@EH3rp8Ez96q@y)7hgM^l-y>f_^Wd)-Ko0y9rZSF! zG9$8C(ofg9YBOzwQoe`rLYf499l1Zimug z8$1IYwyI_`lwH{XrJs5z1FKWBYoYYBSIw^R;QW`4_8@UOI;vL99Z))|gz_S@2}*}$ zP&zDCvrC|KSgd9jLg{Y}oP+)fR5Kq+f4NXLGzrRUO@db!vg4pkt&7%UKS;a_%0Ar$ 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, where a higher priority means the conversation is meant to appear /// earlier in the pinned conversation list. /// e - Disappearing messages expiration type. Omitted if disappearing messages are not enabled /// for the conversation with this contact; 1 for delete-after-send, and 2 for /// delete-after-read. /// E - Disappearing message timer, in seconds. Omitted when `e` is omitted. +/// j - Unix timestamp (seconds) when the contact was created ("j" to match user_groups +/// equivalent "j"oined field). Omitted if 0. /// Struct containing contact info. struct contact_info { @@ -51,12 +56,17 @@ struct contact_info { bool approved = false; bool approved_me = false; bool blocked = false; - bool hidden = false; // True if the conversation with this contact is not visible in the convo - // list (typically because it has been deleted). - int priority = 0; // If >0 then this message is pinned; higher values mean higher priority - // (i.e. pinned earlier in the pinned list). + int priority = 0; // If >0 then this message is pinned; higher values mean higher priority + // (i.e. pinned earlier in the pinned list). If negative then this + // conversation is hidden. Otherwise (0) this is a regular, unpinned + // conversation. + notify_mode notifications = notify_mode::defaulted; + int64_t mute_until = 0; // If non-zero, disable notifications until the given unix timestamp + // (overriding whatever the current `notifications` value is until the + // timestamp expires). expiration_mode exp_mode = expiration_mode::none; // The expiry time; none if not expiring. std::chrono::seconds exp_timer{0}; // The expiration timer (in seconds) + int64_t created = 0; // Unix timestamp when this contact was added explicit contact_info(std::string sid); @@ -127,12 +137,13 @@ class Contacts : public ConfigBase { void set_approved(std::string_view session_id, bool approved); void set_approved_me(std::string_view session_id, bool approved_me); void set_blocked(std::string_view session_id, bool blocked); - void set_hidden(std::string_view session_id, bool hidden); void set_priority(std::string_view session_id, int priority); + void set_notifications(std::string_view session_id, notify_mode notifications); void set_expiry( std::string_view session_id, expiration_mode exp_mode, std::chrono::seconds expiration_timer = 0min); + void set_created(std::string_view session_id, int64_t timestamp); /// Removes a contact, if present. Returns true if it was found and removed, false otherwise. /// Note that this removes all fields related to a contact, even fields we do not know about. diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.h index 94e103ea5..8d141b801 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.h +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.h @@ -61,34 +61,39 @@ int convo_info_volatile_init( /// Fills `convo` with the conversation info given a session ID (specified as a null-terminated hex /// string), if the conversation exists, and returns true. If the conversation does not exist then -/// `convo` is left unchanged and false is returned. +/// `convo` is left unchanged and false is returned. If an error occurs, false is returned and +/// `conf->last_error` will be set to non-NULL containing the error string (if no error occurs, such +/// as in the case where the conversation merely doesn't exist, `last_error` will be set to NULL). bool convo_info_volatile_get_1to1( - const config_object* conf, convo_info_volatile_1to1* convo, const char* session_id) + config_object* conf, convo_info_volatile_1to1* convo, const char* session_id) __attribute__((warn_unused_result)); /// Same as the above except that when the conversation does not exist, this sets all the convo /// fields to defaults and loads it with the given session_id. /// /// Returns true as long as it is given a valid session_id. A false return is considered an error, -/// and means the session_id was not a valid session_id. +/// and means the session_id was not a valid session_id. In such a case `conf->last_error` will be +/// set to an error string. /// /// This is the method that should usually be used to create or update a conversation, followed by /// setting fields in the convo, and then giving it to convo_info_volatile_set(). bool convo_info_volatile_get_or_construct_1to1( - const config_object* conf, convo_info_volatile_1to1* convo, const char* session_id) + config_object* conf, convo_info_volatile_1to1* convo, const char* session_id) __attribute__((warn_unused_result)); /// community versions of the 1-to-1 functions: /// /// Gets a community convo info. `base_url` and `room` are null-terminated c strings; pubkey is /// 32 bytes. base_url and room will always be lower-cased (if not already). +/// +/// Error handling works the same as the 1-to-1 version. bool convo_info_volatile_get_community( - const config_object* conf, + config_object* conf, convo_info_volatile_community* comm, const char* base_url, const char* room) __attribute__((warn_unused_result)); bool convo_info_volatile_get_or_construct_community( - const config_object* conf, + config_object* conf, convo_info_volatile_community* convo, const char* base_url, const char* room, @@ -96,21 +101,23 @@ bool convo_info_volatile_get_or_construct_community( /// Fills `convo` with the conversation info given a legacy group ID (specified as a null-terminated /// hex string), if the conversation exists, and returns true. If the conversation does not exist -/// then `convo` is left unchanged and false is returned. +/// then `convo` is left unchanged and false is returned. On error, false is returned and the error +/// is set in conf->last_error (on non-error, last_error is cleared). bool convo_info_volatile_get_legacy_group( - const config_object* conf, convo_info_volatile_legacy_group* convo, const char* id) + config_object* conf, convo_info_volatile_legacy_group* convo, const char* id) __attribute__((warn_unused_result)); /// Same as the above except that when the conversation does not exist, this sets all the convo /// fields to defaults and loads it with the given id. /// /// Returns true as long as it is given a valid legacy group id (i.e. same format as a session id). -/// A false return is considered an error, and means the id was not a valid session id. +/// A false return is considered an error, and means the id was not a valid session id; an error +/// string will be set in `conf->last_error`. /// /// This is the method that should usually be used to create or update a conversation, followed by /// setting fields in the convo, and then giving it to convo_info_volatile_set(). bool convo_info_volatile_get_or_construct_legacy_group( - const config_object* conf, convo_info_volatile_legacy_group* convo, const char* id) + config_object* conf, convo_info_volatile_legacy_group* convo, const char* id) __attribute__((warn_unused_result)); /// Adds or updates a conversation from the given convo info diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/notify.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/notify.h new file mode 100644 index 000000000..6dc825bf7 --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/notify.h @@ -0,0 +1,8 @@ +#pragma once + +typedef enum CONVO_NOTIFY_MODE { + CONVO_NOTIFY_DEFAULT = 0, + CONVO_NOTIFY_ALL = 1, + CONVO_NOTIFY_DISABLED = 2, + CONVO_NOTIFY_MENTIONS_ONLY = 3, +} CONVO_NOTIFY_MODE; diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/notify.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/notify.hpp new file mode 100644 index 000000000..5de1f5eec --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/notify.hpp @@ -0,0 +1,12 @@ +#pragma once + +namespace session::config { + +enum class notify_mode { + defaulted = 0, + all = 1, + disabled = 2, + mentions_only = 3, // Only for groups; for DMs this becomes `all` +}; + +} diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.h index 533614ad5..5a2451d5f 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.h +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.h @@ -5,6 +5,7 @@ extern "C" { #endif #include "base.h" +#include "notify.h" #include "util.h" // Maximum length of a group name, in bytes @@ -26,9 +27,12 @@ typedef struct ugroups_legacy_group_info { // terminator). int64_t disappearing_timer; // Minutes. 0 == disabled. - bool hidden; // true if hidden from the convo list - int priority; // pinned message priority; 0 = unpinned, larger means pinned higher (i.e. higher - // priority conversations come first). + int priority; // pinned message priority; 0 = unpinned, negative = hidden, positive = pinned + // (with higher meaning pinned higher). + int64_t joined_at; // unix timestamp when joined (or re-joined) + CONVO_NOTIFY_MODE notifications; // When the user wants notifications + int64_t mute_until; // Mute notifications until this timestamp (overrides `notifications` + // setting until the timestamp) // For members use the ugroups_legacy_group_members and associated calls. @@ -43,8 +47,12 @@ typedef struct ugroups_community_info { // info (that one is always forced lower-cased). unsigned char pubkey[32]; // 32 bytes (not terminated, can contain nulls) - int priority; // pinned message priority; 0 = unpinned, larger means pinned higher (i.e. higher - // priority conversations come first). + int priority; // pinned message priority; 0 = unpinned, negative = hidden, positive = pinned + // (with higher meaning pinned higher). + int64_t joined_at; // unix timestamp when joined (or re-joined) + CONVO_NOTIFY_MODE notifications; // When the user wants notifications + int64_t mute_until; // Mute notifications until this timestamp (overrides `notifications` + // setting until the timestamp) } ugroups_community_info; int user_groups_init( @@ -59,9 +67,11 @@ int user_groups_init( /// normalized/lower-cased; room is case-insensitive for the lookup: note that this may well return /// a community info with a different room capitalization than the one provided to the call. /// -/// Returns true if the community was found and `comm` populated; false otherwise. +/// Returns true if the community was found and `comm` populated; false otherwise. A false return +/// can either be because it didn't exist (`conf->last_error` will be NULL) or because of some error +/// (`last_error` will be set to an error string). bool user_groups_get_community( - const config_object* conf, + config_object* conf, ugroups_community_info* comm, const char* base_url, const char* room) __attribute__((warn_unused_result)); @@ -76,8 +86,10 @@ bool user_groups_get_community( /// /// Note that this is all different from convo_info_volatile, which always forces the room token to /// lower-case (because it does not preserve the case). +/// +/// Returns false (and sets `conf->last_error`) on error. bool user_groups_get_or_construct_community( - const config_object* conf, + config_object* conf, ugroups_community_info* comm, const char* base_url, const char* room, @@ -85,11 +97,11 @@ bool user_groups_get_or_construct_community( /// Returns a ugroups_legacy_group_info pointer containing the conversation info for a given legacy /// group ID (specified as a null-terminated hex string), if the conversation exists. If the -/// conversation does not exist, returns NULL. +/// conversation does not exist, returns NULL. Sets conf->last_error on error. /// /// The returned pointer *must* be freed either by calling `ugroups_legacy_group_free()` when done /// with it, or by passing it to `user_groups_set_free_legacy_group()`. -ugroups_legacy_group_info* user_groups_get_legacy_group(const config_object* conf, const char* id) +ugroups_legacy_group_info* user_groups_get_legacy_group(config_object* conf, const char* id) __attribute__((warn_unused_result)); /// Same as the above except that when the conversation does not exist, this sets all the group @@ -104,8 +116,10 @@ ugroups_legacy_group_info* user_groups_get_legacy_group(const config_object* con /// /// This is the method that should usually be used to create or update a conversation, followed by /// setting fields in the group, and then giving it to user_groups_set(). +/// +/// On error, this returns NULL and sets `conf->last_error`. ugroups_legacy_group_info* user_groups_get_or_construct_legacy_group( - const config_object* conf, const char* id) __attribute__((warn_unused_result)); + config_object* conf, const char* id) __attribute__((warn_unused_result)); /// Properly frees memory associated with a ugroups_legacy_group_info pointer (as returned by /// get_legacy_group/get_or_construct_legacy_group). diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.hpp index a4d0119bd..9ab69117e 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.hpp +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.hpp @@ -9,6 +9,7 @@ #include "base.hpp" #include "community.hpp" #include "namespaces.hpp" +#include "notify.hpp" extern "C" { struct ugroups_legacy_group_info; @@ -29,11 +30,16 @@ namespace session::config { /// a - set of admin session ids (each 33 bytes). /// E - disappearing messages duration, in seconds, > 0. Omitted if disappearing messages is /// disabled. (Note that legacy groups only support expire after-read) -/// h - hidden: 1 if the conversation has been removed from the conversation list, omitted if -/// visible. -/// + - the conversation priority, for pinned messages. Omitted means not pinned; otherwise an -/// integer value >0, where a higher priority means the conversation is meant to appear -/// earlier in the pinned conversation list. +/// @ - notification setting (int). Omitted = use default setting; 1 = all, 2 = disabled, 3 = +/// mentions-only. +/// ! - mute timestamp: if set then don't show notifications for this contact's messages until +/// this unix timestamp (i.e. overriding the current notification setting until the given +/// time). +/// + - the conversation priority, for pinned/hidden messages. Integer. Omitted means not +/// pinned; -1 means hidden, and a positive value is a pinned message for which higher +/// priority values means the conversation is meant to appear earlier in the pinned +/// conversation list. +/// j - joined at unix timestamp. Omitted if 0. /// /// o - dict of communities (AKA open groups); within this dict (which deliberately has the same /// layout as convo_info_volatile) each key is the SOGS base URL (in canonical form), and value @@ -45,14 +51,29 @@ namespace session::config { /// appropriate). For instance, a room name SudokuSolvers would be "sudokusolvers" in /// the outer key, with the capitalization variation in use ("SudokuSolvers") in this /// key. This key is *always* present (to keep the room dict non-empty). -/// + - the conversation priority, for pinned messages. Omitted means not pinned; otherwise -/// an integer value >0, where a higher priority means the conversation is meant to -/// appear earlier in the pinned conversation list. +/// @ - notification setting (see above). +/// ! - mute timestamp (see above). +/// + - the conversation priority, for pinned messages. Omitted means not pinned; -1 means +/// hidden; otherwise an integer value >0, where a higher priority means the +/// conversation is meant to appear earlier in the pinned conversation list. +/// j - joined at unix timestamp. Omitted if 0. /// /// c - reserved for future storage of new-style group info. +/// Common base type with fields shared by all the groups +struct base_group_info { + int priority = 0; // The priority; 0 means unpinned, -1 means hidden, positive means + // pinned higher (i.e. higher priority conversations come first). + int64_t joined_at = 0; // unix timestamp (seconds) when the group was joined (or re-joined) + notify_mode notifications = notify_mode::defaulted; // When the user wants notifications + int64_t mute_until = 0; // unix timestamp (seconds) until which notifications are disabled + + protected: + void load(const dict& info_dict); +}; + /// Struct containing legacy group info (aka "closed groups"). -struct legacy_group_info { +struct legacy_group_info : base_group_info { static constexpr size_t NAME_MAX_LENGTH = 100; // in bytes; name will be truncated if exceeded std::string session_id; // The legacy group "session id" (33 bytes). @@ -61,9 +82,6 @@ struct legacy_group_info { ustring enc_pubkey; // bytes (32 or empty) ustring enc_seckey; // bytes (32 or empty) std::chrono::seconds disappearing_timer{0}; // 0 == disabled. - bool hidden = false; // true if the conversation is hidden from the convo list - int priority = 0; // The priority; 0 means unpinned, larger means pinned higher (i.e. - // higher priority conversations come first). /// Constructs a new legacy group info from an id (which must look like a session_id). Throws /// if id is invalid. @@ -108,7 +126,7 @@ struct legacy_group_info { }; /// Community (aka open group) info -struct community_info : community { +struct community_info : base_group_info, community { // Note that *changing* url/room/pubkey and then doing a set inserts a new room under the given // url/room/pubkey, it does *not* update an existing room. @@ -119,9 +137,6 @@ struct community_info : community { community_info(const struct ugroups_community_info& c); // From c struct void into(ugroups_community_info& c) const; // Into c struct - int priority = 0; // The priority; 0 means unpinned, larger means pinned higher (i.e. - // higher priority conversations come first). - private: void load(const dict& info_dict); @@ -212,6 +227,8 @@ class UserGroups : public ConfigBase { DictFieldProxy community_field( const community_info& og, ustring_view* get_pubkey = nullptr) const; + void set_base(const base_group_info& bg, DictFieldProxy& info) const; + public: /// Removes a community group. Returns true if found and removed, false if not present. /// Arguments are the same as `get_community`. @@ -253,7 +270,7 @@ class UserGroups : public ConfigBase { /// if (auto* comm = std::get_if(&group)) { /// // use comm->name, comm->priority, etc. /// } else if (auto* lg = std::get_if(&convo)) { - /// // use lg->session_id, lg->hidden, etc. + /// // use lg->session_id, lg->priority, etc. /// } /// } /// diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.h index 9a49320b2..b7fb66842 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.h +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.h @@ -50,18 +50,14 @@ user_profile_pic user_profile_get_pic(const config_object* conf); // Sets a user profile int user_profile_set_pic(config_object* conf, user_profile_pic pic); -// Gets the current note-to-self priority level. Will always be >= 0. +// Gets the current note-to-self priority level. Will be negative for hidden, 0 for unpinned, and > +// 0 for pinned (with higher value = higher priority). int user_profile_get_nts_priority(const config_object* conf); -// Sets the current note-to-self priority level. Should be >= 0 (negatives will be set to 0). +// Sets the current note-to-self priority level. Set to -1 for hidden; 0 for unpinned, and > 0 for +// higher priority in the conversation list. void user_profile_set_nts_priority(config_object* conf, int priority); -// Gets the current note-to-self priority level. Will always be >= 0. -bool user_profile_get_nts_hidden(const config_object* conf); - -// Sets the current note-to-self priority level. Should be >= 0 (negatives will be set to 0). -void user_profile_set_nts_hidden(config_object* conf, bool hidden); - #ifdef __cplusplus } // extern "C" #endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.hpp index 57e2badc0..2ce3ca697 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.hpp +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.hpp @@ -15,9 +15,7 @@ namespace session::config { /// p - user profile url /// q - user profile decryption key (binary) /// + - the priority value for the "Note to Self" pseudo-conversation (higher = higher in the -/// conversation list). Omitted when 0. -/// h - the "hidden" value for the "Note to Self" pseudo-conversation (true = hide). Omitted when -/// false. +/// conversation list). Omitted when 0. -1 means hidden. class UserProfile final : public ConfigBase { @@ -57,18 +55,13 @@ class UserProfile final : public ConfigBase { void set_profile_pic(std::string_view url, ustring_view key); void set_profile_pic(profile_pic pic); - /// Gets the Note-to-self conversation priority. Will always be >= 0. + /// Gets the Note-to-self conversation priority. Negative means hidden; 0 means unpinned; + /// higher means higher priority (i.e. hidden in the convo list). int get_nts_priority() const; - /// Sets the Note-to-self conversation priority. Should be >= 0 (negatives will be set to 0). + /// Sets the Note-to-self conversation priority. -1 for hidden, 0 for unpinned, higher for + /// pinned higher. void set_nts_priority(int priority); - - /// Gets the Note-to-self hidden flag; true means the Note-to-self "conversation" should be - /// hidden from the conversation list. - bool get_nts_hidden() const; - - /// Sets or clears the `hidden` flag that hides the Note-to-self from the conversation list. - void set_nts_hidden(bool hidden); }; } // namespace session::config diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index 17c2dcb35..3f33981da 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -282,7 +282,7 @@ public final class OpenGroupManager { } .flatMap { _ in dependencies.storage - .readPublisherFlatMap(receiveOn: OpenGroupAPI.workQueue) { db in + .readPublisherFlatMap { db in // Note: The initial request for room info and it's capabilities should NOT be // authenticated (this is because if the server requires blinding and the auth // headers aren't blinded it will error - these endpoints do support unauthenticated @@ -296,6 +296,7 @@ public final class OpenGroupManager { ) } } + .subscribe(on: OpenGroupAPI.workQueue) .receive(on: OpenGroupAPI.workQueue) .flatMap { response -> Future in Future { resolver in @@ -1000,13 +1001,14 @@ public final class OpenGroupManager { // Try to retrieve the default rooms 8 times let publisher: AnyPublisher<[OpenGroupAPI.Room], Error> = dependencies.storage - .readPublisherFlatMap(receiveOn: OpenGroupAPI.workQueue) { db in + .readPublisherFlatMap { db in OpenGroupAPI.capabilitiesAndRooms( db, on: OpenGroupAPI.defaultServer, using: dependencies ) } + .subscribe(on: OpenGroupAPI.workQueue) .receive(on: OpenGroupAPI.workQueue) .retry(8) .map { response in diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift index d3cec5194..860df5069 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift @@ -79,7 +79,7 @@ extension MessageReceiver { members: membersAsData.map { $0.toHexString() }, admins: adminsAsData.map { $0.toHexString() }, expirationTimer: expirationTimer, - messageSentTimestamp: sentTimestamp, + formationTimestampMs: sentTimestamp, calledFromConfigHandling: false ) } @@ -92,7 +92,7 @@ extension MessageReceiver { members: [String], admins: [String], expirationTimer: UInt32, - messageSentTimestamp: UInt64, + formationTimestampMs: UInt64, calledFromConfigHandling: Bool ) throws { // With new closed groups we only want to create them if the admin creating the closed group is an @@ -118,7 +118,7 @@ extension MessageReceiver { let closedGroup: ClosedGroup = try ClosedGroup( threadId: groupPublicKey, name: name, - formationTimestamp: (TimeInterval(messageSentTimestamp) / 1000) + formationTimestamp: (TimeInterval(formationTimestampMs) / 1000) ).saved(db) // Clear the zombie list if the group wasn't active (ie. had no keys) diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift index 02200d27a..cb7a9675e 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift @@ -190,7 +190,7 @@ extension MessageReceiver { members: [String](closedGroup.members), admins: [String](closedGroup.admins), expirationTimer: closedGroup.expirationTimer, - messageSentTimestamp: message.sentTimestamp!, + formationTimestampMs: message.sentTimestamp!, calledFromConfigHandling: false // Legacy config isn't an issue ) } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift index df303e064..d363078b0 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift @@ -549,7 +549,7 @@ extension MessageSender { ) .flatMap { _ -> AnyPublisher in Storage.shared - .writePublisherFlatMap(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db in + .writePublisherFlatMap { db in generateAndSendNewEncryptionKeyPair( db, targetMembers: members, diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift b/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift index f28d33886..4c6421b34 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift @@ -131,29 +131,23 @@ extension MessageSender { }() return Storage.shared - .readPublisherFlatMap(receiveOn: queue) { db -> AnyPublisher<(attachments: [Attachment], openGroup: OpenGroup?), Error> in + .readPublisher { db -> (attachments: [Attachment], openGroup: OpenGroup?) in let attachmentStateInfo: [Attachment.StateInfo] = (try? Attachment .stateInfo(interactionId: interactionId, state: .uploading) .fetchAll(db)) .defaulting(to: []) // If there is no attachment data then just return early - guard !attachmentStateInfo.isEmpty else { - return Just(([], nil)) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - } + guard !attachmentStateInfo.isEmpty else { return ([], nil) } // Otherwise fetch the open group (if there is one) - return Just(( + return ( (try? Attachment .filter(ids: attachmentStateInfo.map { $0.attachmentId }) .fetchAll(db)) .defaulting(to: []), try? OpenGroup.fetchOne(db, id: threadId) - )) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() + ) } .flatMap { attachments, openGroup -> AnyPublisher<[String?], Error> in guard !attachments.isEmpty else { @@ -171,8 +165,7 @@ extension MessageSender { to: ( openGroup.map { Attachment.Destination.openGroup($0) } ?? .fileServer - ), - queue: DispatchQueue.global(qos: .userInitiated) + ) ) } ) diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index f861fad72..0f620b89f 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -644,7 +644,7 @@ public final class MessageSender { in: namespace ) .subscribe(on: DispatchQueue.global(qos: .default)) - .flatMap { response -> AnyPublisher in + .flatMap { response -> AnyPublisher in let updatedMessage: Message = message updatedMessage.serverHash = response.1.hash @@ -669,7 +669,7 @@ public final class MessageSender { }() return dependencies.storage - .writePublisher(receiveOn: DispatchQueue.global(qos: .default)) { db -> Void in + .writePublisher { db -> Void in try MessageSender.handleSuccessfulMessageSend( db, message: updatedMessage, @@ -684,34 +684,34 @@ public final class MessageSender { JobRunner.add(db, job: job) return () } - .flatMap { _ -> AnyPublisher in + .flatMap { _ -> AnyPublisher in let isMainAppActive: Bool = (UserDefaults.sharedLokiProject?[.isMainAppActive]) .defaulting(to: false) guard shouldNotify && !isMainAppActive else { - return Just(true) + return Just(()) .setFailureType(to: Error.self) .eraseToAnyPublisher() } guard let job: Job = job else { - return Just(true) + return Just(()) .setFailureType(to: Error.self) .eraseToAnyPublisher() } return Deferred { - Future { resolver in + Future { resolver in NotifyPushServerJob.run( job, queue: DispatchQueue.global(qos: .default), - success: { _, _ in resolver(Result.success(true)) }, + success: { _, _ in resolver(Result.success(())) }, failure: { _, _, _ in // Always fulfill because the notify PN server job isn't critical. - resolver(Result.success(true)) + resolver(Result.success(())) }, deferred: { _ in // Always fulfill because the notify PN server job isn't critical. - resolver(Result.success(true)) + resolver(Result.success(())) } ) } @@ -720,7 +720,6 @@ public final class MessageSender { } .eraseToAnyPublisher() } - .filter { $0 } .handleEvents( receiveCompletion: { result in switch result { @@ -761,7 +760,7 @@ public final class MessageSender { // Send the result return dependencies.storage - .readPublisherFlatMap(receiveOn: DispatchQueue.global(qos: .default)) { db in + .readPublisherFlatMap { db in OpenGroupAPI .send( db, @@ -780,7 +779,7 @@ public final class MessageSender { let updatedMessage: Message = message updatedMessage.openGroupServerMessageId = UInt64(responseData.id) - return dependencies.storage.writePublisher(receiveOn: DispatchQueue.global(qos: .default)) { db in + return dependencies.storage.writePublisher { db in // The `posted` value is in seconds but we sent it in ms so need that for de-duping try MessageSender.handleSuccessfulMessageSend( db, @@ -829,7 +828,7 @@ public final class MessageSender { // Send the result return dependencies.storage - .readPublisherFlatMap(receiveOn: DispatchQueue.global(qos: .default)) { db in + .readPublisherFlatMap { db in return OpenGroupAPI .send( db, @@ -844,7 +843,7 @@ public final class MessageSender { let updatedMessage: Message = message updatedMessage.openGroupServerMessageId = UInt64(responseData.id) - return dependencies.storage.writePublisher(receiveOn: DispatchQueue.global(qos: .default)) { db in + return dependencies.storage.writePublisher { db in // The `posted` value is in seconds but we sent it in ms so need that for de-duping try MessageSender.handleSuccessfulMessageSend( db, diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift index 4ad5a9396..78886b8c8 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift +++ b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift @@ -59,7 +59,7 @@ public enum PushNotificationAPI { // Unsubscribe from all closed groups (including ones the user is no longer a member of, // just in case) Storage.shared - .readPublisher(receiveOn: DispatchQueue.global(qos: .background)) { db -> (String, Set) in + .readPublisher { db -> (String, Set) in ( getUserHexEncodedPublicKey(db), try ClosedGroup @@ -84,6 +84,7 @@ public enum PushNotificationAPI { .collect() .eraseToAnyPublisher() } + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .sinkUntilComplete() // Unregister for normal push notifications diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift index 0819c5abc..d4ff78b6c 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift @@ -91,7 +91,7 @@ extension OpenGroupAPI { let server: String = self.server return dependencies.storage - .readPublisherFlatMap(receiveOn: Threading.pollerQueue) { db -> AnyPublisher<(Int64, PollResponse), Error> in + .readPublisherFlatMap { db -> AnyPublisher<(Int64, PollResponse), Error> in let failureCount: Int64 = (try? OpenGroup .filter(OpenGroup.Columns.server == server) .select(max(OpenGroup.Columns.pollFailureCount)) @@ -114,6 +114,7 @@ extension OpenGroupAPI { .eraseToAnyPublisher() } .subscribe(on: Threading.pollerQueue) + .receive(on: Threading.pollerQueue) .handleEvents( receiveOutput: { [weak self] failureCount, response in guard !calledFromBackgroundPoller || isBackgroundPollerValid() else { @@ -282,7 +283,7 @@ extension OpenGroupAPI { } return dependencies.storage - .readPublisherFlatMap(receiveOn: OpenGroupAPI.workQueue) { db in + .readPublisherFlatMap { db in OpenGroupAPI.capabilities( db, server: server, @@ -291,6 +292,7 @@ extension OpenGroupAPI { ) } .subscribe(on: OpenGroupAPI.workQueue) + .receive(on: OpenGroupAPI.workQueue) .flatMap { [weak self] _, responseBody -> AnyPublisher in guard let strongSelf = self, isBackgroundPollerValid() else { return Just(()) diff --git a/SessionMessagingKit/Shared Models/MessageViewModel.swift b/SessionMessagingKit/Shared Models/MessageViewModel.swift index 353fe9526..880f9c0bb 100644 --- a/SessionMessagingKit/Shared Models/MessageViewModel.swift +++ b/SessionMessagingKit/Shared Models/MessageViewModel.swift @@ -712,7 +712,7 @@ public extension MessageViewModel { WHERE ( \(groupMember[.groupId]) = \(interaction[.threadId]) AND \(groupMember[.profileId]) = \(interaction[.authorId]) AND - \(SQL("\(thread[.variant]) = \(SessionThread.Variant.openGroup)")) AND + \(SQL("\(thread[.variant]) = \(SessionThread.Variant.community)")) AND \(SQL("\(groupMember[.role]) IN \([GroupMember.Role.moderator, GroupMember.Role.admin])")) ) ) AS \(ViewModel.isSenderOpenGroupModeratorKey), diff --git a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift index a3cb4c4b9..f2585e182 100644 --- a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift +++ b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift @@ -792,10 +792,6 @@ public extension SessionThreadViewModel { \(SQL("\(thread[.variant]) != \(SessionThread.Variant.contact)")) OR \(SQL("\(thread[.id]) = \(userPublicKey)")) OR \(contact[.isApproved]) = true - ) AND ( - -- Only show the 'Note to Self' thread if it has an interaction - \(SQL("\(thread[.id]) != \(userPublicKey)")) OR - \(interaction[.timestampMs]) IS NOT NULL ) """ } @@ -826,7 +822,7 @@ public extension SessionThreadViewModel { return SQL(""" (IFNULL(\(thread[.pinnedPriority]), 0) > 0) DESC, - IFNULL(\(interaction[.timestampMs]), (\(thread[.creationDateTimestamp]) * 1000)) END DESC + IFNULL(\(interaction[.timestampMs]), (\(thread[.creationDateTimestamp]) * 1000)) DESC """) }() diff --git a/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigContactsSpec.swift b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigContactsSpec.swift index e7f34e1a8..ad31995a5 100644 --- a/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigContactsSpec.swift +++ b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigContactsSpec.swift @@ -14,6 +14,8 @@ class ConfigContactsSpec { static func spec() { it("generates Contact configs correctly") { + let createdTs: Int64 = 1680064059 + let nowTs: Int64 = Int64(Date().timeIntervalSince1970) let seed: Data = Data(hex: "0123456789abcdef0123456789abcdef") // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately @@ -33,7 +35,7 @@ class ConfigContactsSpec { // Empty contacts shouldn't have an existing contact let definitelyRealId: String = "050000000000000000000000000000000000000000000000000000000000000000" - var cDefinitelyRealId: [CChar] = definitelyRealId.cArray + var cDefinitelyRealId: [CChar] = definitelyRealId.cArray.nullTerminated() let contactPtr: UnsafeMutablePointer? = nil expect(contacts_get(conf, contactPtr, &cDefinitelyRealId)).to(beFalse()) @@ -48,6 +50,9 @@ class ConfigContactsSpec { expect(contact2.blocked).to(beFalse()) expect(contact2.profile_pic).toNot(beNil()) // Creates an empty instance apparently expect(String(libSessionVal: contact2.profile_pic.url)).to(beEmpty()) + expect(contact2.created).to(equal(0)) + expect(contact2.notifications).to(equal(CONVO_NOTIFY_DEFAULT)) + expect(contact2.mute_until).to(equal(0)) expect(config_needs_push(conf)).to(beFalse()) expect(config_needs_dump(conf)).to(beFalse()) @@ -61,6 +66,9 @@ class ConfigContactsSpec { contact2.nickname = "Joey".toLibSession() contact2.approved = true contact2.approved_me = true + contact2.created = createdTs + contact2.notifications = CONVO_NOTIFY_ALL + contact2.mute_until = nowTs + 1800 // Update the contact contacts_set(conf, &contact2) @@ -76,6 +84,10 @@ class ConfigContactsSpec { expect(String(libSessionVal: contact3.profile_pic.url)).to(beEmpty()) expect(contact3.blocked).to(beFalse()) expect(String(libSessionVal: contact3.session_id)).to(equal(definitelyRealId)) + expect(contact3.created).to(equal(createdTs)) + expect(contact2.notifications).to(equal(CONVO_NOTIFY_ALL)) + expect(contact2.mute_until).to(equal(nowTs + 1800)) + // Since we've made changes, we should need to push new config to the swarm, *and* should need // to dump the updated state: @@ -92,7 +104,7 @@ class ConfigContactsSpec { // Pretend we uploaded it let fakeHash1: String = "fakehash1" - var cFakeHash1: [CChar] = fakeHash1.cArray + var cFakeHash1: [CChar] = fakeHash1.cArray.nullTerminated() config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash1) expect(config_needs_push(conf)).to(beFalse()) expect(config_needs_dump(conf)).to(beTrue()) @@ -130,9 +142,10 @@ class ConfigContactsSpec { expect(contact4.profile_pic).toNot(beNil()) // Creates an empty instance apparently expect(String(libSessionVal: contact4.profile_pic.url)).to(beEmpty()) expect(contact4.blocked).to(beFalse()) + expect(contact4.created).to(equal(createdTs)) let anotherId: String = "051111111111111111111111111111111111111111111111111111111111111111" - var cAnotherId: [CChar] = anotherId.cArray + var cAnotherId: [CChar] = anotherId.cArray.nullTerminated() var contact5: contacts_contact = contacts_contact() expect(contacts_get_or_construct(conf2, &contact5, &cAnotherId)).to(beTrue()) expect(String(libSessionVal: contact5.name)).to(beEmpty()) @@ -152,7 +165,7 @@ class ConfigContactsSpec { // Check the merging let fakeHash2: String = "fakehash2" - var cFakeHash2: [CChar] = fakeHash2.cArray + var cFakeHash2: [CChar] = fakeHash2.cArray.nullTerminated() var mergeHashes: [UnsafePointer?] = [cFakeHash2].unsafeCopy() var mergeData: [UnsafePointer?] = [UnsafePointer(pushData4.pointee.config)] var mergeSize: [Int] = [pushData4.pointee.config_len] @@ -195,7 +208,7 @@ class ConfigContactsSpec { // Client 2 adds a new friend: let thirdId: String = "052222222222222222222222222222222222222222222222222222222222222222" - var cThirdId: [CChar] = thirdId.cArray + var cThirdId: [CChar] = thirdId.cArray.nullTerminated() var contact7: contacts_contact = contacts_contact() expect(contacts_get_or_construct(conf2, &contact7, &cThirdId)).to(beTrue()) contact7.nickname = "Nickname 3".toLibSession() @@ -223,9 +236,9 @@ class ConfigContactsSpec { .to(equal([fakeHash2])) let fakeHash3a: String = "fakehash3a" - var cFakeHash3a: [CChar] = fakeHash3a.cArray + var cFakeHash3a: [CChar] = fakeHash3a.cArray.nullTerminated() let fakeHash3b: String = "fakehash3b" - var cFakeHash3b: [CChar] = fakeHash3b.cArray + var cFakeHash3b: [CChar] = fakeHash3b.cArray.nullTerminated() config_confirm_pushed(conf, pushData6.pointee.seqno, &cFakeHash3a) config_confirm_pushed(conf2, pushData7.pointee.seqno, &cFakeHash3b) @@ -260,7 +273,7 @@ class ConfigContactsSpec { .to(equal([fakeHash3a, fakeHash3b])) let fakeHash4: String = "fakeHash4" - var cFakeHash4: [CChar] = fakeHash4.cArray + var cFakeHash4: [CChar] = fakeHash4.cArray.nullTerminated() config_confirm_pushed(conf, pushData8.pointee.seqno, &cFakeHash4) config_confirm_pushed(conf2, pushData9.pointee.seqno, &cFakeHash4) pushData8.deallocate() diff --git a/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigConvoInfoVolatileSpec.swift b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigConvoInfoVolatileSpec.swift index 83707b38c..668d54d1e 100644 --- a/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigConvoInfoVolatileSpec.swift +++ b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigConvoInfoVolatileSpec.swift @@ -33,7 +33,7 @@ class ConfigConvoInfoVolatileSpec { // Empty contacts shouldn't have an existing contact let definitelyRealId: String = "055000000000000000000000000000000000000000000000000000000000000000" - var cDefinitelyRealId: [CChar] = definitelyRealId.cArray + var cDefinitelyRealId: [CChar] = definitelyRealId.cArray.nullTerminated() var oneToOne1: convo_info_volatile_1to1 = convo_info_volatile_1to1() expect(convo_info_volatile_get_1to1(conf, &oneToOne1, &cDefinitelyRealId)).to(beFalse()) expect(convo_info_volatile_size(conf)).to(equal(0)) @@ -67,7 +67,7 @@ class ConfigConvoInfoVolatileSpec { expect(config_needs_dump(conf)).to(beTrue()) let openGroupBaseUrl: String = "http://Example.ORG:5678" - var cOpenGroupBaseUrl: [CChar] = openGroupBaseUrl.cArray + var cOpenGroupBaseUrl: [CChar] = openGroupBaseUrl.cArray.nullTerminated() let openGroupBaseUrlResult: String = openGroupBaseUrl.lowercased() // ("http://Example.ORG:5678" // .lowercased() @@ -75,7 +75,7 @@ class ConfigConvoInfoVolatileSpec { // [CChar](repeating: 0, count: (268 - openGroupBaseUrl.count)) // ) let openGroupRoom: String = "SudokuRoom" - var cOpenGroupRoom: [CChar] = openGroupRoom.cArray + var cOpenGroupRoom: [CChar] = openGroupRoom.cArray.nullTerminated() let openGroupRoomResult: String = openGroupRoom.lowercased() // ("SudokuRoom" // .lowercased() @@ -97,12 +97,12 @@ class ConfigConvoInfoVolatileSpec { // We don't need to push since we haven't changed anything, so this call is mainly just for // testing: - var pushData1: UnsafeMutablePointer = config_push(conf) + let pushData1: UnsafeMutablePointer = config_push(conf) expect(pushData1.pointee.seqno).to(equal(1)) // Pretend we uploaded it let fakeHash1: String = "fakehash1" - var cFakeHash1: [CChar] = fakeHash1.cArray + var cFakeHash1: [CChar] = fakeHash1.cArray.nullTerminated() config_confirm_pushed(conf, pushData1.pointee.seqno, &cFakeHash1) expect(config_needs_dump(conf)).to(beTrue()) expect(config_needs_push(conf)).to(beFalse()) @@ -136,26 +136,26 @@ class ConfigConvoInfoVolatileSpec { community2.unread = true let anotherId: String = "051111111111111111111111111111111111111111111111111111111111111111" - var cAnotherId: [CChar] = anotherId.cArray + var cAnotherId: [CChar] = anotherId.cArray.nullTerminated() var oneToOne5: convo_info_volatile_1to1 = convo_info_volatile_1to1() expect(convo_info_volatile_get_or_construct_1to1(conf2, &oneToOne5, &cAnotherId)).to(beTrue()) oneToOne5.unread = true convo_info_volatile_set_1to1(conf2, &oneToOne5) let thirdId: String = "05cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" - var cThirdId: [CChar] = thirdId.cArray + var cThirdId: [CChar] = thirdId.cArray.nullTerminated() var legacyGroup2: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() expect(convo_info_volatile_get_or_construct_legacy_group(conf2, &legacyGroup2, &cThirdId)).to(beTrue()) legacyGroup2.last_read = (nowTimestampMs - 50) convo_info_volatile_set_legacy_group(conf2, &legacyGroup2) expect(config_needs_push(conf2)).to(beTrue()) - var pushData2: UnsafeMutablePointer = config_push(conf2) + let pushData2: UnsafeMutablePointer = config_push(conf2) expect(pushData2.pointee.seqno).to(equal(2)) // Check the merging let fakeHash2: String = "fakehash2" - var cFakeHash2: [CChar] = fakeHash2.cArray + var cFakeHash2: [CChar] = fakeHash2.cArray.nullTerminated() var mergeHashes: [UnsafePointer?] = [cFakeHash2].unsafeCopy() var mergeData: [UnsafePointer?] = [UnsafePointer(pushData2.pointee.config)] var mergeSize: [Int] = [pushData2.pointee.config_len] @@ -203,7 +203,7 @@ class ConfigConvoInfoVolatileSpec { } let fourthId: String = "052000000000000000000000000000000000000000000000000000000000000000" - var cFourthId: [CChar] = fourthId.cArray + var cFourthId: [CChar] = fourthId.cArray.nullTerminated() expect(config_needs_push(conf)).to(beFalse()) convo_info_volatile_erase_1to1(conf, &cFourthId) expect(config_needs_push(conf)).to(beFalse()) diff --git a/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserGroupsSpec.swift b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserGroupsSpec.swift index 1812b8f0b..6ace1db75 100644 --- a/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserGroupsSpec.swift +++ b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserGroupsSpec.swift @@ -84,6 +84,8 @@ class ConfigUserGroupsSpec { } it("generates UserGroup configs correctly") { + let createdTs: Int64 = 1680064059 + let nowTs: Int64 = Int64(Date().timeIntervalSince1970) let seed: Data = Data(hex: "0123456789abcdef0123456789abcdef") // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately @@ -103,7 +105,7 @@ class ConfigUserGroupsSpec { // Empty contacts shouldn't have an existing contact let definitelyRealId: String = "055000000000000000000000000000000000000000000000000000000000000000" - var cDefinitelyRealId: [CChar] = definitelyRealId.cArray + var cDefinitelyRealId: [CChar] = definitelyRealId.cArray.nullTerminated() let legacyGroup1: UnsafeMutablePointer? = user_groups_get_legacy_group(conf, &cDefinitelyRealId) expect(legacyGroup1?.pointee).to(beNil()) expect(user_groups_size(conf)).to(equal(0)) @@ -112,12 +114,14 @@ class ConfigUserGroupsSpec { expect(legacyGroup2.pointee).toNot(beNil()) expect(String(libSessionVal: legacyGroup2.pointee.session_id)) .to(equal(definitelyRealId)) - expect(legacyGroup2.pointee.hidden).to(beFalse()) expect(legacyGroup2.pointee.disappearing_timer).to(equal(0)) expect(String(libSessionVal: legacyGroup2.pointee.enc_pubkey, fixedLength: 32)).to(equal("")) expect(String(libSessionVal: legacyGroup2.pointee.enc_seckey, fixedLength: 32)).to(equal("")) expect(legacyGroup2.pointee.priority).to(equal(0)) expect(String(libSessionVal: legacyGroup2.pointee.name)).to(equal("")) + expect(legacyGroup2.pointee.joined_at).to(equal(0)) + expect(legacyGroup2.pointee.notifications).to(equal(CONVO_NOTIFY_DEFAULT)) + expect(legacyGroup2.pointee.mute_until).to(equal(0)) // Iterate through and make sure we got everything we expected var membersSeen1: [String: Bool] = [:] @@ -155,9 +159,12 @@ class ConfigUserGroupsSpec { "055555555555555555555555555555555555555555555555555555555555555555", "056666666666666666666666666666666666666666666666666666666666666666" ] - var cUsers: [[CChar]] = users.map { $0.cArray } + var cUsers: [[CChar]] = users.map { $0.cArray.nullTerminated() } legacyGroup2.pointee.name = "Englishmen".toLibSession() legacyGroup2.pointee.disappearing_timer = 60 + legacyGroup2.pointee.joined_at = createdTs + legacyGroup2.pointee.notifications = CONVO_NOTIFY_ALL + legacyGroup2.pointee.mute_until = (nowTs + 3600) expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[0], false)).to(beTrue()) expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[1], true)).to(beTrue()) expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[2], false)).to(beTrue()) @@ -216,8 +223,8 @@ class ConfigUserGroupsSpec { let communityPubkey: String = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" var cCommunityPubkey: [UInt8] = Data(hex: communityPubkey).cArray - var cCommunityBaseUrl: [CChar] = "http://Example.ORG:5678".cArray - var cCommunityRoom: [CChar] = "SudokuRoom".cArray + var cCommunityBaseUrl: [CChar] = "http://Example.ORG:5678".cArray.nullTerminated() + var cCommunityRoom: [CChar] = "SudokuRoom".cArray.nullTerminated() var community1: ugroups_community_info = ugroups_community_info() expect(user_groups_get_or_construct_community(conf, &community1, &cCommunityBaseUrl, &cCommunityRoom, &cCommunityPubkey)) .to(beTrue()) @@ -240,7 +247,7 @@ class ConfigUserGroupsSpec { // Pretend we uploaded it let fakeHash1: String = "fakehash1" - var cFakeHash1: [CChar] = fakeHash1.cArray + var cFakeHash1: [CChar] = fakeHash1.cArray.nullTerminated() config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash1) expect(config_needs_dump(conf)).to(beTrue()) expect(config_needs_push(conf)).to(beFalse()) @@ -294,9 +301,11 @@ class ConfigUserGroupsSpec { expect(String(libSessionVal: legacyGroup4?.pointee.enc_seckey, fixedLength: 32)).to(equal("")) expect(legacyGroup4?.pointee.disappearing_timer).to(equal(60)) expect(String(libSessionVal: legacyGroup4?.pointee.session_id)).to(equal(definitelyRealId)) - expect(legacyGroup4?.pointee.hidden).to(beFalse()) expect(legacyGroup4?.pointee.priority).to(equal(3)) expect(String(libSessionVal: legacyGroup4?.pointee.name)).to(equal("Englishmen")) + expect(legacyGroup4?.pointee.joined_at).to(equal(createdTs)) + expect(legacyGroup2.pointee.notifications).to(equal(CONVO_NOTIFY_ALL)) + expect(legacyGroup2.pointee.mute_until).to(equal(nowTs + 3600)) var membersSeen3: [String: Bool] = [:] var memberSessionId3: UnsafePointer? = nil @@ -357,8 +366,8 @@ class ConfigUserGroupsSpec { ])) } - var cCommunity2BaseUrl: [CChar] = "http://example.org:5678".cArray - var cCommunity2Room: [CChar] = "sudokuRoom".cArray + var cCommunity2BaseUrl: [CChar] = "http://example.org:5678".cArray.nullTerminated() + var cCommunity2Room: [CChar] = "sudokuRoom".cArray.nullTerminated() var community2: ugroups_community_info = ugroups_community_info() expect(user_groups_get_community(conf2, &community2, &cCommunity2BaseUrl, &cCommunity2Room)) .to(beTrue()) @@ -383,7 +392,7 @@ class ConfigUserGroupsSpec { expect(config_needs_dump(conf2)).to(beTrue()) let fakeHash2: String = "fakehash2" - var cFakeHash2: [CChar] = fakeHash2.cArray + var cFakeHash2: [CChar] = fakeHash2.cArray.nullTerminated() let pushData7: UnsafeMutablePointer = config_push(conf2) expect(pushData7.pointee.seqno).to(equal(2)) config_confirm_pushed(conf2, pushData7.pointee.seqno, &cFakeHash2) @@ -413,8 +422,8 @@ class ConfigUserGroupsSpec { expect(config_merge(conf, &mergeHashes1, &mergeData1, &mergeSize1, 1)).to(equal(1)) pushData8.deallocate() - var cCommunity3BaseUrl: [CChar] = "http://example.org:5678".cArray - var cCommunity3Room: [CChar] = "SudokuRoom".cArray + var cCommunity3BaseUrl: [CChar] = "http://example.org:5678".cArray.nullTerminated() + var cCommunity3Room: [CChar] = "SudokuRoom".cArray.nullTerminated() var community3: ugroups_community_info = ugroups_community_info() expect(user_groups_get_community(conf, &community3, &cCommunity3BaseUrl, &cCommunity3Room)) .to(beTrue()) @@ -442,12 +451,12 @@ class ConfigUserGroupsSpec { expect(config_needs_push(conf2)).to(beTrue()) expect(config_needs_dump(conf2)).to(beTrue()) - var cCommunity4BaseUrl: [CChar] = "http://exAMple.ORG:5678".cArray - var cCommunity4Room: [CChar] = "sudokuROOM".cArray + var cCommunity4BaseUrl: [CChar] = "http://exAMple.ORG:5678".cArray.nullTerminated() + var cCommunity4Room: [CChar] = "sudokuROOM".cArray.nullTerminated() user_groups_erase_community(conf2, &cCommunity4BaseUrl, &cCommunity4Room) let fakeHash3: String = "fakehash3" - var cFakeHash3: [CChar] = fakeHash3.cArray + var cFakeHash3: [CChar] = fakeHash3.cArray.nullTerminated() let pushData10: UnsafeMutablePointer = config_push(conf2) config_confirm_pushed(conf2, pushData10.pointee.seqno, &cFakeHash3) @@ -470,13 +479,13 @@ class ConfigUserGroupsSpec { expect(user_groups_size_legacy_groups(conf)).to(equal(1)) var prio: Int32 = 0 - var cBeanstalkBaseUrl: [CChar] = "http://jacksbeanstalk.org".cArray + var cBeanstalkBaseUrl: [CChar] = "http://jacksbeanstalk.org".cArray.nullTerminated() var cBeanstalkPubkey: [UInt8] = Data( hex: "0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff" ).cArray ["fee", "fi", "fo", "fum"].forEach { room in - var cRoom: [CChar] = room.cArray + var cRoom: [CChar] = room.cArray.nullTerminated() prio += 1 var community4: ugroups_community_info = ugroups_community_info() @@ -491,7 +500,7 @@ class ConfigUserGroupsSpec { expect(user_groups_size_legacy_groups(conf)).to(equal(1)) let fakeHash4: String = "fakehash4" - var cFakeHash4: [CChar] = fakeHash4.cArray + var cFakeHash4: [CChar] = fakeHash4.cArray.nullTerminated() let pushData11: UnsafeMutablePointer = config_push(conf) config_confirm_pushed(conf, pushData11.pointee.seqno, &cFakeHash4) expect(pushData11.pointee.seqno).to(equal(4)) @@ -500,11 +509,11 @@ class ConfigUserGroupsSpec { // Load some obsolete ones in just to check that they get immediately obsoleted let fakeHash10: String = "fakehash10" - let cFakeHash10: [CChar] = fakeHash10.cArray + let cFakeHash10: [CChar] = fakeHash10.cArray.nullTerminated() let fakeHash11: String = "fakehash11" - let cFakeHash11: [CChar] = fakeHash11.cArray + let cFakeHash11: [CChar] = fakeHash11.cArray.nullTerminated() let fakeHash12: String = "fakehash12" - let cFakeHash12: [CChar] = fakeHash12.cArray + let cFakeHash12: [CChar] = fakeHash12.cArray.nullTerminated() var mergeHashes3: [UnsafePointer?] = [cFakeHash10, cFakeHash11, cFakeHash12, cFakeHash4].unsafeCopy() var mergeData3: [UnsafePointer?] = [ UnsafePointer(pushData10.pointee.config), diff --git a/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserProfileSpec.swift b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserProfileSpec.swift index 87e2f10d4..15cafd306 100644 --- a/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserProfileSpec.swift +++ b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserProfileSpec.swift @@ -204,7 +204,7 @@ class ConfigUserProfileSpec { // So now imagine we got back confirmation from the swarm that the push has been stored: let fakeHash1: String = "fakehash1" - var cFakeHash1: [CChar] = fakeHash1.cArray + var cFakeHash1: [CChar] = fakeHash1.cArray.nullTerminated() config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash1) pushData2.deallocate() @@ -289,13 +289,13 @@ class ConfigUserProfileSpec { expect(config_needs_push(conf2)).to(beTrue()) let fakeHash2: String = "fakehash2" - var cFakeHash2: [CChar] = fakeHash2.cArray + var cFakeHash2: [CChar] = fakeHash2.cArray.nullTerminated() let pushData3: UnsafeMutablePointer = config_push(conf) expect(pushData3.pointee.seqno).to(equal(2)) // incremented, since we made a field change config_confirm_pushed(conf, pushData3.pointee.seqno, &cFakeHash2) let fakeHash3: String = "fakehash3" - var cFakeHash3: [CChar] = fakeHash3.cArray + var cFakeHash3: [CChar] = fakeHash3.cArray.nullTerminated() let pushData4: UnsafeMutablePointer = config_push(conf2) expect(pushData4.pointee.seqno).to(equal(2)) // incremented, since we made a field change config_confirm_pushed(conf, pushData4.pointee.seqno, &cFakeHash3) @@ -365,9 +365,9 @@ class ConfigUserProfileSpec { expect(user_profile_get_nts_priority(conf2)).to(equal(9)) let fakeHash4: String = "fakehash4" - var cFakeHash4: [CChar] = fakeHash4.cArray + var cFakeHash4: [CChar] = fakeHash4.cArray.nullTerminated() let fakeHash5: String = "fakehash5" - var cFakeHash5: [CChar] = fakeHash5.cArray + var cFakeHash5: [CChar] = fakeHash5.cArray.nullTerminated() config_confirm_pushed(conf, pushData5.pointee.seqno, &cFakeHash4) config_confirm_pushed(conf2, pushData6.pointee.seqno, &cFakeHash5) pushData5.deallocate() diff --git a/SessionMessagingKitTests/LibSessionUtil/Utilities/LibSessionTypeConversionUtilitiesSpec.swift b/SessionMessagingKitTests/LibSessionUtil/Utilities/LibSessionTypeConversionUtilitiesSpec.swift index ac2f5d825..5aeeff2b3 100644 --- a/SessionMessagingKitTests/LibSessionUtil/Utilities/LibSessionTypeConversionUtilitiesSpec.swift +++ b/SessionMessagingKitTests/LibSessionUtil/Utilities/LibSessionTypeConversionUtilitiesSpec.swift @@ -49,10 +49,10 @@ class LibSessionTypeConversionUtilitiesSpec: QuickSpec { it("returns a string when valid") { let test: [CChar] = [84, 101, 115, 116] let result = test.withUnsafeBufferPointer { ptr in - String(pointer: UnsafeRawPointer(ptr.baseAddress), length: 5) + String(pointer: UnsafeRawPointer(ptr.baseAddress), length: 4) } - expect(result).to(equal("Test\0")) + expect(result).to(equal("Test")) } } diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift b/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift index 77629645d..379c634eb 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift +++ b/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift @@ -186,7 +186,7 @@ class OpenGroupAPISpec: QuickSpec { it("generates the correct request") { mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI.poll( db, server: "testserver", @@ -221,7 +221,7 @@ class OpenGroupAPISpec: QuickSpec { it("retrieves recent messages if there was no last message") { mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI.poll( db, server: "testserver", @@ -250,7 +250,7 @@ class OpenGroupAPISpec: QuickSpec { } mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI.poll( db, server: "testserver", @@ -279,7 +279,7 @@ class OpenGroupAPISpec: QuickSpec { } mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI.poll( db, server: "testserver", @@ -308,7 +308,7 @@ class OpenGroupAPISpec: QuickSpec { } mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI.poll( db, server: "testserver", @@ -340,7 +340,7 @@ class OpenGroupAPISpec: QuickSpec { it("does not call the inbox and outbox endpoints") { mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI.poll( db, server: "testserver", @@ -439,7 +439,7 @@ class OpenGroupAPISpec: QuickSpec { it("includes the inbox and outbox endpoints") { mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI.poll( db, server: "testserver", @@ -466,7 +466,7 @@ class OpenGroupAPISpec: QuickSpec { it("retrieves recent inbox messages if there was no last message") { mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI.poll( db, server: "testserver", @@ -495,7 +495,7 @@ class OpenGroupAPISpec: QuickSpec { } mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI.poll( db, server: "testserver", @@ -519,7 +519,7 @@ class OpenGroupAPISpec: QuickSpec { it("retrieves recent outbox messages if there was no last message") { mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI.poll( db, server: "testserver", @@ -548,7 +548,7 @@ class OpenGroupAPISpec: QuickSpec { } mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI.poll( db, server: "testserver", @@ -609,7 +609,7 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI.poll( db, server: "testserver", @@ -639,7 +639,7 @@ class OpenGroupAPISpec: QuickSpec { it("errors when no data is returned") { mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI.poll( db, server: "testserver", @@ -668,7 +668,7 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI.poll( db, server: "testserver", @@ -697,7 +697,7 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI.poll( db, server: "testserver", @@ -726,7 +726,7 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI.poll( db, server: "testserver", @@ -787,7 +787,7 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI.poll( db, server: "testserver", @@ -825,7 +825,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Capabilities)? mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI.capabilities( db, server: "testserver", @@ -895,7 +895,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: [OpenGroupAPI.Room])? mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI.rooms( db, server: "testserver", @@ -986,7 +986,7 @@ class OpenGroupAPISpec: QuickSpec { var response: OpenGroupAPI.CapabilitiesAndRoomResponse? mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI.capabilitiesAndRoom( db, for: "testRoom", @@ -1041,7 +1041,7 @@ class OpenGroupAPISpec: QuickSpec { var response: OpenGroupAPI.CapabilitiesAndRoomResponse? mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .capabilitiesAndRoom( db, @@ -1113,7 +1113,7 @@ class OpenGroupAPISpec: QuickSpec { var response: OpenGroupAPI.CapabilitiesAndRoomResponse? mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .capabilitiesAndRoom( db, @@ -1202,7 +1202,7 @@ class OpenGroupAPISpec: QuickSpec { var response: OpenGroupAPI.CapabilitiesAndRoomResponse? mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI.capabilitiesAndRoom( db, for: "testRoom", @@ -1261,7 +1261,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .send( db, @@ -1306,7 +1306,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .send( db, @@ -1346,7 +1346,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .send( db, @@ -1381,7 +1381,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .send( db, @@ -1414,7 +1414,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .send( db, @@ -1454,7 +1454,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .send( db, @@ -1494,7 +1494,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .send( db, @@ -1529,7 +1529,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .send( db, @@ -1570,7 +1570,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .send( db, @@ -1623,7 +1623,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .message( db, @@ -1675,7 +1675,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: Data?)? mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .messageUpdate( db, @@ -1716,7 +1716,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: Data?)? mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .messageUpdate( db, @@ -1755,7 +1755,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: Data?)? mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .messageUpdate( db, @@ -1789,7 +1789,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: Data?)? mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .messageUpdate( db, @@ -1821,7 +1821,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: Data?)? mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .messageUpdate( db, @@ -1860,7 +1860,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: Data?)? mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .messageUpdate( db, @@ -1899,7 +1899,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: Data?)? mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .messageUpdate( db, @@ -1933,7 +1933,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: Data?)? mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .messageUpdate( db, @@ -1973,7 +1973,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: Data?)? mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .messageUpdate( db, @@ -2010,7 +2010,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: Data?)? mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .messageDelete( db, @@ -2054,7 +2054,7 @@ class OpenGroupAPISpec: QuickSpec { it("generates the request and handles the response correctly") { mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .messagesDeleteAll( db, @@ -2094,7 +2094,7 @@ class OpenGroupAPISpec: QuickSpec { var response: ResponseInfoType? mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .pinMessage( db, @@ -2132,7 +2132,7 @@ class OpenGroupAPISpec: QuickSpec { var response: ResponseInfoType? mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .unpinMessage( db, @@ -2170,7 +2170,7 @@ class OpenGroupAPISpec: QuickSpec { var response: ResponseInfoType? mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .unpinAll( db, @@ -2209,7 +2209,7 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .uploadFile( db, @@ -2245,7 +2245,7 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .uploadFile( db, @@ -2281,7 +2281,7 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .uploadFile( db, @@ -2319,7 +2319,7 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .downloadFile( db, @@ -2376,7 +2376,7 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.SendDirectMessageResponse)? mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .send( db, @@ -2425,7 +2425,7 @@ class OpenGroupAPISpec: QuickSpec { it("generates the request and handles the response correctly") { mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .userBan( db, @@ -2455,7 +2455,7 @@ class OpenGroupAPISpec: QuickSpec { it("does a global ban if no room tokens are provided") { mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .userBan( db, @@ -2487,7 +2487,7 @@ class OpenGroupAPISpec: QuickSpec { it("does room specific bans if room tokens are provided") { mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .userBan( db, @@ -2534,7 +2534,7 @@ class OpenGroupAPISpec: QuickSpec { it("generates the request and handles the response correctly") { mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .userUnban( db, @@ -2563,7 +2563,7 @@ class OpenGroupAPISpec: QuickSpec { it("does a global ban if no room tokens are provided") { mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .userUnban( db, @@ -2594,7 +2594,7 @@ class OpenGroupAPISpec: QuickSpec { it("does room specific bans if room tokens are provided") { mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .userUnban( db, @@ -2640,7 +2640,7 @@ class OpenGroupAPISpec: QuickSpec { it("generates the request and handles the response correctly") { mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .userModeratorUpdate( db, @@ -2672,7 +2672,7 @@ class OpenGroupAPISpec: QuickSpec { it("does a global update if no room tokens are provided") { mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .userModeratorUpdate( db, @@ -2706,7 +2706,7 @@ class OpenGroupAPISpec: QuickSpec { it("does room specific updates if room tokens are provided") { mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .userModeratorUpdate( db, @@ -2740,7 +2740,7 @@ class OpenGroupAPISpec: QuickSpec { it("fails if neither moderator or admin are set") { mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .userModeratorUpdate( db, @@ -2804,7 +2804,7 @@ class OpenGroupAPISpec: QuickSpec { it("generates the request and handles the response correctly") { mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .userBanAndDeleteAllMessages( db, @@ -2833,7 +2833,7 @@ class OpenGroupAPISpec: QuickSpec { it("bans the user from the specified room rather than globally") { mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .userBanAndDeleteAllMessages( db, @@ -2890,7 +2890,7 @@ class OpenGroupAPISpec: QuickSpec { } mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .rooms( db, @@ -2917,7 +2917,7 @@ class OpenGroupAPISpec: QuickSpec { } mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .rooms( db, @@ -2944,7 +2944,7 @@ class OpenGroupAPISpec: QuickSpec { } mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .rooms( db, @@ -2975,7 +2975,7 @@ class OpenGroupAPISpec: QuickSpec { it("signs correctly") { mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .rooms( db, @@ -3011,7 +3011,7 @@ class OpenGroupAPISpec: QuickSpec { mockSign.when { $0.signature(message: anyArray(), secretKey: anyArray()) }.thenReturn(nil) mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .rooms( db, @@ -3044,7 +3044,7 @@ class OpenGroupAPISpec: QuickSpec { it("signs correctly") { mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .rooms( db, @@ -3081,7 +3081,7 @@ class OpenGroupAPISpec: QuickSpec { .thenReturn(nil) mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .rooms( db, @@ -3108,7 +3108,7 @@ class OpenGroupAPISpec: QuickSpec { .thenReturn(nil) mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { db in + .readPublisherFlatMap { db in OpenGroupAPI .rooms( db, diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift index e995baee8..dfcad2e76 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift +++ b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift @@ -815,7 +815,7 @@ class OpenGroupManagerSpec: QuickSpec { var didComplete: Bool = false // Prevent multi-threading test bugs mockStorage - .writePublisherFlatMap(receiveOn: DispatchQueue.main) { (db: Database) -> AnyPublisher in + .writePublisherFlatMap { (db: Database) -> AnyPublisher in openGroupManager .add( db, @@ -846,7 +846,7 @@ class OpenGroupManagerSpec: QuickSpec { var didComplete: Bool = false // Prevent multi-threading test bugs mockStorage - .writePublisherFlatMap(receiveOn: DispatchQueue.main) { (db: Database) -> AnyPublisher in + .writePublisherFlatMap { (db: Database) -> AnyPublisher in openGroupManager .add( db, @@ -883,7 +883,7 @@ class OpenGroupManagerSpec: QuickSpec { var didComplete: Bool = false // Prevent multi-threading test bugs mockStorage - .writePublisherFlatMap(receiveOn: DispatchQueue.main) { (db: Database) -> AnyPublisher in + .writePublisherFlatMap { (db: Database) -> AnyPublisher in openGroupManager .add( db, @@ -939,7 +939,7 @@ class OpenGroupManagerSpec: QuickSpec { var error: Error? mockStorage - .writePublisherFlatMap(receiveOn: DispatchQueue.main) { (db: Database) -> AnyPublisher in + .writePublisherFlatMap { (db: Database) -> AnyPublisher in openGroupManager .add( db, @@ -3598,7 +3598,7 @@ class OpenGroupManagerSpec: QuickSpec { var result: Data? mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { (db: Database) -> AnyPublisher in + .readPublisherFlatMap { (db: Database) -> AnyPublisher in OpenGroupManager .roomImage( db, @@ -3617,7 +3617,7 @@ class OpenGroupManagerSpec: QuickSpec { it("does not save the fetched image to storage") { var didComplete: Bool = false mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { (db: Database) -> AnyPublisher in + .readPublisherFlatMap { (db: Database) -> AnyPublisher in OpenGroupManager .roomImage( db, @@ -3648,7 +3648,7 @@ class OpenGroupManagerSpec: QuickSpec { it("does not update the image update timestamp") { var didComplete: Bool = false mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { (db: Database) -> AnyPublisher in + .readPublisherFlatMap { (db: Database) -> AnyPublisher in OpenGroupManager .roomImage( db, @@ -3690,7 +3690,7 @@ class OpenGroupManagerSpec: QuickSpec { dependencies = dependencies.with(onionApi: TestNeverReturningApi.self) let publisher = mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { (db: Database) -> AnyPublisher in + .readPublisherFlatMap { (db: Database) -> AnyPublisher in OpenGroupManager .roomImage( db, @@ -3716,7 +3716,7 @@ class OpenGroupManagerSpec: QuickSpec { var result: Data? mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { (db: Database) -> AnyPublisher in + .readPublisherFlatMap { (db: Database) -> AnyPublisher in OpenGroupManager .roomImage( db, @@ -3736,7 +3736,7 @@ class OpenGroupManagerSpec: QuickSpec { var didComplete: Bool = false mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { (db: Database) -> AnyPublisher in + .readPublisherFlatMap { (db: Database) -> AnyPublisher in OpenGroupManager .roomImage( db, @@ -3768,7 +3768,7 @@ class OpenGroupManagerSpec: QuickSpec { var didComplete: Bool = false mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { (db: Database) -> AnyPublisher in + .readPublisherFlatMap { (db: Database) -> AnyPublisher in OpenGroupManager .roomImage( db, @@ -3816,7 +3816,7 @@ class OpenGroupManagerSpec: QuickSpec { var result: Data? mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { (db: Database) -> AnyPublisher in + .readPublisherFlatMap { (db: Database) -> AnyPublisher in OpenGroupManager .roomImage( db, @@ -3846,7 +3846,7 @@ class OpenGroupManagerSpec: QuickSpec { var result: Data? mockStorage - .readPublisherFlatMap(receiveOn: DispatchQueue.main) { (db: Database) -> AnyPublisher in + .readPublisherFlatMap { (db: Database) -> AnyPublisher in OpenGroupManager .roomImage( db, diff --git a/SessionShareExtension/ThreadPickerVC.swift b/SessionShareExtension/ThreadPickerVC.swift index 4ef64aa8f..3e67719ac 100644 --- a/SessionShareExtension/ThreadPickerVC.swift +++ b/SessionShareExtension/ThreadPickerVC.swift @@ -189,7 +189,7 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView NotificationCenter.default.post(name: Database.resumeNotification, object: self) Storage.shared - .writePublisher(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db -> MessageSender.PreparedSendData in + .writePublisher { db -> MessageSender.PreparedSendData in guard let threadVariant: SessionThread.Variant = try SessionThread .filter(id: threadId) @@ -253,6 +253,7 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView threadVariant: threadVariant ) } + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .flatMap { MessageSender.performUploadsIfNeeded( queue: DispatchQueue.global(qos: .userInitiated), diff --git a/SessionSnodeKit/Jobs/GetSnodePoolJob.swift b/SessionSnodeKit/Jobs/GetSnodePoolJob.swift index 45a0f1d5c..365ef1773 100644 --- a/SessionSnodeKit/Jobs/GetSnodePoolJob.swift +++ b/SessionSnodeKit/Jobs/GetSnodePoolJob.swift @@ -23,7 +23,10 @@ public enum GetSnodePoolJob: JobExecutor { // to block if we have no Snode pool and prevent other jobs from failing but avoids having to // wait if we already have a potentially valid snode pool guard !SnodeAPI.hasCachedSnodesInclusingExpired() else { - SnodeAPI.getSnodePool().sinkUntilComplete() + SnodeAPI + .getSnodePool() + .subscribe(on: DispatchQueue.global(qos: .default)) + .sinkUntilComplete() success(job, false) return } diff --git a/SessionSnodeKit/Networking/SnodeAPI.swift b/SessionSnodeKit/Networking/SnodeAPI.swift index c2ecd415b..58d33734e 100644 --- a/SessionSnodeKit/Networking/SnodeAPI.swift +++ b/SessionSnodeKit/Networking/SnodeAPI.swift @@ -169,15 +169,16 @@ public final class SnodeAPI { .catch { _ in getSnodePoolFromSeedNode() } .eraseToAnyPublisher() }() - - getSnodePoolPublisher.mutate { $0 = publisher } + + /// Actually assign the atomic value + result = publisher return publisher .tryFlatMap { snodePool -> AnyPublisher, Error> in guard !snodePool.isEmpty else { throw SnodeAPIError.snodePoolUpdatingFailed } return Storage.shared - .writePublisher(receiveOn: Threading.workQueue) { db in + .writePublisher { db in db[.lastSnodePoolRefreshDate] = now setSnodePool(db, to: snodePool) diff --git a/SessionUtilitiesKit/Database/Storage.swift b/SessionUtilitiesKit/Database/Storage.swift index 32583c068..f2095bd52 100644 --- a/SessionUtilitiesKit/Database/Storage.swift +++ b/SessionUtilitiesKit/Database/Storage.swift @@ -98,6 +98,13 @@ open class Storage { // MARK: - Migrations + public static func appliedMigrationIdentifiers(_ db: Database) -> Set { + let migrator: DatabaseMigrator = DatabaseMigrator() + + return (try? migrator.appliedIdentifiers(db)) + .defaulting(to: []) + } + public func perform( migrations: [TargetMigrations], async: Bool = true, @@ -336,30 +343,50 @@ open class Storage { ) } - open func writePublisher( - receiveOn scheduler: S, + open func writePublisher( updates: @escaping (Database) throws -> T - ) -> AnyPublisher where S: Scheduler { + ) -> AnyPublisher { guard isValid, let dbWriter: DatabaseWriter = dbWriter else { return Fail(error: StorageError.databaseInvalid) .eraseToAnyPublisher() } - return dbWriter.writePublisher(receiveOn: scheduler, updates: updates) - .eraseToAnyPublisher() + /// **Note:** GRDB does have a `writePublisher` method but it appears to asynchronously trigger + /// both the `output` and `complete` closures at the same time which causes a lot of unexpected + /// behaviours (this behaviour is apparently expected but still causes a number of odd behaviours in our code + /// for more information see https://github.com/groue/GRDB.swift/issues/1334) + /// + /// Instead of this we are just using `Deferred { Future {} }` which is executed on the specified scheduled + /// which behaves in a much more expected way than the GRDB `writePublisher` does + return Deferred { + Future { resolver in + do { resolver(Result.success(try dbWriter.write(updates))) } + catch { resolver(Result.failure(error)) } + } + }.eraseToAnyPublisher() } - open func readPublisher( - receiveOn scheduler: S, + open func readPublisher( value: @escaping (Database) throws -> T - ) -> AnyPublisher where S: Scheduler { + ) -> AnyPublisher { guard isValid, let dbWriter: DatabaseWriter = dbWriter else { return Fail(error: StorageError.databaseInvalid) .eraseToAnyPublisher() } - return dbWriter.readPublisher(receiveOn: scheduler, value: value) - .eraseToAnyPublisher() + /// **Note:** GRDB does have a `readPublisher` method but it appears to asynchronously trigger + /// both the `output` and `complete` closures at the same time which causes a lot of unexpected + /// behaviours (this behaviour is apparently expected but still causes a number of odd behaviours in our code + /// for more information see https://github.com/groue/GRDB.swift/issues/1334) + /// + /// Instead of this we are just using `Deferred { Future {} }` which is executed on the specified scheduled + /// which behaves in a much more expected way than the GRDB `readPublisher` does + return Deferred { + Future { resolver in + do { resolver(Result.success(try dbWriter.read(value))) } + catch { resolver(Result.failure(error)) } + } + }.eraseToAnyPublisher() } @discardableResult public final func read(_ value: (Database) throws -> T?) -> T? { @@ -423,20 +450,18 @@ open class Storage { // MARK: - Combine Extensions public extension Storage { - func readPublisherFlatMap( - receiveOn scheduler: S, + func readPublisherFlatMap( value: @escaping (Database) throws -> AnyPublisher - ) -> AnyPublisher where S: Scheduler { - return readPublisher(receiveOn: scheduler, value: value) + ) -> AnyPublisher { + return readPublisher(value: value) .flatMap { resultPublisher -> AnyPublisher in resultPublisher } .eraseToAnyPublisher() } - func writePublisherFlatMap( - receiveOn scheduler: S, + func writePublisherFlatMap( updates: @escaping (Database) throws -> AnyPublisher - ) -> AnyPublisher where S: Scheduler { - return writePublisher(receiveOn: scheduler, updates: updates) + ) -> AnyPublisher { + return writePublisher(updates: updates) .flatMap { resultPublisher -> AnyPublisher in resultPublisher } .eraseToAnyPublisher() } diff --git a/SignalUtilitiesKit/Shared View Controllers/OWSViewController.m b/SignalUtilitiesKit/Shared View Controllers/OWSViewController.m index 0f842a238..c35b43035 100644 --- a/SignalUtilitiesKit/Shared View Controllers/OWSViewController.m +++ b/SignalUtilitiesKit/Shared View Controllers/OWSViewController.m @@ -4,8 +4,9 @@ #import "OWSViewController.h" #import "UIView+OWS.h" -#import #import "AppContext.h" +#import +#import #import NS_ASSUME_NONNULL_BEGIN diff --git a/_SharedTestUtilities/SynchronousStorage.swift b/_SharedTestUtilities/SynchronousStorage.swift index 8d3806d99..c7feaf137 100644 --- a/_SharedTestUtilities/SynchronousStorage.swift +++ b/_SharedTestUtilities/SynchronousStorage.swift @@ -5,10 +5,9 @@ import GRDB import SessionUtilitiesKit class SynchronousStorage: Storage { - override func readPublisher( - receiveOn scheduler: S, + override func readPublisher( value: @escaping (Database) throws -> T - ) -> AnyPublisher where S: Scheduler { + ) -> AnyPublisher { guard let result: T = super.read(value) else { return Fail(error: StorageError.generic) .eraseToAnyPublisher() @@ -19,10 +18,9 @@ class SynchronousStorage: Storage { .eraseToAnyPublisher() } - override func writePublisher( - receiveOn scheduler: S, + override func writePublisher( updates: @escaping (Database) throws -> T - ) -> AnyPublisher where S: Scheduler { + ) -> AnyPublisher { guard let result: T = super.write(updates: updates) else { return Fail(error: StorageError.generic) .eraseToAnyPublisher() From ad3e53d235b1ed3325636cb79e019d880849c18b Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 14 Apr 2023 18:19:18 +1000 Subject: [PATCH 049/135] Fixed issues found during QA Fixed a crash when removing a member from a group Fixed an issue with accepting a message request breaking the UI Fixed an issue where the mute optimistic update wasn't working --- Session.xcodeproj/project.pbxproj | 14 ++++----- .../ConversationTitleView.swift | 2 +- Session/Home/HomeViewModel.swift | 7 +++-- .../GIFs/GifPickerViewController.swift | 5 +--- Session/Shared/FullConversationCell.swift | 27 +++++++++++++---- .../MessageSender+ClosedGroups.swift | 30 +++++++++---------- .../Utilities/ReachabilityManager.swift | 2 ++ 7 files changed, 51 insertions(+), 36 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 787c07f3f..65b1a294c 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -111,7 +111,6 @@ 7B1D74B027C365960030B423 /* Timer+MainThread.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1D74AF27C365960030B423 /* Timer+MainThread.swift */; }; 7B2561C22978B307005C086C /* MediaInfoVC+MediaInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2561C12978B307005C086C /* MediaInfoVC+MediaInfoView.swift */; }; 7B2561C429874851005C086C /* SessionCarouselView+Info.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2561C329874851005C086C /* SessionCarouselView+Info.swift */; }; - 7B2E985829AC227C001792D7 /* UIContextualAction+Theming.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2E985729AC227C001792D7 /* UIContextualAction+Theming.swift */; }; 7B3A392E2977791E002FE4AC /* MediaInfoVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3A392D2977791E002FE4AC /* MediaInfoVC.swift */; }; 7B3A3930297A3919002FE4AC /* MediaInfoVC+MediaPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3A392F297A3919002FE4AC /* MediaInfoVC+MediaPreviewView.swift */; }; 7B3A39322980D02B002FE4AC /* SessionCarouselView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3A39312980D02B002FE4AC /* SessionCarouselView.swift */; }; @@ -816,15 +815,15 @@ FDD250702837199200198BDA /* GarbageCollectionJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD2506F2837199200198BDA /* GarbageCollectionJob.swift */; }; FDD250722837234B00198BDA /* MediaGalleryNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD250712837234B00198BDA /* MediaGalleryNavigationController.swift */; }; FDDC08F229A300E800BF9681 /* LibSessionTypeConversionUtilitiesSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDDC08F129A300E800BF9681 /* LibSessionTypeConversionUtilitiesSpec.swift */; }; - FDDF074429C3E3D000E5E8B5 /* FetchRequest+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDDF074329C3E3D000E5E8B5 /* FetchRequest+Utilities.swift */; }; - FDE658A129418C7900A33BC1 /* CryptoKit+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE658A029418C7900A33BC1 /* CryptoKit+Utilities.swift */; }; - FDE658A329418E2F00A33BC1 /* KeyPair.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE658A229418E2F00A33BC1 /* KeyPair.swift */; }; FDDCBDA829E776BF00303C38 /* seed2-2023-2y.crt in Resources */ = {isa = PBXBuildFile; fileRef = FDDCBDA229E776BF00303C38 /* seed2-2023-2y.crt */; }; FDDCBDA929E776BF00303C38 /* seed1-2023-2y.crt in Resources */ = {isa = PBXBuildFile; fileRef = FDDCBDA329E776BF00303C38 /* seed1-2023-2y.crt */; }; FDDCBDAA29E776BF00303C38 /* seed1-2023-2y.der in Resources */ = {isa = PBXBuildFile; fileRef = FDDCBDA429E776BF00303C38 /* seed1-2023-2y.der */; }; FDDCBDAB29E776BF00303C38 /* seed2-2023-2y.der in Resources */ = {isa = PBXBuildFile; fileRef = FDDCBDA529E776BF00303C38 /* seed2-2023-2y.der */; }; FDDCBDAC29E776BF00303C38 /* seed3-2023-2y.crt in Resources */ = {isa = PBXBuildFile; fileRef = FDDCBDA629E776BF00303C38 /* seed3-2023-2y.crt */; }; FDDCBDAD29E776BF00303C38 /* seed3-2023-2y.der in Resources */ = {isa = PBXBuildFile; fileRef = FDDCBDA729E776BF00303C38 /* seed3-2023-2y.der */; }; + FDDF074429C3E3D000E5E8B5 /* FetchRequest+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDDF074329C3E3D000E5E8B5 /* FetchRequest+Utilities.swift */; }; + FDE658A129418C7900A33BC1 /* CryptoKit+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE658A029418C7900A33BC1 /* CryptoKit+Utilities.swift */; }; + FDE658A329418E2F00A33BC1 /* KeyPair.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE658A229418E2F00A33BC1 /* KeyPair.swift */; }; FDE77F6B280FEB28002CFC5D /* ControlMessageProcessRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE77F6A280FEB28002CFC5D /* ControlMessageProcessRecord.swift */; }; FDED2E3C282E1B5D00B2CD2A /* UICollectionView+ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDED2E3B282E1B5D00B2CD2A /* UICollectionView+ReusableView.swift */; }; FDF0B73C27FFD3D6004C14C5 /* LinkPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B73B27FFD3D6004C14C5 /* LinkPreview.swift */; }; @@ -1955,15 +1954,15 @@ FDD2506F2837199200198BDA /* GarbageCollectionJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GarbageCollectionJob.swift; sourceTree = ""; }; FDD250712837234B00198BDA /* MediaGalleryNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaGalleryNavigationController.swift; sourceTree = ""; }; FDDC08F129A300E800BF9681 /* LibSessionTypeConversionUtilitiesSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibSessionTypeConversionUtilitiesSpec.swift; sourceTree = ""; }; - FDDF074329C3E3D000E5E8B5 /* FetchRequest+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FetchRequest+Utilities.swift"; sourceTree = ""; }; - FDE658A029418C7900A33BC1 /* CryptoKit+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CryptoKit+Utilities.swift"; sourceTree = ""; }; - FDE658A229418E2F00A33BC1 /* KeyPair.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyPair.swift; sourceTree = ""; }; FDDCBDA229E776BF00303C38 /* seed2-2023-2y.crt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "seed2-2023-2y.crt"; sourceTree = ""; }; FDDCBDA329E776BF00303C38 /* seed1-2023-2y.crt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "seed1-2023-2y.crt"; sourceTree = ""; }; FDDCBDA429E776BF00303C38 /* seed1-2023-2y.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = "seed1-2023-2y.der"; sourceTree = ""; }; FDDCBDA529E776BF00303C38 /* seed2-2023-2y.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = "seed2-2023-2y.der"; sourceTree = ""; }; FDDCBDA629E776BF00303C38 /* seed3-2023-2y.crt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "seed3-2023-2y.crt"; sourceTree = ""; }; FDDCBDA729E776BF00303C38 /* seed3-2023-2y.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = "seed3-2023-2y.der"; sourceTree = ""; }; + FDDF074329C3E3D000E5E8B5 /* FetchRequest+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FetchRequest+Utilities.swift"; sourceTree = ""; }; + FDE658A029418C7900A33BC1 /* CryptoKit+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CryptoKit+Utilities.swift"; sourceTree = ""; }; + FDE658A229418E2F00A33BC1 /* KeyPair.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyPair.swift; sourceTree = ""; }; FDE7214F287E50D50093DF33 /* ProtoWrappers.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = ProtoWrappers.py; sourceTree = ""; }; FDE72150287E50D50093DF33 /* LintLocalizableStrings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LintLocalizableStrings.swift; sourceTree = ""; }; FDE77F68280F9EDA002CFC5D /* JobRunnerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JobRunnerError.swift; sourceTree = ""; }; @@ -5946,7 +5945,6 @@ C3548F0624456447009433A8 /* PNModeVC.swift in Sources */, FD71164828E2CE8700B47552 /* SessionCell+AccessoryView.swift in Sources */, B80A579F23DFF1F300876683 /* NewClosedGroupVC.swift in Sources */, - FD71163A28E2C53700B47552 /* SessionAvatarCell.swift in Sources */, 7B3A392E2977791E002FE4AC /* MediaInfoVC.swift in Sources */, 7BA68909272A27BE00EFC32F /* SessionCall.swift in Sources */, B835247925C38D880089A44F /* MessageCell.swift in Sources */, diff --git a/Session/Conversations/Views & Modals/ConversationTitleView.swift b/Session/Conversations/Views & Modals/ConversationTitleView.swift index 69c695b0b..d4a0c78f0 100644 --- a/Session/Conversations/Views & Modals/ConversationTitleView.swift +++ b/Session/Conversations/Views & Modals/ConversationTitleView.swift @@ -142,7 +142,7 @@ final class ConversationTitleView: UIView { guard Date().timeIntervalSince1970 > (mutedUntilTimestamp ?? 0) else { subtitleLabel?.attributedText = NSAttributedString( - string: "\u{e067} ", + string: FullConversationCell.mutePrefix, attributes: [ .font: UIFont.ows_elegantIconsFont(10), .foregroundColor: textPrimary diff --git a/Session/Home/HomeViewModel.swift b/Session/Home/HomeViewModel.swift index 8abcec895..eb641d25c 100644 --- a/Session/Home/HomeViewModel.swift +++ b/Session/Home/HomeViewModel.swift @@ -265,7 +265,7 @@ public class HomeViewModel { /// **MUST** have the same logic as in the 'PagedDataObserver.onChangeUnsorted' above let currentData: [SectionModel] = (self.unobservedThreadDataChanges?.0 ?? self.threadData) let updatedThreadData: [SectionModel] = self.process( - data: currentData.flatMap { $0.elements }, + data: (currentData.first(where: { $0.model == .threads })?.elements ?? []), for: currentPageInfo ) @@ -335,7 +335,10 @@ public class HomeViewModel { SectionModel( section: .threads, elements: data - .filter { $0.id != SessionThreadViewModel.invalidId } + .filter { threadViewModel in + threadViewModel.id != SessionThreadViewModel.invalidId && + threadViewModel.id != SessionThreadViewModel.messageRequestsSectionId + } .sorted { lhs, rhs -> Bool in guard lhs.threadPinnedPriority == rhs.threadPinnedPriority else { return lhs.threadPinnedPriority > rhs.threadPinnedPriority diff --git a/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift b/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift index 6ee71df52..9727b5b96 100644 --- a/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift +++ b/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift @@ -35,9 +35,7 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect var activityIndicator: UIActivityIndicatorView? var hasSelectedCell: Bool = false var imageInfos = [GiphyImageInfo]() - - var reachability: Reachability? - + private let kCellReuseIdentifier = "kCellReuseIdentifier" var progressiveSearchTimer: Timer? @@ -115,7 +113,6 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect createViews() - reachability = Environment.shared?.reachabilityManager.reachability NotificationCenter.default.addObserver( self, selector: #selector(reachabilityChanged), diff --git a/Session/Shared/FullConversationCell.swift b/Session/Shared/FullConversationCell.swift index ecdf6b34e..1a0bf371f 100644 --- a/Session/Shared/FullConversationCell.swift +++ b/Session/Shared/FullConversationCell.swift @@ -6,6 +6,7 @@ import SignalUtilitiesKit import SessionMessagingKit public final class FullConversationCell: UITableViewCell, SwipeActionOptimisticCell { + public static let mutePrefix: String = "\u{e067} " public static let unreadCountViewSize: CGFloat = 20 private static let statusIndicatorSize: CGFloat = 14 @@ -486,12 +487,26 @@ public final class FullConversationCell: UITableViewCell, SwipeActionOptimisticC isPinned: Bool?, hasUnread: Bool? ) { - // TODO: Decide on this + // Note: This will result in the snippet being out of sync while the swipe action animation completes, + // this means if the day/night mode changes while the animation is happening then the below optimistic + // update might get reset (this should be rare and is a relatively minor bug so can be left in) if let isMuted: Bool = isMuted { - if isMuted { - - } else { - + let attrString: NSAttributedString = (self.snippetLabel.attributedText ?? NSAttributedString()) + let hasMutePrefix: Bool = attrString.string.starts(with: FullConversationCell.mutePrefix) + + switch (isMuted, hasMutePrefix) { + case (true, false): + self.snippetLabel.attributedText = NSAttributedString( + string: FullConversationCell.mutePrefix, + attributes: [ .font: UIFont.ows_elegantIconsFont(10) ] + ) + .appending(attrString) + + case (false, true): + self.snippetLabel.attributedText = attrString + .attributedSubstring(from: NSRange(location: FullConversationCell.mutePrefix.count, length: (attrString.length - FullConversationCell.mutePrefix.count))) + + default: break } } @@ -536,7 +551,7 @@ public final class FullConversationCell: UITableViewCell, SwipeActionOptimisticC if Date().timeIntervalSince1970 < (cellViewModel.threadMutedUntilTimestamp ?? 0) { result.append(NSAttributedString( - string: "\u{e067} ", + string: FullConversationCell.mutePrefix, attributes: [ .font: UIFont.ows_elegantIconsFont(10), .foregroundColor: textColor diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift index d363078b0..a6722a847 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift @@ -223,23 +223,23 @@ extension MessageSender { /// Store it **after** having sent out the message to the group Storage.shared.write { db in try newKeyPair.insert(db) + + // Update libSession + try? SessionUtil.update( + db, + groupPublicKey: closedGroup.threadId, + latestKeyPair: newKeyPair, + members: allGroupMembers + .filter { $0.role == .standard || $0.role == .zombie } + .map { $0.profileId } + .asSet(), + admins: allGroupMembers + .filter { $0.role == .admin } + .map { $0.profileId } + .asSet() + ) } - // Update libSession - try? SessionUtil.update( - db, - groupPublicKey: closedGroup.threadId, - latestKeyPair: newKeyPair, - members: allGroupMembers - .filter { $0.role == .standard || $0.role == .zombie } - .map { $0.profileId } - .asSet(), - admins: allGroupMembers - .filter { $0.role == .admin } - .map { $0.profileId } - .asSet() - ) - distributingKeyPairs.mutate { if let index = ($0[closedGroup.id] ?? []).firstIndex(of: newKeyPair) { $0[closedGroup.id] = ($0[closedGroup.id] ?? []) diff --git a/SignalUtilitiesKit/Utilities/ReachabilityManager.swift b/SignalUtilitiesKit/Utilities/ReachabilityManager.swift index cafa683ec..c9b884db8 100644 --- a/SignalUtilitiesKit/Utilities/ReachabilityManager.swift +++ b/SignalUtilitiesKit/Utilities/ReachabilityManager.swift @@ -4,6 +4,8 @@ import Foundation import Reachability import SignalCoreKit +/// **Warning:** The simulator doesn't detect reachability correctly so if you are seeing odd/incorrect reachability states double +/// check on an actual device before trying to replace this implementation @objc public class SSKReachabilityManagerImpl: NSObject, SSKReachabilityManager { From 6fd574916b8aac427ef99776303fa87e447671b1 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 21 Apr 2023 16:34:06 +1000 Subject: [PATCH 050/135] Fixed a few bugs and build libSession-util from source Added libSession-util as a submodule and wired into build Updated the logic to run migrations when returning from the background as well (since we will have feature-flag controlled migrations it's possible for a "new" migration to become available at this point) Fixed an issue where the 'Note to Self' conversation could appear when linking a device for a new user Fixed an issue where the app would process the ConfigSyncJob before completing onboarding --- .gitmodules | 3 + LibSession-Util | 1 + Scripts/build_libSession_util.sh | 83 +++ Session.xcodeproj/project.pbxproj | 82 ++- .../xcshareddata/xcschemes/Session.xcscheme | 18 + .../xcschemes/SessionMessagingKit.xcscheme | 20 +- ...ssionNotificationServiceExtension.xcscheme | 18 + .../xcschemes/SessionShareExtension.xcscheme | 18 + .../xcschemes/SessionUtilitiesKit.xcscheme | 20 +- .../xcschemes/SignalUtilitiesKit.xcscheme | 20 +- Session/Home/HomeVC.swift | 3 +- Session/Meta/AppDelegate.swift | 51 +- Session/Notifications/SyncPushTokensJob.swift | 7 +- Session/Onboarding/Onboarding.swift | 16 +- SessionMessagingKit/Configuration.swift | 1 + .../_014_GenerateInitialUserConfigDumps.swift | 7 +- .../Database/Models/Profile.swift | 24 +- .../Jobs/Types/ConfigurationSyncJob.swift | 17 +- .../libsession-util.xcframework/Info.plist | 40 -- .../ios-arm64/libsession-util.a | Bin 2089392 -> 0 bytes .../libsession-util.a | Bin 4601496 -> 0 bytes .../module.modulemap | 21 - .../session/bt_merge.hpp | 58 -- .../session/config.h | 13 - .../session/config.hpp | 352 ---------- .../session/config/base.h | 154 ----- .../session/config/base.hpp | 650 ------------------ .../session/config/community.h | 48 -- .../session/config/community.hpp | 254 ------- .../session/config/contacts.h | 160 ----- .../session/config/contacts.hpp | 231 ------- .../session/config/convo_info_volatile.h | 229 ------ .../session/config/convo_info_volatile.hpp | 347 ---------- .../session/config/encrypt.h | 36 - .../session/config/encrypt.hpp | 69 -- .../session/config/error.h | 23 - .../session/config/expiring.h | 7 - .../session/config/expiring.hpp | 8 - .../session/config/namespaces.hpp | 14 - .../session/config/notify.h | 8 - .../session/config/notify.hpp | 12 - .../session/config/profile_pic.h | 23 - .../session/config/profile_pic.hpp | 57 -- .../session/config/user_groups.h | 271 -------- .../session/config/user_groups.hpp | 366 ---------- .../session/config/user_profile.h | 63 -- .../session/config/user_profile.hpp | 67 -- .../session/config/util.h | 16 - .../session/export.h | 8 - .../session/fields.hpp | 43 -- .../session/types.hpp | 18 - .../session/util.hpp | 44 -- .../session/version.h | 19 - .../session/xed25519.h | 34 - .../session/xed25519.hpp | 38 - ...essageReceiver+ConfigurationMessages.swift | 3 +- .../SessionUtil+Contacts.swift | 0 .../SessionUtil+ConvoInfoVolatile.swift | 4 +- .../Config Handling/SessionUtil+Shared.swift | 8 +- .../SessionUtil+UserGroups.swift | 3 +- .../SessionUtil+UserProfile.swift | 0 .../QueryInterfaceRequest+Utilities.swift | 2 +- .../SessionUtil.swift | 54 +- .../SessionUtilError.swift | 0 .../Utilities/TypeConversion+Utilities.swift | 0 SessionMessagingKit/Utilities/AppReadiness.h | 3 +- SessionMessagingKit/Utilities/AppReadiness.m | 10 + .../Utilities/Identity+Utilities.swift | 19 + .../Utilities/ProfileManager.swift | 2 +- SessionUtilitiesKit/Database/Storage.swift | 18 +- .../Database/Types/Migration.swift | 7 +- SignalUtilitiesKit/Utilities/AppSetup.swift | 12 +- 72 files changed, 426 insertions(+), 3929 deletions(-) create mode 100644 .gitmodules create mode 160000 LibSession-Util create mode 100755 Scripts/build_libSession_util.sh delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/Info.plist delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64/libsession-util.a delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64_x86_64-simulator/libsession-util.a delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/module.modulemap delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/bt_merge.hpp delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config.h delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config.hpp delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.h delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.hpp delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/community.h delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/community.hpp delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.h delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.hpp delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.h delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.hpp delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/encrypt.h delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/encrypt.hpp delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/error.h delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/expiring.h delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/expiring.hpp delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/namespaces.hpp delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/notify.h delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/notify.hpp delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/profile_pic.h delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/profile_pic.hpp delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.h delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.hpp delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.h delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.hpp delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/util.h delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/export.h delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/fields.hpp delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/types.hpp delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/util.hpp delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/version.h delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/xed25519.h delete mode 100644 SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/xed25519.hpp rename SessionMessagingKit/{LibSessionUtil => SessionUtil}/Config Handling/SessionUtil+Contacts.swift (100%) rename SessionMessagingKit/{LibSessionUtil => SessionUtil}/Config Handling/SessionUtil+ConvoInfoVolatile.swift (99%) rename SessionMessagingKit/{LibSessionUtil => SessionUtil}/Config Handling/SessionUtil+Shared.swift (97%) rename SessionMessagingKit/{LibSessionUtil => SessionUtil}/Config Handling/SessionUtil+UserGroups.swift (99%) rename SessionMessagingKit/{LibSessionUtil => SessionUtil}/Config Handling/SessionUtil+UserProfile.swift (100%) rename SessionMessagingKit/{LibSessionUtil => SessionUtil}/Database/QueryInterfaceRequest+Utilities.swift (97%) rename SessionMessagingKit/{LibSessionUtil => SessionUtil}/SessionUtil.swift (90%) rename SessionMessagingKit/{LibSessionUtil => SessionUtil}/SessionUtilError.swift (100%) rename SessionMessagingKit/{LibSessionUtil => SessionUtil}/Utilities/TypeConversion+Utilities.swift (100%) create mode 100644 SessionMessagingKit/Utilities/Identity+Utilities.swift diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..ae3d45a8c --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "LibSession-Util"] + path = LibSession-Util + url = git@github.com:oxen-io/libsession-util.git diff --git a/LibSession-Util b/LibSession-Util new file mode 160000 index 000000000..53c824de0 --- /dev/null +++ b/LibSession-Util @@ -0,0 +1 @@ +Subproject commit 53c824de0d514307f3bad6a62449166bd10da6f8 diff --git a/Scripts/build_libSession_util.sh b/Scripts/build_libSession_util.sh new file mode 100755 index 000000000..cfd934ac6 --- /dev/null +++ b/Scripts/build_libSession_util.sh @@ -0,0 +1,83 @@ +#!/bin/bash + +# XCode will error during it's dependency graph construction (which happens before the build +# stage starts and any target "Run Script" phases are triggered) +# +# In order to avoid this error we need to build the framework before actually getting to the +# build stage so XCode is able to build the dependency graph +# +# XCode's Pre-action scripts don't output anything into XCode so the only way to emit a useful +# error is to return a success status and have the project detect and log the error itself then +# log it, stopping the build at that point +# +# The other step to get this to work properly is to ensure the framework in "Link Binary with +# Libraries" isn't using a relative directory, unfortunately there doesn't seem to be a good +# way to do this directly so we need to modify the '.pbxproj' file directly, updating the +# framework entry to have the following (on a single line): +# { +# isa = PBXFileReference; +# explicitFileType = wrapper.xcframework; +# includeInIndex = 0; +# path = "{FRAMEWORK NAME GOES HERE}"; +# sourceTree = BUILD_DIR; +# }; + +# Need to set the path or we won't find cmake +PATH=${PATH}:/usr/local/bin:/opt/homebrew/bin:/sbin/md5 + +# Direct the output to a log file +exec > "${TARGET_BUILD_DIR}/libsession_util_output.log" 2>&1 + +# Remove any old build errors +rm -rf "${TARGET_BUILD_DIR}/libsession_util_error.log" + +# First ensure cmake is installed (store the error in a log and exit with a success status - xcode will output the error) +if ! which cmake > /dev/null; then + echo "error: cmake is required to build, please install (can install via homebrew with 'brew install cmake')." > "${TARGET_BUILD_DIR}/error.log" + exit 0 +fi + +# Generate a hash of the libSession-util source files and check if they differ from the last hash +NEW_SOURCE_HASH=$(find "${SRCROOT}/LibSession-Util/src" -type f -exec md5 {} + | awk '{print $NF}' | sort | md5 | awk '{print $NF}') +NEW_HEADER_HASH=$(find "${SRCROOT}/LibSession-Util/include" -type f -exec md5 {} + | awk '{print $NF}' | sort | md5 | awk '{print $NF}') + +if [ -f "${TARGET_BUILD_DIR}/libsession_util_source_hash.log" ]; then + read -r OLD_SOURCE_HASH < "${TARGET_BUILD_DIR}/libsession_util_source_hash.log" +fi + +if [ -f "${TARGET_BUILD_DIR}/libsession_util_header_hash.log" ]; then + read -r OLD_HEADER_HASH < "${TARGET_BUILD_DIR}/libsession_util_header_hash.log" +fi + +if [ -f "${TARGET_BUILD_DIR}/libsession_util_archs.log" ]; then + read -r OLD_ARCHS < "${TARGET_BUILD_DIR}/libsession_util_archs.log" +fi + +# Start the libSession-util build if it doesn't already exists +if [ "${NEW_SOURCE_HASH}" != "${OLD_SOURCE_HASH}" ] || [ "${NEW_HEADER_HASH}" != "${OLD_HEADER_HASH}" ] || [ "${ARCHS[*]}" != "${OLD_ARCHS}" ] || [ ! -d "${TARGET_BUILD_DIR}/libsession-util.xcframework" ]; then + echo "info: Build is not up-to-date - creating new build" + echo "" + + # Remove any existing build files (just to be safe) + rm -rf "${TARGET_BUILD_DIR}/libsession-util.a" + rm -rf "${TARGET_BUILD_DIR}/libsession-util.xcframework" + rm -rf "${BUILD_DIR}/libsession-util.xcframework" + + # Trigger the new build + cd "${SRCROOT}/LibSession-Util" + result=$(./utils/ios.sh "libsession-util" false) + + if [ $? -ne 0 ]; then + echo "error: Failed to build libsession-util (See details in '${TARGET_BUILD_DIR}/pre-action-output.log')." > "${TARGET_BUILD_DIR}/error.log" + exit 0 + fi + + # Save the updated source hash to disk to prevent rebuilds when there were no changes + echo "${NEW_SOURCE_HASH}" > "${TARGET_BUILD_DIR}/libsession_util_source_hash.log" + echo "${NEW_HEADER_HASH}" > "${TARGET_BUILD_DIR}/libsession_util_header_hash.log" + echo "${ARCHS[*]}" > "${TARGET_BUILD_DIR}/libsession_util_archs.log" +fi + +# Move the target-specific libSession-util build to the parent build directory (so XCode can have a reference to a single build) +rm -rf "${BUILD_DIR}/libsession-util.xcframework" +cp -r "${TARGET_BUILD_DIR}/libsession-util.xcframework" "${BUILD_DIR}/libsession-util.xcframework" diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 65b1a294c..e70d113f3 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -436,7 +436,6 @@ C3C2A5A7255385C100C340D1 /* SessionSnodeKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; C3C2A5C0255385EE00C340D1 /* Snode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5B7255385EC00C340D1 /* Snode.swift */; }; C3C2A5C2255385EE00C340D1 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5B9255385ED00C340D1 /* Configuration.swift */; }; - C3C2A5DE2553860B00C340D1 /* String+Trimming.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D22553860900C340D1 /* String+Trimming.swift */; }; C3C2A5E02553860B00C340D1 /* Threading.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D42553860A00C340D1 /* Threading.swift */; }; C3C2A5E42553860B00C340D1 /* Data+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D82553860B00C340D1 /* Data+Utilities.swift */; }; C3C2A67D255388CC00C340D1 /* SessionUtilitiesKit.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A67B255388CC00C340D1 /* SessionUtilitiesKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -735,7 +734,6 @@ FD87DCFE28B7582C00AF0F98 /* BlockedContactsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD87DCFD28B7582C00AF0F98 /* BlockedContactsViewModel.swift */; }; FD87DD0028B820F200AF0F98 /* BlockedContactCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD87DCFF28B820F200AF0F98 /* BlockedContactCell.swift */; }; FD87DD0428B8727D00AF0F98 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD87DD0328B8727D00AF0F98 /* Configuration.swift */; }; - FD8ECF7929340F7200C0D1BB /* libsession-util.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FD8ECF7829340F7100C0D1BB /* libsession-util.xcframework */; }; FD8ECF7B29340FFD00C0D1BB /* SessionUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7A29340FFD00C0D1BB /* SessionUtil.swift */; }; FD8ECF7D2934293A00C0D1BB /* _013_SessionUtilChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7C2934293A00C0D1BB /* _013_SessionUtilChanges.swift */; }; FD8ECF7F2934298100C0D1BB /* ConfigDump.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7E2934298100C0D1BB /* ConfigDump.swift */; }; @@ -906,12 +904,15 @@ FDF848F329413DB0007DCAE5 /* ImagePickerHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848F229413DB0007DCAE5 /* ImagePickerHandler.swift */; }; FDF848F529413EEC007DCAE5 /* SessionCell+Styling.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848F429413EEC007DCAE5 /* SessionCell+Styling.swift */; }; FDF848F729414477007DCAE5 /* CurrentUserPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848F629414477007DCAE5 /* CurrentUserPoller.swift */; }; + FDFC4D9A29F0C51500992FB6 /* String+Trimming.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D22553860900C340D1 /* String+Trimming.swift */; }; + FDFC4E1929F1F9A600992FB6 /* libsession-util.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FDFC4E1829F1F9A600992FB6 /* libsession-util.xcframework */; }; FDFD645927F26C6800808CA1 /* Array+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D12553860800C340D1 /* Array+Utilities.swift */; }; FDFD645D27F273F300808CA1 /* MockGeneralCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */; }; FDFDE124282D04F20098B17F /* MediaDismissAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE123282D04F20098B17F /* MediaDismissAnimationController.swift */; }; FDFDE126282D05380098B17F /* MediaInteractiveDismiss.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE125282D05380098B17F /* MediaInteractiveDismiss.swift */; }; FDFDE128282D05530098B17F /* MediaPresentationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE127282D05530098B17F /* MediaPresentationContext.swift */; }; FDFDE12A282D056B0098B17F /* MediaZoomAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE129282D056B0098B17F /* MediaZoomAnimationController.swift */; }; + FDFF61D729F2600300F95FB0 /* Identity+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFF61D629F2600300F95FB0 /* Identity+Utilities.swift */; }; FE43694493EC2E1E438EBEB3 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 13D1714FDC4DAB121DA2C73A /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework */; }; /* End PBXBuildFile section */ @@ -923,13 +924,6 @@ remoteGlobalIDString = 453518671FC635DD00210559; remoteInfo = SignalShareExtension; }; - 7B251C3827D82D9E001A6284 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = D221A080169C9E5E00537ABF /* Project object */; - proxyType = 1; - remoteGlobalIDString = C3C2A678255388CC00C340D1; - remoteInfo = SessionUtilitiesKit; - }; 7BC01A40241F40AB00BC7C55 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = D221A080169C9E5E00537ABF /* Project object */; @@ -1233,7 +1227,6 @@ 7B2561C12978B307005C086C /* MediaInfoVC+MediaInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MediaInfoVC+MediaInfoView.swift"; sourceTree = ""; }; 7B2561C329874851005C086C /* SessionCarouselView+Info.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionCarouselView+Info.swift"; sourceTree = ""; }; 7B2DB2AD26F1B0FF0035B509 /* si */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = si; path = si.lproj/Localizable.strings; sourceTree = ""; }; - 7B2E985729AC227C001792D7 /* UIContextualAction+Theming.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIContextualAction+Theming.swift"; sourceTree = ""; }; 7B3A392D2977791E002FE4AC /* MediaInfoVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaInfoVC.swift; sourceTree = ""; }; 7B3A392F297A3919002FE4AC /* MediaInfoVC+MediaPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MediaInfoVC+MediaPreviewView.swift"; sourceTree = ""; }; 7B3A39312980D02B002FE4AC /* SessionCarouselView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCarouselView.swift; sourceTree = ""; }; @@ -1252,7 +1245,6 @@ 7B81682728B310D50069F315 /* _007_HomeQueryOptimisationIndexes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _007_HomeQueryOptimisationIndexes.swift; sourceTree = ""; }; 7B81682928B6F1420069F315 /* ReactionResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionResponse.swift; sourceTree = ""; }; 7B81682B28B72F480069F315 /* PendingChange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PendingChange.swift; sourceTree = ""; }; - 7B89FF4529C016E300C4C708 /* _012_AddFTSIfNeeded.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _012_AddFTSIfNeeded.swift; sourceTree = ""; }; 7B8C44C428B49DDA00FBE25F /* NewConversationVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewConversationVC.swift; sourceTree = ""; }; 7B8D5FC328332600008324D9 /* VisibleMessage+Reaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VisibleMessage+Reaction.swift"; sourceTree = ""; }; 7B93D06927CF173D00811CB6 /* MessageRequestsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageRequestsViewController.swift; sourceTree = ""; }; @@ -1877,7 +1869,6 @@ FD87DCFD28B7582C00AF0F98 /* BlockedContactsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedContactsViewModel.swift; sourceTree = ""; }; FD87DCFF28B820F200AF0F98 /* BlockedContactCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedContactCell.swift; sourceTree = ""; }; FD87DD0328B8727D00AF0F98 /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; - FD8ECF7829340F7100C0D1BB /* libsession-util.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = "libsession-util.xcframework"; sourceTree = ""; }; FD8ECF7A29340FFD00C0D1BB /* SessionUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionUtil.swift; sourceTree = ""; }; FD8ECF7C2934293A00C0D1BB /* _013_SessionUtilChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _013_SessionUtilChanges.swift; sourceTree = ""; }; FD8ECF7E2934298100C0D1BB /* ConfigDump.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigDump.swift; sourceTree = ""; }; @@ -2048,12 +2039,14 @@ FDF848F229413DB0007DCAE5 /* ImagePickerHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImagePickerHandler.swift; sourceTree = ""; }; FDF848F429413EEC007DCAE5 /* SessionCell+Styling.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SessionCell+Styling.swift"; sourceTree = ""; }; FDF848F629414477007DCAE5 /* CurrentUserPoller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CurrentUserPoller.swift; sourceTree = ""; }; + FDFC4E1829F1F9A600992FB6 /* libsession-util.xcframework */ = {isa = PBXFileReference; explicitFileType = wrapper.xcframework; includeInIndex = 0; path = "libsession-util.xcframework"; sourceTree = BUILD_DIR; }; FDFD645A27F26D4600808CA1 /* Data+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+Utilities.swift"; sourceTree = ""; }; FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockGeneralCache.swift; sourceTree = ""; }; FDFDE123282D04F20098B17F /* MediaDismissAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaDismissAnimationController.swift; sourceTree = ""; }; FDFDE125282D05380098B17F /* MediaInteractiveDismiss.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaInteractiveDismiss.swift; sourceTree = ""; }; FDFDE127282D05530098B17F /* MediaPresentationContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPresentationContext.swift; sourceTree = ""; }; FDFDE129282D056B0098B17F /* MediaZoomAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaZoomAnimationController.swift; sourceTree = ""; }; + FDFF61D629F2600300F95FB0 /* Identity+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Identity+Utilities.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -2124,7 +2117,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - FD8ECF7929340F7200C0D1BB /* libsession-util.xcframework in Frameworks */, + FDFC4E1929F1F9A600992FB6 /* libsession-util.xcframework in Frameworks */, FDC4386C27B4E90300C60D73 /* SessionUtilitiesKit.framework in Frameworks */, C3C2A70B25539E1E00C340D1 /* SessionSnodeKit.framework in Frameworks */, BE25D9230CA2C3A40A9216EF /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework in Frameworks */, @@ -3265,6 +3258,7 @@ FD772899284AF1BD0018502F /* Sodium+Utilities.swift */, C3A3A170256E1D25004D228D /* SSKReachabilityManager.swift */, C3ECBF7A257056B700EA7FCE /* Threading.swift */, + FDFF61D629F2600300F95FB0 /* Identity+Utilities.swift */, ); path = Utilities; sourceTree = ""; @@ -3343,9 +3337,9 @@ C352A2F325574B3300338F3E /* Jobs */, C3A7215C2558C0AC0043A11F /* File Server */, C3A721332558BDDF0043A11F /* Open Groups */, + FD8ECF7529340F4800C0D1BB /* SessionUtil */, FD3E0C82283B581F002A425C /* Shared Models */, C3BBE0B32554F0D30050F1E3 /* Utilities */, - FD8ECF7529340F4800C0D1BB /* LibSessionUtil */, FDC438C027BB4E6800C60D73 /* SMKDependencies.swift */, FD245C612850664300B966DD /* Configuration.swift */, ); @@ -3502,7 +3496,6 @@ D221A08C169C9E5E00537ABF /* Frameworks */, D221A08A169C9E5E00537ABF /* Products */, 2BADBA206E0B8D297E313FBA /* Pods */, - FD368A6629DE86A9000DBF1E /* Recovered References */, ); sourceTree = ""; }; @@ -3527,6 +3520,7 @@ D221A08C169C9E5E00537ABF /* Frameworks */ = { isa = PBXGroup; children = ( + FDFC4E1829F1F9A600992FB6 /* libsession-util.xcframework */, B8DE1FAF26C228780079C9CE /* SignalRingRTC.framework */, C35E8AA22485C72300ACB629 /* SwiftCSV.framework */, B847570023D568EB00759540 /* SignalServiceKit.framework */, @@ -3794,15 +3788,6 @@ path = Database; sourceTree = ""; }; - FD368A6629DE86A9000DBF1E /* Recovered References */ = { - isa = PBXGroup; - children = ( - 7B2E985729AC227C001792D7 /* UIContextualAction+Theming.swift */, - 7B89FF4529C016E300C4C708 /* _012_AddFTSIfNeeded.swift */, - ); - name = "Recovered References"; - sourceTree = ""; - }; FD37E9C428A1C701003AE748 /* Themes */ = { isa = PBXGroup; children = ( @@ -4095,17 +4080,16 @@ path = Types; sourceTree = ""; }; - FD8ECF7529340F4800C0D1BB /* LibSessionUtil */ = { + FD8ECF7529340F4800C0D1BB /* SessionUtil */ = { isa = PBXGroup; children = ( FD2B4B022949886900AB4848 /* Database */, FD8ECF8E29381FB200C0D1BB /* Config Handling */, FD432435299DEA1C008A0213 /* Utilities */, - FD8ECF7829340F7100C0D1BB /* libsession-util.xcframework */, FD8ECF882935AB7200C0D1BB /* SessionUtilError.swift */, FD8ECF7A29340FFD00C0D1BB /* SessionUtil.swift */, ); - path = LibSessionUtil; + path = SessionUtil; sourceTree = ""; }; FD8ECF802934385900C0D1BB /* LibSessionUtil */ = { @@ -4122,11 +4106,11 @@ FD8ECF8E29381FB200C0D1BB /* Config Handling */ = { isa = PBXGroup; children = ( - FD8ECF8F29381FC200C0D1BB /* SessionUtil+UserProfile.swift */, FD2B4AFC294688D000AB4848 /* SessionUtil+Contacts.swift */, FD43EE9E297E2EE0009C87C5 /* SessionUtil+ConvoInfoVolatile.swift */, - FD43EE9C297A5190009C87C5 /* SessionUtil+UserGroups.swift */, FDA1E83A29A5F2D500C5C3BD /* SessionUtil+Shared.swift */, + FD43EE9C297A5190009C87C5 /* SessionUtil+UserGroups.swift */, + FD8ECF8F29381FC200C0D1BB /* SessionUtil+UserProfile.swift */, ); path = "Config Handling"; sourceTree = ""; @@ -4620,6 +4604,7 @@ isa = PBXNativeTarget; buildConfigurationList = C3C2A6F925539DE700C340D1 /* Build configuration list for PBXNativeTarget "SessionMessagingKit" */; buildPhases = ( + FDFC4E1729F14F7A00992FB6 /* Validate pre-build actions */, 2014435DF351DF6C60122751 /* [CP] Check Pods Manifest.lock */, C3C2A6EB25539DE700C340D1 /* Headers */, C3C2A6EC25539DE700C340D1 /* Sources */, @@ -4629,7 +4614,6 @@ buildRules = ( ); dependencies = ( - 7B251C3927D82D9E001A6284 /* PBXTargetDependency */, ); name = SessionMessagingKit; productName = SessionMessagingKit; @@ -4734,7 +4718,7 @@ isa = PBXProject; attributes = { DefaultBuildSystemTypeForWorkspace = Original; - LastSwiftUpdateCheck = 1340; + LastSwiftUpdateCheck = 1430; LastTestingUpgradeCheck = 0600; LastUpgradeCheck = 1400; ORGANIZATIONNAME = "Rangeproof Pty Ltd"; @@ -5340,6 +5324,7 @@ }; FDE7214D287E50820093DF33 /* Lint Localizable.strings */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -5357,6 +5342,26 @@ shellScript = "\"${SRCROOT}/Scripts/LintLocalizableStrings.swift\"\n"; showEnvVarsInLog = 0; }; + FDFC4E1729F14F7A00992FB6 /* Validate pre-build actions */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Validate pre-build actions"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [ -f \"${TARGET_BUILD_DIR}/libsession_util_error.log\" ]; then\n read -r line < \"${TARGET_BUILD_DIR}/libsession_util_error.log\"\n echo \"${line}\"\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -5534,8 +5539,8 @@ FDF848BC29405C5A007DCAE5 /* SnodeRecursiveResponse.swift in Sources */, FDF848C029405C5A007DCAE5 /* ONSResolveResponse.swift in Sources */, FD17D7A427F40F8100122BE0 /* _003_YDBToGRDBMigration.swift in Sources */, + FDFC4D9A29F0C51500992FB6 /* String+Trimming.swift in Sources */, FDF848C629405C5B007DCAE5 /* DeleteAllMessagesRequest.swift in Sources */, - C3C2A5DE2553860B00C340D1 /* String+Trimming.swift in Sources */, FDF848D429405C5B007DCAE5 /* DeleteAllBeforeResponse.swift in Sources */, FDF848D629405C5B007DCAE5 /* SnodeMessage.swift in Sources */, FDF848D129405C5B007DCAE5 /* SnodeSwarmItem.swift in Sources */, @@ -5551,7 +5556,6 @@ FDF848D229405C5B007DCAE5 /* LegacyGetMessagesRequest.swift in Sources */, FDF848CB29405C5B007DCAE5 /* SnodePoolResponse.swift in Sources */, FDF848C429405C5A007DCAE5 /* RevokeSubkeyResponse.swift in Sources */, - C3C2A5DE2553860B00C340D1 /* String+Trimming.swift in Sources */, FD26FA6D291DADAE005801D8 /* (null) in Sources */, FDF848E529405D6E007DCAE5 /* SnodeAPIError.swift in Sources */, FDF848D529405C5B007DCAE5 /* DeleteAllMessagesResponse.swift in Sources */, @@ -5740,6 +5744,7 @@ FD716E6628502EE200C96BF4 /* CurrentCallProtocol.swift in Sources */, FD8ECF9029381FC200C0D1BB /* SessionUtil+UserProfile.swift in Sources */, FD09B7E5288670BB00ED0B66 /* _008_EmojiReacts.swift in Sources */, + FDFF61D729F2600300F95FB0 /* Identity+Utilities.swift in Sources */, FDC4385F27B4C4A200C60D73 /* PinnedMessage.swift in Sources */, 7BFD1A8C2747150E00FB91B9 /* TurnServerInfo.swift in Sources */, B8BF43BA26CC95FB007828D1 /* WebRTC+Utilities.swift in Sources */, @@ -6188,11 +6193,6 @@ target = 453518671FC635DD00210559 /* SessionShareExtension */; targetProxy = 453518701FC635DD00210559 /* PBXContainerItemProxy */; }; - 7B251C3927D82D9E001A6284 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = C3C2A678255388CC00C340D1 /* SessionUtilitiesKit */; - targetProxy = 7B251C3827D82D9E001A6284 /* PBXContainerItemProxy */; - }; 7BC01A41241F40AB00BC7C55 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 7BC01A3A241F40AB00BC7C55 /* SessionNotificationServiceExtension */; @@ -7350,7 +7350,6 @@ ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; - FRAMEWORK_SEARCH_PATHS = "\"$(SRCROOT)/SessionMessagingKit/LibSessionUtil\""; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -7391,7 +7390,6 @@ ); "OTHER_SWIFT_FLAGS[arch=*]" = "-D DEBUG"; SDKROOT = iphoneos; - SWIFT_INCLUDE_PATHS = "\"$(SRCROOT)/SessionMessagingKit/LibSessionUtil\""; SWIFT_VERSION = 4.0; VALIDATE_PRODUCT = YES; }; @@ -7427,7 +7425,6 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; - FRAMEWORK_SEARCH_PATHS = "\"$(SRCROOT)/SessionMessagingKit/LibSessionUtil\""; GCC_NO_COMMON_BLOCKS = YES; GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES; GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES; @@ -7465,7 +7462,6 @@ ); SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_INCLUDE_PATHS = "\"$(SRCROOT)/SessionMessagingKit/LibSessionUtil\""; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 4.0; VALIDATE_PRODUCT = YES; diff --git a/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme b/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme index ea85c66b2..f2e5c8744 100644 --- a/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme +++ b/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme @@ -5,6 +5,24 @@ + + + + + + + + + + + version = "1.7"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + version = "1.7"> + + + + + + + + + + + version = "1.7"> + + + + + + + + + + Void) { AppReadiness.runNowOrWhenAppDidBecomeReady { - guard Identity.userExists() else { return } + guard Identity.userCompletedRequiredOnboarding() else { return } SessionApp.homeViewController.wrappedValue?.createNewConversation() completionHandler(true) @@ -662,7 +691,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD func syncConfigurationIfNeeded() { // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard !Features.useSharedUtilForUserConfig else { return } + guard !SessionUtil.userConfigsEnabled else { return } let lastSync: Date = (UserDefaults.standard[.lastConfigurationSync] ?? .distantPast) diff --git a/Session/Notifications/SyncPushTokensJob.swift b/Session/Notifications/SyncPushTokensJob.swift index bd83701f8..4ac93c7a8 100644 --- a/Session/Notifications/SyncPushTokensJob.swift +++ b/Session/Notifications/SyncPushTokensJob.swift @@ -24,12 +24,7 @@ public enum SyncPushTokensJob: JobExecutor { // Don't run when inactive or not in main app or if the user doesn't exist yet guard (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false), - Identity.userExists(), - // If we have no display name then the user will be asked to enter one (this - // can happen if the app crashed during onboarding which would leave the user - // in an invalid state with no display name - the user is likely going to be - // taken to the PN registration screen next which will re-trigger this job) - !Profile.fetchOrCreateCurrentUser().name.isEmpty + Identity.userCompletedRequiredOnboarding() else { deferred(job) // Don't need to do anything if it's not the main app return diff --git a/Session/Onboarding/Onboarding.swift b/Session/Onboarding/Onboarding.swift index 93047b95d..902143c9c 100644 --- a/Session/Onboarding/Onboarding.swift +++ b/Session/Onboarding/Onboarding.swift @@ -10,7 +10,7 @@ import SessionMessagingKit enum Onboarding { private static let profileNameRetrievalPublisher: Atomic> = { // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard Features.useSharedUtilForUserConfig else { + guard SessionUtil.userConfigsEnabled else { return Atomic( Just(nil) .setFailureType(to: Error.self) @@ -39,7 +39,7 @@ enum Onboarding { ) .tryFlatMap { receivedMessageTypes -> AnyPublisher in // FIXME: Remove this entire 'tryFlatMap' once the updated user config has been released for long enough - guard !receivedMessageTypes.isEmpty else { + guard receivedMessageTypes.isEmpty else { return Just(()) .setFailureType(to: Error.self) .eraseToAnyPublisher() @@ -149,9 +149,19 @@ enum Onboarding { Contact.Columns.didApproveMe.set(to: true) ) - // Create the 'Note to Self' thread (not visible by default) + /// Create the 'Note to Self' thread (not visible by default) + /// + /// **Note:** We need to explicitly `updateAllAndConfig` the `shouldBeVisible` value to `false` + /// otherwise it won't actually get synced correctly try SessionThread .fetchOrCreate(db, id: x25519PublicKey, variant: .contact, shouldBeVisible: false) + + try SessionThread + .filter(id: x25519PublicKey) + .updateAllAndConfig( + db, + SessionThread.Columns.shouldBeVisible.set(to: false) + ) } // Set hasSyncedInitialConfiguration to true so that when we hit the diff --git a/SessionMessagingKit/Configuration.swift b/SessionMessagingKit/Configuration.swift index a9bc44b35..0be9fa4f0 100644 --- a/SessionMessagingKit/Configuration.swift +++ b/SessionMessagingKit/Configuration.swift @@ -32,6 +32,7 @@ public enum SNMessagingKit { // Just to make the external API nice _013_SessionUtilChanges.self, // Wait until the feature is turned on before doing the migration that generates // the config dump data + // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent (Features.useSharedUtilForUserConfig ? _014_GenerateInitialUserConfigDumps.self : (nil as Migration.Type?) diff --git a/SessionMessagingKit/Database/Migrations/_014_GenerateInitialUserConfigDumps.swift b/SessionMessagingKit/Database/Migrations/_014_GenerateInitialUserConfigDumps.swift index 0478650e8..703acf6a1 100644 --- a/SessionMessagingKit/Database/Migrations/_014_GenerateInitialUserConfigDumps.swift +++ b/SessionMessagingKit/Database/Migrations/_014_GenerateInitialUserConfigDumps.swift @@ -16,9 +16,12 @@ enum _014_GenerateInitialUserConfigDumps: Migration { static func migrate(_ db: Database) throws { // If we have no ed25519 key then there is no need to create cached dump data - guard let secretKey: [UInt8] = Identity.fetchUserEd25519KeyPair(db)?.secretKey else { return } + guard let secretKey: [UInt8] = Identity.fetchUserEd25519KeyPair(db)?.secretKey else { + Storage.update(progress: 1, for: self, in: target) // In case this is the last migration + return + } - // Load the initial config state if needed + // Create the initial config state let userPublicKey: String = getUserHexEncodedPublicKey(db) SessionUtil.loadState(db, userPublicKey: userPublicKey, ed25519SecretKey: secretKey) diff --git a/SessionMessagingKit/Database/Models/Profile.swift b/SessionMessagingKit/Database/Models/Profile.swift index 4b73b0d2d..59aa9fe73 100644 --- a/SessionMessagingKit/Database/Models/Profile.swift +++ b/SessionMessagingKit/Database/Models/Profile.swift @@ -229,25 +229,15 @@ public extension Profile { /// /// **Note:** This method intentionally does **not** save the newly created Profile, /// it will need to be explicitly saved after calling - static func fetchOrCreateCurrentUser() -> Profile { - var userPublicKey: String = "" - - let exisingProfile: Profile? = Storage.shared.read { db in - userPublicKey = getUserHexEncodedPublicKey(db) - - return try Profile.fetchOne(db, id: userPublicKey) - } - - return (exisingProfile ?? defaultFor(userPublicKey)) - } - - /// Fetches or creates a Profile for the current user - /// - /// **Note:** This method intentionally does **not** save the newly created Profile, - /// it will need to be explicitly saved after calling - static func fetchOrCreateCurrentUser(_ db: Database) -> Profile { + static func fetchOrCreateCurrentUser(_ db: Database? = nil) -> Profile { let userPublicKey: String = getUserHexEncodedPublicKey(db) + guard let db: Database = db else { + return Storage.shared + .read { db in fetchOrCreateCurrentUser(db) } + .defaulting(to: defaultFor(userPublicKey)) + } + return ( (try? Profile.fetchOne(db, id: userPublicKey)) ?? defaultFor(userPublicKey) diff --git a/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift b/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift index 40e36ec74..bd7e183e1 100644 --- a/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift +++ b/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift @@ -20,10 +20,10 @@ public enum ConfigurationSyncJob: JobExecutor { failure: @escaping (Job, Error?, Bool) -> (), deferred: @escaping (Job) -> () ) { - guard Features.useSharedUtilForUserConfig else { - success(job, true) - return - } + guard + SessionUtil.userConfigsEnabled, + Identity.userCompletedRequiredOnboarding() + else { return success(job, true) } // On startup it's possible for multiple ConfigSyncJob's to run at the same time (which is // redundant) so check if there is another job already running and, if so, defer this job @@ -175,14 +175,13 @@ public extension ConfigurationSyncJob { static func enqueue(_ db: Database, publicKey: String) { // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard Features.useSharedUtilForUserConfig else { + guard SessionUtil.userConfigsEnabled else { // If we don't have a userKeyPair (or name) yet then there is no need to sync the // configuration as the user doesn't fully exist yet (this will get triggered on // the first launch of a fresh install due to the migrations getting run and a few // times during onboarding) guard - Identity.userExists(db), - !Profile.fetchOrCreateCurrentUser(db).name.isEmpty, + Identity.userCompletedRequiredOnboarding(db), let legacyConfigMessage: Message = try? ConfigurationMessage.getCurrent(db) else { return } @@ -232,13 +231,13 @@ public extension ConfigurationSyncJob { static func run() -> AnyPublisher { // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard Features.useSharedUtilForUserConfig else { + guard SessionUtil.userConfigsEnabled else { return Storage.shared .writePublisher { db -> MessageSender.PreparedSendData in // If we don't have a userKeyPair yet then there is no need to sync the configuration // as the user doesn't exist yet (this will get triggered on the first launch of a // fresh install due to the migrations getting run) - guard Identity.userExists(db) else { throw StorageError.generic } + guard Identity.userCompletedRequiredOnboarding(db) else { throw StorageError.generic } let publicKey: String = getUserHexEncodedPublicKey(db) diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/Info.plist b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/Info.plist deleted file mode 100644 index 97310ed4d..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/Info.plist +++ /dev/null @@ -1,40 +0,0 @@ - - - - - AvailableLibraries - - - LibraryIdentifier - ios-arm64_x86_64-simulator - LibraryPath - libsession-util.a - SupportedArchitectures - - arm64 - x86_64 - - SupportedPlatform - ios - SupportedPlatformVariant - simulator - - - LibraryIdentifier - ios-arm64 - LibraryPath - libsession-util.a - SupportedArchitectures - - arm64 - - SupportedPlatform - ios - - - CFBundlePackageType - XFWK - XCFrameworkFormatVersion - 1.0 - - diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64/libsession-util.a b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64/libsession-util.a deleted file mode 100644 index a0903f5efd8aee2784a915f502eed2b5bb0a1bb0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2089392 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

    }7dqo|#W8 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

    ;~gS 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

  • NU_S-=DYXRkNC8&6ndVE*ERPskc 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

    `&>CW{%+LSc~fcIw#O!(yTYAA zEc}=`RoaYcpWgRa5$hpQ<`|i~eRnHo@_z>VE#dP{Xgz5pa*Fz|Jv0@&Yd`(+V{37% z`;@OVZt0tpSocpdf2}y}BQ2?GpI3ThY7cZDw$dfad~y_-Mb2|$CWeYmL}xxUKL5VR z&D;;aTj6)m1}WSrllDDEyM0>sz8^8?&_?(kBe9d4RoANYT6|(5ZCjM|x}3Nae)^}6z04ZX zPxGDjH@Txi>2(G6ntOck%(bqYX}Ectv5wyj<-A1hC}t(zd;j#YLifE3dG80upU->u ztY-hGX{X=f`RALpc5~kD5bvpVl00}<7kF)OjPKAB@%kP-a~C=1`ruyY-5h0p^BC#$ z_vBT54|kr^xNRn0d>403BKWN`oqc_TJY~-j_~6#1%+qooMI@5kcd9fGrnfzN+S}jV z`WjV~@7(Z<`s%rtql-CPH_!b^$W24q;*yDf%roKjO!nTZdyuoJL*z}ZSNP**O^&D^0#Bb@R=_WWJg0=9@!qzG)`k=x!6`n>i-mEG6H_&9qM+a`TPfjq;7$4dk0c zDfy;^d=sUvDy&OSQ|d!EO(#(9OPy(m~(7l|0;gcKrDmvblp}?N;)5Z$bR| ziIZ4I)^l8VpNsHfn&pvDTi*xWRbMRGx%4B;#%`?kn#INGS$?efAB69xzd-u<>0buie>dP>h12i;?*i_%&gbVZ4Y+?c z;9lj-@BYUDe(-+dA+4Y=!qJ71^|uckIy}gzE`f9I3BJ94(~yKWt#59qU&kWMx|*SR zV8Lj39$ahhV)x{xrOMZgkc}s@I>SXPPn$^P(FU4bq$*0_LH^&2~u1%

    j~BZUSzJPHGIdPIxAwMobnl$MxD=mN;+#{?B7SX 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

    OfsJ)+%Uq0*fvUf)O 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

    =y#9A3`lAYv+X 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

    }~L5&plZmW3oY_amMI-I2aZRc_$d_EEyH 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

    Vy~TSQG-6D$~QmfC_af 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

    |^h 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

  • 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
    YQlHMsTn9gGr9p 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

    }gJFa}Z_&Y(Znl9-@_DX&+(&|MTE5Vtw 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�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

    2CM$mvCmAe*5=Ud81uB>#C&(+{YTt+blPsl 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Œ=BU1G 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

    PFXbFJ+gZY~czZvmU8ULs)Ik`KmNPX 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%0D)vyhlz$sN&-->~F#Y#%YNvyeZz`TlXY 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∓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 *Xo_(Fv1D|M!Dt~H$t|c$xJDu~5hnCJR_lj3w zor_p08Q;=M4^y_tD4!;L`!(9vNZW*GY~;SdZS^HQzd@TNKl47xo!{>>IqPb%R_Yp2 zkp{j_l{%Lr2c;c7$*2rU6-^{nM)H}54F!ZvL-(45!n|!}7-8di_ZP-Ls!ig z9~RB#xBKMW7*}v6-!7uQ2Gdm|wyD6p9Jmy%vd2T-5B1i{x1~;lHoD+>i+*-!*xZKNK-=~olA=^uZj z2O3uFS2{rBe6ONy7c_eI#c7$n`@K{3BO^L*=}*Sj-oI&l?I2*9*6-b_24v#~^G9PF z8JEpwO3Q{7#@mN%+IRKP{4UNXZpk zSHP}I@ag>ZrKxw=F*OA}b=AJJ?~M;M;1}7;wPaINipjTI>6;XEsGFRfA9wz3~5n_=@`x^_rwMF0v^n+bQ{U466rCeSS{VIs^ zZY%iN@|+np%X5m5(?xeiK#S!EiZ(@79$IldzAI?uaAakJf0A|IKdRD@-)2TtHe9o4NV(@G0cy$@{=n#1N08}qN&9O8#@VaGO( zL7%Dd!n@qTiZSrr<@m}Hv>iU|3yE!!g)hVpZ>d;C{3$)U9=~6Z7-v=f$B6qpm{_6Y z$zSF@vtls&M-0Y)nRaFX_p4@43i)SF3e8!)`oPw+|MKB#;t0+mj^L2br~ZCTg7)*9 zwGGp@QQyg7=~s=@PSVfPQW z-~R=AEP(FcBo^69blnPcUS-R{EBxq=t89IjxcsZ$$c$P4vaRp7qVE>QpuTlQ(*i#Rmt1-ZyrFmS2>F71hqD9^#KAkd#~z&iUoiG%`mp9@oLw`Au@OJO z-ptMGqxMJ7jOI$=`V2XF zlbOz$tem@rjBpW;C4-o_dx`hC($=r~aYhv}8qYKm$IzcUDYW{QNui2qlR~R%a%(_0@d;IPX{LuB=vr`>1N-S88WYlCIU`+-1}se5N`rhWe|)#}nMw>zo;*$GR(! zr;E6r;jE1{6S69|5g##EkIi$t#72nqXCc$_rG3Pls)pZACPh{y$J;piCVu79@T|n* zdq)5!$s<&lz}c-%GrsPw8!C zUs*x9UButqzU-N*3?nw!7rFg5ZRra?GVFZypPvf7f<&pxDuo)$uC9RBi4J7&Enr>Rqqq0djeHLsxpW5csw@68w>C3Y zyV?(LT@6o_-veS8ewyXS+2FSH zDB;tY@ZO3)xofA=Mh|$nig8#?*;vXA9=UD(s*MqW@^9|WDPQ*JSowVjzimCFSMGXP z3zRS4Gj`eX*Tybi{+qFzmQ{@1xNPOv4a*)MTfY4NW$#_!s;bh)|Gm%QaySPN6qVEw zycMXJmX(wYM^Qr{yw2qG)(eOv2#U8FFBy2520dC*);Q#hDQQE|Lv z(>U|?8}ZUaVk$FMU>g7Lv-jF^Kq#Ace!tIre*gD;*gSjPpXXW6de-GxYwh>&e;@zf z4PClq|Im$#R}sFFa1Y@t2tPphLBgvDKQwg7DxdO|HlN~`7C%Gy(?eH*7jx{pJkO47 zSVft^;4+{fn0_PJv-uPj)lH9Y+S z--FQ|o~M!J_1Cnr^}5!uk9qWpx9!Dg(7yaH&dQ(wZE(%y#@&(dPUam&=d{Y@)W-_y z=O=oAvx4v<@RarA;jH@?frqhH+>5nh)`N3py_hjl26vr_e-!>Mbfkl|Ad$OLrB9YK z-z1OPfW^y8lE4XDL)D5~+m$)$8h3wwsd4Z^(mc+ZT6Smb9IRi^pN`A!tW{=Di1KK@ z31tzB>}By|y_>;bJiWq+YW>*eYJ)8-f9pAKfe9#pq$fDx;C-LgtQAuB5lZ`eKLfM;`8+eih-| z0XL1i6#G(s{4l+8B=|{NiWl27ZBEwXVsz&X>v6pZlQvSI2N>fM59@KP5sdFhd|6|Y zH5JM=?d|Z=Scmhnw@Mtr>A;g6zxO(vK?{0F*5MAY4kvPDt;2oE94flU?q=(7vhIB| z_V)GI+eJr3iOgbe9}WH|0<>Kg-=dLCmp!0rJNj#-?yM@%18W~2w6f$Q>UkSFC0%!V zoX|g%wF-yc&EtU2y3vQ2GO`ssOx(M9B(Egymt?ZoAZhyca=sXid-Sg~qb?)$NmovB z@2*w0>>Z#_+vQD*tnozmxN+|Q`!3q#yHP&nMHd8^d`D-9pRwlw8CbqxB=({%p5+UC z81Px4OtHFToXGq{J_1B|{(bhqf|n%n4Ku+awme0>S~M8XQ}U23C{5$|#D zM(NMc?aWv268%n_+-2#1D-T%{ia=*YhORAXX*0hvbp-Rp1m=sw4cocQ;fm1zQdg2j z#yg_p>N^yb)Y85hm&(2EA80%bC_FN>`p&{Qt?+k*?I(==AB6{oR{Pt_9f7p1 z0C#1XFXJC{3~MBnj^9Un!q{W-(@xwo3x7Mw8bc>{WunqUHJ!A|&&Ef2?&^_H7LOhh z-Ln+_1aMz6YaqrQ{?$uNJw&?edZ?PYzqIGp!%R8*f_bUvqZ;&4W23FCF+ywj?C<&; zIX`;-Z;nL=UhGO4gKgUGp&}=~tYb<03^mKBwwryS%xx{)e|)8Jx6D1nE@9=j%U9d= zcz1WhNA%}E?Md2==K9HAWVSE)&ZjRKg6@q%_gXYZp?jmyy`3I0b#J51{2oNjH==vP z4VojBjwPKJ%_d!~hnefvUn-ivvac=q5}Y3Yi@nUkX&-#ut!cYuPfQIuH``BZs07FC zY2e1%N#j%v$QtuTzD@s(R+&W~_a3r2wM4#`tYa4s6T7(aT^O;8V|zAi;}O_MH}I|A zC%VwbkQaP!s9yT^(CVdyLqCha7I#rETRvg5(a-gSFQN~#XTT?9Rr+Dat=LEc=`Zx6 zl6ljz>#@HV>x-K5ew+0MiC;r|k+8*27LJ#-dZ~XY`)%&G%~&y_%)+sk!cFYrpQ<%I ziDzez(L%~T#oE@qzm6`G#TIIGs^+^k&{{@aqc#c5085?Q+{ddH`sh&4LO z9ZcQrDm>GR^*kA0o9nnm@Y(1)Cj?ZU#=f|OIH#b;+H)j)%w{~9Il%NgMZYa$bJ<5= z@Dn?CBK)!9JFpkedwFD;?5()v+JxhL8_yH((q>iB=Q<8dINlALau>aHyu;qbW7mft zJ8kP!C21vJAN;L4+Q8W8Bb(;QHgv4!S$Z8$a6pw`g$a9pJtaK;FIio`H9H!Z!zVFDWygx)3=RyK**i znS)$9$Bj1ZY^>!PeRjaOfXaZ3(Us1e(bTUqIbad)`2m%W(T`=LYn*+CM@xSywuNc_ zT|G&x3HPI~H}|IncsfSg%Q#28n{z;|_A{1_+9mk~liy}{bo3(kbCTyl>YuSjs8e)g zPyjkoL-eqfsC{N8pE`Mj_8fS1ZQ+m5*%Q&-6VTz~(dFal1ICu2+l}=?p_RR)GS*mQ z=m2Sxlp*<9Yv#YDFL)e&NL=|wNFgRG1frQh;RK{V{XjUCYc^L;CEhyKOAVBd$=M0 zMBx-a1#U?HunMSVBMExyhV(;wn(4ziqtP0D?kMAYSWFa*O4+!Zo{eiGFCIxwt{MX5X3~&k%mc_V%;?E@6fXtx{zRv_o zcnVO$BZ0z?Edi$gGNAZRQt$)~OX6<=ivJWKq{ci!d!Wi=uKMs#{(${~Ayfjai+L9& zN&GDe76Ap{Lgk*KAmbe)oua^V#xa{AHZOGF_T>ghJ#Tm9;-y z?s1TFX4d}mDiz;qpBKdK6^>o3S^CrBhL_g<@a?#1@0!*Aqmrs7-rCR({I{WwOsEqyi`TqQqCKmGwX&g;KLE!=$HqgnWT@b!+*np!h= zU5ogW$=N&pGcDX~if9&orA2=19cUK*T#NXfT7-{i;r{m)?q9ZWSGUOTsTSe)wg^Z4 zG{fhm7VeWR+~2ftzuUt7n-=-A{@g5o)-9X4S(|R=j%$(M>n+@oE%JY+MS7gg@>|g& zzH!zlbLM>WY|8@^(>RKjm5?xZo;vK(+)4PHoCR6=kEVG=xxt56bMr^#JvJvhHUFn7 zpGlJv?;e~sC1+~Z9ChA~STi_cGJ3|$$L3BT)5M$^DkRN}Y@8eOMq@4!FOwZyd+#HpflV%Bqr+H`$>P(jk&YAIeMxJqEZG6^DITdE5o1OJ& z*6geqnOP&}$-%meJWi)QCT=T&lsPqPM%HZe^~dROZ4cv$Qmh!Cgm9g_;>~ z25w&JWHQ&%CNIdEoo@!hAP^rVw2YmdKUI!3=Hzp_PY$~gmNO$~Zo-7Qv$Ha$^KRnD zVZb>*&EcTnfVpxo&N!IZR@4>;CbOpI%z%H7XF%SRrVPzkKb@U3H!CG$&fJmHXU?5( z9IjLZWX{jb%Sy?gdL1qE6rN&t+E24{^0E@tp-egblQwqDm=q;M$^l_izafaz^B=d) z=~)M{67pxv&6)9-%lV@(AEa%Li6w!tc381G&yDtBmgo5n1(0WZ*b=_WWT}u9s(E`9MYlmB74@57>ug!C6ceQ>_?aq5*2b$;F z(w*6lJ(@Np%fhDFF*U8Vb7~dn9xYNzHP5L@fOh;N;RyKVII?Hl5KR1{@)AZk>DsTt~`{|Okqf5>6ZVZ33%^2qYI{?gI zj5=qe1ra7?1Mri#u}_hf3@7r8>yMga?-<}NI7Cco?F=$OaIm|DiEnw!UxAzp9# z$C}W1B8N3DwXr7Z!#vuT`LrMNYJcX}oS|D7z?!J7Sk?;HyF1rSW@tIrxA55vzGL}! z_PqR+JufxbU1e?Yad(H}sik`HCoZirU^;t1S?4)(Mk_x1cUi~jv{jFk{lOi!exmkk z$y#Cr>nr;Yq4ts>hGQiEjieSI{UGH*HfD%_{!Ozo zcs4RfPp##=(|Pb)tnZEMxi&J@)@^d?uw^|`7yB+AnmbIZ8-@)z4I6SgYc`#2;djgV zsB^OyN!nnK&i6})btsl~{|w!Ijr75!4<>zZ#?Q2?oP~P*dU|N6wK=PjNRvdG6u$Sk zo;APE?aRB!d6$%q+K^n%LP`91zSSw~Ea#xPmUW!Gho)8JE$CN4_%Zf7j}!Y!Ht_=fPH?$+Z8vq*Jf>Ftx|Yd8?24XvpGGhhE0eqr>p@v(<_cpZ@1^f zR5)`640(X}r$DZFKG~y2SWHDbNf)Gru6$rZ z>`EigJe6mE(hV5Gclg*F;i^rb?x~C1ZPY8RCroG%H#TA8JAqZxrVYs@egB-?idEzt zgDuOM(|-s&D{f>zoi03s=6Loh+kb4(GkzQ2v&uvMrbFkeyuWq_-~QwbXud6^_#$U2 zUTbeFzSx2Npxp^;uNPm$UbUeU@8gNb9{=9p@Yki<8jLFhuuHzQj3?i0;~~BIK4z3wdeU%prrE zx+l^W@zF^?ZB{M&i!a&(w>Gd=b`jaT0z8RqXF_wnZx_!%&Vhxnf2)yw5=;0->=@kn zzU}b4#S>v`S9`h>drPUeORS~duHC}7RNHxu+4QQhJ|Uh{d^axqZxKWETeTsB{LHg- z$5{)La{F-xskL&KQtp_q3yxdm&LY2fTS#qB*1Ts1bn>*bHI==jLsQvOrd583RrUvz zeLBL#VU&r3$p0G(hnlkC3u^i9VmP|t%9BEW6CcjNhqMH(t{wZC$Gr1Q31^p%HU37g zOJskId}nqXI9|kFaLU*2k+tu3wp%>ya`K1By6Bl7%-fm&gx`y2&|UdMu6WE z|8>!`eEYZd6gC6l`!IM=$J%@!Kf5Oso`>elGU@3)Z|5wbC)kl%$62j2e6O~L-_Y_N zesMfM;5HM{*s^-pTs^Y^49}>k*S4n{xHNNdl81A_daKD%)Un# zJgc|UQ!ir6xJ3KD$XTLK&;hdd$*1JW^3W9p<*#Y_v5ke=v0mL?NjSBy*Ixo}$s_$lh_;+isC&g ze+Tcgc$V>O#+@k-bZq8X#xtn#9& z@xlKr{;w(j9m?Mq|4;DGSN=1Vza9TG_!qacm9OrsmoJO3mp|oNwjKGdTl^*cx4l!X z`*+lh-;;k!zeVb%%ho?X{^YY6t}Y&t=^gZC$Aa2<`uKJAe64q>vW;@TC43z-)ERt6 zzpeY2p~1mteh`?NxhWDqTku)@GP^}WzhCfK{FmTA9QW{viDNS5ftSKp;mKj?q`Fa)c;PP)c;7H zw|N9-!K-DRwpAAwRs2h;F2bPevec2P>zS&qJK_H&{w-xA9RGU!$Ef&YRs8n&{|)~H z+GtC;vE=C)RrYFCb{EoIAD+tXiM)a<|Q?q1|_+URtH4Oz8eb>JjYM zf8+g@h>FxBOZDP<-v8MBP5f=eU-JHZ_g(ngiZAni#BMMCMBq|;@oAvw(ua^K`L?LE z_tIUaOfk7K+dDF~pq<_%Q=#oV_aRfC(AJw|$`(8hxe>YQ)jbe?JA*~8M5aW}M4l!i zOH1(+854ODxe|F2xyn~G&r)(FawM`pM)^|}cg?lN&Z4hg2TJ?+0w^?fAY5#j*A|Xuo|edb?SAHMqnN*q z#11+FJE+aDgBJO?kg(L z7rc{#?8y&o)Lr&0nF1{jKbY zw$)a%kIAA5^GF!{953PJ0b2XR!=kGs&2pXncZ`GHW*k)NtKAjZnOV6FiN2Pq&%S!g(b) z8~AFj+KtSmSbMV@0SM3IPPy=5a@P+!R8UP)K`|K}C^$n)qcgG%P-de5)yV`}B z^HrTbQpzmUhhNF29DC^El4{07B5zinOX-upV!T$L_7<|LBeP9+PJF?c{jZ46_i?YE z**}H*H2!B8Bc0`JzgnkQW2_f|Uxe1t_=fUn_E2oJY3#9(Gpk|5?LeC_@1gkeLCz+p zy}LzZUSzPPzp+oOmG;qSx3d=FM_ckIy#ss$z}LxIh^wq1N~@y|`TmpcN!8r*lW9i} zINb9O-l7#J>MoB%5AtMl*0Fz}R{Y$x;m5LKgT$m8sABqCGCvmq}7?4R{S&C*`s{dd9%aMQ>sOLG;W|f_MYxz zcaH1tj{Rx_-vW;UE&=uiP6kE-hXcC-dxm7F9ZcA|A{vCXWBw;pf>nN zn$uID@SLG{^c)7y3odQ&v>w{AbGK^^oB4M5VbYxdS8T{(o7r5FOATW9pp&%g_geP z*k|ZFqV&M32xz&m%~n>|OKbR=J1dql_FJ!W_Xe=W-ZhzhMD4S$6deCzqh97A6tS46=7~HS|1I5=&VPzPEUWQqCY-H&-lW{?bQ_?P%}ln%CT4=$udK3zBqySB0+@`>Gb~dau{vlFfvF z;_K^qh49UUAJ%H32Ag`Nj*;KNVQ_r-x)>@@glr4{}?O!^u4*P)|Z@-{(u8zkku z*DTEg_y?0F?LMupA7iz?1@;O5Zg*D2y0rEWWVAdstM;+%WJKH(kKVLx?f?a#5#a#T)c{bnu5#adZi zEq(Gyr=sMDc4 zd4iozYBx!nZt$bIz5cijy3pzuay^6lM!dRDe>S@FHjy8Ll!epMrI1`vB?>GFl*|eTP?h&;rBUxo7@9*h4ye2zMO$(@%uY#VOQw0zoFlANI8THF5PJ> zUu+y!)3lzhtxWNY4*U{{Z)WjH&dokfXL2&_@@v{$0re&8tOZ@QxiCn7y!G?gEfIPkj8eyUx*uz2!;TBRV#=uie?I93~-$ zd_*j^H5m;>ekHuMFp*y$coT};RWq&-I&-K4>?dLJUc@-~0yLhC)#?s19uj(bKJ z5Iw{|Dl7_GIOAf-Df?c+WPBxl2l1=AMH`n!n(Wt$x)^cVIg;u7pm#>SFY&B=M3+hV z8*JS?HSpIOXGz^Gp*#>yIPmfvr5t=aW|B-uakDhg49zX z^;C^cbw*0tZ;sbtyB6yd+|V~NM)cO5QWwYkwb<6ej=9XbYAtLM>GpE|gGNoC^%XLdO+9$%_X?O_ z$$Ucc-$*@(ehN0~!Kd~TdSlkW6(4UZ5Z?FII~cr|K4cF#a_(m$<2r-qlJ6>bE;>J#KB0iS6VAi)ZG%>H z*&OIHcpeDPw-GM=LHV$L+}{x7d7L?10k#a0`<24;p3--jJWqkIqM!0?TI^QvIYd9X zlCbl>fu5%cTWRn-&^5zPi)}4z26MP1?0l_-jl@5XIou59aH7|R9^tvXi`*OZ!Ed2Y z`2BnMEwtr9+j?l*3SC0yX2oxdZlO(R5`Ld&Zd70kF!W3`ylaiN&Cr$CNgKj>mFs+# zveWg6R~(MQ@zq-50={f@MYAU^aBDxh5(%t!jxy$bQCd;S!K?pzbG+*|?gTma(~fyo z2=lE_=3VWXe|2D9$JrmdZ}Ixb9)^8e=FC0em)s*KHflDaZ@ue&cZG(1FJ0?YzO%d5 zK3(frp00(Jr>`rFE4YJuKBybZ7T)Qdfa1^A~;R}*YV&y4&28wM;ueu$T=lrevMsH&eu7f za~9X!r!_b>I*a!V)Mm-Jq+kHH!(XiLlEfL;ZCZe<>Y>~UIft~Zv2iei@A@yeQ|&T# zP8aF(u(zjSi+&;Wxh`9YI||$ENr!1;WKCs!;PHUs-}`9CJlKhI(GO3@YLzRo$zI@2 zMb1eZwkg*(TcvCMtrfQ{bro;3KU5)e724PbpHIshB6i|nWhZWjow$GQ0BOs`a#l{- za|CInyo7r>M?rbj_JCw;wxQ=Zdvnm{n_NZN=drmIJ)l*rnl-$#obOO?(H)FiwNT4u zzTXz$!fqY zW3=13n-OTU+qjc4u(s!*ge#}PQO1P{;2s(1lX@CGEVi>@j)L-p5O=w6Md^$lBR0<% z&XdWrjAw+Vc@{h3%a?{|<-;|-IMdDAJNTbs963UBS1#+lugi$+$co{Ni86gmTK`O# zfxGU0;|Ta)1pn3G9{R+N7q#OW=W6`7Zder3CZ3ts@zYHFik>V_SY6NtexaVlML#TG zx;nlYenO|{Dmk-cbJ;!bX!fcmdn%euAujXv62tp=i%rCEq=3cAXGQ_?`Ka9be@UInN{YCi!q~ zJ2wzH-L>?=`ig`}6>lw_RA2PUlFH@VmQ=3DT~Lwkbi#WFybti0=fXsXcHTNl@qZm{ zo%<`ttzj?2F!GW6vJHG}&GFg$x5kNc3f#|S7d-i#;PqL8n=@(VU0mpziCe*s`*iyz zfnPuN6%?G(jJ^R~`2pua!p^f^uu^w;ayc_OoqBuqvqjHVardx0$bqdRcx$0Pw7gJ{ zc={uJ$=&((M6(o)yvYWhj`9&Lq3X?j3z9%F+tczGW$gQQjKHH&QoqRNXA(PE~IiIfd*F1PAZ3S8`{h)XzV=?ABy-ALWO% zDF1WnWxw7r`7_oeqFGm*hP?D+&&+n@!=t;l<{}^4;e{usZ@th%yPtImx%&qDj3w)) z+1bg&c3I7x8z|4G@PXD&o~nqwJ3bTIdmFhiP&h`$7t4?e$4t$B5N|$tfhU1 zEvbmLA;%-(6C!k=6e5EafG0Xd(B-l*!V?OK7&*Rm5a|BTJXP86ZmE17Grj%K~m!m-lEagT zohWHB5Ia#)1`s<@(l8)2CiP$qMA1;g{Rh&H1$PH_qCnOuTjJk~b(ivv!R`0x39~jZ zuMk@HbM7{#k9O>W-I08lH4&jp=69}8lddt)by0U3_OFYKzbx8h&dD7&*o$(9 z^{o=#MQB0Bo+<1oca794r?vB})I(fji4$_` z;gZ#k5cbu$=hx}l{7|RNJ3?I{!NkRG-c}qdT;jGB=VtP;;#5Y}dZL%iSav1} zOT@+&^Mf1tSzY_GRwo#~jX|xtxPJ9s8;mI-A)O(R=e#lsz#S zdt$a8c-@{j0DIzL?1^2qV9#8}oyE3bPtTq$?TJa`JDU6xDd&F58^v64WEtffbHx?d z3|~M;i%l_2+3Ch8d*Vg(`u)h|!`V~o&!HC|M5hO0Pb{F0u2P3%v4KUjYEPVb-JUoW zdtx{2iDR)RuG@$%!k)NpDX=#%6Br3h0Af#E*DJ(hm2KG*&(RM&h%WveGLHRpa4Pb( zUfJnZVE+;u|5ogY&ta!Kku%5GpYV{f)13gv72uer>~z-N0Iel*to(sIz0!mafL4hBA?-zKx8mH0mynzcrT~ND)$_=#18Zs=H7z9S&L`aVt7ea9n{c@nvxIo%X1Kd$o{SW!Z{)cwP{s$-a&XM5gH0+(+ z9b?!#f#~?n=|FV;W;f7J?43^9zgg}D#S0~5!f)>%_ArKyCrUT(|rW**4Gb5ox;dD^!hzcj^pM{P@Wk-yqAKCw8$b>I@Y0*)7 zs9{UI^q+c}?8UBq##VNQ`}^Zodyl_#WtG+ZcfeV3GDS}V@+?RBha&o@Za0D`Kt(DN%#@w`3;Qw`(t;Lbw7!- znm9*o+Wgfj&R$!fM{Glfdc1c%&Qru;AI$uxRGdsL&{J!3xmNb7GU7Z(K6&)#oWYr& zN8RS>fv#NYnspmbvOUNZM7(m!TjdDy>?E(h6K^MZ2)>cH-A;A+CYFjxavh zZ|MCF`SlI!?PanC+kh=X;?|mHF2=vYTCc>F z_1sUfF_>{}Wt_(MJgEhFK0@x!>mhwt>K*#7;BLQn(47tbtYvGr)KX&$K4nfCS;L-j z^!ZH6ZjFv}j9>BzYl-hx#=>*(_r_})YY@}$t42Ra+mbdWZOybZcC9VKu6WXpy-}bo zwD#{W4lAoU$oh=kT^aI7$b@V=Hb->QM#k0=te@JjIX1F)yNL4w*|%YPqz#Mhkux#v zr9RMC6C1;^JF+*e7QUU+TveZkn>M#|udx2{x~I!m!RETY6TBpj)Y+%*epOO$VsqTU zy*TV+yUWx8hYZ6`4szzF(!zW$jbi9QRs!+KOX^OWd~N+)O?{ zCH*$aa-#=?HwPGNGeHZJ_e93Q24CQx@MSNs#^8&is#ftuY>{6DJCa!oG}3HgJf0oV zHg8L@qx_@1l|64Pg#AnKwk6*_t8bhr>#o8dQzoJenGhS2A%~31m(qr_T`hP6pQC7h z-tt$TT-#Fg=Q4QpZ%TG!gI zXh$+GU(suSNv*weve?=^=*T?!@<7Jrd6c(ajmxJY-|TTP#^n)eTrT=ON%=d~xZJ^< zeTCSbTeROaY_kr|0Ij?gK2p{Lv|Vd|MiTf3chGhvqazP8hNW+-O4I#}{S#G#JY61Y z*VJ|;j_AG`yE63Jz})qcCN!l`wjNA6~_wC)!Y5I@>xLoD$4qI+OP1YjP^T;`jq$mwBPge zC5&(Dl4!gA*>9Qz-BpYYOnaqY?F#t(5O!>7zmi7mn=8Aw&f5XlI{!PowIToKXutmo zylu(11GHb6j|zW8E^6VMtfABzvXNWQ{>Ct|88qTPN7!E4k0GC`U9WPQJd?ge_?SqW zw&b!ea>*VBgLh8)l$({wy~uDlcI=b(pjxY4o4jl4S1h@F2ziut{TF0Y%8ypT4yAjA4e;*Up?ax&EXr zuyzS+Wz#~Dr=EXk-c}iVS>x@4jP16e^MZTcL|bhmPP4Y!MjUC!R=l>#dQ7#|AJbNa zC$g^fZ^)FvANXYOgf{y&?Q|>j@^K>7PCL{lvDaaDFk=>uU3)erwG@VzBZWqnNgVd0-CFMSPpkv?t) z{50f~Hk*Jhit%c*!Yc=DR`?_BmAmr{{v5nXnUuVbvPO22`@gTZRYNYl+NzSv-Kr1% z2-%c!WsOYmle9)#y@_-ZSK6wKdyRHVnj6|Fdq}Llcb{sfyKS^n#`a$xA70j1w>N96 zHqJB}dqAYEvIcGSud=sM+Un!dR+%FtX`NJCwV8eE9iHp^U1VHr?RWWTc)zNN*^k#3 zU}Lu0>I!48OH;p^%UsB6tJoSn+ccN!8QR!I`c-SbG?so<)~Rz@6O^^;BsIQ)YOg<{z5YttD|yR$w(wp05sU9{N?ZMh_n)#n$+~3ci>3 zG3O)fRWQaguc`5jHQ#6}jukF?u&p>*gvngP;_3Ca3T-my{AhSzLtdmUjDw$sd|GXl zH6e>H!Y65~!XIg?Z%SJwkG?kPTfbW-CGWx9nOvYXwN+CtZ{*KrLWIpathQqc{F_A%pMt8zut4zT)$pU z+-UsyPNz%8$?@p=K8)W4uSnbQE3&3tY8$;^uRh_5+di5-RHLqF`sfAKj;6g+&s_P} z+dC!pLF_w&O}ilXxwde}Yg+9FS<^rN#BoQ!-V(qhJ`_ zxTACWIJ>%f42yQeIJ-JJCc3!qHCN77$zG<`*7P^9ektdJdUGbC6<*C)GR&gE&r_ZO|9{CGCchXp33)%)@!!fGTKU8 zylIP<^1UPZ)=>s&g}?tN&dhxme*b@Ix86LC`X{#AtNFjhKB#l| z68FX=L>+UWn;eW`<=L{dd;OVSTCtCN@pwxHw=DhldQ;yT`_ny@o2x7;(r9!I(VyW_XgChdD8`r$0;b%4Mg*vB#hFX^YyLf5o==|5UyFKh$0> zk7?=`IiI&n^nHP**XE#K=NRYeW$ZbOeK2N!x0Alc9A7s1i7q}xU(tO0*%V&Det3y9 zjD3i0#?j1iX|^%%`TpZl8H38VajZE~@1JDXOFv@aUdZ|Uwq&qnpM08cS~MHa(~k-- zM$y*_{bE0`#_RhCYdKyYO1O;8C2U`dvH4wu{WHelGF~zJO6gzuz6N6#;lbPR*&4&h z7{bB$TgK5Fm{YYjj+TCwdji6|)0pFq&c?W-tu)rSpy`;Kj;!1zt4E=yZZfqG~Xi4o-F%*`}Nr0C7bgI5%N7^)>bWdoUtYE!m=Cl)?3Lt=Lh5+Xy$F>{alN@ zNB3DAW#xU3%DeRCro40H{>a7Jv4ht}9)Ezn`CCsddNzf<`9jM}tfQV>WUotPpTTG3 z+m|-()SX*ppTEXF$G*{8S>I^AEPAngcTzL(mvbh9znq`xs>+Mp*i@dyr&Fvww&c@+ zF3=KU(lVL%7JS+oO}ROJ7H<)HW{)ahvIv@DYKEIc}RWKV3V@yV{4?-MV+{ArcN@blQ&zzqfGEnd>^jxsD+jsFIt56%i(lKgb?TTOm@bnYCzi#_7pyVoCC3)BO* zuB6;T&WW7o?A+&^1G<^BBIVT8K4=wL-@U)y|z+a&K1VaxJA6dne~) zUYE8PuLsqhI@vh!UJDn>?K#ZEMap%BSmip|TY6);&Jd?6S5M8PY$?YmpS!)5ZHml) zRPOi!&gAYc^FFWioddbEE9A-aogv%@cNG6)#ywx@J8xC~e)zA(zgYRp{a+H_j{gq) zUswKj;4k-{NmzTr*Qflh+sg*M_NTR!vGBo9;9|Wv1em0A_bl`eV{Jj` zANChHhwa7pQC{?ieFyzvZPPEb3w^=|i$lJ~4u z_$e}x#q%i7Slur7UBMsY9M$W?uq_(=;qJ*d;7>fVvE+d!ne2-^2I{_j3z1<2%6CYDJOdi`L5B-|aY_^FTXr|4yY!@wQ z&Qk5-Db-HA<^5*bM_cu|gL?jFwGUmj51o5D?|>Ih>eR&}a?q1|p5+|!?dXW0uy&qc z6*W)j!Ctt8V{GKni~Wl&GBZFO(?fSn__F~HBfN@ygPZm6D^vQ)& z+@Rn_1vdeukN(*$1?8_G6FZQJ*EM_UX3Zz{1Kh`Puldp2+dsnZef&bZy`CD? zVMEJT+w@dl?t1j^_Dbs1j&bEbv)OomlQ=t5r;hw#`Oj_k)MA@Y>Q4N3@$N@EZ{O_= zBM(U-`waXqmM+#nSFB`eI)z+y!D^eM<#@tdQZlVqG$J&vX>1$_T!AWv^8lz z0*5P@px_ALImS6M4*Z;P(`Mg&Ltln(ufVq*ijSe-eg}8_c7WEo-FHIE%f=m<7r6W4 zJ#?NgypTIH-$VC3jUGHMaIs!|47ieZkVG4o`=I$Yacw&N;b;FDaZi879!|y?f9F1m zzThajAOl~||Dy8$O8Ng0 z|HJqnQT`3e|4;b8ivMfM{|n{+2mDj6hK~%ipF`EL|eH+SJXh>y*um*+PCCHRe;``#gTjUQP{Jwjk^ zUqOD}Kqfk(2Roq$?*ZNkJc|E&Eo9|?^kM7zaG27EZuYqcY>2q0ANnv5eR%xq#?Jle z!-8lZrL>Qh;}z*^KSyRR(8tO>_tKwzV)U~dD1%<1s+v1$L< zf35R?qyYm4OlHm6`04zY$NE|B7w|a2vj`9Ql-X9E-}Bg5Sf2@eo#%HvU2)Fy;ZX;};kb5)#A{ z%+rp?!4tsa2&kgcTp2kmKFkaK=b=N}zBaH7FhU0s-(fldhV8oM+#`5e}VWJm}UwYsV zLVm6P%J!UnjnDJGFZlh!f34$}0VU2CUF!mgAniAgXSGVF^qG-V{z>>so|!6K=}QSR ze*O8sooB2HUu}BR3%61(VUpj+JdtgbCp28;(Nwt7pL|F=c(48~bO;`UsjL0pmNm9`RSJ_|;}??P5^PoUFC4FUw(N0sg};Un9DQTVS_{%e$fAyDF{ zEB}X;|3u(j_`8+=P~{&5l=xTK9VhwMoA8mdKwtdd0^SGQ0c6M*BRjT#2uuOC2hT(k z#&rXZ#9srB09G)*HSjgzBg=pth+k;JxQBs~ev%2}dIOF0KuN!Stch14Q1W@ggpcF_ z1&e0x0!0LWK_n2I2Mr3SIjXP5FD* zgmG@5#2*Tjc;P_7r}};~|5Bj1*8yF?dCKip;X{Qatpi@ zcMX*+=>`4(H{bCx+ybSZij-R*O(A%aatoCDi&AcZh4^&P=E4RQ{+%?pjfiLiG-20VV;1Jwvm0MsB-1C%MU|-yc$}Nzl z7aXqK0_pFA>#0`*e_%ZB6Ur^{9^4hmEif8)k#Y+hgqwS7jr0O(8o>jVTi_kIMQ$X2 zfqigSQ~BZ+h#p{3#dHh26?c(x3;ZGOY~>ahhkJ-}3q;ifYsxKE&-* z*=Kbhf7^~*cirl5yL?;yZT_}RjN$Lp5o^zF~-JLI)=jlOA(zGV%s zSJ&u;&+E&c=k>(%`p3`f)yC@!`o}Nm)i2u4`y|h-pyjHK5*F(S5kN#5s@R#yhq8}~MKP=&O`-}RH7xi5)@>;Y`U%5_Sy^hzj z>-3s+`g!B^S9;B_^z*;M_uXIX`+uz;`Zcem>-A0Rb#}#ru2Tlt(Wz^ zFYE8h>#kSyidXcvUg34kEBe}3^mVV0{PHV${VTf7c7BI`X@{=c&hF58wF#^-;OlzL z>-c~Cx_eT|5y{Q+yiT~BzID>b%+nt`_&Q-|D)z9Oh2Hn{Qh^l*^tTQGw;a&7A0WK+fX=H;;3jdeJ)rYy6Sz*`p@aI- zgTS{A>U$3ozU!dQE9D>5E5yD1pw27hAJlgUEIOpGJOr$-(yvw#eyK|56*{W)%i^x7 z(s_lBD*e2`w~pw0k4U~pbY98#h+c6-A0$cx-C_MEiyK|_R*vcOC-ktlTMj}?e9M0K zkc$7ViqAKujBv~T#@+o!`hzOGM&)-@xjAcKgny*m+eImn$H&V3I%r6F)_VO2mA_>_ zo2K$Vt>Sl6;gc|;g?i-7nFOga$i>NU8?*m%Ket)$K#l0^5d-FLz=ON%FnV-jl)mUTlTC6RC>!kYE$VgduuNh-?C2yD7R&gYOm5; z_RV)xddr^pzM{{vpAJ*_SoTQy(4XLA*%vvhVz?>B`1MiYGOm{2OUiB83;&?vTlVV! z6>iz98%ZPiyGIzwwSBzvcm3xCKX3Pw;P36Ww+NRo@9;w{+*;c7tm&CE=WA(+KN^)b zB`Y(3`pnr`bLI@5l$QP2qhn^|wH7iWVeY(^2?k9{Yt4J`q&r&mAJm5b;7Nnpi0|$H zXii?01K^Rg!0F)DxdgIPIKv*#M5%*g^aC#9u(KogZ_(44HitjxJ1pnXc#?3S^c z7?JkV+&ME_1}m~#4YO!(HPB)}tAQ35S`8eeSmYHrJ1b+#gt-}W=d|#%c+|T0Amu%I zLDuYiEiEl=&fJX5+_cQ>+_XnCa`IY|D&#yiCuhb~GuO178FRB{&&aqTo=|o(@r1~m zi6`{79xo$rYW~EW=~*{o>YbovC{^Z_UCYoGRJIImL0rq=7BsaCZb3?Oo=<%&WA+p+ zZ|?M&cNzZ&8~^Xr@)E|5zB4wBm!yfK?^bZoV0j_#9WBdfA+Twar{~-dr52$c{SU#jG6D=Wg+!APP`XEDs9~;>Gy&8g%;|Ln51ggYF(O zXz(3F?zrR5$jG|}-b27010{6Oz=3xU9vm5Y=iU4>e(6pWvzMk$%a}gp?!f~l49J&^ zk6iiu8`iT7l`sG3UbJwp4uG|JZ7t7t{bc^?{bOxP)~BxbC247Mv*yihijkH!apa_l zP41R&E%PJ1&^r!uaj`QPWiVp89z=I$_)B_0DUpBDx4L-7^2ks8<&mESP1nu;mvr9a zY*o*i)RR#Wiu>2=iRx&rp6(bjP*qgAbHSXsQ_`kP$;q5MAk(a!*6T;s4zx%USE-*G z^R?CgvQ8wwB1Nd>w(3XtCBK`kpB*9~{3Jg~yOxZMp(G#K7JuuPmL?;EjLf-dkIndL z&WtH(X(=PDFo9M+=?bqQ;K|U$s4S@~eimwmY@V%N%*Y zbe;#zcr`X{aFHMHn!^*Ft`$3dmyYkt9@>B?tt`N2$@pmQGjRI-%<%L0uNv=S?|-BZ zOVn%by%rFqmj&=mK<*Z)4Dbc|@-4{#cV%{fR%zhG_h2QSyf@ly$LiC3xWCAkyGQ)E zyUV}q+B^0lQ!~Q-phP<=GsDE?pxD~g}>U4KO5ycvL7>er*YSJ`WyIBPWCkXZnFGq z3rA8ej@wMFZC zkvH!6K8Sn>t^wd356v1hOC9ayP8|nzzC|EyFhJTK`o(DXybCOpux0MDYP(T(m=Q+h z$q%(Czh=5rJe0o$ZwNQF>-E0>hH&sPeii6ix&YNHC{F|2!4myNz&bA7NYRw~!wDVN8D@yoxylJ}gPZ^aj$E z#mL?}fwJC2pA%E6;7|o46+8<5l3w~ONiX}>7|q0lDCh?A=NGAh+Y0Z`IR$<+kMJ0p+&pVYzZ!^}b%Yt$J+0O%d0Bd?%`zdvJ?ziR&F->e<_^ zr9CuZ;)t}od?q*}XUv_QFH0WQ(uWZ=Vgv)VG#RZWV z>GIlofLBN+^NP7yMusZxqqDP`xpQXZ%#{gyPNsLPIXMfm@*hp}_R0iN!#y@*cES3Y0ykvww%|< zm@gdp)*^ZM$+%g@ux@v9&| zrCP72@qYQPaecPijJ;PF#jkX@ncx1ghW%5#R7vt`*i@U(W$a|dMm2i26@;t~Rb^N`5-0$sux1TWvJHJ5G%bING!iJnK3mY8t z(K5yl_|wyj+e>Fle{9{CXY{@JN8&HP5;lGO4nS`5#jeWtl0bXc=;A!{IEd2U9L4mT)mh3bhQSJ@xJ3fpB}GeZyD;= z3**v4&2P#(w3lW(LhfsbpQTmmHRP$vle|weE^_$X*HF#aUhry6(2srjpp7x1o$)4P zPP<;*VXV8XfHAks5!M5(IYNzLsxSQD+QJdUPhjlIn2_Eynz%jO9e;95o9X_VZrtFL%W^jopRkj@?f;%}u=KA?X)P zId%N@>G1{AqADyoNuRc5Xu97*>R!#m_|}9q50iQ-@Vl>J?l%h>1|$20%x|it+&1F2 z=5NjA3ck6w)iSI(kVac*NsDbCa?CI-KE(X4J#maNEo0tFxiiqY`GxW4Ht(6eZDPrI z&%n~z1)+(#wa8}#me=eonphz9MH@y+iLhJG*0|Gg^0gqB|}xiPD)Lb~NWk zHPl%F$4JY-InsB1NkpjGP8n2`M{q~lXmyVE6ud29d=H$sgl{YhjdHggdW<26iB~kV zTXf4jV;H(3K+`>aeYIn9H(e6nf=CAM6!2aN-f7_dIe4S{S04uNs`<2yGt^p?iFNci;` ze3LXq+*1&63u_VJ6wNnWKhwhR8Lda%(>FVPNZM~~bHqxC`Fzg)eAa%}TJO8w%kw_( zb9--5;mSYE@p%wlMExuB4ED1NEehwf)UHrF`h!nak5Br3Y#t_ivXJIZ1WFBhKsxtp zQDm`mjdb>kTXdP)co$_ef#b_cJCle9T)Un9LJ(`Z*Yv9NawGSu z6xvMb@NSyuy8v9zH6N=@VIRJEM*X6K4Z#z66RT_e4}0N5{5K@A8`1)9_#H z>x0k$euTzWc&KZ8*F!7Wq_2ItiEHuO_N-u27G-^wvMRsWuvX65jD0?J)Bk{9vv<2= z_i_G1C9e$B9 zaU1b{+^6Rl`+NG?*DBgbGs}#NltVio^G$zgw)6gDf9T2B?@FPkfyz+lF(Vz+b;khd zNlwL$2%ZiRXK{9qZ|6HvWY5oP7Zsj*?VfEOU!V*dy}$Bw?J@C}d8u<8b;s|r9-kRL z67A<}@eArS3_QcbpOkE@_f*xMUH?_$euZzY!G}ZJ?6KSCW7tWfq-0 z#Xjw8*r&a62z~@;vC0!{IxwWLVgqScj$uDj+Mp_db_KUp-21?JmN_)iy0NM@>s|8| zFZf|iK)Pt7jds#rz%GrT7`^J!#A1gg&z45HGJ0$N4pvg_pO&Pf9p^ZX|rpmxg z12mTeULxSM#2gk`zMXm#BGn(rNY-xYae|VU)J8f7Ed#F^;pXW1pT_^ce)jz&yIQs7;@>8oaC3^$m!1o?sG*mG0(M6+yDVvNM`SeruE@9K6G{N|divqxLW>V5bZ)Vv41N0y- zHpP!0U*rKk4-eS{4>?R8(&HXQ2FHhcm{v! zDe!ob{a>Qk0#WvFQJVglJUi__l(Kq(` z7&BmuC1sA$5Fibeae16Exg^Utu^B#{%Nf!dU&!J9GiWfEGj8{iMr%=!F@cfrolL9# z>i510&<}ir^#go|{rFwRb(XG+`d7_}Cf&I%I@k2Ku&;60UxA%;=ZxrF&ZU-(`2lBH zi=U6={Yc)oU&S~JzG;=ku2W*;bKaXfh37rct8~y@XvJrKzgF!k+Fb(8idHe5(-kgS z_L~`&?lulR@>1zh=#lKHyZR`4WF9&UKFh7!P4=vQaCxVD|3Z01=jLH^lM2y$eP$r+ z8&*|2iZY6xsuJPdgV@)ZJfCMTsMblsfzGHJW#fRl7bdWm%x7J%?mY+Ir0a1up1+H_ zb3UH$NOrc7Zqu_8O_PtNvH+>b)BB%Dk2ez5U-RlPT^Ju}B@ff) zwNL3=(b{L;(39+Yka2)Gg~F3LDqKi^XoX*FfL}aEo3wu0gcTZ3$*&-~rtcLuP+!?Z z?TJQIZPm`%^S%^gzYm*%b8n)DBAf?Jn7cHR7>%h8v1TnW57!I&}%yzPA& zoRc@WoGe@(q26ue?|!Q=_j~NHa69y7n4a*qJvZ-cg?6{m2Kq)gZ3AB_tLi4aiq6D4 z_I%P+yo|D{zDt0<2i8SzgO z%jY|#$yNgypRrfJ?4e&+HpM99`XXfY4an^4k=@rJ!xwS})PjbNvGech+7oBlG{1n} zKH=N`#tTm(&p)=#c=I4%Ok_L05&Rn?1@oW4_I#kMVE+`Z14FH!X)&5tPC>GN~$!!YSm@?{uX^N=ms|6aCJ_AuqL z{0kcYK7FSNPCd|z7rN;g3zuG}ap6zFtLVV;k=5CB(49vLdB9UPLgPMk;_`de;V(^( zRBzl|Tf&@&dBdc`As+`Ncdf9Yg*SD-1S~AnaW!1>#E1);d zu4spMxOJ$;O?_#3*|d57Ksab-y`y~Ld6Pwt!4&9`^32LUKixysU&gn-_^M3m`3K(>M$;ks zz3+L8-%R$Q`>L#>vu(&S@f#CR#pCmwVD%vXMQp_1AZMNvDi& z+m$ch|Az1DJQ)$`Z$GEbdw9P0{dG~j>)@UI43C3fVE54rLq=$-U^Cyl22{>(l4kMA?)9r3OT{lb6J)-PVC{Jx>Q zOEo6@>Z~@i^b7y<#7$-Xh0~_4j!_}pf*;XzRR?}VbJ&1-sH=BM2aVN9`_n@+&_mTX zy1?mP(jBO++MyG*QLfWQqLJCO4?4$W)dL-Laus2UqjG*-^nuL33RyXgd+`eRft3b4 zAU&7&@OB5PIsNPbXyFplD1UriG!LFq{y2IiwBG?f zT|CA1QRTJILp*HcGRmNIGx*-(LxYXgntxJuKfDV)AVm5~SLZEiFILs1-FtprME^=7 zfiylv=EeaF9XymdbyoN}bjd8*Pq>NYe{LN2n~#YX`z}Cw>Zg7Os=q3|BgE_SdhxUQ z;Ol<)dJ?vK0KccF#)m9Gw~&6pd6J_$OD;4{JP%EM0-u+yF1|Q5+o^SQk=ZePIS9{Sz*LsG9JG`Mq9oU2K&{yNU!&_Rf#utSAJ*YDl`Ck1*{I8IH z&Dq;cB@e;Z(KFw}hTC-^GK70eHk%t4g-F8}|Jb5`0Cv&e7$4ob)ZpAp;3L2`(te+Q z{q2Reu3423S_JPV?~KpQyA0lD<^6kXGVZOsu_vBmKAV-dXOorpUy^58++UXGUTDV3 za|^tfdpA!H_sVmiyyPE>t>a$m=AS5^!U$^&p&okb?>wtEc*! z6=&@CWA)w-&oPI#=zPup3mQ}#`xsNT##YIz0Qu7Zo#)B^z1#LP7!!Vw@5fIg{VFMh zem40B`NlOr$PdHM1Nd74({h`#zCpUQwSnKoDEvWw6}}v$8Q@(7ddv!FHyQh=!XIqn zFzzNFJ|2Z9Wg87X{5-n%T_}6Wo_E*RFD8E&@{qD>&Y{0oSR;+Z#npjE#G{~w_+ncNa|8L?+5uRtMHXXSrvE1 zIHMtnvg&^M#b!eex~X*7gUB2CXf?mGfwpXrjTtis&eS{X48PJP{aE=H+b?s8hacXW z(^~rUjI-rS@B^)!5nX;qkME;=IehMp8PU!7%H{izEqWjC?&IAy_@Hp)$G4{XW7ja& zalX$`bkh4>UtBxqvVv>*1s7X>L3@s-{F3fiW!1`c(FXkX@>8|T7x;Gu^G9v@woN<} z8mgjQe9(^CCPdp*QNPXLAVk}QV%nyfwmEPMxOUnn1pd`VqVe8s6x%Ke-%PvsX_w8^ zdGi>v;XBBISYNGh(!0*`rF*3VGuG1)I}a8aVKAn1p3d#6V7I;3pRHGg zy=&Qw_%2Fg=0CVLiRLhihFzOb=PiHg+Jug8z;Nd5sDEH9ciaA&3-R5vT|4gQ8fYE` z`v_v^obMlyEGb1N4#6+bt1Nz9_Gd>gCjH)alUv5t&nbU@b8y_cx1$rF!)_5Fmp{_zXzt25@)8^_R2Zhu#OOZ%xWIt2CYTQ0ka_!?-@5j%}M$?JAwwrDL^gDfFxU zkxN*+)u#45qu%}O|7jZTO}|s9WBM1?vxk1iQXjXT!Cv+JThQ)#{m;=O)NixOa(7m} z!8jhg$C`g7{gXEAJs0af>Qej{)~7d});sm_VUO=!a=uPtpp*Pp`epBVj?zEVp|{+% z7YwodSLhJAYqN&Pho!j+RrfQ#)y2+=L>_4+A4xd>(c1{BJP9k=X4_nvIdLB=W%iGHw-p>9^>9TR9cZyDg zd$%6oq??D=!^790hyT=(H&ujH4WS&G2ZUa|Lv#07hxPPxtU=?J|NQxYj7#kJhb6&=y{9YR4vjGySfdhM%h=pV z+KxH7nx}@&IhVQ>Qpanl>sP7sSMcj=eUZJMY50RPI&&F!tK!?~iP(wW5s{$JDE`^H z2Ronc>au)c!mn)3*0K1oHl6Ros`_!TLB5PuzTvFf{p-9&HEoF>Yh7nHvdn55BcsBA zE(^V*sxy_B8{XCR>SorO9l!?EJC#NE1L2}yJm5c29`!!iL9z?_nL8P@v?wV?X3HZrmSDd9zVaoNN=;oGs?<$;uEuU9KFjk()J&@arl)wlZp4m+41)LL08u@ z2VZTCC-~|I-H^rHyh7UD556^yP~FQ1uQKHr&3{n3g=Xjv?cU};XKgiCcf^IBG|jr& zRGRBund9<|8%n-rtk!s?8&BVr_j>Lfo);4?d@$e7 zwfp`s4&F64XJ4(~!KoF#uP1yA{uR~H*Rjs>JGO#1JzMcs+hMs*J9O7m{uA0`9_@jl zo;IH`kqXZ9E~TEi(15-zH0*CxPt7m&{nF9%2RGm18*I?udgA2nJO3NT`_D@wclG(t z-DO0s_LdbcPyI{h?-yd9WQBq!d=si_{XTGo-D3H0XiKd_%nnsqYx82``Qq@~wBef| zuDd?(i{{R`<1h8H=^1y3wW-?~C+^UkH{<0+MJmsR;EBM*aBU!-Gr`Db1^HAkmzR0a zk?aj&t)lZYMMZi}?Nq(z%}Wu7k%f#D5oW*S z^l`?vE~kFq^Q4BaW}foTsb`}Z2sbi^BI!4eTYi&9<{u@!Y2P2Y)`%uC&Lw|I<0j%@ z_vl{Z>QfjK+Qys(Yn}-6v*e>`pJPOQ_>rvp0`BqQSoc@61{D8|{Cn2(B?%4kmI)e{N3^swhj&Df4a)s@TxbXv-3J5It0GIjd?mgGo$6e z1;*-wj2GB`oD9YSM_YXL47e0cbY2&X`Z$B@Q_tvdA$>J>kjcE0&?5G5otz6knOnZP zYPQb!wf&Eq>2J#)EL)rBPYP$#-_RK?onGJ7rCyC+{ie>Wvxe4xN3%vUCii?{gJhoM zey-n$W;1TAaovp~}6O{6T8`Dw4OQz!KKeb+NYk(u)ZC0 zysP+El!du7&=K={8TV1Wn@KZ|af<@_Tq*ZG8F@S(kvRGY?;um(h|KNVE&)zQule#Wvu^x^F53) z7w`>!4$8c}V@@zy0IoKh_8RWZ_*@I1o7SPt`SQ2AeWe_{9hnqlzMT;*#|J(aA2{=^ zq8(QRqtzE14M&*s(s8x!&4wzr47K~HJ&(^v`^1h3ZvZ!b@YYWG=2FhuUj4_V1Git7 zaX$7lf9!fy{mH^RvR-}a>rS8Y0Y9%#jiygk_1mXLe{P@p+UN8s&1=g8-xi+fYoaIW zJvr~B@a?PEkPU4xmRJZ6&y5-1&3hi(Q*(rnQ|{QV(kS4)+9DS`i}pqLh2W>0OKq0R zm|)d9&9_K9;Sb?QXB|P+I(+^^*7J@zp`)Q?@XS1x)zy46bwhP#9&}27m^>QVU_DNH z1N5IK`bQ>ejYSgcE7Wh2kkvfj(tIvu3Y~epX9=pO=^!4j{uTp}*a%+6=wm!kE$By8QxtIrER z$y_!!j@x%atk1&g+VM2;ea7%`)$psM&+wh_DjqNSCLXpg)wrPmTU~2A@-7WFiMMso zMs<`ebHg*Wr{j%RXYh^2RTOp_xifmW=4E6?Jd8P3dWS`VocUPhq#MfzUsyTBt%6m0$;M8p0f9?l%!%t@m|S* z#puM6Vd8({b6N+aIPK8jKr-|Y{Qa<#r`zA!n`{}H$M;HGV^y)~V!Wh_2a8XSpa zT=T)sm&sK>?<#(XwGK*W@gC+ESiF67xN6MR%(o0J%3zFpptSlcoA1@G;xkTq_0X2v zmcms$Y4uK@<3cjlL36H5lYEn&Y0EVS*B9IJrV9C1jtnlx_ICNEWX>C0bJ?lKamr>u2BAfTLdweanZquCKacz&DbA+-~dXplOS=y&XbC#s1 z=9bK2ZyjTBUWwV1m2a%Rly?f3>=R!q{Ye-7e^^9z_ay3HK)fpY5d3XN9yDBl%_kn0 zhkaQM&BKFQM$nwO`QY1%dL(Ap(Yn1kGp2gdI@Ez3w{WxPLX|+G! zHAOsw_xtsJ=B;K^CSi&@0_f6=;->Q~KYQ$Y-jmF~*WAgru-QYh#)5Br&5D*fWVn$s zc$ikTKV%je_Dg%829pTbE!tZ{`<@$G#{MwZSzQF4rJ!day0dK3aW;#qfQ!lRXS;Nn zTwbn3u0dQ`Tm@W}TwS_YGT7ziO5_^EmBm%SRmt34Z(MvrqR*ccNKQ!|ls0(C(DY%$ zGe(Te95wn&7ckUhug6!}n4fKE8N83n&qaT*)~h4Kta7tPQ+tS5>)hG)tPyM+GE|w=E~t3!!?%cBCbog;CtHRUHiamuXyeM zZOq}ik!vZ}CN9Q_4ebHyMJRYFs22yHH=8TS72-08@C84UpU30%#>K_=;KlhyUz0rP z{B?N2ZnE$pT#VpS-LtuJxh8N0xk6l4F_jVZunk$C-;?`~CypHojaw*8WS?bom0OdIqG-{FMk4iNsX6MowO z;nSQNU1q1;Cw-m8rQbm1Tj7M4TCw`p|4k?SLMl&D`rz-lIyS%2gmc>`JXsx{pA*oh zd}&Vj{PUvO5Bk5&34dsS{Qo3o!tY+L{>nEWgGWE%zrXM*Cww6MY<0qqQaStved_m+ z7!|)~xcaOA2r)5!>W2LK)IZk=&!&Fxz&`00iUIN)sC?Hu;g{Lb`lSDm6aFrpt-thd z)1|7Es9?epjWebPUktx)Ui z9DzRgnVh571NYxQI^hHHhZSS&@7n?Wm9KcL9X=5K-t2@A#GjTq;RChb3MX95r9b?y zxbO?gchTpE&%D?U7ykQ8f5Ihpcq_o$4}Nv7kAClR^%s8B34f7rs?eu>z9-?!ub8X9 z@S{$+_;`QeGf}?v(|%cyQlIpL%5&#zB-y%X;6pFZJ7zu^1a%k6|! z`2Nzz&bNL8(Ok~fT314f6_rc#Uo$yZhTz~j| z%|YC4eBWRBJ|=uB`?PIEsUq?5%Yb(Rch0cRogaT2a5B%y4s4!oKhFoQ)OXVi<91*f za2aqKYVS9J?~v0?sgz;6;i4yg8f7oryZH3M%2 zRsmN5%Yf=H6CAh~;@0ycz}tYiz~#Wh5V_iMFHqt40`CCod}u%U=Grhn8Cc5uGCF~e z_seXUKN=Y1eHt(h*b1?$TxG!9ffoYxKHY)8hbR=D22?phG`7kyA1FMe0agA#(#R@D zJy7Mk45)aWG^XnRGBA;H=zKU}{$;>&-m9^d{x4}{;iVoZyc7ek1&#m;zv(v2PX-FV zJ{#umOttHIJ@9(sx6=5Ffwuugzt;o50eq20SN_{@+`mF>WHLAuI$nFFPdE8by{F8Cr);lCtv;iZa9^~{m3+!V5tLx4$O9-;lS2-=X(d%J22$HQU?Yd znC(ErfvtA^6C8Xc)H}~12bMZ8=)i0T8V+m)KUVo2Snt4)14|tkbYQjv4F|S@PwRUJ z);lodz)}YW9hmJv!-1`QY<=&*dIyFaSn9x_1G61yIIxv=<2Iq$f%OgyIk41$K?i0# z5MX_m=-KLf%9rSQ#8<`tUjLhZPFeFe^S^E6iz7Xr`jI~xndo`yg700B=&8D33;!?X zyqv?yZR6U<5$CcOFH7`1GpT+Or?E}iI*EhPrhYuNb1Jc>y*!O`+@@{gzkd3UrYCy7 zH~ks@x6If&gWP6R&ES-^8NZydm!}tJzC4q2tY$tlGtu*A{_pcSYbw7vKhbkM_;HZ) zx`M6zzjkx;&BVO**Zg-LPxG>0F5~^}FW=6YXcg~PET*5u?lXHU zbYh*S6jY7rYtHk8p!I0nN;@jPYyZ|lmwI;X(OZ=sZIzvG$NzD!=l^ulGo17;)Ayu# zz4QE_;`1DGzW-XU=Rr>VdMEr|^0Ll*`m&whSttFV^Ss0fPcN{;6P)koJI@tP_`A;Y z-A;KYIO)IcJO`cUA2`p~JI{AI&!x`uQ+lTSRnBwRdEVkYZ*-owI?p?u@9%b=Uvk2q za-Of#Gkvej!A}NhYu{d1e@DDo&&3zn^;-d-QMhZreaU^k&<-C?eHHH7FF$v_ckQhP zC%$W+DXuQpo?761@7fU4O!#NK5It{((E3 zXV;(bb*DV8e`mK-Ki8ju47R>^{VzXu(s%6{okykgUHj!VCwkqrq zDc>)%?E;0I{Es`&%})BR|LzJWJ=foN(7~VUAJlnnD!*%wGgxgsyY~0DoM+eG-|CbX zoyGb|uIqc(pK^!u?E2RhI^}cyov%92u77hPso*~|Tz}IDC*1X4J?^xZ>+h1^QTe(4 z$KN^S501CX^G^VUyFbZKJvU!rzc~Pi2{x|b``G7Wy~6*4?_gxe=NGAO?DKDEuh{1az4DLtO7Ba(!msUB{&Bt1e~!ds(@*M^-=tpU zpV904DZSF4)9d?ddetwnSAOm;*k!jbtXd|L;qS!whfKx7}MX1#QF>+XqV?I+&z7_*&t&r5eV@t!wIvE-iH@3@|; zoDbww z?6sIQW8Vz8i+WFeor9x#HdyZontl77?Y%dCcN?YmXW)*5?AW^9C|NMSWJ&qGZ1X?`@TEat6qU6ca^V<3AE2GwA!uDTf2VF>zFol-dKd{P?Gb?>2c?MXtm{eZ({o0 zdGFm;c3<{ATCclKPO;fdzar*DJuA3;<(k{?xp%gLoKkgf?@vb-3Y^z&dn{PI_9oD` z>VeoWyA0RecW=x~w?Ull923&h)ni`X(bIS_ZykiqT^aMPyMT7CF(G8R)T&C%yE~Sy zDY>Kk_7%%w-YqL%*5@7H_K@}Mv9U!hpA%#e(j4bV%kw_$p?!yogUfqvzne1|?z#8t zi&xy|qSoe)_uajWLlx|YCP#x~6qwwU~P1+?99VcW?27ci(QTC|UDB`Mpca zF27eNRbJjt44bse*I4E3-NrpAv`(IgjU$A>C!I4XVncfBwcx%L_m;;7m9M$6qWtc= zRhHP->WKXYEV-|I#WLi9bSEqb3LK}o)H&poT|nZwzcvD9b@ylyFY{t*+gz2Q#l z(8HcC?sNxno6kwE_n8#bf%7&b-Fk&s@46*k@6gyoFnWDRy(}czENg%d?H1}27<)p7 zT{HXmjXCzA8TKoSu&e_#+;beA6AP`wG<2-sWcPf*E3F&O6}<8)=N!Q)Q`{T(q`@gy z>WvN~)DeTaxzf7Pv4<0!H{Car^v3>izTKp$**(AR^X+&!!1;Dp+41|F*HN-Woa*xK zqwcH>`a8E{uN1g!JAd4@Mn7i*+JOrDrhUu&D(mMyqqfOKCO47w`%BNebI&zadZ>m* zc8OuUqZIm_+p+X>&)IO((E>32R(#D)&wlFr|3&9){7Hr&zfz~3&D2|U`~B_q93yU3 z-0t6%+{vdX9ANPinr7Wy&cE(*@4z3;7y3x?CY0IphnjH~@8uBlw`-7o5&HK|=z5^TymS-ThM^`;r#r8hM32!#WF~k~wzEnV;xm|F%kN zuDZPr=k%SwV10_oI+gR!vnU}C&4s;>Z?wM7oztfCD>_SNv7Y5u);{Mt>&{wB|4;UM z>v2YK`pxW7PgAis5$6DaqColDX?g7Nru7wtDV`hWGVi#KHA0&wuqQfY!GBt;b!oGJ zRi5C3)k(qWh16mA*=yb|V}2fZPM^znS~H~Ys#sf8z;~_eIj6aUvsmX?!hY<}v$ykR z=li*QKZWn_=lfR^jaPBmq_?x4wT!a_OE}A5PPD67X_8+P^Pa7A!xO4&H3ztq^pr0C zCDzG>7A1cz?nd9kUw^xLh{|>UPaAoM?UMc~^EsQCTdFdvtcARXj?%vl?sK_kEw;56 za;0IM(76lq%s|qstPiUGB=IWEo1Vkol((~Ar#tuh4c6V1um}8ZC*LIYC8BKilyZ(q zfVs=1U%omzj&!lF(^XH^@9R9j%3N4XvGlKz?^K@Y9y|81#$?w<)?xjbGuW7WIfwbX zwMI3 zPvQMH7rwom_gfg^TuM1qzk9*SDR1()0~>>@eIIode~q;ryF8xAwhz0CE2;M();Qmu zWK2nB&iQ=uebbX1u2WgqH!46`ShID{V$N{9j5;r-ESHl067oGTJ9t8Ctjbw0m1hob zNjg)vDBI*5Na9^OE4W|fo=4m~F8Zof_9X7Z`?WV7`$juBteAau^vDvkX_z@Y+{l_G z=B8WwFsY1H?d%WE{9x7qPToB`$bMP&y3Hyp4PUs*GdygP*BH&6ClAdf_LEN8A7(`F zKR6>=PTF0hJqaAx`_0?yM#{dtKI+D~jIigZi`FlefpecZti?}VG$+{y{(Vke%1BEz z)lPaFmft@OM)8ze4n3<3gKfO5X0b={3UIaoya;DSe8+HDx(~S7Dm(ilJ8i2nx@jJy zZ!2B5eD_g@e`UXQFJ%ny?H0aWO#8TPv4U?Slqd4*U{eM2a1SITuO3UABr~tJA~hJj zi}hyDnW5-oXJ@~?#WZVn{i@)^QufKq8bm)O-bQGA8Sz@-8}?pgKFwEN)InM*2mCZ$ zdw;9FHW9a)GeB<#YW>k+)oZn`?^l;kH3FP-7d*k|ubHhwyNSEoUNZVbP zxqt2DYu{q-bETOY34z}+Yd>1c8rO+!^s$Kx#)r3)*X0RD(?q_Hjr%&`Y2>|&Gg7oq zWD5DR4?AnrLw`u6Jo~FWH6BlB(Fc2s|3iKLm^oBw)uee6!@JBtBna=Fw08U2r_4d& zt;~I{ycixwn)iJ<_~fqH#_FGK+?e^!#m4>v2W?q3+zeT?`p&sIZ~q(g)p_LMCr5V8 zd0X$Nez5MRMyhY&bkCshY1YnVZ~F4~>`evRr+dwsTgDnEHb6gJo)M8&_GRC?d}`-A zV~m;uo?(&adH)~Ksb~+pEgD3*kAcTyDaK&Y#TLq|b(ta7olIDJbZr)AQdaQIVB(Hh zaCvxs9Q(7Ise!lHN1nBn7261#m|zSp@T65}Z^z@L-9mbIksf`4^)sPAeCSOX_s`MF zx1TVRs((*^8{9Z+`V}dI!Y5ew`-GViIL7<5f41Kp=iL_4-@>~JzuL$bnFEd*;*7yb z6F83ndY;ev-p5Gqt+Kn;{*F2HzbhNR_P4C*-CjOw(Rk8Za3X8kZ=r$hrmtwh>sf0b zD_b#bL0i_e6>C@Qod2k?vm)L&(Z-rk_iQO!HviI+%{tHa^qpSlu-_D5mp)0Mi6%Q^TtTK-Mka;%P6BgAeeKWHE zd&rYa_I;jRnQ&tWp8CFLSc{LfNd7&+CiUTeqz^|~!mZi0?2{lE{*Lr>^n zzj=7})(;HrgO=X76ghAqwDF$9pJMkzV~_0O*%@&+YHzO)`EHnPog)#B&GRqK>k#Y3 z;|9oQ8+0>!kmp7}>sYOPpt~LP@%KIH-F5p*^V!6{hIV~yd7?EL+rVoTYx)fK-9txA zKXA^wT5BsR(Y?Z~VTG5}ln)86O=^8&(Nr@jPzyblqK}XhQ#64Sy>>#oqin+t}S}V<&4IpqX)Q zyH%s##qQyoP9NFEdP(#w*3RD6X~zq)_K7uy(0?}ihWH%u`rY%8IS9qf!ASo#duD7y zm&`J~72D8<_N`+ds5aJ>t|j{55&fm3$+=u`WzoBa8n~&U%}1lyzRtzh%y&zt21I@Y|Q7A1y&<`jD4dtZ~@{ z@3HXMAO1*(X)x*cLI?eop-p|2{xi_Nkss4<`}?jGz9boVX4Su-+xNeZbk`i;e!Jgl zeWJbAuIkJU=;+3dDCzz(z&a)N`WC%(I{S_vLe2}HS`&Sc_lM@Njs|?b=ipywiNyBp zvir^A&8#8Uo_!-Y>)}(6ucd=^<}+Sd(A4%*4{f0{e6&x!y~nimz53F^8qlCn7qjds}s?e5ApRzC)mUL0n9(Q@`ysK_yD#`FBo$l8_2mACxC z=?A{S9u)tuIrE<{dLt9LIr#FfuE_!T%yKiSC@<%YOyym`e%Kz??aw98Bb0lNIV`;4 z59^|B-!Y=q=pRQ|`~Dtu^h5S}+_-#QRBM1cZgkG_I+@($hrf1woBdP9+fla~{ct;LRChrOZwxYuA4@ZKRHHKu zqR!|oEnTKB(m1giT_dT*dPm*1q$Cxsg%&!%_Z85m^z0|-Cl;N8_ss{{j~RPRboo!v z>UW{l{_t;jgBFdJdcHl=4__^xZA6cLy$Anc%7o1d{sR{N$Aka7z<-1A|1}5yUo(r3 zT?PF+ZGgdGmFr{9;A_9prq6aW-KI~@^++Erx)Q&zZ7|hA_S=onRu{N?5FYy7B=)eU zy;EpQ<+qu3EO6Q}30%%I?LAo*mY1_9ORBXei=TEJPdoZ)$L-HzSJ95!HvnnJ?Tdl5 zspSU@* zf_uHYi~7Bl;xBp-npJzNeUoVOBWhp2F(j${u1?X~G4ONByr4z=EV9OkZiT-MW`Ei% zJjsEh-a$q4FM6|* zdOM$Qly~zD(c3tO-sU}P)7!iaHoeVTY}4DkSvI}R%khV;bRBxzceQEpjo!5OICc6R zw06`KttA(E!RJ!&ITu=6wRY9cJabTWIkYCeQFxu*wn+<(;tv;oKF?SMt%+wW_gKD% zKdenK(kBRS;7qnoO;{^DWk<=1TJH1DnxNX&FpqsdKMYv1nqARt@JkxGVrC;ey zi)6E&Ui{&b)P(S9_V-z*GhbD@8fH|kZWyfaQY|7 z!ReQ99Y``(d!dC{Ilrsr%&nrF3*W9iW)2S=dV+I%;7xhP1>p_ek%1gzc%b8nw1yjr zcgj1uXwAj%)Sfa&2i917lo+dznNMfl;{8r-&S+yu0eh;SV?Fk4^jACGkyg4VO{J3& zF7PV;cQV^N8^~)!;3WOx#*5yrWzN~wPd!G>saw|WT;myDy^?)^j+w){!#}y@KHd+j zzQdCcR)0Bp%bh!qnTdf9`2O09epCB_G1TfyhmE+fbc{EwKOY1sDS2hyRWb9J2PD!!WbVD8y!_Uf3@#@;3J z9Ufv`dHK!dol9?C+$kTbzS$eYL$*6@ZqmLvw4=^_cKfE@wK;A6MLb09EIhtM9$FXA zI^>MkGk%z}k^WVlB>Eij~@vh8#3mQ-1 zoH5Cp4&rZtkFU7c=Hr8)Y56)%AXi?wXn*ZX>sIZ&!ZWCP)i_(82=|gF)$}*!UGDfT zX%9wrO!TC*6u=knn)n#o!FL3lR8XGCBqRDU?Q$6W zBjeM3DRaBOl^k}zUw-8^4R?+CYQw78Uup29q=avUA5^C#g%`p%y{Uu3i+R2tS$_k( zp_FTZ8GNt^-cZJM9lF4Lc*An8ET6IZ`N2l<68xUCv-i~=@)||6FWgg$-y?7f=KwGM zGW&L*54B+*<-otzB_uXX9BfRP=t&Ep%LOK006+M9_PrvF)ufR+^qhPK64lR{I_N@xKAm%{NlH2 zU%X|-PV~m=F!-(j|1aQUIO+`)U3T%CwT-td-&u{m5dj~C$dWSXB=TK!V$&a%u2o|C zBJ2@SJe_m7syrjvcWHP;cKS2eZB^dPmQwb}3VAqFX_j$<6t2y`@b2`#a1fE@!rKHkrMr$TQrp=Pb2$>_73B z_pk*I!%Ir$ux~qd!DjEMuo3_I%uwQnnRTQsAEElCzIV?Ri~X*E{jm0WOnm+$!yQGl zn*NFX0ZWH6{sS%g*fXkZICZ`NThV0S8^$u0XWIAK$2b?6@s?!lIcOi5loY9+7|zt* z6*g@q72O4I=vWnKI1R5lNdG@Xd8LoGL67f$hw~h$^Ly0e1L`c>N%c^DWYfy8c>v!* zC}8uBJdKw(K8oc$Uo28Un^~nXUraqgF>9T9Wi9q@cgE7-(_k;%l zky#b!+2!TsJGJLyE;z(r)U-L#Ixi&MFoS19Y&lVR2{tUgEgyVs^H`$>ze!6q?Oh3t zy#Ze~9u1z!e$+T4TU}++UYw!@yPWpBM|dZG(p@k3#YA+W)$ofv!gA6#WU8IqzM6-O z$T>4RT!2sRS@KvsEf^hx|MQl#4YkSO;~+d`cCs0seHk=}oVX=@V{HN9imUcipBB9( zk+z`Q*A=ESWYjJZWaTkvq6~iL(wl4((cHlgjc%ITz`f$n0+xDyGtpEOV^g} zMD|vf!l#t~`|$2^aEx4Rlt0WZ%VX@1t2{j9I~%=0XCS&|(YqyIP!^@ZwL_ zVyY3Ij@;Uv!aj}g{6X;k50C+K;Q1G#AHD}Yf6MR&g7Ezb=!hR72WG?fC!!-BgT{Bk z|4-5O-sxuXjX7`De&UTs_xsn{EoOY+A#|zc*~VGX#;**XFHdZsK3my{ux8nGbISYB ziR2b*0*BP06MAOg=YnS(=8V)b=@Z!9d*+x^s!UIS@r0tHi&|>myk*Hw_P4C2PlYSs z`9AnUPuOZFEY0HiK68*2HjnRGkQaLJK?=^+oHhn!%XzPCJi4*#dEhwSavLrhB# zVUJhz5FdI-9(ssq>o_zi9prb=mh=#G(C|u&CR5Nue3l;a+gtA1`CBuk`bEZ$SJ1yX z9~V8uXXzm=w^ZyzKe1@C%uKRqQhcrof1gd8zDVBDuHt_JcO63m4QG(G?OAgh+P&Cb z$lSy9t5)_mlgy>xS$^mDp`TM+YGcvFIq1Zt5sf!V{;HijkpHY{N-v*-tie9*k$Lvm zqs{@9tdNhtnYk6RZCpA1kRzwnuidnjmy6fcpL9*O<@erh`K^9>AvE3_-_PR%{Q#O2 zjpfl#l;&5cZyoZs9l!PFe&p>79$VhNkYLN(7m{pw`$Fo#^0py?aX<9|(jT0Tehf55 z+xo$5@(GQw?+tv`<^Z}vO}c2=D9R)q$?bZNakh?g$-MLp$?XPss^oSf{cj+-EuFr% z+*X}00N;iqyRCd2*{v|i?^rw^X58u~;!B<{NtWKdx7Ig+EWZs|oBugv5A1N6ni(CQ(}&mP}c1fTs7dj5!Um1DpaoV&|d zZgJLEjaTLa-LPi#o_h2%jnzvRy4*+(f9MVHO`tJ=&c4DN)baxTt&Ti3f8%a%0)7HB zOdoCe9b@{t%t4VQvy5WxEj$Kz3_BW|!m5w({T}1Sr}6opqwFpYJ_hH>!-La~4l|~8 z@!hlJI~Ke$$LqwU(2vgTJVafzzac)K#tQPRU@U-jag2>8M=Bouer<(0s-==L9RlZ< zF}_~uO^%E?$=nuiAY5Q1M|S_&C_YNPKcxPykAX1~%Hp9c-k7q)HNL>Oc9^o%foI`B z`!9Z`^Ns1_0qpJwHi>I@f6D{@uS;#ZWfT72S-}(1FHT==MCW(K$}{%IJ!j7;sm9Kk zXZpgfY?Hm?%C;)gwCo*Mww3piZMk)}Y?B=8Prh{wAlpu}x2i4Q;=+<^u8ec>@5;AL zl<~hM-)e37wo~$L-2Zy{CjBVrWou)Q>!T0C_lgXkU4_8y%;Ad1%|^ zl80}_$V17(-gddqOb&dA%o~64Z_(dY>_irJ+vV7lmX3Dm#VxgOV3#w7=;&yEOCBCU z9?nIs7D)fXCjWPHw4;AzMz9Oezf#e^GFpNTjUK>W+J;Tu0sV_cyU>3`bFTiSXJ0kr z%-y=zaCEQHmhN>LnzeN=&V{5ONrt}a8Q$_P`%1Rcug`JC(xYg$Yqo8hHxNgBJyz$E zoZSrnVXc^>d)an=f9WW0*`qdXjz~0C*P}zax>p(Yc&zR<#|-8-<~*A8 zpWNd3u~NS`wH9rbv@(ey~pQW`S(ZX+d96@J$0`5KfAxR$L7lz zo7Why=Ca7wFw*q5eASCCYMLjO{LIkYxPw%{r`o=MO@sPU8}_WuN0pyjzIyr3rGu-^@_Vb_Z@Zm+W|_a61&`|hmw6L^h`)<5 zRWpIJt3%Q3oKUnpfX(#HpL{=Owb5{~--zCVjW!%!xIWPseC!vTvCY_^XDK{mX)t=c zLVmn7%SP+CQ}_NzHn=;2ei<3g8x}_byaEvij>82gT>3)X}s&OU1 ziSLCm!b)#^#keJ;^OFS)M_;QJaxbHz1!R zqpFcnCCJ7-)6ku61ux-X^pWh})xPib6g_g`uNn981n@hA-_tk~I_6UN$oq^h*m(lkFZ0I;XfY4 z90JR#rdbXj{bX*FJ;NQ?YA7iSY;FCUs zPW&}QnkG`k=UfZcvD3qP{XW2V2T-yF=Me;|8zdpu$F zn|At@+WnN3meN$(qpf~*hW_>bx4+Vmh5zFaa|b&3MzXjS|A*wgaH0NoAw22~`HTUF zzHcUee{@^0!Noy)o>Bad-ee00mT!mWn|O|`e;xHN>{b8G(tl&>o_nq89#iLU$2Xi` zXIs}`4v@xXyX&jEO3&!Ot~USZuHyvN(bhM5*YON^JHi}o7mup9@TPS8!<~4Li$C$A zegDbX{mxJ4SQjOS~zOjp&*Sz*i-4 z#&G{3?eq~e*@B(&0(QzS=vwVC270v5!mxFr{>x|A*D4$JQN2~Rj(56>58|Iw+Ly6! z{NrXy%Zp}e_yA?uLs?dFFWc_96vq3Y;Uz%Zw!<#Oc8gfHTe4-l711A8!28fG*zZ0G zo;|#=V=OcbZOc{)Kp*QL3wFw`{28zgyD6D|`k+H29acZ_N6JRIbTYg}-)EzTs;`Jv z_Kv}S!TN$~=85483%mX105mDO`EU^9PNMtkdisyTd{d0#_o15+-F;<5&zKS4F-I_l zZbr0>Vw~VyKz^YMEV-igmb?+(BH-`CL7aIeJ@q2djXj1GOE;g5>XAhq$ZT|0r;SEg zeX+ZZ$m6ps<}|otwn|@Vt8J>7tCPzbjL(x{v%od`)<%EKCHd7c6nsVv z*8y{IB%5*h1OA8q%pO$5%_kmy@+Ex5cgtUF4rY$UVDwF+rIhiMdd5@mgTEqM9=$fw zhAzDh9`OWuE!f0(0eKzw4r*+UuiU;nW%rBqY%k|#+K@o7xHy0c`fj! zg;$#RpUpwx8@z+?JEU28Wq->!b1cWss}9*b>jUr7VwSM*GSLa zonuTngx~Ej<74jyg3%w}ysY#2`D4Rs59wd(({5XI%g5hzO+JJiT#76`>@|y)UieF7 zsTp_(J-a`-x%AhpKR^fDhCH%#26*vtOJ_)n7`$skAMluIMP7Uvl}1|RddAIs)XCNv z(ux+)Hb>Ei9zq{_?|IudY3W3i@35m6wbfmNZIOT;WZM@5=|sPW4(8428G{QV%Sy}) zi*GC?O!=(;5A-f*rjPCgElKw}<>+1?AY(qG??&5*934pvMr8|SgQEfmNAD4)I%ur=Z&`;))It8vR{WhMG5*dD$KQDb zd0Pp;dxQRK^ctt^&)?Z!z4NIv~ze*Vk6|`z{Y!8rp6$|0#G7pS^!D1Dv zH4kX%nQOvYccF3HL*EWYmobL>t+Y+nT%f~@BRrDq4L@=j<426~ek=WvTCIU8Rorke zYRv~i|2_n5S^7VGPPW`O?=5}SKfHdk=I4)WWG;P8?{lwD-#Lpj;(l$d-O>D^`|tHB=qbOY(jMX z;-lZ;Y&rUr^y_MLfD&|&efWkN(E;j5Fz1JP-6iPSRo^;ST!c{>P_g_mcJQz96f_E;>PTR*Jm2&l3AWX7BAXL9#4U1&7<~xDZ$bsrBnYS zX-kKeZhh3!t&=0tr#&_EPZwS+J)y5YU5F3T)eEFM`>9W?KDmwYk^c0@&){`)sbhaS zWIKFLJVp3A$Ngd2&&8p1P)NkkfWO+<7p=TmH?H zfzR#p>+yUpx{5c$()B}$J#%8zKh+O6W1D;(zV7Oq`0mn6&}*5efv%c@E|A=^5WRMn znHt%SUb`K=b{Dj~WoHcLgwu2%YCoViamUp|Pqd*s$>-hp{+4F8}dqsH;gmX(j zdcUh!IuHAR4Hn(M-obi5^sHmh*l}oVA^L1H;}##Gr(`iVRQl{-)=zbz&v+P9$?~LH z>zSFW%=kcB<4fq(Pgwr{R7;=q>|sa8 zRXdNOogbt<)y@adai!OK_Rl@dJRj&a)v|v=jBQJ&RXel)E;?;$ADvch+yQThrQN;% zvv-@@^PbMo=Aw_@wELgd+^5kN?ViK7>qEB}h;IApufn}(z2JcP${zIBe}`AuZGOJ~ zYWIip)73Z6{kz>Z?URtp=oW4pJ^OA~@g=lT7P{pUwFm9aHn!=553hT&4c*YQ(Wp6u zE*gxh+&(*b_lwza1>46YZ+vm7ql;!4=%QNV;d}VcA?Qamhd&`)58au^AAa&EV^S#| zllj?$(G#VYnk~k+f@fM|^iuMUJj+<^6VUO?=%ouc88uIk_X%jA6}|KzdTBFy>2CB= zzPEJXUFeske6!2ZOJ6}RUFaPQJrBl~OA80lQOUC}4N zp-e}Ke+TzZ@XbQLS6SK51enc4oZAqyi2qoeOL1qT>qK+Rrz~e#owg82hx`s2vh#+ zoie+=x=-=XS!Jfp-7-sOl&<)N`9d?Bz*7NfOW(?cpZqQLtxLd}biDAa*l~G#F4|AX z)6)O0=&S!_(mpDW){ypBp738ePi;KqarHmxPwaJPjd%CxG6U;RS~DnLjIF2IJ^u5^)$>C5kGoj2?dn{c$GY?BGX5$()#4k_UMxSd?4t~(B>kcp`9!z%ZUu=$$V{5aAYWOX9hihv$_p-I?u(iJ^-Dl&v zii+;E|3&FO>+LGermVlG&a&k{`m`%!JZY{V z%~altrxak%e}#F^5$yR7u;)+0*VZgCYaY7g&d!QkDmvXfde4(P{Ofa*i^AwMRL@f8U81o-HbCXvfCw!$k zYt@WgGa`7}Kxgu;m=X0a)47G5k7%%OOWBO5xs0oHMzq;m@=om8>D~Jx^-7~`C40oM zMp9|Wjw@R}BYN*0TuWxKC(2OPNvAhzKa>P6^@%F<+cLt=uAjwu;=3)|u(RYf_G>bE z_HZt_mvhODng!XxhM~}1u3@xPGIr;uygy~_v(nW0<{w`-n{Q^Z7lgex#BzMQqC>3} zRNOV#s{_UT7=7gwylt3~5-ucvk{rSVgBi{yc5rT&f&S!p-1gCv5z)9%Ge+BErK7rID+3f zoiO=vj5zE|!|oT@-swjP`;fTR=<*Z}|G6FZ4Z=PmteI~@_9QI>XHQ?trg8nOmy)*l?R?}yio4cKP{!6;-~ZRU#7rM)8Kvcu#v@2gVHg3@zcfZPbD4aCGwIC!p>>A0lwLWZIg{( zcMN`ABcTBQ?#35$99>#|{UCf>I&BdAzD#}<=%vzUHScmi?J#~3ESA2st+8psYJKL!7b1ly{ z_AB3V=6gnH4}A*`B0kE&+}jqtJIXti=UK`#p7OZwc2TY*tN+?@WJi@kZx^&$R)ApLYU{j?OlXbF1Jc1JHNFq0$drv*+w9Z$cR2d@io zr7~~wI<76`-|F<4Esow(V5US~XCDOh)5pn6V=u=k+x13Tn00o2#$R09vySqrpVm43 z^ad0d*8T0wsmKk41Bl1u%`iwa9# z1pZ=qQH{LVhP+VQe387+IL0Yx_G4(H&}2P7@}h)(q_)Ze?uDK*G`Ew!)&f6BC0%Rm zp8KO4js1@iXAgcUjoohnegwT9gP*rTFU`=)0oqG6G!I-MW5SHDwuHHt&b5^IU!pxK zkx4DwC)39jM`N88F*4~8WtmUe>d31Bnv#4fAC4aQnR%18Mv&UD4kgmdd=edWp z7Lvc(RW^kDs2hPFL31ki5z=|X@il22O>+RM9Nu}vGtA+Z!&#) z0eO9qy;F~GN%A)v{7U{N!#5Vt&(v2Wg9nnoIrxU2Ca>!#)8kz7EiK@iWWLe(r;(bI zWemA^R+29v&SS*>!$J9(Tplju8QY!O=Pk4T$-oC`oy+M5B5N5ww9lz!?Z0*w)wy8S zo}$niP67KI;}JlP@-niya=9jO1-U|8-5DtpA6FVzHdl}<#AQVg?D_S7cCTrweEgJ> zlCfYmMOR(;#j7rYRshh6AFn#;z3L>WdI_>VPQL`zi*m%PXo9MjAbLGNPrT{|rsA_L z7#FYlWg7|cLF+j&K4d|pt5uMs1iNI(Z2MqU)hUh<=ay4=&nADjoX8sM*)6Bgq;R*K zY6E@=YJ=>Ua;gpV?3PpQklkHQZ@k)q&*Fmor#Ud&aLemYun52iB&c1elT{pwY?U*c z{M~X2uX=XNDZJ|0EvN9RXO&aX#j9vV<#Wp^yk>(>yPP&&RbG`d+pzIUNlD)14J%th z03$WQq7@@0fk@U*p}JHa;RJqakM^HA|zZNm`*xwxs&3BOTHjna5PXE zjuPa9OhICd6eOb&f`2t%VSgZkN$t^taQ)<;;#Lrzl0wZaE!Zl@QzaiISkU z6jVD3stpB^;TFV(uwae_%!IKHyu^Z@gi9^xO_*fCxP&z32yRVrsT<#zFE^L`^ibjL5j=KV&3=uOX}H9@=Y8420eV;b#9#_%X_NH0gYj)N9pF1zeR}UAl;%-lU$TUM3cyZYvf* zx8gFnyj+P~gSfJ|3b-n{x^$UbUamy0L0nl}1zi6(YwrRV)pafYpEH9pJOx3;i4B8~ z2>3)%G0Bt>NTPzq2WhOS1$-pH@DRnsx8oy;IwV50t=HstA;z>qq&2tL4!5~KG1^qc zSgkg-(ce#eBtg-tjY-6q@c*uJ&ftio?fu<9AIEj}d+oi~UVH7m*It_(biPT~YS;@( zzCS_L`xE@+Tl!bp%xEvZcrQx(U!OhnPyK(xU%|e%)Xk{z-55KhQ}|m40^J<8$+3NqYDlJ?H!KA3jA7&-{+`xziM7pu8suWLyui817@_3teU$(V=|@knx{Ur9*oZ zP}1F^10>x+ptzf<4|F@^M?l&$V}=gxH^=Ml>pHa0H{1n=doWPSi`AiB0ZMsyp3tDZ z94Psf>Co;1GR}tt8~zr<|K#I}(jWg_K#9LohxSavJ;iV-2KyZ zd~78zwpcv;VtvIP1}e_41N*Xb%HQ`^kIJK>KppOcZ=| zXrBU{y%J45U{82*H@|mJTdm2#UM;iWNhW~Xu#oxw%Qf?m|+P6>% zahL1RUQXwTyG)1nK88EWaHm0}(jN{T+U0#`saK>9?REY1^t*LvpJBM=@+kS*#{-3K zN@MhR^L1#S3l#hk4gcYWf284mFA066v;9x$@sM4YR3=Y9_s{y%pEppqCgTa~%H^c>`3ksI&aMlbU zO+IA2;h$#shZ*i*!+o8}NYY)=;VgMKSlsIk_i|vAhuDnpK1TSlKt28u9nRWq zxOW-u^*~U~Sf@j~Y&uK+Q;hJ200aW^-3u~mom89+%l z9>~9pG#%O>1zKCm1C^v6A1hxY3PO1d+^F8H6)q5T-p5BCur+7ANFxDV*iE}NLr z{tlq@R}`=>FboLE4&lDJVgsHc6G(E%6ksgy7MTd#4U}~9zNWyPOkXPb@6e&$frEct zZ3YyFxSRCwt_=pPGN8+V*#>kN&}Kk@mY%n4)txGD%!Z!{=at&5Dw20^d#vx_XWrFgcaJ*Yksil- zoCLnnv#RHgo_^-_y~=u(_wqAe>2bTnd9^paJe`+< zCvBNTZj-Af^F3wD6!M&MlK-oo+VK=PJavWt8=gM!w7==fjO)DC+c4t*{~wrna3((4 z*R%aibtRq>-VrV-liw6gm0+!3LS0W`wCr$m+5HSx$~aQ;k~;wBNor| zb@1Vxz79UVjpzFcU57T;o@g7PWQ~7Ha+qVhC73uPD|gYePZkv{(cZ4inq8Q8Uz~&) z30d0vb1yg-&hIn^xhz`9o0dA-okmIMBuW8~x{b;jp<$Mw=q{lCaGVy#3vfX1+i>y_ z9Phl17;d}!_s`yWOGrvc(&J|b&XFGa7v6QtWh^Q-e*E?3cglC*X})*=YZ|0~ua`9Z z9W>m?Kq3cuH&UVTQr=XQ82VpUR_0?f4Bqbb6Ij(>cS(Mdwo8%$}XAJ_+%UR?hpe4IV%)90MK8g0+h4n_>vW`SvZ?hE-J;MyQ2b4{LK9P zdaSDL`aSd#*ELOkbKSYg@2@*Q`NFyjlbxK~#&9lb!{5O#o1fyj@nZ|Np+z^NiL!!% z1G(!T*qHyKPc~C9qMR;4zgX2BpmQEzCgtwX1 z{lAb%j@dlj>LTIrfz--+uO|off5aXMaci1K^lzR_d$6 z9C4N0+3(-mv9xdaxZ7*WCoH|h`E4}s;-TM>>8;-!>Hyy5{3>$L5$|j0(Ma9!+wyQ1 z*J};nyP5aSzC`yT`HuO9=$S}9>D=S+<|FYfHr~8KH{IJ#l3x4;{wKO-za$^YZx>L~ z2Vt+)o8A`>wafi@+?IDbNGCQ?Z=yT!1-b)Yan|1S7VREZ^x!$%=S3$TeKslYHevF1 zODy-OMQ1*b_Z<{<&cE+7-AepR+r+(tY;1u@nQh$vXadJ}_tUt!OV{q^-ewben(gjKaC7HPOG`Vo zWhwRj=xgb7##^M&4*Lj^ZQNti@Pc-vy}Wq&(oRk%wDbRho5QHfZGX{ww4x(9m2o$P z@%JR-a5Cd@5_j=3s@vB3Rp|V~#5gtK-}!r5UouX;bSv*+UeI`DWPD}c`+24CO!kc< zg;(z6evI%);g#)t5_gy0t7`qny}!$sPx`%dv7J|rgMa4mTg@*9UK#7dD}_(iga*3P z;gz{QymGZ^&Vw<8nNB@e2MDdCav<0`GZ&6O_SN#nLyZ^QfU@Ov9Qm{Zgtx}Q?g zm%a!9XDdJ1I`e*y#h!Z~{FrVc0~W4$t%kbjd_UkGzMlr)fBe6}_pQ_=S&h>8ejs<_ zszU?ZgQ&|=?te`*`2HpApN+lKmY58F!e3=B5?q)EBjds49nv__4-tGsPteF}7YIkKSRtuSi?0!j5Ty< zSFK+Btf^XT7Gb2n;)Ms5)>iH|dwB-!QIl=G%R_|6W@D##J@*6^c(CM`jqWJ>Y;{%q z{*B+NzDn!!(|DgzQCc&3?@7j7oXpekZfu)tGK6JD+%4pY@MJa8IRROsV_gtx%C~^; zS{-MNUytKmIxoL=8FhI9d)j~TSL(8{r7gD3w#(fg%A?=qE!N2Pe5J2xqt5cp=_KF9 zV>B9dO_Mrb?us4YE=ocZbU*Q%>=W!U?P>no z^4BiGQvzvE2lrtEkq=kQ>*jH-$XSymE-HdMR@K{xC^k#9M#f5--yng+yfB3 zy(UxgN8=-u3hdogG<@5Zm`i(aFj*SocwZHsH;wmf_IyS^+!A}x9l{>w?uiq6D8Np< z*hwr>c~c2`5xk1vU0b?yuh$gW(sY}zsLb%~9@YMl0P~jwY0E8#KbbEwrso!%Nl$xzMT5 zE^lx4YTGaKXcy+uuFSLk%(oWi-2mj>!0K<-nkrhEM=wMu+&2{YPpz>eftP}P$AEyK ziOAh?L5Hk^R+y$Ihbtv{11haS0lM6sk+9xs2{>Y1r3N}qV#;`D#IL%kpSjw? zJ(mr^u4&N~@@~=w>je07bR2eyS8uR3_re|(eW>wbLr(M3g7kUwFH);J@>VY`{53}J z>mJlGarCBXH$V9Gf>wAXv=J%&)!fU`8f~_;tg$JXJ9!u7ax^kpz~OD9-VA)bHCm}_ z?&oNIU`3AymYOX=&D>RsC~?F!Q0Boh*xm~Ws>CW9ti)BaK3Tt-wpqKu+Opbh zCEu28Wb`RZmU)f`B0sN;eBcIe2`K%KTaQn&xf8H?p4?SQa4|1%7x%=zyc|!m66UOe zM)!4h)Q9tqsSVLs%J9Zz1)FIi?)!POJz2z+cW4EV2I8=2t#}t+j6JrB;qdM--V&Y9 zyEDq2X&Tq9CszA{IJ;5Kpn!V z1LdXL9%``eqYg3HSxe?U+=j2(?!imy@IG}=sDlH$^83Kg8`tq=Tc#Nt_UFyqkv;HILe#6Y+*!-pM<9nudiYX+>d)ManQ3*$fhs)X*7Th3&JH%ilS~j5Mpw# zGvYYtgWu!+IpgLJ%OfB7jQ)H=@lXFuQ3KH&bEZ-bicWEoPZR5{&hk)+6G0uN-MK%K zF7txedAfpL@hUaIDg8Y8hfdGmcw>(HKF$Mij-_R)+j^Eee6?NN)*{-e1s(WgHPk8f z;*BZp-Gn&zp_47&me1M}o6W)Tm!bQK*l%qy$HvF0K|#n?LCM^c@4Ncnwq+^Yg{2+G znoW&j2g$~rzDv+LvJke>EN&SazGL?t+E)C$yg#;!$S0P@!0&IEHjy_7<&Mfh;>3#F z1^>wBTX=PE>X!b)JWt!wK^~C@_ENvq@V`sg#c96XHY$X0Z&`|}@Z_t$caY6^yM!DQ ztM+c(2!FAx2z=cVaA;ez&wMKBB%VMi>s#(7`{FF}fNy?!7`)><@(`Kmn`IN>ncO3P zdOSSz33%z_tW_SXhPUc#mHjF_R>j{$xS4o<#P0%+?Fx@oE5uIoZB0kNi}2cNc?b9w zyjJ8Y;n%hx$2Z>ff!k5JZI#-k@M1oc%kU6K0AsIB%UvNA9ykGT+un8$lz~6FMfn)_qkYP zCfw2ILXnAZTc7hxVTydO`#I`m?e`UPt<1L)Pxck$ZQ4hWA^Y&dUFX{#&Hm8TxJqREo-M4sGe44Y z;nnh1=_S_hW1-8N$QZGF&tXpY1BwldbMR%E&p$Et1!V8S!dQ+}BHdB_l!~$ZKI*F6 z7)RYrw5jCbee=e<$FR(>#VL5|Z@fNEzdlC)rqR!-^mht$Fb+CUH9ELST{`CRz_)FC zZU{Z|u2S?Jr8+uT=0DeKmRO}#)(4!()GA>vyR3O#lxOjC@CLmmPezJgQwKkVyB!Xn zaBmv+Pbw^aigquuv->CTXx^VzYMwgdsQ!UMn^F#Gj!PO#EHWuF()+Zp+@s9qj#(9D zs^bmoI@$N|+BsdsJ=WE09JOmq4ffexS@U#v*W{Fdjo0kLHTcHZw*UkDolL)Qe5^uNaSCQ*WWs z&yfv1*ad3BO@103%X=P1TlY$A9T}=H#s--R8=@`$BS`PUx_Y z_I~a~Xt9;w?S69C*IM)Glgx+Y`z84b%;xPOZ6E4olfDlX%{~-+Q0Ity9zJwob4v9m z$TVWR^)q8XO5XBa!`^}A0Y^RhA&Km@wVv(jcVjJUqT8fdk1d)n-%_iUzvirr4Ql?A zsy?Uh*~)%IY|ewt++9_(e4`ANp6yvd>OcQrE)nABeF`(5#?vS0h5%KM+s z$-bAweW1N=nk~l;b(1|sshi9EkLuRbr*4tdO>ax-52>GtImu<7^Y2Pb_JbSw7Q3Ba z-ClL#OXh+$Q_q$;$kU5vV#|ZNf}@nbYNb;B0%7p`NNMlR!k;Jnn`M)s zl}XS{2DFn74NYW>PpH1sZ+s!NrHykbOU8iC6aI00%I*}G<)r@vzEJvOrCPoPpR2gr z)^|1CZ6@fwB1Y?6KbJ>CfBRw0j=BQ`&zpP-OlK@QS^rAm>}abzPK1E|h9}*mm)zh@u4`{om=(|k%ZhB$MplOA)t=50O_&KpB=#>Y2%js;-{A=+0ls=OC*-flt zs)DH__JS`*1jL?P7K9ly7J6=Lt%_DjL zi!t2{k7{Q98ORyZNc^L_-NmKj-nbVBX-~nu=`G=noKwIPg|{l>7!S0$@Qm#PmCTVQ zWwe}C$T@`)mB;>T{)3j^>E{!Ymz+yrm!?&C+;_E?v`sB{?VHCq>Sb>DxQjB1qrS+6 zrd~m-Og)^r*q)NSm|NTTbOZFo2=AN=WL6&I^&bo-Yw10 zRhBuzxf7_-0&)s-M{CnC+6S3LB^~=&6<&Ig_HJQ)iT!$;KU{td&NPlMXTt8&*|^m?6>>&HTs_S@V0bh z-G7FgCi@w5@c&nT9>hk1w?7+rQ(5{{`b=@@eMVnseP+4HUa(8=GsU&`(TH4goF!8E zrjJdjj~;%v&n%Dr&-<*AIrx9lXB+5q_9vJt@Alas`tcw2*?9H?q|aLDvtQ6>LSN17 zfBsL-r`XGxHZzFv(``armf7MIUXzFY>EE)B1eZveqvnZ?E#|0>w7JjcyjRAU;4V1r zG~{*dn>CK))8uTIKABg#lc&%@XL!B&9e9m5@VW;V8JFNam^!wl z%2_$@X+9Usd>X>M8p{0I4H<;c24yR8MOaf;tK&QJYnEjIdKy3!hDfxmK| zA$f_s)y$r~f~^K|iyY0_!#_Cl4b{(lmnrqVwKHG+EK}B$+!1KaX768w;@r23R^PAYq*S-EZ_zmo@8yvq7Y(}WMqbd`T$>YC`}CEZ{Yn4&rfa+_U)_^V&S_;2(l@RXS=n&k z6L*}?nXc@S^y1fB)}%TQXTH>FpvhPRJkOr?m(cA&psX1Wa?Wuu-qD)OUTa_Wm)Mij z){E?;vNntISug7C*&y;Q>Hor-QMWIm`=4U%_i>S*);|qv);zjz&Duw$j}CsyKC*F! z{uB1wLj)|;A z55}`@0v~T)fBvRz&oy|8w@m`3vL{~hz3J=;zm^|!cw2G|`{C-wk1e!SAp3rSdHJ{E zd3#|H-xl74MyFvT`YA2VKdZD_Vk&2f%|v9n?s20w%oJJMhKw!iR$m)NpCd!d*k~_P z_Tl*jQAV~J;Lc&s#{?cShAK@FE#TeVGHN|}D5IN zJ6_%JaRB30@{9*xY^Bu4fomXmV;`v1y7oYK7FqMQXwYzy<2&a(|zWXoG|opMMWwd9 zQrp7Z;zb2>ZL{auzHhfB437a1iYm-mVk?-JqZN>2TexU8#ra?rV|#j@ZGk917S1ao z(t@14B2?aN#YF}x3vD^2lEK1K@?1RFmP4%h3+6qGqTTF*CHM_rtRpTM7eA|g-$gOT z7O13(j-icWa|-97pSRGKS2U-10cDnY!wMIXdv4D2^X`^aTr`h(^Qqahc?I(V70FW9 zjZMv&pED<~bcsz%MQz*h8?Y!)c^q9?y)| zuq04PS(H~WS1NlKU%{vdt-nQcO3CG}za%sIBv6^)#own=*pFGTMDn78=jJ_2^UN!v zOXig>l%#hnPfhIi;z@g#=H(Pf^UWzP5|zk6#n~ZFdtH1p3K_6yzyO>6r-Td`Gj<`I zBD1(Or@*!_r*MA3yoEZ-_t0A`_)SWQ8{AHyTCIqp`BMumebJ)A*)*DTrSdar@q%tTMY4|_8Kv&Vun@7r&1@m&8l1l3*x;L+g zQ48Yp<`>V&)jGPJMmoX7z~DYIyc>1lyaGmr57`9@kMaJ!p?|Pld+(p&ujJwlKSE@` z_fInbf!7q>`@Zz(D`yp2@9Q1Y;zB618mwUdQg&$!9 zLjL;e;6D7W8{rzm?c|=XpAlgDj`aPF@b5~WWCR=_JypEF{zkY_&HM1PAo$DQci}(S z2xnfCzx&cB8sQDnKI;AD-xSH4zcMml|KmRVTsAFO@%xVcTh~ty&(^_x<~=fehe`{yMbx0ZP76I-C^@G!bsmq5V#bUe0wO zO_gy)hxUs=N!O&qSto&#?wAhkb-=3B6kKwlHZo3~ajr6zJ?V@T!nt+nuDWH^l3@G>?0GcVcL5KDo zz$E3Kq+UJ4(;=Sk`H@(TD|82>FSJZ9onY= zrG172C0;C0+NVEI+Q+6tyBLuYyah`BcTgr5w?L`KEuhrnI#BXy1`6J1fP(ia9oi29 zCH(;%+8clp-vg9#>wtpaZXMcPM)+bQycDPsK3|9SL||9aMFJ(CV4&n<0YX|Cemb-_ zP#GCVd-y~DDlSwC4L$ZQp#}X&>jm+Wjy!S;Vc_a z>eoky_J-aVa3ozBQ1V#^ly+GR>>}mp&|UzPd~$U->qkJzXNC^#{ehCc4^YyF0U?Eq zU>(~1fRbJ=^Z;jFiPGa=1Pb0wKtJN2(V_haP~sod;jH(7Ql3YL_LIz0LWf5Tc))<{ zlj`H$fb7R=ZifNyu!+y%`jCUbM}ZB%M}RJ%w7<;==bdLQ{31}ocLOE73@G&IGT?Ba z;IWH&Tf(;hCAD+LOj%-5kk7btWxSBLf=0i``>=x|mBP}+064(*QuB|o{JFZm4z zLLwQ1b!ZO;N_vY9XZZnTd0rtTOFZKd}c2U5V^ua($j~;+@WyT#k7ihl;l=Nap066O+ zP|`Q)(7qKofpqH(SZcuWK*`qul>U1ZNEc@$>ChevlzjW^a8?vh@|8_B!7CUj>HUCO z{{zvX%edpOK|5zjTKbDRoOKE)=}+p=z8fg%cLF8-4j|{98C5#8mjR{x^*Wrj94P4* z>(D+Q2+3s>=+MqSs22Z6I-E5HDDg9NXg>z?dV+Eq47l5XRR-Jw6nwDxskO5U2uWlt z*P*=>DEQ3R;jFnp$=?B#d=m}W-+++@^fMrOFI{-<;} z>j+Tte-9}9tsF=b519)TJbwfn51ay|iWwO?w5I`6$;Y9?S&2Z&XE-o}_)$QihbBBF zpHo1==Oj?(#bY|OHvm(~=RF2eWDI$ zMFAy#7*NU!2GTSc79HAeQMkmvsl!>kQJ}>?2$cNa1A=;nM~C*^K#9LghqKlL$tq)= z4((DIDesD+mv<3}S4NW#?Z<$U{)i4|RgswRt}X*+8_;1un*kLAHW69kHyE(WfGz`O z8_;1un*kLAHc>90T^kHoWk8n!vkmAlpv`~)EgiNe)QhUcbVNOdO$CqnJ+sBM%e>o+ zeFncWKa1(6`4-;|eh2uj^Yi#&$Dzx~E*8^!T^hPzi=peWt`<{6*8^R#58;2s-(ouD zf5smh4i;B{#dO_r(_%6m2|X5yHHXmap%&Ab(56t6=~n0+e5$(b=w>mk?^f2$WIEaH z6#s7t+ZsmNZmuwk>0-C$Zr~I49^X5{c7~ZuP2m@@?r|ra{aMrX@SEW#!^X$S?x*lM z*!@U%lj*&P25fwsiD-(TMiIv%Os0m&1K9bf>*48v9T4n%U@xOb75_K)xY7fBdNlF9 zrRUb3*rDiI-qU0{)%y$;=Y2m~J{O=mIZWN)3xAF8;s%$jwJ+!$&VZiVYF_ z8+J;xAF!KD>%U+2ec~kV!v08u;vNSAur0q=e zGjC0MBdsdUWU5Q^;CC$TBz_0d4yGLeUQfH3c8joePpp4}etzQ46P#!1_9ukk_?r!} ziv5V){dKop+zepvJ>|<1pWnd&diV~*9rBRw{)6GZXoSCvx7erf?kD}&2+uL%BO7Vq z-una_-eyF%5WbOH)FHPz^}D~+iJvLG*HK9j5odE{xfL>|MP}hrEcO5iq+%) z+KB(O;r_^Qk2Ku=hI^Nh-Yof1Kkt4DXR(@F_ATU3__^f2)JWgM2>0%vd~LYB`z?h= z{0bxfHp6W*%F8s|iH7?H!#%`s_cz@CX0%th;r^B3J}c$n_ZP$6WQ2chxMQWi`MGQw z`pSP9;i{2;kkMZM$A~|~2yZs6EZxbGk|%HL0o_}=}y z#YVVyzi%ILB)xb4PUZ`7d;i2%in!k}JR12HKkt6tFAcYM|8+m<@oN8D*}?7Z;Fh?) z@$+!_x_{Ln{)rCpCwFjP?cmPp;BM>SzTTm{K^@8)(ZM~bgS)(g`}q#;4?DQO?oi%{ z4*1k}a2IuOW0NFpLTYB#vw20@aZ6hJ(VUW%`s5EDo0g>+AMy3}+I}&DIfcMb2**Rk z*n^olJu@w94o3o|^HNhw7aJZN7#Plyg~ba>+jEwr3rjg|@cPUtU5xdXymt3&iE*FD z3poStBdNY2I(j}n8h*N4&+Ubr1w~I5O)un#DYbaP{6*NO z(E~~sESb`NW{0tgN%J_boclyU@oY@Tcq7U2<)YGgk7)-WN3Dj3`sik?xNqW?<&d zTd-ihZp5b$XtW?^J?4~V&AIF2^SN+Qp^`O+vqMbjJo{|HqJ_EMWLg(NJ$d5w?0igv zls@YXn+vI;?n((d0uD#(KZTdUF)pC3+>3yk68Y$r~@B{j2>WVT?Dh{bR&v zuNOvD^zI$n{xM>d*DIm@iw}&|Bzglzw0|YEfAK+-k?m31zY^NN_#g^qa5RD%I->n6 zq5Z4fMvf5$D>?dC!rd>g;hfYdy1^VHDkgM{sKa3q|84s@BSt4ACfVdKJ8*8^^Bf(@ zNWW`D=a09qe#`!X#){=XrZh#?LW+$af9#bgvUd5dzu`a9Lu5TAe_ppg-|}P1)c*ce z`#Dpl>+vN&$ujrHit?g(`6(^|-oJllKgXMo%}B8Rsk^v{7i;-@{=_~FXX)*J9qs4L zH}H~0uKevJKcmXJM~DCanZ29&&*%vnjC4C^Y3oO&e73fJ72kWZm-&OOORY^c>`m;3 z@Hdp7d^M162K^+7Bwo~9ZJ$UPKM$D~H``U-GgaA7(tZ+G+ClpKzx#Llx!<9mqoSu{ zLSKh>wwC#!XQo!5`_tVPF85c|nsZ@Fo!r;h9(HTY816QTz6=UD=)Z6;pey$T(MKo# zG~OSpU(WW|E$$L5)%Ay_tv5NwZs_V5i){dB^T0~$r7@M(RWsu}am3G?8S9xZ+hs0Y zPgZozTuRk6>Y(YbC4s|Ff|a`G`H4=^IrNj{-ixj`rt2E9cV1G6&Q)%A<@jNA(-7w( zx&MCrP#2}{5;hUg`3jGp6*ZlAvEmy)e#hes407)Z3~-lqan%P01Z!`mtVc&U1br;t z)alM1NhEJ)#DDm4t*0nTN-3LKi%o`VT@U zb<@O>q&Lxo2%}!yEp}#;C*vlP&7G;PiP=fq8+H!=Y57bEbCJj2?zD}mRU;Z}x#N6P z4WzuGp7ZEFt)I9wX~V=NNy*$}oiOlcGi#Ra^yCKyx+hRp8)b>zo4mtg-09)mwWU4i zbKYwi8lUl);>p7Pg51}d41Pz@cN1ONS0)xDRe{$Z1C*(oCKe~PaX&Ey_Z4$!dNkin zd^^Fz2_El!9P8ORvz9ip@aA$zO#yjMq#Vh24me96d-cHX>U>3U2OtgFNri?|prvu> zdi{Q<=rY|Y{!hMD{7i(Ii34pdU7$gcvI7z1xdVz<9v(pAmtR2X3~o1e|?yA6UpZ=>0TlK z%iJZb;GkXTdGBpC-xyEczl7$mQJ%ah9c~JZPr%kh zLw*U{)`3BX^AkKb)R6e&GfO;irhp(`%LuK7R+G`+ zD+9OKXX-rTxrZ1)8VhMGq=_ZXq5Pem%aq$f{?Bv?N+;d%L;10ur-IOZr~IAo$GV&U z;#YlhX`Cl_m1A@BHEeNoBaAyC$Ke}IwAGi~Bl(Iu`Zv-4l=e^P%H0q6%ZHD0x8%oB z)6Wy{hEu6OH%Fu$;Dl z=UhD;%iDYbHLpJA@SMdS$5wU2wpDH2N}8TjTF-A*>aV0Kt$%YX^##yTjT+TB;hU7r za=#v4(DEVUbUb4;T1_hn;{J?K2GTc(oM$~`r7SGiB~@qet}cJhw@=$_yz z2JG@ISEbP z#YN_TK3cpe^8Wgn)atL$Vg39GM}1e?Bx+go^!22zgg=OlibK4cUfSkA`T}|HIh@q! zo6kk^`2>7Fr5;kQ+#QG>=O}p}I*$Hc)B ziSMmU#b!hIz_`O!=4Q=KM*d&(J%d)L)1BWVET++#oTHSyL;nSY9L6pKV^!v)6>5a9 zI}SJJPLY8tqNc}lZ!d1elzYn@rH|tKwAgU&ozhQ zc*E-fXYL7XOF(n)9N)O@;E9dfuJ#MAevj|Z@%N>_!B@mSf-fEEeRmW4S>OI{V)Yk; zum=Q9MfBz_WqUlizp3jiOB}J&BXfHt`p*sWuHn8)&t-EQ_g$2diSKUM<~D~m7C?`c z!qd5%86N~+8Rf{k%>Dddnu6TBO(D$nAeuTd3OaC7RW~qHzQqWyz|7R3D7W)H(;?t2Mgttk%RGLFu@{pa}=D@}x z+U_iE_YS-zSnbnT=50&dn?i>qo)WxPH8N^-5Tjvsb@YF8^2F*eUFW`OF z5Z=b?QzNwXA?<&i-!WvWtNierDmZ3PKfygq4b|@WThyS&JG8UZrMkIobijvMo=a_Q zV`#%(&2H{KGH+ZT=xDv+L?`@7N9(yc+;!aSsNdJvmU)mdaUqqvf=X0NU)*uam=l@% zl96dPg41R0nZSQ|?~(VIY3qQ)ZKLy;!{#ssB+VRfN#_oVv~%2{w#XG6rOp!uhz+2-yjS>c{z>A3r_3o=neSU>=6Zrmx5l9B zKkc&F5`P(aIT${BkLI30P!N4Gp$qrun4{Sv&AiOqEMp`9 zfA2UsczTe>o6bqS4=x+ziAB~)rr!#{b1(D6Z0Ke$^Mr#jv2%rGdOP3zA$*g0Lf)6I zPIJJkLeei!SG2wuOk8Lhku>dqBm_vQod?<}7ylnrl^qkElK zOPTQT&f>z$GT$dI`d_lfX^-Do+>qZYne1;@Sg=KOxMt(DnwpvgmKE`@2Y$2agavz7 zU6DVV&Al3%o!BR%%`MzjZ$^gse80J(nf~|^UU3^a;}h=da9?6t+ghc5qxxXuWn`_1 zFQ#n1%w0s}x0@Wqf>%%z2yo?V&Ee{WfKD3-abF==I8f zxUDse|Jn3GVwYQE_P(mzKz}APOR=`3Aj`F|Cc2JXB(lys(6FopuaoC0m2hn5$vUIk zP)FRg6^_k=PdhzBmK9^)Cd7G>F?SUn&DvwsF531AymuG$CwFd&h%5YD!n*}>SJCu9 z%YMe@b@1!K)S=r z276W!N74nV0gYMElE|LUA<}+*Y9`ViPWo-t0Hr#2g<4%&XsTY?TWQ_MJ;lNFb3u-O z+#vOV#zJKD4C?kOaIYHL!n-z^tH`VI2TJB7@+j&V;{I!lQq3ixR^psE3!QHNG2()@L(GzypS}qZroL3j?-isBTXe~s#dXHC(W--*iS)Ty7N{@ z_0Df)U7(Z%yuIEka-_2{&K<2nfuKFR% z3RqW#I79Z8Sqt{Nx8?17ecO=Jg`RZ61}$@X;K$CZ4=b62aStZ0@cY60Hg5ZqD)IyN zCGPmPd4u|h6Iq;Jr5fOjK^|az z(>;&)am11Qg@$G$|A-BrRnW+6p(Ex@=2$mtP`9jKf}sfs4}m6*EJJ;mcsea9t&_-S zm-Lw_*vUA<4o+}-hB>HlBlI+x@hSa1gmN#lei+Q0c$NAH?VP3mTbkP1>8KDo+BHO} zu7(CS`YYjeIp(-baJ>r6>u*W-uKAkzjP>3L<|x({@G@jv;bpe*vi`74M_w(5hgWRC zhEpOm(y^WwxVP(zZw+^YU+S+U{tbToktsCj1J@Xt z^@>)%V6A?~!P5<2D}b*R_6&Aczv;ksTxfM|Z%6%*5lW)0!)~*#^eDH+Ts23<3oXmO z+An)6^+U`LYWrQG%oj(gEBi20(JwqM^0@Gu^U$Zjl(Cv@SrLtWob!p;7$aQnzX^@d z|Hq?EN=sk-Q=rRnN{DkVV|f2FIoL4^b*g-;N1{FNFPrT#E4>=$++l8DeSS?{YyIOg ztjI!}!tZ|8T^UohV%+A!WxG7XmKA#PRxnnVReOfRe4>rL&H`-6 z37rew6+m+ZW~JWgjPpFjI1It|UUB*m_b%qkqV&OT?-;udehJIwGseQ5Vf)IhdHXkQ zyT%wxI6dEUjIiMnW(srGQr0Eham(_E8|J*U@6~O{j*YVSG+zyAELKC@AtuSI(34HP zm}S|dQ=RYbb6G>)cW-mu^XCQWu#dpym%UI0xwR5ABr3!bTSBjna5gD_IX5B zkoU0*mkkx!^F|1AXc;tL_zX0Mys&9W^vE_th8{{kxu73yzs{nBXP^F+$3{QroPO66 zvn+>e4XQKYKQ>v1?RHt)q#fa9#q@dc>ERw5dnJud_yxJX??_hpu#NK1Vx+ z0?z_p7^aM2ENXTY^Ppw1byvG$<>q2%p{E#n&T|g&ZEE)EQ6cN;y~e zJL+rI;GnDfk!9eo`38UO$Rjo0iu@M*o3_j!^;bqA|3>b7DeZ*p0p_uv#QtKV%vCb4 z6#<7amRa99p=oO&G#Cf11>rvw8W#E%Iz0=Ww(N6TC+}Oo%>qrwpDyqWI_-ougPeh+ zd519(OPI9FVA^Gpmc9`B4t4I@x4}Az@tnVZ)1lQM@=ri4jeHAZq@Ui;?F11DDpA+O{kZ?u^LTh6ft zIBV+V6uIeB#)8P;w-^@)Q<HJ(MIfQt~|l-j#?IlD`Vw3BUH1bB8=dMq4{j$xI_nyS#!ND)Ndq zUey5JqGunCy{Gb8=CNJKI-4k`Jw4%xr1#DdGLQJq6T{&9ZP=QYxuq{S$lAS4Wqv@` zm%RyeoFkVZFRwD|c3tN~-(eFL6cMtc-fuSSLWq+wZtmX5bB6Y~SG=k^V}Xl2y&nh<5w<&{(J zZA38bkl+8fte;jF z?|LTghXtO*ocnq1g+0jE)KlZBw7HiTzCQ?gmwtA{E18Y-uUAfS!#}wj7JijJ6?qsW zGrhFYmo!2f4(u7d(@C0U(j5N2k|;9x?mMj0ShITb-GcnnaXv`#v}L{wZe`3NUV7u6 z!l(@PQ178P`oc?ZX5LiTN?%-FIpf~`qV5avmw3XjE+fO_BNqkH#_7l~m*G_t)m}mG zJ;K<6UkU$u2^^17cF_Q3R2geY?;N7Z^OV_{Jpb04ZJB~^25Fq6=|!C-k5I$y%|n}q zeDd&}hc2+k<0MTBywFV^--34;ZI*}3a>*PNFKg;<@ZO?j!#oAZwwy=Yz`rI0S#}We zTLELFaE0TwqG#Um6v2Zeyn((&-kK)on8H78z=S|0Jm++kryui>%t!r!vUeHtA1-Uf z?zPrrQ#a>_?1z+`&heBmA0;e~o-XNZi)B7ip~DbuJ{q?Ceq4^Qe1P^B2X4qSei=V*Rti*{Uj_lpnJe1PjpQZ3JUB-R@ zxjeY$iXq3^=&ua+zwe<9`rS(#{h(FuJVX|-&iBUo75vH@F6+uG@Tb9Qu(nSsbNW@r z_Aj+H#8BoOeGSoZEh=e(?@1HjlSW^QzAAeFvVMd=%bDz1?7ayue>^}*lyf&^cte&C zN*~VpGl+eIsjNQ-1DRV|J_I*x#kGj6Q7Mphgjc?o_3RSlAI|Gqi;(M=#OP~U>KG|; zCy_?dUhSBM_3Kh^8rH#i#yYr&HE@g8rtJU0gJmsqlYQWWU$^y2?uqP$taq7k*^6&s zPI5w<&YnT;d|6YIU$NlOA-}(XOEhWU`B%~!_{*AHaM?pzeS8K5$=K{{{0^hdrqEu( z??r}^vYgDJowbpy)ruJN?~-qu$dVo78SUbYw}p7J29$N|70$M9vJWcxwjr|=%x}wF zM!V?rXt^gmx;x>;ZJ7%RcSCdSybd1vzsS?s!LLgE825v^E2A3Wao+eE&xKc%6Ye&` z-}#R4GWNE-JVxVF(0^xq>iox?2ZNWB_$#GcpL7~;@=52*n^LgfGzOmRCVnpI%BX{H zd7YJIUecENJJy#{UvFF~!yDH(fAfyEOl&S|YXI+FjhD8ku*WQPr?1H^HA3rN+Pus> zDYVIEcc!yvn7hEBMWLb5;CT-n82vl-ALbWcqtQ@~MQXl86eYHJB^RUFYlNq^!1tQXj@EQ%Zqj)4M=qPY#b43%MuKXxh}XiL7?hWv zG?6(|c>P}12C{Y2JPJVF2tGU!^fv(>q`_hAB z0^-Y9x4n{E;2B~HNv{P5)=crQoQn5U@$DuL7@khyjQ*IIY4fQ0Lx~0E@{?sd{YMI+2ouY>^ z9J%6Ee#hzKVc7j|8>ig3Ks%lb40E@AJ+-CDztSo?C+7o0-Kk1=V*)?+ELvulr`*Qo z2exIEG4HBj?gUe4{A`J`vX?hqI*_r4ot4)ZHD7vCiQrVLpk1;cNzXhz4W!P+S0_{*bT=0 zHSD-wTbkfOuj6?{yOy1GLC+2f=19+s`zHvW9TrY zq05wtPE!iHP2;d>^?|1ABzxCw`~R^`@;>x;!%(H}3-DhJ6y2xPp|{3xhot5Nx(Y|2 z88sB!?PkZ=ohD`MqtHF}+aqsdA`d2Nv#Y+s&&(C$@iGhK>|> z0c*&kdt$s&*Nfk+IMJh%HKn|}(MvmD_r9_9I`4Wd<#%wU-*ijJ58^EWV}C7_#|b$5&C-63cG=^E>I)^o>YpDz%aKA;-%6kJ-9jE(wI_7nlbgZ+cNggg_ z)*H9m#)O$78^c&rv0en<1kazyhd!S64f%`S&=AgS*3&+|dWjoopN@KoL8ssGxY0|z z@-OHmhMcbTyz&q95;c89+GJRINm7$}SiDPB>SJ*G>Lm&fnF7D{>M+V$QuGvU>G$d> zD%811(^HIW*Ha8RJ=7!TvhmJ^Nmpmqc*?-#C~1c|i;}LRH`JyEI?qGL(P~7#oP#^z zEt5_U^625i;>*G3EVS-~&RJ7UdxdrU73lGA{5BmP?Upsmb^2vDXXHl-BYimgk1Zzb zfF4&J^}MN}+09KhS@NkHeLg>_fjqNR#d!`lYn75g&=m{rNN6YVx;Y{K7~yhGWrKDV z`ZxWwjsCrxPINVERlSZ^KaPj@R?)_R?#s}g zved|yINWmPs+VqVnV!jyGJH8hc|_H<69-kcA|A7&2BpdOXz zd90Ab?8HRUb~uNyHa?4vp;%s@L{jbOL>Ffn`Qs_t%77#yZ6oDY zuHfxC&X7d^^D+DizL#<$o;BhtCR4>PUs5WNA+eF4d}wW?pm{_r)su2HrsG(h_Wf7w=7 zNuRuh{oTLG`CWL*i89({5^Wx@1~np2R2)@<+(*@bMsNA61NQBTu_=?^d{Mb`G)8$> z-kgxU+<$M|^9D4MQMl~2qQ}sMF@?H?*L%}lL)O_Bu(Ec4_$m$O_d^`D2YQUFO##n0 zH-I0yGe=cZ#kJMS4dnSup$!*!Wp!Ec_iXt0)X*R|uo&FlVIJ5NDEc{} zjbXHp!d=yK27Wot!w|Q~ZNuN!=R`>>Z{5h*&kfez{UOO8`#_0IKg)cb4*kpiXa6=s zZS}v$?>e&2i}2cT>R;SV871??A8Ds+J9|P*Lg-!{u0hfz9McCYtG&u|Hdp~vKj(>L4HQ9JM&=LnaF4uR>fwfe_9UN3ej^wE!ZVV7 zAI0}a?DzHhnD$xxTTc>g)C(Ntdo174=HAY~jQELl)X1&Yy`~6fHF)*-_ytdoj}tv# zQZGsW1$CCUUG^GyeGWdd$A_-+pG?5bxc!%o^!x#O2qAq0xJ6DJ=>7?i`^g$^;RQt9bJ-3Bgx+cOY4KFPkf zx6Rv>9?m+~7OVf7m$oIIs`Dh~mnMDSD&IB|nvi;9+tqm~M#=mGX$I03lDY)Oa-H$0#iNc- zlCPv4K-#lP7i~U%SqW>r2JNN3+N9GoJ;n&N}(|b%D>3)vBmOhjA?oWI7&!3z08@|UZhE|}H zN~M?cH?E&r{nq?s+nzP$+eV)f|L2o_N}SQe8O=BEh&T_yJ9cP!?9lS4RC+phtl6^d zra7W3xATQHTdgPP*AHAjrJUbt=Uk^Z2JPb`OVQv&o2V~8-E)Z>Hd^BiHm=((Z?$Z(NwMV1NRi;R19I~&ZyGxgm&X-*;ZJPt#35^LKiZ*p?*+5&~Kn}ct z%zFd*ZYuP}na}28nR_goT57>h_A%3;+dmG8fBkxZQoS452D++Ge(?FfH;hBq_tEjp zW5u3p%xl4%ugV^U>@Ni!=B$V`=R=fg=BV(i38pyNcaU^8?nn5hV~rNMZe7}mbvIX@ z=z{L^XSDI>jLkZ&?m7?rh;N~li;7>P(2Lan*tKgb*`7U4P+e4IDP1p-rjA{{Y#|(#hT6AB52!0(#bgALHyrqWxuVpdnNqz zZP#Y&fKzWX9_J;Ugb#e7h40bAyFyEQ;TP+`<3;=jK;JLX*Amy;ms0Lt#b4tOpTNtd z-}{5FjDyeN1K2elPS zjX9|}>g&Mk59n9lBz+!o_f7JW^t_dMd@rgD?{oI>6K(wL(c1qkW%k2Q(tk;MY!uXg zfo`{?|J2+yp8dnfFX8Dum^X!If580r8S`%^eEmlDas3_{_Rpt{hQ_6T$MTJxu5jze z_1gUR2=kwJE_{^lRpvfUEA!yik*`~MW7Js4eWqx-{x^QFw(ny}{?}XtVd>8!5(rWI}_z|+_U(nOvW9pUuIden`^FstO zXH<$(QE%z(9*e&IZCR(iysEbECe}!sl51@*C)Y;tZopW6GyCbf`pAAQHtBEL^m%Z6 znznd>ar*<>0>7zysi*7>o>w9pr&Isgioez#DbRKnvcVI(H(PHa!>~t@IeYxT#!A}a z0y5PeOQd@bxc}7T-}tfD z z7oG8IK69Vwic5ZyHbi84@?Qv#TNA2O&oT0bAS$H%TE^^rlhQhWaNz4*I>~z+ymwQ7 zSsxaAF3_&$*<)YM8T1F_f83%}zY7k>!<2f#ql&z4fJgX}5>G;3C43}!R0QgAD!S`& z8al*DB+f9-z3=89Rx_9UuW4)2Vvp1@8#m{et>M(4{f5@J@f%>|=c4_5%Zw+#66#Yz zeI)L7;`TM-HVIGh!Eb<1*~eJdifkZl+^wflFS0`l01*X2QNZ$0fyxxN zXhs^RajScza`vFHJZ|=XO&K?Z+jHT1~pK(ukn8Kh*lL|_Q_j^6}^WcK8+K2b^ ze%{ajT_0|K&wV~$_vyN?Tk7Gt#D3A!zQ*0gsvGWPtTDqAzY-UW{q<2^{>6L|am*!- z3$$yYNpj}j*Z#ijjb2~>DC_?W_5l{MAMgnK0uQsV|2^KA^wr-kll`8rz015Q=xtf| zlV9E~?zwl+NPhRwAN<)9cm;aCkN(}p9{xwrsDb1~_6db&V_#^X=Y{*UJr$gbU$%@K zyrsW3Ny>5y=YUn#k%Qa%X_GF(pDz0;)r51I@j=!P7w8LRn!VK>F82fAVaj6fmA;n@ zUV(N`hlVvlbC$?@c!ah}_ThfVyljZo*Hc3OE~B62!>hiOJ%UKux|MHPyL>>IaZVC^ zivHJ*It?AUio$HJgekOSf}hPZ2D*RksuxWeF6Pc$b+53C?GpBC1-4e4@ zi=6l#b|1hevguc>z3ev6`1*w}zz&jo!;T@PYmvQ$nC6w_5B|WJ(gT_DCVcBl zTOUqHP4LM3aSvODdme$eUCwF}Z9_fZwGH$9IKXws8Ps8GSiy_z3vc5-@mbU^TSV)p z?5o_t8_gKOC9c-^WE3FS;-lAZ@`N;_%%6gna4 zlsVNT^6+bLzMcnd-c{SAm{W4UPU`PIEnVtq5_yq1mz06>AJpxCNDu#!^Ths-M%bZ7 z860)X`)%d*?Sx%M8EpO4kG6mC#Qc~^jAv3drM8Z#34A|Kd6?-`WgV=S^=n2trL4_# znrWO#`RZvj+)TE!M?^eRQm1%6*R+XcR!i|~))%&ap#3C#ojzAfh>^0Vz9v3~K7_vY zDRnCKDm3>#?g+eR4Qg%UP0}s>Bj@evm%c1;@YZmlK?g#EJXYv*C~x3wq%WPP??_*= zhBJns{~>xgtC06Lz5CDr!t8IgHl30_1@HNC_+Vt-Uizy|;k#&Ot?)DsAsz6JLAJG! zee{nXS%w5`T^Bj=$Ci+QcKYXG%54B+&5yZ%{Mb`Z{b-nVsHcwc{qtpO>OOz!C$sKo zoa%W@8`|`)H4NpdfA^^uW=(0F;+b0D%Kr--aXaY;Yfv)h{fs`)WeqRr1hZaSxK8%Y z9-$vRWC@dYnDD~2u7t-pD{P}L2yg$;Yh6=h-{%oaxM!hdgh%8lJW3n;o-NGtF6}f9 zC3hKQU+)C%^t-6{Y$NnNz=vt8yWp?vG}@||OIvm0e+T>MZo>Epxb|At^M99f zpYnGCmzCBr7q31+Ux~8@H9f=GM4&aq^8)kYbfX|!4@oAaPQ zllL}TLQElTT+;IrdDhcI+*NO8N}6OHGL^khPbg*wNsHN@_g&)sJkpZTtDU#O>;5?A zfk!gyg6W%8Pgc!}YmD=Zu?Ksep6kjlT;Ph9JD}FfAuUo7_NKma*f=bk5kk#th~;bIXh|V+mu%y;^?{ z>rU<|_iLSbD`N`bvLw)I@5d17~?m@GZ#BG##r~B zW_~ysnj-7PrF@ptEr=IH~;9GDrcJCMB_&>kNdCE_zvD!7aIS-mt9l;$lkc@;YnFu<&N!D z%3}!o`TDrc9l9a+xa!1i5^Y89YWhYQ>&oL3n1}mF z{zCTtk^DV_J8>g>(w~w)U;2g6@KDD6$55xSKTUZY#Q$OX(h>Gs4`MIzw&OksH%n35 zi1z_Yh)2p>ctw6yOJFcGe?uZyos((38;Cs|Gz0 zbD<|y`2R63<}27lDrHJN>^A*`DcZuilq7WvqxxEnI-=Fl$8wmyOLj7|5s^5Y(K zMZZp4e_C(rq555Z>bBqmy{$j6E;dk0Pq)}SDYVPq4b&2Xc@wjmwq8hEp8$QVh9^n( zYL_tPcDH}%SsUx%K+5BGWL3t(1D(foHJE$FZGAq-&)4{Tjmp& z*l)Z{+wo=2AmjWg{N2hN;eF_!*r!7CmXh{W%xTU1%(>n*SknQo3fnBr%u#6ykp?`6wieP2e+Ex+YH` zKXb2kP5udST_Aq5ZeRVRYqHEsPEj_c(DG*(bN@y2g}*2y&6lq$|1ssUlsWG!%`KQy3K1IKh^}NW( z=B#KJrHQZ+w>dwJ#O?H?hwcsptSSu_=B{`vX}Kk^0^7So3;1P z_4YnYPsgA7XzyLLvEJ=HhxU%#{OTOqdk*;XcGcdiKkb^l$7t_nxnIF9qTjNM2|L7jed}C&=e_4 z?{-oIef^(mCqV{%HR}nv$CGdU7VQLi&*}f&b^^az!a&xha<0!hDmyMvI+_o_WjKW_G zXIZmopHc8SeM%Z7>}B|sJ0h8yzb@+zc^`YzxF-^@M((Am_?GK&Vb8oI6&@*xOYV4y zpJdF^*b@)L{Vv=$F}_M%fyCuzA3T8jBodc|D`y3Q!sC=ipK*&D=riju{mek6-@o+; zeJ_pln_&`;9%h`lC;hQT`Ztg-?|95Ky@a`UnuJ$C(#rUqE(fLV`ma;8Wmp*h>k1mI{lBR=tJeNcR=(u_|5yL#mI2kT z%lYMeN#5mxeCqEVYRpwM&RuwT3E9i2iKxk_si-?pccGjp7b*#rg1Q@ZFKQ+#4aHca z%|th5A`tW5mW~1QB)Qx8^xHR<)Cs=OHfNud8lQm0#qStIcf!p z^(2BmHLVy`f+|I=Mm>gl9JL0u7KIF8Z9NK^z}k~2?vH8ZhS~sbL~TMNv#U?9O?%s=G`!SD#g4~dmi;8)K(O0CG7=6{RHHGul7%w-7UIbr4y=7kC)eV5m1iQAbcm4Rs9st)c!6{GFj1!Q+N{ z3v4pf2~gC(qkeCwKY%9<^)@K#9n>j9od!ky5%sR2{sf9TgZkfw`ZFl%FNQh`{)eId z3X0-fNNYCKIZ)JjLtOwxT}1uOQ164HJ}^`ZDC!dGLqoNKqS_4g5h&{KhWZ%1Y^W=s zs80-a6%^HOs82yrpBd_NP*jJZIzdri80t$<)HOq02SxqEP+g$;TWHnaFY__=CW^WI zE|29MkDj8=b#wQvI%u`NecQL+rtA}f`nHdnuIxty_iaC8j1d=znaO___5&eP zLEs?BLZHG7F5&31v^-Q^=6gtB|2`5Bh#xc_G2Pxy8l>T1X zLeP$WfkNk4!`@-o?;fx0YZN+XfMjiaf`M@c`b6n-uJLCmzrom_FtEYERB#~XA)uru zNMS}4^*|-L$0~H%4ZV+{-#J>@Z&T=8Y3K_L{WL?LVCXeNe{GcR&)K5TE${6TkF(Lx zA2am(41KMk-)88y8v3!3Dt?DTr_pHK*G8)FuH=qn6;x}l$A=mQPC-Ow+HQ2x^uI_3RR32&uBXP}|C z8~T02_4(rXkYUPQkV5CSp-R71p>y95rLR@!tfUc0xD^VWRY5A8L@HOzrzv!fHS`Wc zA86?9hQ66bA@0vAbao6-`gVoRMniwh(C;+#+YCLIWOV;?41H~&((hL2tf6sRX?JT3 z90B$tK6wWbbV}ow_^aXHu zpI+Bp8zrT-dOTVrCw((|M?ZAD!{-$1q+c~I)SKb;2o#4N``^MX3K|A@sdw4Ccu$>G) z9ga!F&In)Yv4}?gw?}mF|Lk{~zr*`LTqacUp4#PvcK)B9a)wvcDyLQPdfLfpr}|IK$?ZZMD`svilMKFWkM5ci|SE<$puQu?#|e zbl;;swylqDdvqsw`N{StJD&8hoqX!_Q)iy?u~pSrw$xa*)=;Ya>QMvLr>-As=*@8k zTC3Ze%cZr?`Z@#M`Nsr{8IqroSdO-ip7~~)d0l1Eme-ZE%th;Ri;J|((&EgOJhqfy%3DLcT_jI0 zedrK_`xyQ5fscwR>9>wTF#>QQr` z_(7(%Gu{QSo8I^*?@-IHQvC3XH}sGreLW1#(tqFfeVL9>m7z+ zr9)=sqLLCXgUrksN$E*949s$ZCU^ggr0V+KMgC3i)xb}t|7sLyVU(L5-mUt$A*MZ& zI}rmZ5~(M8CblXix1p1-DEvj?Lp+JQInnz2*4oPPnpRH00b+)d-#9~wTfFJ(U*JFW z>)Pb=_N6fa%dZs$!lyR?e!YS4?G1u|FNphl!MrWvQ~fz-Y_;*OdYaglRGU`s_m2{E>cZFAtvpgIAH{{%28tpwm_1X3G)RwJ&eseE3Lm$466K^^s$=Raq>tBO|m`+3bDqvW2!rfsyd;w6Hgc zu&=qX>=$OPWl#2>X01!c%!lv~|HF2r5*|*uN86y}X( z`>QN%#ayu7Uj95a5F-8x*kc}o8}XN;?6=?s8Rg~kMzinpY+=y`_uV#KYhpV z`>%X*Y3`X%_Kf`7C-x_g@92;D5v%5T*{U^lEY1Vl(w`U?Am0dKiTL{KD38^w2S_<4`14eo&MxUf3jA;Kg_zv?#deZ8Fkn}pXj7d zoEwC^Z}KOy=+0p#X}pGBv*nD`?0F;Y+S-v)|Jtamk$y?L!Xs>cY4RPCv@KlfN8WlG zM?IVJD8#>*JMK^Lh|ELLi<=hP(3tl|BiqK)qhcOe)`9FDk9|ROM>OXi(=A~IGdZh?l0G%+kJ~pG zeJg9E$aL4XxOiX7&bf&6SA;f*yoD`WDZ5Q5;?eu-n_m63T$yjp8tLkM?7`IN!rAbL z4R&=hj`XOD0P^l#7hPWUwu|;EW$*6g$3r>y6+d&^5Iv2w2jOGr%gqL#8#Ki`jZ#)+ z{tPFoITw9f2RrSuE_FsRpdB>kV$-mI~ z$+D}zdFNTam+?K*vU&Syn|4s{{Rh~~=M`8&)7osjO+{TTC;VSnT>5)M^46HhLdYUN zv#|T++GQOlsS~-|7e!sHqzr?Xt>~EWl;4gCw=``R`8t-oRdu7dlVQo5z#AEXo?wf} z&l%KuoHN8W%8OC>o$Kpo(w|f5*Z0!D@1dXH4c(ss-A{q;TMZuf@^_93J+N818*jyp z4Yxk{>4)F`&;s8**Nj>EmQ%`k1%GU{b7^vtl5>Q6$}6Q zdHnMbdYbT|O4=n4k{9Hs6bPLRA`hH}#@v>Cd($9mu;+59t0U+s{~bZJrJyg$POVzD za*gn6u38qqhP=HhcM-R6#$3*M>_Gf}%=tUA>Q;>*U)9hI(NDMf6}$}X4SsCo;M*<1 z0nPBNpTnH82p)qR#Kdmp?Qm9znQ zALv<&f50q@JzzRAB9k}#)Amg6O4z7lRM=8>_@y= zgB}{PC7L-+R6pisoLBpQck{eiT1_$M)vcAx{i1{h1|uV;Rk@e8PWn2fjlNEQtwCLa zR!3QeH$5B3{g5a9ci8=oBmeitcqH+NJMkmm(ynJwheFFE$04&59$*{oV>ssG#!RE9 z7p;`IW&YJW>@32TaMc{}Ui$Mr^y|Cn-!tgvDbRxCJ@k8>4s>nm&wOAA^MMfTLUA(` zx5Mxg1`Qa_e4u}|ipP|XF}$ND&~@GJ$Qo%0T0YWVl|6Eg!xin%TybAqQgj(}>xV3! z@LBzloy2#VWplXy7v=LF29IAw?to=ic)tIi!ly%{r+j}`_;K26B<>O|mEkhx$AD3o zg@#s!S2CXL!p_f96<+J0-1)XMZ+`>YV`px0iSjV#994Wrj@MS%Xq#oe)DLxTjw{~t zBnx56I3aF4wDZ2)yy!KKnPoYNIW_b1kTUodm;R!Kvx?R#I5S$1xc%&Uel-~S%s!*-3g2vB`R;4Jf0guIAa1#9_!0E|QmkL}`9Zcl7w^^T zYl5f~qdE>MeL-UK4Px?>Fw&B zJ!xZN{tjZ>8^#=W^&-<=czmR$CWYi_!r21XPdVCk_ z@eQ=S4{f0_nU>+L$AuO^Uywn`8>T8vHWk0n+a7CXGhN3ibJp_(@|Gs^;knaU!z2GX z-K?AD+g+Uv9O;_t1GDaI^j))D^mYaPk$iU3OvVsD#*mr%7~@-fPw>rL5(l&C;eN?gGM>HXw1l@)uo}-dEOk-unyX23>szf8t;E=)P`$ zPSFP9Dt|N4Qhp?{#y4@lR(>?G#OSz;#p2i)bnaxC2Sg-@94iw%^QNjrod$UBi{zqGLi!;el6l9;qQ~#MBWaHv z+TZwu{igd!{n$r3&OXw4_L2O5s4n zjDSX@U^j$s`h7ELlKeZk|KgqBS8n8=q~FNz2Cw#FmWQV7nKKU6^OD6nwRgIjBZ)si z#%JP>&^pf%?|ITGaho)-TRxt+mGp^QuQZx_e>dBkH1{R%Nt&OYa{q&O&vS6ZlD#~) zG`G0Kk-s9RsJJ*cr_^7&VZVHJNvR_*`>|X{cB!K#r@& z&nZn_y<|ylaZ2{e?4114wGQ3aA}>GVRud@4Ho1!(rA7GWR42PIe_gJF_vK4-*AQj$ z>iojRoGdsbIF5X>i`V*VDf0S!@#>sX{M|5j6fJR-h-V3PrkQF-$;!g~(m6$(GI^V3 z=j7xTdK(nLUthddVj8!aKqcL|D@q+Dxuqo%@Qnf?XHJQsK>vRu_1T4mMLF4}xjmc? z7`J-dIEVUa1IA6gqlA62^rF)2LPtsV@|A_TB`V4JB@{rBqcEETDn}G4xGy<6&apgu zP5$!L%e$TE=A={izhd?BMY+Y2w$kEkijI_drNrUgD!R*+W4l$Wb64c7 zb?E1NI7%s6PJ5F)n0EQOCA691-0T7|)mssjNZl7n+H+SH<>cwr+1*b1NQ{w)I~_%| zWwNuIbJGF^zD?xonQ?(72F0&KDL08?h254bC)Z0 zt^p;UQiT~=;AGs*0j0cV7?=ohRyRHYlmCHpoCKkI>Fr_S#Z}Vbe4k>&JzkV)_|1PZb2!BQbR9@-nkNl2Vwji!(33@xedLb zxJw1aT_Ol;)A)FB5*VfQ?y(A;VPFjA9drUoC$iD>{9XiU%I;=`&NHB-^R&W@ouI_K zO`&rkDD}eq6TLiz-%i2{0VUr-pyZo(Y6P8aK`Oi!g&7w?$+w_{+id6sB|m3D$Y-)@D@8KC6br7*(| zO1wS_oyZl?{T~A*pAFy$><=k)9stGvK7|>(K`Ey#ptQR+;6#ZZg!YVgfs!8K*OKrO zK=zN_@d|bREeX$o9?ZA~(SmllI}|!wKxx0i$CU(DfD+ylpoF&ugoVsqs?b>o(pBAg z3Y}S?guhT>h6~iw1M2AkIcIjqD|C(p_4Fvr2mz&hf)qN>LQJEH_aK-c=>R3XQc%h} z4}?^@7b|ow1SPx$3NymMY3M@~W(aW}j{9rylY!25P~2Zum~j@A@K1x1-jkpe_a_uO z4}s#oPGLp`sFx2Y=~)S~K64i;bS?(PeU`!uH^`*lovP63GVBu#`%37yq_;w01~O0e z@Z&*APaG)aAEnUg0Hyp#D0GH^5?_$Q3?ER+Qy2?CXEQ`i(t8#Z{b^9oCn){vghJ;& zP{OHIn6Vp_{v{~k)fjp~31=rL{fn8IZokqnXJV$CXBp-T!6e-I5SaLDVLX%cG=q|k zvmhkVeMX_P5tMWsQJN{UOgl&h7MY7l2YPZV*!CPF3i1ff8<_ z!VKZ(lW?OzNQ`@|Lgxr&Ul^t^BM=mKc7@IkIuBjh-LBBt0!p|S6=s|P#rU=oo7Lb z|BOQCX;9ps0HwSeLAr$dm_p}4P~tzJFk?3;{%aIE%R%wK1{D9LAWhf3QlWDpDE=2H z%y5I^KUJaA1=1AVi3*+Zptz4ynBf4${Ro9l_HDEY#B;{LZ3a#QB^~jgl;c>CrsZ}h zbcTTvZivDRJ1F5^qccl8X*UqQ6J6g7N_b~L$=7L+y;Ao{h0bH3gx8=j;{Yh()q+!S zp9RvS$2&l&ALJwG;rW1)z78DIRNd_goo%3m*P<}vEXbex4F80lBZFVpH-I#?@iib- z?cS--$$q^aeg!DuuK_7Ccd0^WAt>SJDa?omC4cPw>h9Sq)yJtYh0Y*Q+y^Sm=+Kn= z%b=7;8%S2%Eef4yKyiOsVMaWNS2ul8&{?jqf0ltR104ne;;z51wZ+nAv0G1D&RF2> zwC=RptyR`7R(Mmb9elS~+xXsTtFd8X>#$*BYvcQ5ztjEf)`ot^;DD{^zq`NPy0!l{ zgc0qu*VuinhwKe@yLF%afZb|sw4cD_vb`OXGxoEX)cEc8gG1EstRK9levN*1>jA%m ze7E~`@PBteZGhdnEnsJW)p|PMOaSHq9hh7UXu+g1uqqHYfwh6S3EatdW8ewC4+S;^ zTCE2M)xmjtX3*I|cI&Z0jeK7YX%B%rH}qtv-C7&EFBERnq5FoCqoFlJ?baKp$EbzdpCol{NR<*^(MdKBZhsLVgFa$2p_G^#3pJM1OhQ8L& zbNH>hf7kGTuVMeFVK4Mg!rx@*|7y5DZ0G|F`&own-x~Ih8hW9F;{PE-|6@b{zm5F- z%FrJ*^cxNRhlak$(0^{|OO5ytlj6vF_gmG~W6( z*m&#b_0n(dr5F0@?LM`aK9cl#+sE~?-`*>{-}JKI*UO&sksjgs_R@#+(o5Xl@gM0G z{3Q#Jsk=N{iRd>E5{Ywpg6IBztvXX_9Pu zdb`NHPdMTi-&wJCti_O@XC3hDW7xist z*r)>5cPQ^EE?TZOgkjY$DNQMQEEliJLD9<6{N-#U>YI&yI~0EX#ks}BE6t>otX|&N z1%Mp3VGCgLUsAZbB+qoKR}$5nFGfpN!mD1ogpbFI^Gmr9Fz5dJ-I+P;8g^^c6#-Qo zJ+gey&F&89VPr49af=``N8heBfkax0CL#3Ef|O;DlQddJDO2V)xgC z8(+E;u1pwCy1ypK+;7Z;+b6~wyW;{Arc9fVFmc+%iBlbp$?^PiDC-IF@lz5KL^q-P z{=_<|7O%S#1oz**KXHc50{K-!H{@sPnS;wbT;H!1^KW~9!ZBW4tMDYqtf|~7hek2! zF6V#uFEf)PirnJt!fDE=_x%Yo9M+8bul9DemuzTd4&UR~9Ud)4e!atsH`3+OZMoM$ zc-~>K7VPo+ru!Xo4?%v&W!8Tkgy;P;inH73o9<7nVe?FWSyEQ=BO46U+L|PF*MZL- zL9+Jw+WQkH@h596@g!4&%PE?+5IiuGZ!*r>vitsoge8h~nXJk5{d?d)^^1fTz4<4* z!W)oPBD@~WQ_CMFfRO3Pak1&=gRc5ZPj8*)_w>KQN@v&ZwCrCu=jOoTuDhP^tMlr!P$>r+TeGHHUhKl7OW9^-Fp<=lDb z+pf#jpY!R#@Iwv3eF*+S@jsNj3?nc7s%wY9Kg!f9nln&Qdu-ty`Q%;r&F+LB?G4W0 zr)xe}F2FY-Z#c^Rkm<-j%0d4*GPOG3jp~F)R_>0-S@#=PlcQTGQ#)naGDNFaJo7_& z1GM}dDUX&-mS06_TRyQr(a1URqo*!S51we>;1^*ZaujW$Po6)qldv}_+`@OY@VbpAA7XCWb8cL%jnb;WxLJ$XO7UWG}3lj zM&ll{J$y^K4W_NZ+wcy32HtG=`YhI_RBf0i0-1r2S%-NVtfBD#tMl`7q)pC|<*iCd zvw6otzWb6_l|=h(hF9tO`iH2`2dUQwsNZzzc|Lq^^Wbq*Ja6LvYvpGHO#TF4$1~1YP^6W=m zh3}hbeu&_AK)+Q_J|83Jh&52*ed__OkUw6PFq&5*s) zH^!hO?Lx=&Jiw>u(rR=wp;eqa9h7&4IX60pJmZ631ZbU~!TZn-PlKf0KI)I*@OI6M z+~8Vee|2;CUj}OlBPj1MDf=f~Ka#e=y^>D7oL?GW{RL@w_(uWNSHY5G-|BX7YqG7n zbC}l2xE>O<{)vt-VG2eL=`SkdL4lox@z6&wbIAa6H`AndeD4C9>0E38#Q? z+yw<9J1f{-5L?IH1{KawSEsj|!ez=$;W95b+!2W}(&91F;vp^4wk0ix;nlwISyy@^ zJV<`+G22=jTb@ zg7fpE>O`hu3%o?B?Oh2&p{?%{?oq|xq-ZF7%q9(OF!-KC7OD7&9KidNpLF%O-$2>+ ztShZ+=PriyFO5E6`Vm>ahmlu#4qiyT|I&BP(N0G}3(hlU ziwQV>y}_s*bdhY!aM7VY{srOQs9BjJ%<#Q15@Wc}?@N#ke2KSljjj_bQF zEPa&YDC)p3_^IcGhJQ#|%&HEk7FwRv@6ALa(7GaPL4BZQ-@K`qk}11+}M+R zt1q3JTdyTGBwCV=CR&r;Otd8(OY})PlGrclcw+yg-zNGdH6;cm{Vp*$>8-@UN&l8O zB`~y0pyW&P zV<$g0i(h~$^L{Fw(D_lMvx5F%rc>yOPH*5DZk~F~dG4-b&hzt+IWOFQ%y}{8nDcMb zk2&9e=$P|^v}4Z8_a1ZhfBIi$zf4>?75_4OW5r9v`*W1w%iVF?h+B`>VKw5l>G3Y+ z9+w%f$i0_%&odv>%aHbZH8TksIZUVX7ihB=4W2%;&2EGT{!8wHJYgN)r2C=#g?7p~ zb(H!y@2;9?GY{2bDLk$IQ& zAv5hS;l9!q-ozb-E2+G*E$#DWwBk*#J8WkEk^7yGF@EU&kU!m5zuVB)eO}sGzTuB~ zU7!9eUNq2*uM?W_2>v?p|2@Nhcl>-)SDI%LbJ^v!AZwy$L<bV1CVeV{Y4sByD?Zce9A8RD;0&cPpM&R2$=E1h%9h~VVTw}g0@6}(` z*FQGaLRRk1TUZatonN1rT@G#4MwzQJ4n?5Y9uKK4e)9Rv$m7&$DRTzz`S4ig!?*M? zAD(rS`EUgDVVM^-6UQE_y{U4b-Q&-ESl(`t`HRrxGMoL%BJ6p~RU6pi}@En z{m#Eedu^b-l83>6FVZ?2X|oG>OG6u@%2jBf`F^&nAs6WDIwcF>9Qs|{D`_r3hAM3o z-jD8kbE<*V~W)BdDRr9Pst zx7Yl#nEbR#e7l$v$h@RSyxdP~={0XNF=`lAZF(L-`pROd|hCj@b@BDXQRxE8N(uTxjQ#ubND67TJAVrpq~AZS3Ui1?F#23 z>8wxZN&bWu$~SxpAuYrowsu+_=S}q;aoWMiA=;IV(W<>>&@N&IxSpRr!1YN0G+FM` z%G>R|cMaVUI?5I8X9*3+w1u|1#?1WUW!^E4 ze33dL+z9GP{BOoxH0zm9Z?){X*6W_M8GZ!s0Da27N$pVAG`|gUSIM$UW4$xVD{qp= z^}gB_nZwH+JhNR$JNTHpY@5iJ*%qY#%l#8+3twKCxo0$a{IArr`2C-%>rm>tle;l8 zXMR?%Z-2eMW2kT5o9LcDAGTUo9bt|?iT#ack^8Lo4YhyN=UvGk%bwAYy^Lp!iT5zZ zu^xD624h|dV_!1+MoHY4{9U2!dmi>*(CWA=dEEupH|E-~LDo%-zb!dE+m)D0-1dMl zPo}S3mvKIw{x7sb>{6xA_o6YTpBCs>WT>`E>PYHF?v;vNi`?tFi68GZYEr73QhzQ}Mr z4`H&eBIQC{?@nZ|l0KS4AL*Fy>g32EggL_VE9fthE*U!-iHEyBG4H3$?)czik#`WG z)8-4%Xm8msvhOQ-%fzoAL$Fu)1`(MlQ=kzPD&tHFAUBUX> z>k8M$*QNQD*DqfmTPL&(y2swBU#rkQNmn!d@;Kv2%-Y(zy!HAXh@RdJl!J^dHrk0f zj=ikfpH7qB^qMc%D06)bkugI0dYDyveK~ixL@)Jkh9PeC`#QbaoOc)E@& z7`|oRV5eSUXXfF?zo3S4*qjpOxn%Y8#8_N+L|@6AmdX6?67S7HgJN*I>L_h;v(4gh z&@N?P*b0s!9F4vq`ICG~ekI=*(&TQQ z-r_BZ*OzMpJfi=>bFPkGGVlGpWzbu{=;}HsvOx9`W))%18sPT033D0usCP-2c6^Bc{e<@%;T7^8favcdydMzWe#^kO_7mPmg!gM}MB01EV<122hpiC> zpCPL#y0a_&5__C`t$t0J?12dFO8fMw=kqOoq{|*tm2_8A75b0Z%iKTnF3&IM0|$QV z${(9|+qAKHx1-`w6Q=E_?N{Mn`go=#pf!s^4_TU6aODk=6{DSNk3xl6U=)H)$dsbv}SQXHh%qb z)2=RxWrydfyMq->-DbJmudbU(Z|y6+_XF1rg)1w zKG;I~BF1TxTR%>&ZyA!rLtNb%isgf&WAYjVAZauc(SqW|EAK3Vh^ zZs@I|fA5C=$XN7$xuM@E`aj*!CyV}+(zj8sLQl?*!R!w`%{GczQ%|$eV)nb9W=BS2 zcC@G2A~8GM)66PnZ}c>KX%uF^>1mcMX0P=$JMX~kmp#okirFhY%|?sa&wH93;XS&x zmwTEm60==B&8%X!qo>(RBQg6)PqSn(`%zD`^WVYjhds?UikYXU*=R9)rl;8v=FDxI zdYUZ~v$CFMRxw-O)9fYQAa8rTr&+R?mGm?_&)d3fMLo?nidjKVv(aL^PqUZ8Fng${S+bbT?`d|PcS_st?`gJC%+h+AjTWzta=xO#6Z(jyL2h4uD2z{)Vel+?tFMSjAuVAt#yb3zzfsPgEV`Z9W7IZ8V zIyMVB##nahM4mdQI*}J!mkAxaYr~H4%1)uxFf+Pkn`%0SHN)41HxAmQUEfJ zhopfaU@SNo3;=_`R^|r-!6q;ed^IU%v26$}7nOqcOp=6Jp5 zxF4k4yC*O?)>Fm1y;aO12l#7muV$XPf-$<4_31DCwYk5r4D@`wY{fL@baOGE+r^k7 z?`HiHeHU*aZTbao%=%jD|9;d~9m||x*6NY-KOAJKKg`}?4fCsLKfeI>T>>r*!R!{! z$}xM3wrn5gAJH-lvl!%*GT*pl3y7ITdd)U`A7`YsqXHuu^Y!!A>gmX|8b!P71^n*J$S!f<~t5c&d75pEi8NFCCEm7Z?72K$m645dYa}d@8Sqh#%!hmnj`R~=nur)F+jlu{e2;hfm{+IQu6?ZT65&T$ zf?8i>-X4Q2FJx~OoM#>+_4(nlybg~I8C&|?!T+sRnW$CyqKS) zbukaR#9oUT?r3DKeMnup*td$JF1+XT-g%I8%e&xTK9aKMvnR7&W4}1&T9NG4jbN{F zsJ>Ts(coKY=PaK~FA0}e6IN3G_La3eWKL>jUq{>vpODP&E}%?5S)6@Hc?=?tl{T&Z zGuwce+Ex2>e{#OP`KheuM_cUdtD61?;D5jk|HLQqnV6T=Gk@fLt%w5IC)>k(kh6rQ zw~7OITw=~2`|qxfG>g`m7V4@`W8YTh`ncE6)=j_7?g=LmD= zs{Q&N)pX)GueUS%-dU>#?1;tOO+IIl-erW%9iGk@KW%Or<^`C^TCog2FH@J&mt-9u zjU9Yoop<4$bOcM@TbM`N*$1F5=E}NB*19jVUnOfUXSi-^pv4ziQ-c&QP0Q_C{oibAU;g^~8GE`?EK2Sh;dEqL*!!}w2L?}{5$09O zxSc&_k)@)+`<3L--mbM;Jwq)aOysG>v!{W ztk2-_5_!=boL6X&qlLs&SYs)w9E9Dyw4f=NF`<_+!3ttGA za{i2Re$VP%&f)BBDPBA=&)WC`<=pokY1dKJjy`+ne^C#XJpu55ma#`-#U%_v8g}=$3X4(_4K$DMemvWj#dAKPHSwqX15W`+mCS?`< z{p#>bxa}RDl$GgE#*&%%>Fs6!ac;5rrg;c|pCw?2=Q&qh0d?geyvY9AD#q>fU~6!| z74(Cs%S+Uy%+^21m$;r(&zdAac=XgHZ=12J8^xcuLa2@q7Y}9cHa;9=cl9V2oV_xn`RoNm?=ti|YJU@L#rdcP+xl8j`WUeluMpNXTm1Bt# zN9FAyr)YKI;vO|=)S)SEis1cq*+{udmm@T)#Ib}bTO6m$X7ZMigu6o8r#O`hNeGuR z+|%+Vt-cfGShTj3wun%zl|=|~nzlNQs2(G6XlRzo_6h9?LK@|8+{lx3UW+s^$%vPe zp8276ZU_+b*HrdIR+&ym^i)%L#ko?@x@a}Mii9ZQn0IxgW-$^$ZMrxtuj=%2$$dFp z^cr-I!d+n!RUYiorII1CE5C?n@ZpbavxM6wE^&+-=NO>rt=}Q7S?}B`3e{^J-*r01 z<#)GlnzTbx*9l3dWY@6LpZ6hTy$=~)iARX%V}!?NlpZ~?6K%AE6nOpETacd&89jc6 zxyYW7-&gGAZjk)q6nw>ghBDV0K;|Jbg!TCO$e_eexZe zIQD(W`8s*4vQJg;mH5M=l*JjKuk>9u>{pV`zS5V)W{~_ENWabT)$&Qe{(H!ExE!r% zF7OaYBF0yN>~oE;03QU`fMnHOs?b?z*e?d>W1j^Qg?phwXF5od-14ps=u8FKCo7zx zFyjR2mUy>-67ND#;y zIab*hIuvGvfpc(olFc!}i{q4>v)u-+G;kWoe&G1hu__&fpoE_Xjwk%Z3Z3zwq%Tfk zhA^{By4@iAhT~m^ei|t55)@{{gA!gGNE>xWDRhP?`@$fF8G)dL=VO?QFek#vxW=MN z!oLhkxNQnE7SrA&-Klf{@#g|1ocrRXRQpTmV+Fxg`E8K)v07RH?gEp|cPa`^BK7Ckv!1+zS;t=NR^>hP?}< zirk3`o$;W=AEz**okMpCuMK<8 z7KP42!+x=0p9PM_UgX+=PB$p&NmZEP1Kx*TQ<%}grs)I3d)dH_Au7F>L8-TshQ1M; zkNy}qitrm0I#+^{zdTU#w-9t-zd)gLnqeOg&c|Nl%s~g-Q3`eWa^gSGu(yNk_qu%) zI@<@U^j}t(aWPn>_pE^;r%lq|0L}&XffC+sa6VWAayQE@a@;^CXU}?h$UBmhOGY9n z?h`<99}lw6?2c3D6!~sae%t}m-Ctu<`F`9rg5s|M6n}?6p)Vqz4Rjs=g}&@l=-drT zI5i40)6pA|+NY`=8JC~qS^)PIY@jAl^E?+kc1coMt^+zs}}ON~NjB`EnV2c@1%LAsQC zr9!93H01a`>_f$fb^fN$-ClQo*;z62-8(t+r=U7nU zaVX5VY{w9H4d5eS9XJs82NgPZgA(3OP{P{+Qf2Nch0Y34!Yfypu>h3t;z98j1xkMj z0Vx`HkV5A*UuEA8O1SK8${lI1^FRrIvBC^_Z&lo5ifVm=KdN_T=n zXDXAP`$)$Og&97e8+RfPOw3Ox%n-R?5`G;hbb|d_eSRbIQV2?y119;Ed8FjG1L7q? zwJX%+fQkJ@^iqD!AWq#P4-9mk2KjTJROoC3rF@Pl%-90TJWAxnNV=jFW}MIz{W)e} z9XJzryTMst6)5zq+(7!aF1O5ufByPA3>1y%o3R&cFtEnJas#srbQ$O{5YYYk*lMl& ztOrD{7gD^C{A6nuDPFeINc1{pYqa^;PV^I*UOu*C{TutAKrYxpdmVDS>INPfhw*=d*<-rxfgcDrH z|9yiG47OWq2Jaq>gsEXChuN(S!;T?yt0JrtIa?jWuJJ9Bw~z%Eb`Y6c*TTw?xpg_L zo$o`#8~8pj{2=nTI)-24yKVSoz7K>SMDEt^@LJ?w}^0F>+!baBd|D@wIGPaa-t$m{oj3Rxbb|YzP-{=EK+S)dH z=V;_-jj2J#R^^x~WNg*mvJWX*6}MC(QS0)!cBE^ax#cV-4YwY<)yGyIQxQ{%{H(Jv z%`tXsW6TM@x5RFZ#U!?w|Hon*`QIMb5r;`!Gyf08HN;W2aR>NryZ!R*c5CzP7jGv; z@rUBcS9~r14^FA$ec#$C`=;>D?v$-lwt?kSDyCF|ZIdrgZl6pDsRvRi|I`-V;ype4 z%4;f|{~Ep}{H?dE z_?8;_-x~Vy;vTixa4(%(^#5$=-!|OW8~Rm-{yz-;KTG;Cn`7uX@YMYuGxWbR^vjL- z(hYsmZ7Mxe4gCs3|DF;5E<^v4q5r+1UvB8%H}qo+eXtQeJbQZlE<-;>>@hRtiM-KE z|FofZ8R<?r^=*Jl0A!bJZHHkgypAG#i!~UvSo`yamUb)|9xX(89e>C)y41JEF z&o%VJ4gEnw-)4mOxuO48L+_fP!mlv&I}Ck>p>H+xE<=BZkzVd^=i1j8FV^|6btB zTfeuLo^!t*_Qk#Q(Y?ZJCcWO_9qkpKjC?ctwunI8HVdg(b+>S6zEulQv?z+d-I<`Ld{Td(rl-79|1f_k|3 z@8w?FjdytAz1+Xi%l-$w{F~Ch`jWq<@>kUni=eF z8v3QVrMeV0B(CWhD#=}yUL*%nC5E6Z%yq$Z`KExiZ!IOwD7M9mjq=gYu1ao5R5L^F zC6P@DQZvM>sh8Ff*`@Nxd+YlC@7D-h>v^QoQvloD^X6reVO<{FeDzBB4GC;2xmZi- zq~>Omg83YI>BYMo0dY5EwCPcBik4g2eRh+(I9Z~a~{bq9nrKX=vq(xPH9(aX?` zdo~Z6XqmkZvy_1@;ZL1znZ6LEr_0780XLKQrZTNdjnj{gGP4&ymc4=vcQXv6!QG@5 zyqqrK1&bW=tAxefNV62k%o*K+({2cj>;CHgn0P~cT=!S^$HZ - -#include -#ifndef NDEBUG -#include -#endif - -namespace session::bt { - -using oxenc::bt_dict; -using oxenc::bt_list; - -/// Merges two bt dicts together: the returned dict includes all keys in a or b. Keys in *both* -/// dicts get their value from `a`, otherwise the value is that of the dict that contains the key. -bt_dict merge(const bt_dict& a, const bt_dict& b); - -/// Merges two ordered bt_lists together using a predicate to determine order. The input lists must -/// be sorted to begin with. `cmp` must be callable with a pair of `const bt_value&` arguments and -/// must return true if the first argument should be considered less than the second argument. By -/// default this skips elements from b that compare equal to a value of a, but you can include all -/// the duplicates by specifying the `duplicates` parameter as true. -template -bt_list merge_sorted(const bt_list& a, const bt_list& b, Compare cmp, bool duplicates = false) { - bt_list result; - auto it_a = a.begin(); - auto it_b = b.begin(); - - assert(std::is_sorted(it_a, a.end(), cmp)); - assert(std::is_sorted(it_b, b.end(), cmp)); - - if (duplicates) { - while (it_a != a.end() && it_b != b.end()) { - if (!cmp(*it_a, *it_b)) // *b <= *a - result.push_back(*it_b++); - else // *a < *b - result.push_back(*it_a++); - } - } else { - while (it_a != a.end() && it_b != b.end()) { - if (cmp(*it_b, *it_a)) // *b < *a - result.push_back(*it_b++); - else if (cmp(*it_a, *it_b)) // *a < *b - result.push_back(*it_a++); - else // *a == *b - ++it_b; // skip it - } - } - - if (it_a != a.end()) - result.insert(result.end(), it_a, a.end()); - else if (it_b != b.end()) - result.insert(result.end(), it_b, b.end()); - - return result; -} - -} // namespace session::bt diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config.h deleted file mode 100644 index eea54c1dd..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -typedef int64_t seqno_t; - -#ifdef __cplusplus -} -#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config.hpp deleted file mode 100644 index 450446155..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config.hpp +++ /dev/null @@ -1,352 +0,0 @@ -#pragma once - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "types.hpp" - -namespace session::config { - -// FIXME: for multi-message we encode to longer and then split it up -inline constexpr int MAX_MESSAGE_SIZE = 76800; // 76.8kB = Storage server's limit - -// Application data data types: -using scalar = std::variant; - -using set = std::set; -struct dict_value; -using dict = std::map; -using dict_variant = std::variant; -struct dict_value : dict_variant { - using dict_variant::dict_variant; - using dict_variant::operator=; -}; - -// Helpers for gcc-10 and earlier which don't like visiting a std::variant subtype: -constexpr inline dict_variant& unwrap(dict_value& v) { - return static_cast(v); -} -constexpr inline const dict_variant& unwrap(const dict_value& v) { - return static_cast(v); -} - -using hash_t = std::array; -using seqno_hash_t = std::pair; - -class MutableConfigMessage; - -/// Base type for all errors that can happen during config parsing -struct config_error : std::runtime_error { - using std::runtime_error::runtime_error; -}; -/// Type thrown for bad signatures (bad or missing signature). -struct signature_error : config_error { - using config_error::config_error; -}; -/// Type thrown for a missing signature when a signature is required. -struct missing_signature : signature_error { - using signature_error::signature_error; -}; -/// Type thrown for an unparseable config (e.g. keys with invalid types, or keys before "#" or after -/// "~"). -struct config_parse_error : config_error { - using config_error::config_error; -}; - -/// Class for a parsed, read-only config message; also serves as the base class of a -/// MutableConfigMessage which allows setting values. -class ConfigMessage { - public: - using lagged_diffs_t = std::map; - -#ifndef SESSION_TESTING_EXPOSE_INTERNALS - protected: -#endif - dict data_; - - // diff data for *this* message, parsed during construction. Subclasses may use this for - // managing their own diff in the `diff()` method. - oxenc::bt_dict diff_; - - // diffs of previous messages that are included in this message. - lagged_diffs_t lagged_diffs_; - - // Unknown top-level config keys which we preserve even though we don't understand what they - // mean. - oxenc::bt_dict unknown_; - - /// Seqno and hash of the message; we calculate this when loading. Subclasses put the hash here - /// (so that they can return a reference to it). - seqno_hash_t seqno_hash_{0, {0}}; - - bool verified_signature_ = false; - - // This will be set during construction from configs based on the merge result: - // -1 means we had to merge one or more configs together into a new merged config - // >= 0 indicates the index of the config we used if we did not merge (i.e. there was only one - // config, or there were multiple but one of them referenced all the others). - int unmerged_ = -1; - - public: - constexpr static int DEFAULT_DIFF_LAGS = 5; - - /// Verification function: this is passed the data that should have been signed and the 64-byte - /// signature. Should return true to accept the signature, false to reject it and skip the - /// message. It can also throw to abort message construction (that is: returning false skips - /// the message when loading multiple messages, but can still continue with other messages; - /// throwing aborts the entire construction). - using verify_callable = std::function; - - /// Signing function: this is passed the data to be signed and returns the 64-byte signature. - using sign_callable = std::function; - - ConfigMessage(); - ConfigMessage(const ConfigMessage&) = default; - ConfigMessage& operator=(const ConfigMessage&) = default; - ConfigMessage(ConfigMessage&&) = default; - ConfigMessage& operator=(ConfigMessage&&) = default; - - virtual ~ConfigMessage() = default; - - /// Initializes a config message by parsing a serialized message. Throws on any error. See the - /// vector version below for argument descriptions. - explicit ConfigMessage( - ustring_view serialized, - verify_callable verifier = nullptr, - sign_callable signer = nullptr, - int lag = DEFAULT_DIFF_LAGS, - bool signature_optional = false); - - /// Constructs a new ConfigMessage by loading and potentially merging multiple serialized - /// ConfigMessages together, according to the config conflict resolution rules. The result - /// of this call can either be one of the config messages directly (if one is found that - /// includes all the others), or can be a new config message that merges multiple configs - /// together. You can check `.merged()` to see which happened. - /// - /// This constructor always requires at least one valid config from the given inputs; if all are - /// empty, - /// - /// verifier - a signature verification function. If provided and not nullptr this will be - /// called to verify each signature in the provided messages: any that are missing a signature - /// or for which the verifier returns false will be dropped from consideration for merging. If - /// *all* messages fail verification an exception is raised. - /// - /// signer - a signature generation function. This is not used directly by the ConfigMessage, - /// but providing it will allow it to be passed automatically to any MutableConfigMessage - /// derived from this ConfigMessage. - /// - /// lag - the lag setting controlling the config merging rules. Any config message with lagged - /// diffs that exceeding this lag value will have those early lagged diffs dropping during - /// loading. - /// - /// signature_optional - if true then accept a message with no signature even when a verifier is - /// set, thus allowing unsigned messages (though messages with an invalid signature are still - /// not allowed). This option is ignored when verifier is not set. - /// - /// error_handler - if set then any config message parsing error will be passed to this function - /// for handling with the index of `configs` that failed and the error exception: the callback - /// typically warns and, if the overall construction should abort, rethrows the error. If this - /// function is omitted then the default skips (without failing) individual parse errors and - /// only aborts construction if *all* messages fail to parse. A simple handler such as - /// `[](size_t, const auto& e) { throw e; }` can be used to make any parse error of any message - /// fatal. - explicit ConfigMessage( - const std::vector& configs, - verify_callable verifier = nullptr, - sign_callable signer = nullptr, - int lag = DEFAULT_DIFF_LAGS, - bool signature_optional = false, - std::function error_handler = nullptr); - - /// Returns a read-only reference to the contained data. (To get a mutable config object use - /// MutableConfigMessage). - const dict& data() const { return data_; } - - /// The verify function; if loading a message with a signature and this is set then it will - /// be called to verify the signature of the message. Takes a pointer to the signing data, - /// the data length, and a pointer to the 64-byte signature. - verify_callable verifier; - - /// The signing function; this is not directly used by the non-mutable base class, but will be - /// propagated to mutable config messages that are derived e.g. by calling `.increment()`. This - /// is called when serializing a config message to add a signature. If it is nullptr then no - /// signature is added to the serialized data. - sign_callable signer; - - /// How many lagged config diffs that should be carried forward to resolve conflicts, - /// including this message. If 0 then config messages won't have any diffs and will not be - /// mergeable. - int lag = DEFAULT_DIFF_LAGS; - - /// The diff structure for changes in *this* config message. Subclasses that need to override - /// should populate into `diff_` and return a reference to it (internal code assumes `diff_` is - /// correct immediately after a call to this). - virtual const oxenc::bt_dict& diff(); - - /// Returns the seqno of this message - const seqno_t& seqno() const { return seqno_hash_.first; } - - /// Calculates the hash of the current message. For a ConfigMessage this is calculated when the - /// message is first loaded; for a MutableConfigMessage this serializes the current value to - /// properly compute the current hash. Subclasses must ensure that seqno_hash_.second is set to - /// the correct value when this is called (and typically return a reference to it). - virtual const hash_t& hash() { return seqno_hash_.second; } - - /// After loading multiple config files this flag indicates whether or not we had to produce a - /// new, merged configuration message (true) or did not need to merge (false). (For config - /// messages that were not loaded from serialized data this is always true). - bool merged() const { return unmerged_ == -1; } - - /// After loading multiple config files this field contains the index of the single config we - /// used if we didn't need to merge (that is: there was only one config or one config that - /// superceded all the others). If we had to merge (or this wasn't loaded from serialized - /// data), this will return -1. - int unmerged_index() const { return unmerged_; } - - /// Returns true if this message contained a valid, verified signature when it was parsed. - /// Returns false otherwise (e.g. not loaded from verification at all; loaded without a - /// verification function; or had no signature and a signature wasn't required). - bool verified_signature() const { return verified_signature_; } - - /// Constructs a new MutableConfigMessage from this config message with an incremented seqno. - /// The new config message's diff will reflect changes made after this construction. - virtual MutableConfigMessage increment() const; - - /// Serializes this config's data. Note that if the ConfigMessage was constructed from signed, - /// serialized input, this will only produce an exact copy of the original serialized input if - /// it uses the identical, deterministic signing function used to construct the original. - /// - /// The optional `enable_signing` argument can be specified as false to disable signing (this is - /// typically for a local serialization value that isn't being pushed to the server). Note that - /// signing is always disabled if there is no signing callback set, regardless of the value of - /// this argument. - virtual ustring serialize(bool enable_signing = true); - - protected: - ustring serialize_impl(const oxenc::bt_dict& diff, bool enable_signing = true); -}; - -// Constructor tag -struct increment_seqno_t {}; -struct retain_seqno_t {}; -inline constexpr increment_seqno_t increment_seqno{}; -inline constexpr retain_seqno_t retain_seqno{}; - -class MutableConfigMessage : public ConfigMessage { - protected: - dict orig_data_{data_}; - - friend class ConfigMessage; - - public: - MutableConfigMessage(const MutableConfigMessage&) = default; - MutableConfigMessage& operator=(const MutableConfigMessage&) = default; - MutableConfigMessage(MutableConfigMessage&&) = default; - MutableConfigMessage& operator=(MutableConfigMessage&&) = default; - - /// Constructs a new, empty config message. Takes various fields to pre-fill the various - /// properties during construction (these are for convenience and equivalent to setting them via - /// properties/methods after construction). - /// - /// seqno -- the message's seqno, default 0 - /// lags -- number of lags to keep (when deriving messages, e.g. via increment()) - /// signer -- if specified and not nullptr then this message will be signed when serialized - /// using the given signing function. If omitted no signing takes place. - explicit MutableConfigMessage( - seqno_t seqno = 0, int lag = DEFAULT_DIFF_LAGS, sign_callable signer = nullptr) { - this->lag = lag; - this->seqno(seqno); - this->signer = signer; - } - - /// Wraps the ConfigMessage constructor with the same arguments but always produces a - /// MutableConfigMessage. In particular this means that if the base constructor performed a - /// merge (and thus incremented seqno) then the config stays as is, but contained in a Mutable - /// message that can be changed. If it did *not* merge (i.e. the highest seqno message it found - /// did not conflict with any other messages) then this construction is equivalent to doing a - /// base load followed by a .increment() call. In other words: this constructor *always* gives - /// you an incremented seqno value from the highest valid input config message. - /// - /// This is almost equivalent to ConfigMessage{args...}.increment(), except that this - /// constructor only increments seqno once while the indirect version would increment twice in - /// the case of a required merge conflict resolution. - explicit MutableConfigMessage( - const std::vector& configs, - verify_callable verifier = nullptr, - sign_callable signer = nullptr, - int lag = DEFAULT_DIFF_LAGS, - bool signature_optional = false, - std::function error_handler = nullptr); - - /// Wrapper around the above that takes a single string view to load a single message, doesn't - /// take an error handler and instead always throws on parse errors (the above also throws for - /// an erroneous single message, but with a less specific "no valid config messages" error). - explicit MutableConfigMessage( - ustring_view config, - verify_callable verifier = nullptr, - sign_callable signer = nullptr, - int lag = DEFAULT_DIFF_LAGS, - bool signature_optional = false); - - /// Does the same as the base incrementing, but also records any diff info from the current - /// MutableConfigMessage. *this* object gets pruned and signed as part of this call. If the - /// sign argument is omitted/nullptr then the current object's `sign` callback gets copied into - /// the new object. After this call you typically do not want to further modify *this (because - /// any modifications will change the hash, making *this no longer a parent of the new object). - MutableConfigMessage increment() const override; - - /// Constructor that does the same thing as the `m.increment()` factory method. The second - /// value should be the literal `increment_seqno` value (to select this constructor). - explicit MutableConfigMessage(const ConfigMessage& m, const increment_seqno_t&); - - /// Constructor that moves a immutable message into a mutable one, retaining the current seqno. - /// This is typically used in situations where the ConfigMessage has had some implicit seqno - /// increment already (e.g. from merging) and we want it to become mutable without incrementing - /// the seqno again. The second value should be the literal `retain_seqno` value (to select - /// this constructor). - explicit MutableConfigMessage(ConfigMessage&& m, const retain_seqno_t&); - - using ConfigMessage::data; - /// Returns a mutable reference to the underlying config data. - dict& data() { return data_; } - - using ConfigMessage::seqno; - - /// Sets the seqno of the message to a specific value. You usually want to use `.increment()` - /// from an existing config message rather than manually adjusting the seqno. - void seqno(seqno_t new_seqno) { seqno_hash_.first = new_seqno; } - - /// Returns the current diff for this data relative to its original data. The data is pruned - /// implicitly by this call. - const oxenc::bt_dict& diff() override; - - /// Prunes empty dicts/sets from data. This is called automatically when serializing or - /// calculating a diff. Returns true if the data was actually changed, false if nothing needed - /// pruning. - bool prune(); - - /// Calculates the hash of the current message. Can optionally be given the already-serialized - /// value, if available; if empty/omitted, `serialize()` will be called to compute it. - const hash_t& hash() override; - - protected: - const hash_t& hash(ustring_view serialized); - void increment_impl(); -}; - -} // namespace session::config - -namespace oxenc::detail { - -template <> -struct bt_serialize : bt_serialize {}; - -} // namespace oxenc::detail diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.h deleted file mode 100644 index fd932b37e..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.h +++ /dev/null @@ -1,154 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include - -#include "../config.h" - -// Config object base type: this type holds the internal object and is initialized by the various -// config-dependent settings (e.g. config_user_profile_init) then passed to the various functions. -typedef struct config_object { - // Internal opaque object pointer; calling code should leave this alone. - void* internals; - // When an error occurs in the C API this string will be set to the specific error message. May - // be empty. - const char* last_error; - - // Sometimes used as the backing buffer for `last_error`. Should not be touched externally. - char _error_buf[256]; -} config_object; - -// Common functions callable on any config instance: - -/// Frees a config object created with one of the config-dependent ..._init functions (e.g. -/// user_profile_init). -void config_free(config_object* conf); - -typedef enum config_log_level { - LOG_LEVEL_DEBUG = 0, - LOG_LEVEL_INFO, - LOG_LEVEL_WARNING, - LOG_LEVEL_ERROR -} config_log_level; - -/// Sets a logging function; takes the log function pointer and a context pointer (which can be NULL -/// if not needed). The given function pointer will be invoked with one of the above values, a -/// null-terminated c string containing the log message, and the void* context object given when -/// setting the logger (this is for caller-specific state data and won't be touched). -/// -/// The logging function must have signature: -/// -/// void log(config_log_level lvl, const char* msg, void* ctx); -/// -/// Can be called with callback set to NULL to clear an existing logger. -/// -/// The config object itself has no log level: the caller should filter by level as needed. -void config_set_logger( - config_object* conf, void (*callback)(config_log_level, const char*, void*), void* ctx); - -/// Returns the numeric namespace in which config messages of this type should be stored. -int16_t config_storage_namespace(const config_object* conf); - -/// Merges the config object with one or more remotely obtained config strings. After this call the -/// config object may be unchanged, complete replaced, or updated and needing a push, depending on -/// the messages that are merged; the caller should check config_needs_push(). -/// -/// `msg_hashes` is an array of null-terminated C strings containing the hashes of the configs being -/// provided. -/// `configs` is an array of pointers to the start of the (binary) data. -/// `lengths` is an array of lengths of the binary data -/// `count` is the length of all three arrays. -int config_merge( - config_object* conf, - const char** msg_hashes, - const unsigned char** configs, - const size_t* lengths, - size_t count); - -/// Returns true if this config object contains updated data that has not yet been confirmed stored -/// on the server. -bool config_needs_push(const config_object* conf); - -/// Returned struct of config push data. -typedef struct config_push_data { - // The config seqno (to be provided later in `config_confirm_pushed`). - seqno_t seqno; - // The config message to push (binary data, not null-terminated). - unsigned char* config; - // The length of `config` - size_t config_len; - // Array of obsolete message hashes to delete; each element is a null-terminated C string - char** obsolete; - // length of `obsolete` - size_t obsolete_len; -} config_push_data; - -/// Obtains the configuration data that needs to be pushed to the server. -/// -/// Generally this call should be guarded by a call to `config_needs_push`, however it can be used -/// to re-obtain the current serialized config even if no push is needed (for example, if the client -/// wants to re-submit it after a network error). -/// -/// NB: The returned pointer belongs to the caller: that is, the caller *MUST* free() it when -/// done with it. -config_push_data* config_push(config_object* conf); - -/// Reports that data obtained from `config_push` has been successfully stored on the server with -/// message hash `msg_hash`. The seqno value is the one returned by the config_push call that -/// yielded the config data. -void config_confirm_pushed(config_object* conf, seqno_t seqno, const char* msg_hash); - -/// Returns a binary dump of the current state of the config object. This dump can be used to -/// resurrect the object at a later point (e.g. after a restart). Allocates a new buffer and sets -/// it in `out` and the length in `outlen`. Note that this is binary data, *not* a null-terminated -/// C string. -/// -/// NB: It is the caller's responsibility to `free()` the buffer when done with it. -/// -/// Immediately after this is called `config_needs_dump` will start returning true (until the -/// configuration is next modified). -void config_dump(config_object* conf, unsigned char** out, size_t* outlen); - -/// Returns true if something has changed since the last call to `dump()` that requires calling -/// and saving the `config_dump()` data again. -bool config_needs_dump(const config_object* conf); - -/// Struct containing a list of C strings. Typically where this is returned by this API it must be -/// freed (via `free()`) when done with it. -typedef struct config_string_list { - char** value; // array of null-terminated C strings - size_t len; // length of `value` -} config_string_list; - -/// Obtains the current active hashes. Note that this will be empty if the current hash is unknown -/// or not yet determined (for example, because the current state is dirty or because the most -/// recent push is still pending and we don't know the hash yet). -/// -/// The returned pointer belongs to the caller and must be freed via `free()` when done with it. -config_string_list* config_current_hashes(const config_object* conf); - -/// Config key management; see the corresponding method docs in base.hpp. All `key` arguments here -/// are 32-byte binary buffers (and since fixed-length, there is no keylen argument). -void config_add_key(config_object* conf, const unsigned char* key); -void config_add_key_low_prio(config_object* conf, const unsigned char* key); -int config_clear_keys(config_object* conf); -bool config_remove_key(config_object* conf, const unsigned char* key); -int config_key_count(const config_object* conf); -bool config_has_key(const config_object* conf, const unsigned char* key); -// Returns a pointer to the 32-byte binary key at position i. This is *not* null terminated (and is -// exactly 32 bytes long). `i < config_key_count(conf)` must be satisfied. Ownership of the data -// remains in the object (that is: the caller must not attempt to free it). -const unsigned char* config_key(const config_object* conf, size_t i); - -/// Returns the encryption domain C-str used to encrypt values for this config object. (This is -/// here only for debugging/testing). -const char* config_encryption_domain(const config_object* conf); - -#ifdef __cplusplus -} // extern "C" -#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.hpp deleted file mode 100644 index 46b71c2f8..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.hpp +++ /dev/null @@ -1,650 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -#include "base.h" -#include "namespaces.hpp" - -namespace session::config { - -template -static constexpr bool is_one_of = (std::is_same_v || ...); - -/// True for a dict_value direct subtype, but not scalar sub-subtypes. -template -static constexpr bool is_dict_subtype = is_one_of; - -/// True for a dict_value or any of the types containable within a dict value -template -static constexpr bool is_dict_value = - is_dict_subtype || is_one_of; - -// Levels for the logging callback -enum class LogLevel { debug = 0, info, warning, error }; - -/// Our current config state -enum class ConfigState : int { - /// Clean means the config is confirmed stored on the server and we haven't changed anything. - Clean = 0, - - /// Dirty means we have local changes, and the changes haven't been serialized yet for sending - /// to the server. - Dirty = 1, - - /// Waiting is halfway in-between clean and dirty: the caller has serialized the data, but - /// hasn't yet reported back that the data has been stored, *and* we haven't made any changes - /// since the data was serialize. - Waiting = 2, -}; - -/// Base config type for client-side configs containing common functionality needed by all config -/// sub-types. -class ConfigBase { - private: - // The object (either base config message or MutableConfigMessage) that stores the current - // config message. Subclasses do not directly access this: instead they call `dirty()` if they - // intend to make changes, or the `set_config_field` wrapper. - std::unique_ptr _config; - - // Tracks our current state - ConfigState _state = ConfigState::Clean; - - static constexpr size_t KEY_SIZE = 32; - - // Contains the base key(s) we use to encrypt/decrypt messages. If non-empty, the .front() - // element will be used when encrypting a new message to push. When decrypting, we attempt each - // of them, starting with .front(), until decryption succeeds. - using Key = std::array; - Key* _keys = nullptr; - size_t _keys_size = 0; - size_t _keys_capacity = 0; - - // Contains the current active message hash, as fed into us in `confirm_pushed()`. Empty if we - // don't know it yet. When we dirty the config this value gets moved into `old_hashes_` to be - // removed by the next push. - std::string _curr_hash; - - // Contains obsolete known message hashes that are obsoleted by the most recent merge or push; - // these are returned (and cleared) when `push` is called. - std::unordered_set _old_hashes; - - protected: - // Constructs a base config by loading the data from a dump as produced by `dump()`. If the - // dump is nullopt then an empty base config is constructed with no config settings and seqno - // set to 0. - explicit ConfigBase(std::optional dump = std::nullopt); - - // Tracks whether we need to dump again; most mutating methods should set this to true (unless - // calling set_state, which sets to to true implicitly). - bool _needs_dump = false; - - // Sets the current state; this also sets _needs_dump to true. If transitioning to a dirty - // state and we know our current message hash, that hash gets added to `old_hashes_` to be - // deleted at the next push. - void set_state(ConfigState s); - - // Invokes the `logger` callback if set, does nothing if there is no logger. - void log(LogLevel lvl, std::string msg) { - if (logger) - logger(lvl, std::move(msg)); - } - - // Returns a reference to the current MutableConfigMessage. If the current message is not - // already dirty (i.e. Clean or Waiting) then calling this increments the seqno counter. - MutableConfigMessage& dirty(); - - public: - // class for proxying subfield access; this class should never be stored but only used - // ephemerally (most of its methods are rvalue-qualified). This lets constructs such as - // foo["abc"]["def"]["ghi"] = 12; - // work, auto-vivifying (or trampling, if not a dict) subdicts to reach the target. It also - // allows non-vivifying value retrieval via .string(), .integer(), etc. methods. - class DictFieldProxy { - private: - ConfigBase& _conf; - std::vector _inter_keys; - std::string _last_key; - - // See if we can find the key without needing to create anything, so that we can attempt to - // access values without mutating anything (which allows, among other things, for assigning - // of the existing value to not dirty anything). Returns nullptrs if the value or something - // along its path would need to be created, or has the wrong type; otherwise a const pointer - // to the key and the value. The templated type, if provided, can be one of the types a - // dict_value can hold to also check that the returned value has a particular type; if - // omitted you get back the dict_value pointer itself. If the field exists but is not the - // requested `T` type, you get back the key string pointer with a nullptr value. - template >> - std::pair get_clean_pair() const { - const config::dict* data = &_conf._config->data(); - // All but the last need to be dicts: - for (const auto& key : _inter_keys) { - auto it = data->find(key); - data = it != data->end() ? std::get_if(&it->second) : nullptr; - if (!data) - return {nullptr, nullptr}; - } - - const std::string* key; - const dict_value* val; - // The last can be any value type: - if (auto it = data->find(_last_key); it != data->end()) { - key = &it->first; - val = &it->second; - } else - return {nullptr, nullptr}; - - if constexpr (std::is_same_v) - return {key, val}; - else if constexpr (is_dict_subtype) { - return {key, std::get_if(val)}; - } else { // int64 or std::string, i.e. the config::scalar sub-types. - if (auto* scalar = std::get_if(val)) - return {key, std::get_if(scalar)}; - return {key, nullptr}; - } - } - - // Same as above but just gives back the value, not the key - template >> - const T* get_clean() const { - return get_clean_pair().second; - } - - // Returns a lvalue reference to the value, stomping its way through the dict as it goes to - // create subdicts as needed to reach the target value. If given a template type then we - // also cast the final dict_value variant into the given type (and replace if with a - // default-constructed value if it has the wrong type) then return a reference to that. - template >> - T& get_dirty() { - config::dict* data = &_conf.dirty().data(); - for (const auto& key : _inter_keys) { - auto& val = (*data)[key]; - data = std::get_if(&val); - if (!data) - data = &val.emplace(); - } - auto& val = (*data)[_last_key]; - - if constexpr (std::is_same_v) - return val; - else if constexpr (is_dict_subtype) { - if (auto* v = std::get_if(&val)) - return *v; - return val.emplace(); - } else { // int64 or std::string, i.e. the config::scalar sub-types. - if (auto* scalar = std::get_if(&val)) { - if (auto* v = std::get_if(scalar)) - return *v; - return scalar->emplace(); - } - return val.emplace().emplace(); - } - } - - template - void assign_if_changed(T value) { - // Try to avoiding dirtying the config if this assignment isn't changing anything - if (!_conf.is_dirty()) - if (auto current = get_clean(); current && *current == value) - return; - - get_dirty() = std::move(value); - } - - void insert_if_missing(config::scalar&& value) { - if (!_conf.is_dirty()) - if (auto current = get_clean(); current && current->count(value)) - return; - - get_dirty().insert(std::move(value)); - } - - void set_erase_impl(const config::scalar& value) { - if (!_conf.is_dirty()) - if (auto current = get_clean(); current && !current->count(value)) - return; - - config::dict* data = &_conf.dirty().data(); - - for (const auto& key : _inter_keys) { - auto it = data->find(key); - data = it != data->end() ? std::get_if(&it->second) : nullptr; - if (!data) - return; - } - - auto it = data->find(_last_key); - if (it == data->end()) - return; - auto& val = it->second; - if (auto* current = std::get_if(&val)) - current->erase(value); - else - val.emplace(); - } - - public: - DictFieldProxy(ConfigBase& b, std::string key) : _conf{b}, _last_key{std::move(key)} {} - - /// Descends into a dict, returning a copied proxy object for the path to the requested - /// field. Nothing is created by doing this unless you actually assign to a value. - DictFieldProxy operator[](std::string subkey) const& { - DictFieldProxy subfield{_conf, std::move(subkey)}; - subfield._inter_keys.reserve(_inter_keys.size() + 1); - subfield._inter_keys.insert( - subfield._inter_keys.end(), _inter_keys.begin(), _inter_keys.end()); - subfield._inter_keys.push_back(_last_key); - return subfield; - } - - // Same as above, but when called on an rvalue reference we just mutate the current proxy to - // the new dict path. - DictFieldProxy&& operator[](std::string subkey) && { - _inter_keys.push_back(std::move(_last_key)); - _last_key = std::move(subkey); - return std::move(*this); - } - - /// Returns a pointer to the (deepest level) key for this dict pair *if* a pair exists at - /// the given location, nullptr otherwise. This allows a caller to get a reference to the - /// actual key, rather than an ephemeral copy of the current key value. - const std::string* key() const { return get_clean_pair().first; } - - /// Returns a const pointer to the string if one exists at the given location, nullptr - /// otherwise. - const std::string* string() const { return get_clean(); } - - /// Returns the value as a ustring_view, if it exists and is a string; nullopt otherwise. - std::optional uview() const { - if (auto* s = get_clean()) - return ustring_view{reinterpret_cast(s->data()), s->size()}; - return std::nullopt; - } - - /// returns the value as a string_view or a fallback if the value doesn't exist (or isn't a - /// string). The returned view is directly into the value (or fallback) and so mustn't be - /// used beyond the validity of either. - std::string_view string_view_or(std::string_view fallback) const { - if (auto* s = string()) - return {*s}; - return fallback; - } - - /// Returns a copy of the value as a string, if it exists and is a string; returns - /// `fallback` otherwise. - std::string string_or(std::string fallback) const { - if (auto* s = string()) - return *s; - return fallback; - } - - /// Returns a const pointer to the integer if one exists at the given location, nullptr - /// otherwise. - const int64_t* integer() const { return get_clean(); } - - /// Returns the value as an integer or a fallback if the value doesn't exist (or isn't an - /// integer). - int64_t integer_or(int64_t fallback) const { - if (auto* i = integer()) - return *i; - return fallback; - } - - /// Returns a const pointer to the set if one exists at the given location, nullptr - /// otherwise. - const config::set* set() const { return get_clean(); } - /// Returns a const pointer to the dict if one exists at the given location, nullptr - /// otherwise. (You typically don't need to use this but can rather just use [] to descend - /// into the dict). - const config::dict* dict() const { return get_clean(); } - - /// Replaces the current value with the given string. This also auto-vivifies any - /// intermediate dicts needed to reach the given key, including replacing non-dict values if - /// they currently exist along the path. - void operator=(std::string&& value) { assign_if_changed(std::move(value)); } - /// Same as above, but takes a string_view for convenience (this makes a copy). - void operator=(std::string_view value) { *this = std::string{value}; } - /// Same as above, but takes a ustring_view - void operator=(ustring_view value) { - *this = std::string{reinterpret_cast(value.data()), value.size()}; - } - /// Replace the current value with the given integer. See above. - void operator=(int64_t value) { assign_if_changed(value); } - /// Replace the current value with the given set. See above. - void operator=(config::set value) { assign_if_changed(std::move(value)); } - /// Replace the current value with the given dict. See above. This often isn't needed - /// because of how other assignment operations work. - void operator=(config::dict value) { assign_if_changed(std::move(value)); } - - /// Returns true if there is a value at the current key. If a template type T is given, it - /// only returns true if that value also is a `T`. - template >> - bool exists() const { - return get_clean() != nullptr; - } - - // Alias for `exists()` - template - bool is() const { - return exists(); - } - - /// Removes the value at the current location, regardless of what it currently is. This - /// does nothing if the current location does not have a value. - void erase() { - if (!_conf.is_dirty() && !get_clean()) - return; - - config::dict* data = &_conf.dirty().data(); - for (const auto& key : _inter_keys) { - auto it = data->find(key); - data = it != data->end() ? std::get_if(&it->second) : nullptr; - if (!data) - return; - } - data->erase(_last_key); - } - - /// Adds a value to the set at the current location. If the current value is not a set or - /// does not exist then dicts will be created to reach it and a new set will be created. - void set_insert(std::string_view value) { - insert_if_missing(config::scalar{std::string{value}}); - } - void set_insert(int64_t value) { insert_if_missing(config::scalar{value}); } - - /// Removes a value from the set at the current location. If the current value does not - /// exist then nothing happens. If it does exist, but is not a set, it will be replaced - /// with an empty set. Otherwise the given value will be removed from the set, if present. - void set_erase(std::string_view value) { - set_erase_impl(config::scalar{std::string{value}}); - } - void set_erase(int64_t value) { set_erase_impl(scalar{value}); } - - /// Emplaces a value at the current location. As with assignment, this creates dicts as - /// needed along the keys to reach the target. The existing value (if present) is destroyed - /// to make room for the new one. - template < - typename T, - typename... Args, - typename = std::enable_if_t< - is_one_of>> - T& emplace(Args&&... args) { - if constexpr (is_one_of) - return get_dirty().emplace(std::forward(args)...); - - return get_dirty().emplace(std::forward(args)...); - } - }; - - /// Wrapper for the ConfigBase's root `data` field to provide data access. Only provides a [] - /// that gets you into a DictFieldProxy. - class DictFieldRoot { - ConfigBase& _conf; - DictFieldRoot(DictFieldRoot&&) = delete; - DictFieldRoot(const DictFieldRoot&) = delete; - DictFieldRoot& operator=(DictFieldRoot&&) = delete; - DictFieldRoot& operator=(const DictFieldRoot&) = delete; - - public: - DictFieldRoot(ConfigBase& b) : _conf{b} {} - - /// Access a dict element. This returns a proxy object for accessing the value, but does - /// *not* auto-vivify the path (unless/until you assign to it). - DictFieldProxy operator[](std::string key) const& { - return DictFieldProxy{_conf, std::move(key)}; - } - }; - - protected: - // Called when dumping to obtain any extra data that a subclass needs to store to reconstitute - // the object. The base implementation does nothing. The counterpart to this, - // `load_extra_data()`, is called when loading from a dump that has extra data; a subclass - // should either override both (if it needs to serialize extra data) or neither (if it needs no - // extra data). Internally this extra data (if non-empty) is stored in the "+" key of the dump. - virtual oxenc::bt_dict extra_data() const { return {}; } - - // Called when constructing from a dump that has extra data. The base implementation does - // nothing. - virtual void load_extra_data(oxenc::bt_dict extra) {} - - // Called to load an ed25519 key for encryption; this is meant for use by single-ownership - // config types, like UserProfile, but not shared config types (closed groups). - // - // Takes a binary string which is either the 32-byte seed, or 64-byte libsodium secret (which is - // just the seed and pubkey concatenated together), and then calls `key(...)` with the seed. - // Throws std::invalid_argument if given something that doesn't match the required input. - void load_key(ustring_view ed25519_secretkey); - - public: - virtual ~ConfigBase(); - - // Proxy class providing read and write access to the contained config data. - const DictFieldRoot data{*this}; - - // If set then we log things by calling this callback - std::function logger; - - // Accesses the storage namespace where this config type is to be stored/loaded from. See - // namespaces.hpp for the underlying integer values. - virtual Namespace storage_namespace() const = 0; - - /// Subclasses must override this to return a constant string that is unique per config type; - /// this value is used for domain separation in encryption. The string length must be between 1 - /// and 24 characters; use the class name (e.g. "UserProfile") unless you have something better - /// to use. This is rarely needed externally; it is public merely for testing purposes. - virtual const char* encryption_domain() const = 0; - - /// The zstd compression level to use for this type. Subclasses can override this if they have - /// some particular special compression level, or to disable compression entirely (by returning - /// std::nullopt). The default is zstd level 1. - virtual std::optional compression_level() const { return 1; } - - // How many config lags should be used for this object; default to 5. Implementing subclasses - // can override to return a different constant if desired. More lags require more "diff" - // storage in the config messages, but also allow for a higher tolerance of simultaneous message - // conflicts. - virtual int config_lags() const { return 5; } - - // This takes all of the messages pulled down from the server and does whatever is necessary to - // merge (or replace) the current values. - // - // Values are pairs of the message hash (as provided by the server) and the raw message body. - // - // After this call the caller should check `needs_push()` to see if the data on hand was updated - // and needs to be pushed to the server again (for example, because the data contained conflicts - // that required another update to resolve). - // - // Returns the number of the given config messages that were successfully parsed. - // - // Will throw on serious error (i.e. if neither the current nor any of the given configs are - // parseable). This should not happen (the current config, at least, should always be - // re-parseable). - virtual int merge(const std::vector>& configs); - - // Same as above but takes the values as ustring's as sometimes that is more convenient. - int merge(const std::vector>& configs); - - // Returns true if we are currently dirty (i.e. have made changes that haven't been serialized - // yet). - bool is_dirty() const { return _state == ConfigState::Dirty; } - - // Returns true if we are curently clean (i.e. our current config is stored on the server and - // unmodified). - bool is_clean() const { return _state == ConfigState::Clean; } - - // The current config hash(es); this can be empty if the current hash is unknown or the current - // state is not clean (i.e. a push is needed or pending). - std::vector current_hashes() const; - - // Returns true if this object contains updated data that has not yet been confirmed stored on - // the server. This will be true whenever `is_clean()` is false: that is, if we are currently - // "dirty" (i.e. have changes that haven't been pushed) or are still awaiting confirmation of - // storage of the most recent serialized push data. - virtual bool needs_push() const; - - // Returns a tuple of three elements: - // - the seqno value of the data - // - the data message to push to the server - // - a list of known message hashes that are obsoleted by this push. - // - // Additionally, if the internal state is currently dirty (i.e. there are unpushed changes), the - // internal state will be marked as awaiting-confirmation. Any further data changes made after - // this call will re-dirty the data (incrementing seqno and requiring another push). - // - // The client is expected to send a sequence request to the server that stores the message and - // deletes the hashes (if any). It is strongly recommended to use a sequence rather than a - // batch so that the deletions won't happen if the store fails for some reason. - // - // Upon successful completion of the store+deletion requests the client should call - // `confirm_pushed` with the seqno value to confirm that the message has been stored. - // - // Subclasses that need to perform pre-push tasks (such as pruning stale data) can override this - // to prune and then call the base method to perform the actual push generation. - virtual std::tuple> push(); - - // Should be called after the push is confirmed stored on the storage server swarm to let the - // object know the config message has been stored and, ideally, that the obsolete messages - // returned by `push()` are deleted. Once this is called `needs_push` will start returning - // false until something changes. Takes the seqno that was pushed so that the object can ensure - // that the latest version was pushed (i.e. in case there have been other changes since the - // `push()` call that returned this seqno). - // - // Ideally the caller should have both stored the returned message and deleted the given - // messages. The deletion step isn't critical (it is just cleanup) and callers should call this - // as long as the store succeeded even if there were errors in the deletions. - // - // It is safe to call this multiple times with the same seqno value, and with out-of-order - // seqnos (e.g. calling with seqno 122 after having called with 123; the duplicates and earlier - // ones will just be ignored). - virtual void confirm_pushed(seqno_t seqno, std::string msg_hash); - - // Returns a dump of the current state for storage in the database; this value would get passed - // into the constructor to reconstitute the object (including the push/not pushed status). This - // method is *not* virtual: if subclasses need to store extra data they should set it in the - // `subclass_data` field. - ustring dump(); - - // Returns true if something has changed since the last call to `dump()` that requires calling - // and saving the `dump()` data again. - virtual bool needs_dump() const { return _needs_dump; } - - // Encryption key methods. For classes that have a single, static key (such as user profile - // storage types) these methods typically don't need to be used: the subclass calls them - // automatically. - - // Adds an encryption/decryption key, without removing existing keys. They key must be exactly - // 32 bytes long. The newly added key becomes the highest priority key (unless the - // `high_priority` argument is set to false' see below): it will be used for encryption of - // config pushes after the call, and will be tried first when decrypting, followed by keys - // present (if any) before this call. If the given key is already present in the key list then - // this call moves it to the front of the list (if not already at the front). - // - // If the `high_priority` argument is specified and false, then the key is added to the *end* of - // the key list instead of the beginning: that is, it will not replace the current - // highest-priority key used for encryption, but will still be usable for decryption of new - // incoming messages (after trying keys present before the call). If the key already exists - // then nothing happens with `high_priority=false` (in particular, it is *not* repositioned, in - // contrast to high_priority=true behaviour). - // - // Will throw a std::invalid_argument if the key is not 32 bytes. - void add_key(ustring_view key, bool high_priority = true); - - // Clears all stored encryption/decryption keys. This is typically immediately followed with - // one or more `add_key` call to replace existing keys. Returns the number of keys removed. - int clear_keys(); - - // Removes the given encryption/decryption key, if present. Returns true if it was found and - // removed, false if it was not in the key list. - // - // The optional second argument removes the key only from position `from` or higher. It is - // mainly for internal use and is usually omitted. - bool remove_key(ustring_view key, size_t from = 0); - - // Returns a vector of encryption keys, in priority order (i.e. element 0 is the encryption key, - // and the first decryption key). - std::vector get_keys() const; - - // Returns the number of encryption keys. - int key_count() const; - - // Returns true if the given key is already in the keys list. - bool has_key(ustring_view key) const; - - // Accesses the key at position i (0 if omitted). There must be at least one key, and i must be - // less than key_count(). The key at position 0 is used for encryption; for decryption all keys - // are tried in order, starting from position 0. - ustring_view key(size_t i = 0) const { - assert(i < _keys_size); - return {_keys[i].data(), _keys[i].size()}; - } -}; - -// The C++ struct we hold opaquely inside the C internals struct. This is designed so that any -// internals has the same layout so that it doesn't matter whether we unbox to an -// internals or internals. -template < - typename ConfigT = ConfigBase, - std::enable_if_t, int> = 0> -struct internals final { - std::unique_ptr config; - std::string error; - - // Dereferencing falls through to the ConfigBase object - ConfigT* operator->() { - if constexpr (std::is_same_v) - return config.get(); - else { - auto* c = dynamic_cast(config.get()); - assert(c); - return c; - } - } - const ConfigT* operator->() const { - if constexpr (std::is_same_v) - return config.get(); - else { - auto* c = dynamic_cast(config.get()); - assert(c); - return c; - } - } - ConfigT& operator*() { return *operator->(); } - const ConfigT& operator*() const { return *operator->(); } -}; - -template , int> = 0> -inline internals& unbox(config_object* conf) { - return *static_cast*>(conf->internals); -} -template , int> = 0> -inline const internals& unbox(const config_object* conf) { - return *static_cast*>(conf->internals); -} - -// Sets an error message in the internals.error string and updates the last_error pointer in the -// outer (C) config_object struct to point at it. -void set_error(config_object* conf, std::string e); - -// Same as above, but gets the error string out of an exception and passed through a return value. -// Intended to simplify catch-and-return-error such as: -// try { -// whatever(); -// } catch (const std::exception& e) { -// return set_error(conf, LIB_SESSION_ERR_OHNOES, e); -// } -inline int set_error(config_object* conf, int errcode, const std::exception& e) { - set_error(conf, e.what()); - return errcode; -} - -// Copies a value contained in a string into a new malloced char buffer, returning the buffer and -// size via the two pointer arguments. -void copy_out(ustring_view data, unsigned char** out, size_t* outlen); - -} // namespace session::config diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/community.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/community.h deleted file mode 100644 index 9530af967..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/community.h +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include - -// Maximum string length of a community base URL -extern const size_t COMMUNITY_BASE_URL_MAX_LENGTH; - -// Maximum string length of a community room token -extern const size_t COMMUNITY_ROOM_MAX_LENGTH; - -// Maximum string length of a full URL as produced by the community_make_full_url() function. -// Unlike the above constants, this *includes* space for a NULL string terminator. -extern const size_t COMMUNITY_FULL_URL_MAX_LENGTH; - -// Parses a community URL. Writes the canonical base url, room token, and pubkey bytes into the -// given pointers. base_url must be at least BASE_URL_MAX_LENGTH+1; room must be at least -// ROOM_MAX_LENGTH+1; and pubkey must be (at least) 32 bytes. -// -// Returns true if the url was parsed successfully, false if parsing failed (i.e. an invalid URL). -bool community_parse_full_url( - const char* full_url, char* base_url, char* room_token, unsigned char* pubkey); - -// Similar to the above, but allows a URL to omit the pubkey. If no pubkey is found, `pubkey` is -// left unchanged and `has_pubkey` is set to false; otherwise `pubkey` is written and `has_pubkey` -// is set to true. `pubkey` may be set to NULL, in which case it is never written. `has_pubkey` -// may be NULL in which case it is not set (typically both pubkey arguments would be null for cases -// where you don't care at all about the pubkey). -bool community_parse_partial_url( - const char* full_url, - char* base_url, - char* room_token, - unsigned char* pubkey, - bool* has_pubkey); - -// Produces a standard full URL from a given base_url (c string), room token (c string), and pubkey -// (fixed-length 32 byte buffer). The full URL is written to `full_url`, which must be at least -// COMMUNITY_FULL_URL_MAX_LENGTH in size. -void community_make_full_url( - const char* base_url, const char* room, const unsigned char* pubkey, char* full_url); - -#ifdef __cplusplus -} -#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/community.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/community.hpp deleted file mode 100644 index 04cc8ac8d..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/community.hpp +++ /dev/null @@ -1,254 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include - -namespace session::config { - -/// Base class for types representing a community; this base type handles the url/room/pubkey that -/// such a type need. Generally a class inherits from this to extend with the local -/// community-related values. -struct community { - - // 267 = len('https://') + 253 (max valid DNS name length) + len(':XXXXX') - static constexpr size_t BASE_URL_MAX_LENGTH = 267; - static constexpr size_t ROOM_MAX_LENGTH = 64; - - community() = default; - - // Constructs an empty community struct from url, room, and pubkey. `base_url` will be - // normalized if not already. pubkey is 32 bytes. - community(std::string_view base_url, std::string_view room, ustring_view pubkey); - - // Same as above, but takes pubkey as an encoded (hex or base32z or base64) string. - community(std::string_view base_url, std::string_view room, std::string_view pubkey_encoded); - - // Takes a combined room URL (e.g. https://whatever.com/r/Room?public_key=01234....), either - // new style (with /r/) or old style (without /r/). Note that the URL gets canonicalized so - // the resulting `base_url()` and `room()` values may not be exactly equal to what is given. - // - // See also `parse_full_url` which does the same thing but returns it in pieces rather than - // constructing a new `community` object. - explicit community(std::string_view full_url); - - // Replaces the baseurl/room/pubkey of this object from a URL. This parses the URL, then stores - // the values as if passed to set_base_url/set_room/set_pubkey. - // - // The base URL will be normalized; the room name will be case-preserving (but see `set_room` - // for info on limitations on "case-preserving", particularly for volatile configs); and the - // embedded pubkey must be encoded in one of hex, base32z, or base64. - void set_full_url(std::string_view full_url); - - // Replaces the base_url of this object. Note that changing the URL and then giving it to `set` - // will end up inserting a *new* record but not removing the *old* one (you need to erase first - // to do that). - void set_base_url(std::string_view new_url); - - // Changes the room token. This stores (or updates) the name as given as the localized room, - // and separately stores the normalized (lower-case) token. Note that the localized name does - // not persist across a push or dump in some config contexts (such as volatile room info). If - // the new room given here changes more than just case (i.e. if the normalized room token - // changes) then a call to `set` will end up inserting a *new* record but not removing the *old* - // one (you need to erase first to do that). - void set_room(std::string_view room); - - // Updates the pubkey of this community (typically this is not called directly but rather - // via `set_server` or during construction). Throws std::invalid_argument if the given - // pubkey does not look like a valid pubkey. The std::string_view version takes the pubkey - // as any of hex/base64/base32z. - // - // NOTE: the pubkey of all communities with the same URLs are stored in common, so changing - // one community pubkey (and storing) will affect all communities using the same community - // base URL. - void set_pubkey(ustring_view pubkey); - void set_pubkey(std::string_view pubkey); - - // Accesses the base url (i.e. not including room or pubkey). Always lower-case/normalized. - const std::string& base_url() const { return base_url_; } - - // Accesses the room token; this is case-preserving, where possible. In some contexts, however, - // such as volatile info, the case is not preserved and this will always return the normalized - // (lower-case) form rather than the preferred form. - const std::string& room() const { return localized_room_ ? *localized_room_ : room_; } - - // Accesses the normalized room token, i.e. always lower-case. - const std::string& room_norm() const { return room_; } - - const ustring& pubkey() const { return pubkey_; } // Accesses the server pubkey (32 bytes). - std::string pubkey_hex() const; // Accesses the server pubkey as hex (64 hex digits). - std::string pubkey_b32z() const; // Accesses the server pubkey as base32z (52 alphanumeric - // digits) - std::string pubkey_b64() const; // Accesses the server pubkey as unpadded base64 (43 from - // alphanumeric, '+', and '/'). - - // Constructs and returns the full URL for this room. See below. - std::string full_url() const; - - // Constructs and returns the full URL for a given base, room, and pubkey. Currently this - // returns it in a Session-compatibility form (https://server.com/RoomName?public_key=....), but - // future versions are expected to change to use (https://server.com/r/RoomName?public_key=...), - // which this library also accepts. - static std::string full_url( - std::string_view base_url, std::string_view room, ustring_view pubkey); - - // Takes a base URL as input and returns it in canonical form. This involves doing things - // like lower casing it and removing redundant ports (e.g. :80 when using http://). Throws - // std::invalid_argument if given an invalid base URL. - static std::string canonical_url(std::string_view url); - - // Takes a room token and returns it in canonical form (i.e. lower-cased). Throws - // std::invalid_argument if given an invalid room token (e.g. too long, or containing token - // other than a-z, 0-9, -, _). - static std::string canonical_room(std::string_view room); - - // Same as above, but modifies the argument in-place instead of returning a modified - // copy. - static void canonicalize_url(std::string& url); - static void canonicalize_room(std::string& room); - - // Takes a full room URL, splits it up into canonical url (see above), room, and server - // pubkey. We take both the deprecated form (e.g. - // https://example.com/SomeRoom?public_key=...) and new form - // (https://example.com/r/SomeRoom?public_key=...). The public_key is typically specified - // in hex (64 digits), but we also accept base64 (43 chars or 44 with padding) and base32z - // (52 chars) encodings (for slightly shorter URLs). - // - // The returned URL is normalized (lower-cased, and cleaned up). - // - // The returned room name is *not* normalized, that is, it preserve case. - // - // Throw std::invalid_argument if anything in the URL is unparseable or invalid. - static std::tuple parse_full_url(std::string_view full_url); - - // Takes a full or partial room URL (partial here meaning missing the ?public_key=...) and - // splits it up into canonical url, room, and (if present) pubkey. - static std::tuple> parse_partial_url( - std::string_view url); - - protected: - // The canonical base url and room (i.e. lower-cased, URL cleaned up): - std::string base_url_, room_; - // The localized token of this room, that is, with case preserved (so `room_` could be - // `someroom` and this could `SomeRoom`). Omitted if not available. - std::optional localized_room_; - // server pubkey - ustring pubkey_; - - // Construction without a pubkey for when pubkey isn't known yet but will be set shortly - // after constructing (or when isn't needed, such as when deleting). - community(std::string_view base_url, std::string_view room); -}; - -struct comm_iterator_helper { - - comm_iterator_helper(dict::const_iterator it_server, dict::const_iterator end_server) : - it_server{std::move(it_server)}, end_server{std::move(end_server)} {} - - std::optional it_server, end_server, it_room, end_room; - - bool operator==(const comm_iterator_helper& other) const { - return it_server == other.it_server && it_room == other.it_room; - } - - void next_server() { - ++*it_server; - it_room.reset(); - end_room.reset(); - } - - bool done() const { return !it_server || *it_server == *end_server; } - - template - bool load(std::shared_ptr& val) { - while (it_server) { - if (*it_server == *end_server) { - it_server.reset(); - end_server.reset(); - return false; - } - - auto& [base_url, server_info] = **it_server; - auto* server_info_dict = std::get_if(&server_info); - if (!server_info_dict) { - next_server(); - continue; - } - - const std::string* pubkey_raw = nullptr; - if (auto pubkey_it = server_info_dict->find("#"); pubkey_it != server_info_dict->end()) - if (auto* pk_sc = std::get_if(&pubkey_it->second)) - pubkey_raw = std::get_if(pk_sc); - - if (!pubkey_raw) { - next_server(); - continue; - } - - ustring_view pubkey{ - reinterpret_cast(pubkey_raw->data()), pubkey_raw->size()}; - - if (!it_room) { - if (auto rit = server_info_dict->find("R"); - rit != server_info_dict->end() && std::holds_alternative(rit->second)) { - auto& rooms_dict = std::get(rit->second); - it_room = rooms_dict.begin(); - end_room = rooms_dict.end(); - } else { - next_server(); - continue; - } - } - - while (it_room) { - if (*it_room == *end_room) { - it_room.reset(); - end_room.reset(); - break; - } - - auto& [room, data] = **it_room; - auto* data_dict = std::get_if(&data); - if (!data_dict) { - ++*it_room; - continue; - } - - val = std::make_shared(Comm{}); - auto& og = std::get(*val); - try { - og.set_base_url(base_url); - og.set_room(room); // Will be replaced with "n" in the `.load` below - og.set_pubkey(pubkey); - og.load(*data_dict); - } catch (const std::exception& e) { - ++*it_room; - continue; - } - return true; - } - - ++*it_server; - } - - return false; - } - - bool advance() { - if (it_room) { - ++*it_room; - return true; - } - if (it_server) { - ++*it_server; - return true; - } - return false; - } -}; - -} // namespace session::config diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.h deleted file mode 100644 index f93d55857..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.h +++ /dev/null @@ -1,160 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include "base.h" -#include "expiring.h" -#include "notify.h" -#include "profile_pic.h" -#include "util.h" - -// Maximum length of a contact name/nickname, in bytes (not including the null terminator). -extern const size_t CONTACT_MAX_NAME_LENGTH; - -typedef struct contacts_contact { - char session_id[67]; // in hex; 66 hex chars + null terminator. - - // These two will be 0-length strings when unset: - char name[101]; - char nickname[101]; - user_profile_pic profile_pic; - - bool approved; - bool approved_me; - bool blocked; - - int priority; - CONVO_NOTIFY_MODE notifications; - int64_t mute_until; - - CONVO_EXPIRATION_MODE exp_mode; - int exp_seconds; - - int64_t created; // unix timestamp (seconds) - -} contacts_contact; - -/// Constructs a contacts config object and sets a pointer to it in `conf`. -/// -/// \param ed25519_secretkey must be the 32-byte secret key seed value. (You can also pass the -/// pointer to the beginning of the 64-byte value libsodium calls the "secret key" as the first 32 -/// bytes of that are the seed). This field cannot be null. -/// -/// \param dump - if non-NULL this restores the state from the dumped byte string produced by a past -/// instantiation's call to `dump()`. To construct a new, empty object this should be NULL. -/// -/// \param dumplen - the length of `dump` when restoring from a dump, or 0 when `dump` is NULL. -/// -/// \param error - the pointer to a buffer in which we will write an error string if an error -/// occurs; error messages are discarded if this is given as NULL. If non-NULL this must be a -/// buffer of at least 256 bytes. -/// -/// Returns 0 on success; returns a non-zero error code and write the exception message as a -/// C-string into `error` (if not NULL) on failure. -/// -/// When done with the object the `config_object` must be destroyed by passing the pointer to -/// config_free() (in `session/config/base.h`). -int contacts_init( - config_object** conf, - const unsigned char* ed25519_secretkey, - const unsigned char* dump, - size_t dumplen, - char* error) __attribute__((warn_unused_result)); - -/// Fills `contact` with the contact info given a session ID (specified as a null-terminated hex -/// string), if the contact exists, and returns true. If the contact does not exist then `contact` -/// is left unchanged and false is returned. -bool contacts_get(config_object* conf, contacts_contact* contact, const char* session_id) - __attribute__((warn_unused_result)); - -/// Same as the above except that when the contact does not exist, this sets all the contact fields -/// to defaults and loads it with the given session_id. -/// -/// Returns true as long as it is given a valid session_id. A false return is considered an error, -/// and means the session_id was not a valid session_id. -/// -/// This is the method that should usually be used to create or update a contact, followed by -/// setting fields in the contact, and then giving it to contacts_set(). -bool contacts_get_or_construct( - config_object* conf, contacts_contact* contact, const char* session_id) - __attribute__((warn_unused_result)); - -/// Adds or updates a contact from the given contact info struct. -void contacts_set(config_object* conf, const contacts_contact* contact); - -// NB: wrappers for set_name, set_nickname, etc. C++ methods are deliberately omitted as they would -// save very little in actual calling code. The procedure for updating a single field without them -// is simple enough; for example to update `approved` and leave everything else unchanged: -// -// contacts_contact c; -// if (contacts_get_or_construct(conf, &c, some_session_id)) { -// const char* new_nickname = "Joe"; -// c.approved = new_nickname; -// contacts_set_or_create(conf, &c); -// } else { -// // some_session_id was invalid! -// } - -/// Erases a contact from the contact list. session_id is in hex. Returns true if the contact was -/// found and removed, false if the contact was not present. You must not call this during -/// iteration; see details below. -bool contacts_erase(config_object* conf, const char* session_id); - -/// Returns the number of contacts. -size_t contacts_size(const config_object* conf); - -/// Functions for iterating through the entire contact list, in sorted order. Intended use is: -/// -/// contacts_contact c; -/// contacts_iterator *it = contacts_iterator_new(contacts); -/// for (; !contacts_iterator_done(it, &c); contacts_iterator_advance(it)) { -/// // c.session_id, c.nickname, etc. are loaded -/// } -/// contacts_iterator_free(it); -/// -/// It is permitted to modify records (e.g. with a call to `contacts_set`) and add records while -/// iterating. -/// -/// If you need to remove while iterating then usage is slightly different: you must advance the -/// iteration by calling either contacts_iterator_advance if not deleting, or -/// contacts_iterator_erase to erase and advance. Usage looks like this: -/// -/// contacts_contact c; -/// contacts_iterator *it = contacts_iterator_new(contacts); -/// while (!contacts_iterator_done(it, &c)) { -/// // c.session_id, c.nickname, etc. are loaded -/// -/// bool should_delete = /* ... */; -/// -/// if (should_delete) -/// contacts_iterator_erase(it); -/// else -/// contacts_iterator_advance(it); -/// } -/// contacts_iterator_free(it); -/// -/// - -typedef struct contacts_iterator { - void* _internals; -} contacts_iterator; - -// Starts a new iterator. -contacts_iterator* contacts_iterator_new(const config_object* conf); -// Frees an iterator once no longer needed. -void contacts_iterator_free(contacts_iterator* it); - -// Returns true if iteration has reached the end. Otherwise `c` is populated and false is returned. -bool contacts_iterator_done(contacts_iterator* it, contacts_contact* c); - -// Advances the iterator. -void contacts_iterator_advance(contacts_iterator* it); - -// Erases the current contact while advancing the iterator to the next contact in the iteration. -void contacts_iterator_erase(config_object* conf, contacts_iterator* it); - -#ifdef __cplusplus -} // extern "C" -#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.hpp deleted file mode 100644 index e0212dbdc..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.hpp +++ /dev/null @@ -1,231 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "base.hpp" -#include "expiring.hpp" -#include "namespaces.hpp" -#include "notify.hpp" -#include "profile_pic.hpp" - -extern "C" struct contacts_contact; - -using namespace std::literals; - -namespace session::config { - -/// keys used in this config, either currently or in the past (so that we don't reuse): -/// -/// c - dict of contacts; within this dict each key is the session pubkey (binary, 33 bytes) and -/// value is a dict containing keys: -/// -/// n - contact name (string). This is always serialized, even if empty (but empty indicates -/// no name) so that we always have at least one key set (required to keep the dict value -/// alive as empty dicts get pruned). -/// N - contact nickname (string) -/// p - profile url (string) -/// q - profile decryption key (binary) -/// a - 1 if approved, omitted otherwise (int) -/// A - 1 if remote has approved me, omitted otherwise (int) -/// b - 1 if contact is blocked, omitted otherwise -/// @ - notification setting (int). Omitted = use default setting; 1 = all; 2 = disabled. -/// ! - mute timestamp: if this is set then notifications are to be muted until the given unix -/// timestamp (seconds, not milliseconds). -/// + - the conversation priority; -1 means hidden; omitted means not pinned; otherwise an -/// integer value >0, where a higher priority means the conversation is meant to appear -/// earlier in the pinned conversation list. -/// e - Disappearing messages expiration type. Omitted if disappearing messages are not enabled -/// for the conversation with this contact; 1 for delete-after-send, and 2 for -/// delete-after-read. -/// E - Disappearing message timer, in seconds. Omitted when `e` is omitted. -/// j - Unix timestamp (seconds) when the contact was created ("j" to match user_groups -/// equivalent "j"oined field). Omitted if 0. - -/// Struct containing contact info. -struct contact_info { - static constexpr size_t MAX_NAME_LENGTH = 100; - - std::string session_id; // in hex - std::string name; - std::string nickname; - profile_pic profile_picture; - bool approved = false; - bool approved_me = false; - bool blocked = false; - int priority = 0; // If >0 then this message is pinned; higher values mean higher priority - // (i.e. pinned earlier in the pinned list). If negative then this - // conversation is hidden. Otherwise (0) this is a regular, unpinned - // conversation. - notify_mode notifications = notify_mode::defaulted; - int64_t mute_until = 0; // If non-zero, disable notifications until the given unix timestamp - // (overriding whatever the current `notifications` value is until the - // timestamp expires). - expiration_mode exp_mode = expiration_mode::none; // The expiry time; none if not expiring. - std::chrono::seconds exp_timer{0}; // The expiration timer (in seconds) - int64_t created = 0; // Unix timestamp when this contact was added - - explicit contact_info(std::string sid); - - // Internal ctor/method for C API implementations: - contact_info(const struct contacts_contact& c); // From c struct - void into(contacts_contact& c) const; // Into c struct - - // Sets a name or nickname; this is exactly the same as assigning to .name/.nickname directly, - // except that we throw an exception if the given name is longer than MAX_NAME_LENGTH. - void set_name(std::string name); - void set_nickname(std::string nickname); - - private: - friend class Contacts; - void load(const dict& info_dict); -}; - -class Contacts : public ConfigBase { - - public: - // No default constructor - Contacts() = delete; - - /// Constructs a contact list from existing data (stored from `dump()`) and the user's secret - /// key for generating the data encryption key. To construct a blank list (i.e. with no - /// pre-existing dumped data to load) pass `std::nullopt` as the second argument. - /// - /// \param ed25519_secretkey - contains the libsodium secret key used to encrypt/decrypt the - /// data when pushing/pulling from the swarm. This can either be the full 64-byte value (which - /// is technically the 32-byte seed followed by the 32-byte pubkey), or just the 32-byte seed of - /// the secret key. - /// - /// \param dumped - either `std::nullopt` to construct a new, empty object; or binary state data - /// that was previously dumped from an instance of this class by calling `dump()`. - Contacts(ustring_view ed25519_secretkey, std::optional dumped); - - Namespace storage_namespace() const override { return Namespace::Contacts; } - - const char* encryption_domain() const override { return "Contacts"; } - - /// Looks up and returns a contact by session ID (hex). Returns nullopt if the session ID was - /// not found, otherwise returns a filled out `contact_info`. - std::optional get(std::string_view pubkey_hex) const; - - /// Similar to get(), but if the session ID does not exist this returns a filled-out - /// contact_info containing the session_id (all other fields will be empty/defaulted). This is - /// intended to be combined with `set` to set-or-create a record. - /// - /// NB: calling this does *not* add the session id to the contact list when called: that - /// requires also calling `set` with this value. - contact_info get_or_construct(std::string_view pubkey_hex) const; - - /// Sets or updates multiple contact info values at once with the given info. The usual use is - /// to access the current info, change anything desired, then pass it back into set_contact, - /// e.g.: - /// - /// auto c = contacts.get_or_construct(pubkey); - /// c.name = "Session User 42"; - /// c.nickname = "BFF"; - /// contacts.set(c); - void set(const contact_info& contact); - - /// Alternative to `set()` for setting a single field. (If setting multiple fields at once you - /// should use `set()` instead). - void set_name(std::string_view session_id, std::string name); - void set_nickname(std::string_view session_id, std::string nickname); - void set_profile_pic(std::string_view session_id, profile_pic pic); - void set_approved(std::string_view session_id, bool approved); - void set_approved_me(std::string_view session_id, bool approved_me); - void set_blocked(std::string_view session_id, bool blocked); - void set_priority(std::string_view session_id, int priority); - void set_notifications(std::string_view session_id, notify_mode notifications); - void set_expiry( - std::string_view session_id, - expiration_mode exp_mode, - std::chrono::seconds expiration_timer = 0min); - void set_created(std::string_view session_id, int64_t timestamp); - - /// Removes a contact, if present. Returns true if it was found and removed, false otherwise. - /// Note that this removes all fields related to a contact, even fields we do not know about. - bool erase(std::string_view session_id); - - struct iterator; - - /// This works like erase, but takes an iterator to the contact to remove. The element is - /// removed and the iterator to the next element after the removed one is returned. This is - /// intended for use where elements are to be removed during iteration: see below for an - /// example. - iterator erase(iterator it); - - /// Returns the number of contacts. - size_t size() const; - - /// Returns true if the contact list is empty. - bool empty() const { return size() == 0; } - - /// Iterators for iterating through all contacts. Typically you access this implicit via a for - /// loop over the `Contacts` object: - /// - /// for (auto& contact : contacts) { - /// // use contact.session_id, contact.name, etc. - /// } - /// - /// This iterates in sorted order through the session_ids. - /// - /// It is permitted to modify and add records while iterating (e.g. by modifying `contact` and - /// then calling set()). - /// - /// If you need to erase the current contact during iteration then care is required: you need to - /// advance the iterator via the iterator version of erase when erasing an element rather than - /// incrementing it regularly. For example: - /// - /// for (auto it = contacts.begin(); it != contacts.end(); ) { - /// if (should_remove(*it)) - /// it = contacts.erase(it); - /// else - /// ++it; - /// } - /// - /// Alternatively, you can use the first version with two loops: the first loop through all - /// contacts doesn't erase but just builds a vector of IDs to erase, then the second loops - /// through that vector calling `erase()` for each one. - /// - iterator begin() const { return iterator{data["c"].dict()}; } - iterator end() const { return iterator{nullptr}; } - - using iterator_category = std::input_iterator_tag; - using value_type = contact_info; - using reference = value_type&; - using pointer = value_type*; - using difference_type = std::ptrdiff_t; - - struct iterator { - private: - std::shared_ptr _val; - dict::const_iterator _it; - const dict* _contacts; - void _load_info(); - iterator(const dict* contacts) : _contacts{contacts} { - if (_contacts) { - _it = _contacts->begin(); - _load_info(); - } - } - friend class Contacts; - - public: - bool operator==(const iterator& other) const; - bool operator!=(const iterator& other) const { return !(*this == other); } - bool done() const; // Equivalent to comparing against the end iterator - contact_info& operator*() const { return *_val; } - contact_info* operator->() const { return _val.get(); } - iterator& operator++(); - iterator operator++(int) { - auto copy{*this}; - ++*this; - return copy; - } - }; -}; - -} // namespace session::config diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.h deleted file mode 100644 index 8d141b801..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.h +++ /dev/null @@ -1,229 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include "base.h" -#include "profile_pic.h" - -typedef struct convo_info_volatile_1to1 { - char session_id[67]; // in hex; 66 hex chars + null terminator. - - int64_t last_read; // milliseconds since unix epoch - bool unread; // true if the conversation is explicitly marked unread -} convo_info_volatile_1to1; - -typedef struct convo_info_volatile_community { - char base_url[268]; // null-terminated (max length 267), normalized (i.e. always lower-case, - // only has port if non-default, has trailing / removed) - char room[65]; // null-terminated (max length 64), normalized (always lower-case) - unsigned char pubkey[32]; // 32 bytes (not terminated, can contain nulls) - - int64_t last_read; // ms since unix epoch - bool unread; // true if marked unread -} convo_info_volatile_community; - -typedef struct convo_info_volatile_legacy_group { - char group_id[67]; // in hex; 66 hex chars + null terminator. Looks just like a Session ID, - // though isn't really one. - - int64_t last_read; // ms since unix epoch - bool unread; // true if marked unread -} convo_info_volatile_legacy_group; - -/// Constructs a conversations config object and sets a pointer to it in `conf`. -/// -/// \param ed25519_secretkey must be the 32-byte secret key seed value. (You can also pass the -/// pointer to the beginning of the 64-byte value libsodium calls the "secret key" as the first 32 -/// bytes of that are the seed). This field cannot be null. -/// -/// \param dump - if non-NULL this restores the state from the dumped byte string produced by a past -/// instantiation's call to `dump()`. To construct a new, empty object this should be NULL. -/// -/// \param dumplen - the length of `dump` when restoring from a dump, or 0 when `dump` is NULL. -/// -/// \param error - the pointer to a buffer in which we will write an error string if an error -/// occurs; error messages are discarded if this is given as NULL. If non-NULL this must be a -/// buffer of at least 256 bytes. -/// -/// Returns 0 on success; returns a non-zero error code and write the exception message as a -/// C-string into `error` (if not NULL) on failure. -/// -/// When done with the object the `config_object` must be destroyed by passing the pointer to -/// config_free() (in `session/config/base.h`). -int convo_info_volatile_init( - config_object** conf, - const unsigned char* ed25519_secretkey, - const unsigned char* dump, - size_t dumplen, - char* error) __attribute__((warn_unused_result)); - -/// Fills `convo` with the conversation info given a session ID (specified as a null-terminated hex -/// string), if the conversation exists, and returns true. If the conversation does not exist then -/// `convo` is left unchanged and false is returned. If an error occurs, false is returned and -/// `conf->last_error` will be set to non-NULL containing the error string (if no error occurs, such -/// as in the case where the conversation merely doesn't exist, `last_error` will be set to NULL). -bool convo_info_volatile_get_1to1( - config_object* conf, convo_info_volatile_1to1* convo, const char* session_id) - __attribute__((warn_unused_result)); - -/// Same as the above except that when the conversation does not exist, this sets all the convo -/// fields to defaults and loads it with the given session_id. -/// -/// Returns true as long as it is given a valid session_id. A false return is considered an error, -/// and means the session_id was not a valid session_id. In such a case `conf->last_error` will be -/// set to an error string. -/// -/// This is the method that should usually be used to create or update a conversation, followed by -/// setting fields in the convo, and then giving it to convo_info_volatile_set(). -bool convo_info_volatile_get_or_construct_1to1( - config_object* conf, convo_info_volatile_1to1* convo, const char* session_id) - __attribute__((warn_unused_result)); - -/// community versions of the 1-to-1 functions: -/// -/// Gets a community convo info. `base_url` and `room` are null-terminated c strings; pubkey is -/// 32 bytes. base_url and room will always be lower-cased (if not already). -/// -/// Error handling works the same as the 1-to-1 version. -bool convo_info_volatile_get_community( - config_object* conf, - convo_info_volatile_community* comm, - const char* base_url, - const char* room) __attribute__((warn_unused_result)); -bool convo_info_volatile_get_or_construct_community( - config_object* conf, - convo_info_volatile_community* convo, - const char* base_url, - const char* room, - unsigned const char* pubkey) __attribute__((warn_unused_result)); - -/// Fills `convo` with the conversation info given a legacy group ID (specified as a null-terminated -/// hex string), if the conversation exists, and returns true. If the conversation does not exist -/// then `convo` is left unchanged and false is returned. On error, false is returned and the error -/// is set in conf->last_error (on non-error, last_error is cleared). -bool convo_info_volatile_get_legacy_group( - config_object* conf, convo_info_volatile_legacy_group* convo, const char* id) - __attribute__((warn_unused_result)); - -/// Same as the above except that when the conversation does not exist, this sets all the convo -/// fields to defaults and loads it with the given id. -/// -/// Returns true as long as it is given a valid legacy group id (i.e. same format as a session id). -/// A false return is considered an error, and means the id was not a valid session id; an error -/// string will be set in `conf->last_error`. -/// -/// This is the method that should usually be used to create or update a conversation, followed by -/// setting fields in the convo, and then giving it to convo_info_volatile_set(). -bool convo_info_volatile_get_or_construct_legacy_group( - config_object* conf, convo_info_volatile_legacy_group* convo, const char* id) - __attribute__((warn_unused_result)); - -/// Adds or updates a conversation from the given convo info -void convo_info_volatile_set_1to1(config_object* conf, const convo_info_volatile_1to1* convo); -void convo_info_volatile_set_community( - config_object* conf, const convo_info_volatile_community* convo); -void convo_info_volatile_set_legacy_group( - config_object* conf, const convo_info_volatile_legacy_group* convo); - -/// Erases a conversation from the conversation list. Returns true if the conversation was found -/// and removed, false if the conversation was not present. You must not call this during -/// iteration; see details below. -bool convo_info_volatile_erase_1to1(config_object* conf, const char* session_id); -bool convo_info_volatile_erase_community( - config_object* conf, const char* base_url, const char* room); -bool convo_info_volatile_erase_legacy_group(config_object* conf, const char* group_id); - -/// Returns the number of conversations. -size_t convo_info_volatile_size(const config_object* conf); -/// Returns the number of conversations of the specific type. -size_t convo_info_volatile_size_1to1(const config_object* conf); -size_t convo_info_volatile_size_communities(const config_object* conf); -size_t convo_info_volatile_size_legacy_groups(const config_object* conf); - -/// Functions for iterating through the entire conversation list. Intended use is: -/// -/// convo_info_volatile_1to1 c1; -/// convo_info_volatile_community c2; -/// convo_info_volatile_legacy_group c3; -/// convo_info_volatile_iterator *it = convo_info_volatile_iterator_new(my_convos); -/// for (; !convo_info_volatile_iterator_done(it); convo_info_volatile_iterator_advance(it)) { -/// if (convo_info_volatile_it_is_1to1(it, &c1)) { -/// // use c1.whatever -/// } else if (convo_info_volatile_it_is_community(it, &c2)) { -/// // use c2.whatever -/// } else if (convo_info_volatile_it_is_legacy_group(it, &c3)) { -/// // use c3.whatever -/// } -/// } -/// convo_info_volatile_iterator_free(it); -/// -/// It is permitted to modify records (e.g. with a call to one of the `convo_info_volatile_set_*` -/// functions) and add records while iterating. -/// -/// If you need to remove while iterating then usage is slightly different: you must advance the -/// iteration by calling either convo_info_volatile_iterator_advance if not deleting, or -/// convo_info_volatile_iterator_erase to erase and advance. Usage looks like this: -/// -/// convo_info_volatile_1to1 c1; -/// convo_info_volatile_iterator *it = convo_info_volatile_iterator_new(my_convos); -/// while (!convo_info_volatile_iterator_done(it)) { -/// if (convo_it_is_1to1(it, &c1)) { -/// bool should_delete = /* ... */; -/// if (should_delete) -/// convo_info_volatile_iterator_erase(it); -/// else -/// convo_info_volatile_iterator_advance(it); -/// } else { -/// convo_info_volatile_iterator_advance(it); -/// } -/// } -/// convo_info_volatile_iterator_free(it); -/// - -typedef struct convo_info_volatile_iterator convo_info_volatile_iterator; - -// Starts a new iterator that iterates over all conversations. -convo_info_volatile_iterator* convo_info_volatile_iterator_new(const config_object* conf); - -// The same as `convo_info_volatile_iterator_new` except that this iterates *only* over one type of -// conversation. You still need to use `convo_info_volatile_it_is_1to1` (or the alternatives) to -// load the data in each pass of the loop. (You can, however, safely ignore the bool return value -// of the `it_is_whatever` function: it will always be true for the particular type being iterated -// over). -convo_info_volatile_iterator* convo_info_volatile_iterator_new_1to1(const config_object* conf); -convo_info_volatile_iterator* convo_info_volatile_iterator_new_communities( - const config_object* conf); -convo_info_volatile_iterator* convo_info_volatile_iterator_new_legacy_groups( - const config_object* conf); - -// Frees an iterator once no longer needed. -void convo_info_volatile_iterator_free(convo_info_volatile_iterator* it); - -// Returns true if iteration has reached the end. -bool convo_info_volatile_iterator_done(convo_info_volatile_iterator* it); - -// Advances the iterator. -void convo_info_volatile_iterator_advance(convo_info_volatile_iterator* it); - -// If the current iterator record is a 1-to-1 conversation this sets the details into `c` and -// returns true. Otherwise it returns false. -bool convo_info_volatile_it_is_1to1(convo_info_volatile_iterator* it, convo_info_volatile_1to1* c); - -// If the current iterator record is a community conversation this sets the details into `c` and -// returns true. Otherwise it returns false. -bool convo_info_volatile_it_is_community( - convo_info_volatile_iterator* it, convo_info_volatile_community* c); - -// If the current iterator record is a legacy group conversation this sets the details into `c` and -// returns true. Otherwise it returns false. -bool convo_info_volatile_it_is_legacy_group( - convo_info_volatile_iterator* it, convo_info_volatile_legacy_group* c); - -// Erases the current convo while advancing the iterator to the next convo in the iteration. -void convo_info_volatile_iterator_erase(config_object* conf, convo_info_volatile_iterator* it); - -#ifdef __cplusplus -} // extern "C" -#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.hpp deleted file mode 100644 index 149fdcbce..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.hpp +++ /dev/null @@ -1,347 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "base.hpp" -#include "community.hpp" - -using namespace std::literals; - -extern "C" { -struct convo_info_volatile_1to1; -struct convo_info_volatile_community; -struct convo_info_volatile_legacy_group; -} - -namespace session::config { - -class ConvoInfoVolatile; - -/// keys used in this config, either currently or in the past (so that we don't reuse): -/// -/// Note that this is a high-frequency object, intended only for properties that change frequently ( -/// (currently just the read timestamp for each conversation). -/// -/// 1 - dict of one-to-one conversations. Each key is the Session ID of the contact (in hex). -/// Values are dicts with keys: -/// r - the unix timestamp (in integer milliseconds) of the last-read message. Always -/// included, but will be 0 if no messages are read. -/// u - will be present and set to 1 if this conversation is specifically marked unread. -/// -/// o - community conversations. This is a nested dict where the outer keys are the BASE_URL of the -/// community and the outer value is a dict containing: -/// - `#` -- the 32-byte server pubkey -/// - `R` -- dict of rooms on the server; each key is the lower-case room name, value is a dict -/// containing keys: -/// r - the unix timestamp (in integer milliseconds) of the last-read message. Always -/// included, but will be 0 if no messages are read. -/// u - will be present and set to 1 if this conversation is specifically marked unread. -/// -/// C - legacy group conversations (aka closed groups). The key is the group identifier (which -/// looks indistinguishable from a Session ID, but isn't really a proper Session ID). Values -/// are dicts with keys: -/// r - the unix timestamp (integer milliseconds) of the last-read message. Always included, -/// but will be 0 if no messages are read. -/// u - will be present and set to 1 if this conversation is specifically marked unread. -/// -/// c - reserved for future tracking of new group conversations. - -namespace convo { - - struct base { - int64_t last_read = 0; - bool unread = false; - - protected: - void load(const dict& info_dict); - }; - - struct one_to_one : base { - std::string session_id; // in hex - - // Constructs an empty one_to_one from a session_id. Session ID can be either bytes (33) or - // hex (66). - explicit one_to_one(std::string&& session_id); - explicit one_to_one(std::string_view session_id); - - // Internal ctor/method for C API implementations: - one_to_one(const struct convo_info_volatile_1to1& c); // From c struct - void into(convo_info_volatile_1to1& c) const; // Into c struct - - friend class session::config::ConvoInfoVolatile; - }; - - struct community : config::community, base { - - using config::community::community; - - // Internal ctor/method for C API implementations: - community(const convo_info_volatile_community& c); // From c struct - void into(convo_info_volatile_community& c) const; // Into c struct - - friend class session::config::ConvoInfoVolatile; - friend struct session::config::comm_iterator_helper; - }; - - struct legacy_group : base { - std::string id; // in hex, indistinguishable from a Session ID - - // Constructs an empty legacy_group from a quasi-session_id - explicit legacy_group(std::string&& group_id); - explicit legacy_group(std::string_view group_id); - - // Internal ctor/method for C API implementations: - legacy_group(const struct convo_info_volatile_legacy_group& c); // From c struct - void into(convo_info_volatile_legacy_group& c) const; // Into c struct - - private: - friend class session::config::ConvoInfoVolatile; - }; - - using any = std::variant; -} // namespace convo - -class ConvoInfoVolatile : public ConfigBase { - - public: - // No default constructor - ConvoInfoVolatile() = delete; - - /// Constructs a conversation list from existing data (stored from `dump()`) and the user's - /// secret key for generating the data encryption key. To construct a blank list (i.e. with no - /// pre-existing dumped data to load) pass `std::nullopt` as the second argument. - /// - /// \param ed25519_secretkey - contains the libsodium secret key used to encrypt/decrypt the - /// data when pushing/pulling from the swarm. This can either be the full 64-byte value (which - /// is technically the 32-byte seed followed by the 32-byte pubkey), or just the 32-byte seed of - /// the secret key. - /// - /// \param dumped - either `std::nullopt` to construct a new, empty object; or binary state data - /// that was previously dumped from an instance of this class by calling `dump()`. - ConvoInfoVolatile(ustring_view ed25519_secretkey, std::optional dumped); - - Namespace storage_namespace() const override { return Namespace::ConvoInfoVolatile; } - - const char* encryption_domain() const override { return "ConvoInfoVolatile"; } - - /// Our pruning ages. We ignore added conversations that are more than PRUNE_LOW before now, - /// and we active remove (when doing a new push) any conversations that are more than PRUNE_HIGH - /// before now. Clients can mostly ignore these and just add all conversations; the class just - /// transparently ignores (or removes) pruned values. - static constexpr auto PRUNE_LOW = 30 * 24h; - static constexpr auto PRUNE_HIGH = 45 * 24h; - - /// Overrides push() to prune stale last-read values before we do the push. - std::tuple> push() override; - - /// Looks up and returns a contact by session ID (hex). Returns nullopt if the session ID was - /// not found, otherwise returns a filled out `convo::one_to_one`. - std::optional get_1to1(std::string_view session_id) const; - - /// Looks up and returns a community conversation. Takes the base URL and room name (case - /// insensitive). Retuns nullopt if the community was not found, otherwise a filled out - /// `convo::community`. - std::optional get_community( - std::string_view base_url, std::string_view room) const; - - /// Shortcut for calling community::parse_partial_url then calling the above with the base url - /// and room. The URL is not required to contain the pubkey (if present it will be ignored). - std::optional get_community(std::string_view partial_url) const; - - /// Looks up and returns a legacy group conversation by ID. The ID looks like a hex Session ID, - /// but isn't really a Session ID. Returns nullopt if there is no record of the group - /// conversation. - std::optional get_legacy_group(std::string_view pubkey_hex) const; - - /// These are the same as the above methods (without "_or_construct" in the name), except that - /// when the conversation doesn't exist a new one is created, prefilled with the pubkey/url/etc. - convo::one_to_one get_or_construct_1to1(std::string_view session_id) const; - convo::legacy_group get_or_construct_legacy_group(std::string_view pubkey_hex) const; - - /// This is similar to get_community, except that it also takes the pubkey; the community is - /// looked up by the url & room; if not found, it is constructed using room, url, and pubkey; if - /// it *is* found, then it will always have the *input* pubkey, not the stored pubkey - /// (effectively the provided pubkey replaces the stored one in the returned object; this is not - /// applied to storage, however, unless/until the instance is given to `set()`). - /// - /// Note, however, that when modifying an object like this the update is *only* applied to the - /// returned object; like other fields, it is not updated in the internal state unless/until - /// that community instance is passed to `set()`. - convo::community get_or_construct_community( - std::string_view base_url, std::string_view room, std::string_view pubkey_hex) const; - convo::community get_or_construct_community( - std::string_view base_url, std::string_view room, ustring_view pubkey) const; - - // Shortcut for calling community::parse_full_url then calling the above - convo::community get_or_construct_community(std::string_view full_url) const; - - /// Inserts or replaces existing conversation info. For example, to update a 1-to-1 - /// conversation last read time you would do: - /// - /// auto info = conversations.get_or_construct_1to1(some_session_id); - /// info.last_read = new_unix_timestamp; - /// conversations.set(info); - /// - void set(const convo::one_to_one& c); - void set(const convo::legacy_group& c); - void set(const convo::community& c); - - void set(const convo::any& c); // Variant which can be any of the above - - protected: - void set_base(const convo::base& c, DictFieldProxy& info); - - // Drills into the nested dicts to access community details; if the second argument is - // non-nullptr then it will be set to the community's pubkey, if it exists. - DictFieldProxy community_field( - const convo::community& og, ustring_view* get_pubkey = nullptr) const; - - public: - /// Removes a one-to-one conversation. Returns true if found and removed, false if not present. - bool erase_1to1(std::string_view pubkey); - - /// Removes a community conversation record. Returns true if found and removed, false if not - /// present. Arguments are the same as `get_community`. - bool erase_community(std::string_view base_url, std::string_view room); - - /// Removes a legacy group conversation. Returns true if found and removed, false if not - /// present. - bool erase_legacy_group(std::string_view pubkey_hex); - - /// Removes a conversation taking the convo::whatever record (rather than the pubkey/url). - bool erase(const convo::one_to_one& c); - bool erase(const convo::community& c); - bool erase(const convo::legacy_group& c); - - bool erase(const convo::any& c); // Variant of any of them - - struct iterator; - - /// This works like erase, but takes an iterator to the conversation to remove. The element is - /// removed and the iterator to the next element after the removed one is returned. This is - /// intended for use where elements are to be removed during iteration: see below for an - /// example. - iterator erase(iterator it); - - /// Returns the number of conversations (of any type). - size_t size() const; - - /// Returns the number of 1-to-1, community, and legacy group conversations, respectively. - size_t size_1to1() const; - size_t size_communities() const; - size_t size_legacy_groups() const; - - /// Returns true if the conversation list is empty. - bool empty() const { return size() == 0; } - - /// Iterators for iterating through all conversations. Typically you access this implicit via a - /// for loop over the `ConvoInfoVolatile` object: - /// - /// for (auto& convo : conversations) { - /// if (auto* dm = std::get_if(&convo)) { - /// // use dm->session_id, dm->last_read, etc. - /// } else if (auto* og = std::get_if(&convo)) { - /// // use og->base_url, og->room, om->last_read, etc. - /// } else if (auto* lcg = std::get_if(&convo)) { - /// // use lcg->id, lcg->last_read - /// } - /// } - /// - /// This iterates through all conversations in sorted order (sorted first by convo type, then by - /// id within the type). - /// - /// It is permitted to modify and add records while iterating (e.g. by modifying one of the - /// `dm`/`og`/`lcg` and then calling set()). - /// - /// If you need to erase the current conversation during iteration then care is required: you - /// need to advance the iterator via the iterator version of erase when erasing an element - /// rather than incrementing it regularly. For example: - /// - /// for (auto it = conversations.begin(); it != conversations.end(); ) { - /// if (should_remove(*it)) - /// it = converations.erase(it); - /// else - /// ++it; - /// } - /// - /// Alternatively, you can use the first version with two loops: the first loop through all - /// converations doesn't erase but just builds a vector of IDs to erase, then the second loops - /// through that vector calling `erase_1to1()`/`erase_community()`/`erase_legacy_group()` for - /// each one. - /// - iterator begin() const { return iterator{data}; } - iterator end() const { return iterator{}; } - - template - struct subtype_iterator; - - /// Returns an iterator that iterates only through one type of conversations - subtype_iterator begin_1to1() const { return {data}; } - subtype_iterator begin_communities() const { return {data}; } - subtype_iterator begin_legacy_groups() const { return {data}; } - - using iterator_category = std::input_iterator_tag; - using value_type = std::variant; - using reference = value_type&; - using pointer = value_type*; - using difference_type = std::ptrdiff_t; - - struct iterator { - protected: - std::shared_ptr _val; - std::optional _it_11, _end_11, _it_lgroup, _end_lgroup; - std::optional _it_comm; - void _load_val(); - iterator() = default; // Constructs an end tombstone - explicit iterator( - const DictFieldRoot& data, - bool oneto1 = true, - bool communities = true, - bool legacy_groups = true); - friend class ConvoInfoVolatile; - - public: - bool operator==(const iterator& other) const; - bool operator!=(const iterator& other) const { return !(*this == other); } - bool done() const; // Equivalent to comparing against the end iterator - convo::any& operator*() const { return *_val; } - convo::any* operator->() const { return _val.get(); } - iterator& operator++(); - iterator operator++(int) { - auto copy{*this}; - ++*this; - return copy; - } - }; - - template - struct subtype_iterator : iterator { - protected: - subtype_iterator(const DictFieldRoot& data) : - iterator( - data, - std::is_same_v, - std::is_same_v, - std::is_same_v) {} - friend class ConvoInfoVolatile; - - public: - ConvoType& operator*() const { return std::get(*_val); } - ConvoType* operator->() const { return &std::get(*_val); } - subtype_iterator& operator++() { - iterator::operator++(); - return *this; - } - subtype_iterator operator++(int) { - auto copy{*this}; - ++*this; - return copy; - } - }; -}; - -} // namespace session::config diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/encrypt.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/encrypt.h deleted file mode 100644 index b29848929..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/encrypt.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -/// Wrapper around session::config::encrypt. message and key_base are binary: message has the -/// length provided, key_base must be exactly 32 bytes. domain is a c string. Returns a newly -/// allocated buffer containing the encrypted data, and sets the data's length into -/// `ciphertext_size`. It is the caller's responsibility to `free()` the returned buffer! -/// -/// Returns nullptr on error. -unsigned char* config_encrypt( - const unsigned char* message, - size_t mlen, - const unsigned char* key_base, - const char* domain, - size_t* ciphertext_size); - -/// Works just like config_encrypt, but in reverse. -unsigned char* config_decrypt( - const unsigned char* ciphertext, - size_t clen, - const unsigned char* key_base, - const char* domain, - size_t* plaintext_size); - -/// Returns the amount of padding needed for a plaintext of size s with encryption overhead -/// `overhead`. -size_t config_padded_size(size_t s, size_t overhead); - -#ifdef __cplusplus -} -#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/encrypt.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/encrypt.hpp deleted file mode 100644 index 75c9f9aff..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/encrypt.hpp +++ /dev/null @@ -1,69 +0,0 @@ -#pragma once - -#include - -#include "../types.hpp" - -namespace session::config { - -/// Encrypts a config message using XChaCha20-Poly1305, using a blake2b keyed hash of the message -/// for the nonce (rather than pure random) so that different clients will encrypt the same data to -/// the same encrypted value (thus allowing for server-side deduplication of identical messages). -/// -/// `key_base` must be 32 bytes. This value is a fixed key that all clients that might receive this -/// message can calculate independently (for instance a value derived from a secret key, or a shared -/// random key). This key will be hashed with the message size and domain suffix (see below) to -/// determine the actual encryption key. -/// -/// `domain` is a short string (1-24 chars) used for the keyed hash. Typically this is the type of -/// config, e.g. "closed-group" or "contacts". The full key will be -/// "session-config-encrypted-message-[domain]". This value is also used for the encrypted key (see -/// above). -/// -/// The returned result will consist of encrypted data with authentication tag and appended nonce, -/// suitable for being passed to decrypt() to authenticate and decrypt. -/// -/// Throw std::invalid_argument on bad input (i.e. from invalid key_base or domain). -ustring encrypt(ustring_view message, ustring_view key_base, std::string_view domain); - -/// Same as above, but modifies `message` in place. `message` gets encrypted plus has the extra -/// data and nonce appended. -void encrypt_inplace(ustring& message, ustring_view key_base, std::string_view domain); - -/// Constant amount of extra bytes required to be appended when encrypting. -constexpr size_t ENCRYPT_DATA_OVERHEAD = 40; // ABYTES + NPUBBYTES - -/// Thrown if decrypt() fails. -struct decrypt_error : std::runtime_error { - using std::runtime_error::runtime_error; -}; - -/// Takes a value produced by `encrypt()` and decrypts it. `key_base` and `domain` must be the same -/// given to encrypt or else decryption fails. Upon decryption failure a `decrypt_error` exception -/// is thrown. -ustring decrypt(ustring_view ciphertext, ustring_view key_base, std::string_view domain); - -/// Same as above, but does in in-place. The string gets shortend to the plaintext after this call. -void decrypt_inplace(ustring& ciphertext, ustring_view key_base, std::string_view domain); - -/// Returns the target size of the message with padding, assuming an additional `overhead` bytes of -/// overhead (e.g. from encrypt() overhead) will be appended. Will always return a value >= s + -/// overhead. -/// -/// Padding increments we use: 256 byte increments up to 5120; 1024 byte increments up to 20480, -/// 2048 increments up to 40960, then 5120 from there up. -inline constexpr size_t padded_size(size_t s, size_t overhead = ENCRYPT_DATA_OVERHEAD) { - size_t s2 = s + overhead; - size_t chunk = s2 < 5120 ? 256 : s2 < 20480 ? 1024 : s2 < 40960 ? 2048 : 5120; - return (s2 + chunk - 1) / chunk * chunk - overhead; -} - -/// Inserts null byte padding to the beginning of a message to make the final message size granular. -/// See the above function for the sizes. -/// -/// \param data - the data; this is modified in place. -/// \param overhead - encryption overhead to account for to reach the desired padded size. The -/// default, if omitted, is the space used by the `encrypt()` function defined above. -void pad_message(ustring& data, size_t overhead = ENCRYPT_DATA_OVERHEAD); - -} // namespace session::config diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/error.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/error.h deleted file mode 100644 index 598a1539a..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/error.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -enum config_error { - /// Value returned for no error - SESSION_ERR_NONE = 0, - /// Error indicating that initialization failed because the dumped data being loaded is invalid. - SESSION_ERR_INVALID_DUMP = 1, - /// Error indicated a bad value, e.g. if trying to set something invalid in a config field. - SESSION_ERR_BAD_VALUE = 2, -}; - -// Returns a generic string for a given integer error code as returned by some functions. Depending -// on the call, a more details error string may be available in the config_object's `last_error` -// field. -const char* config_errstr(int err); - -#ifdef __cplusplus -} // extern "C" -#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/expiring.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/expiring.h deleted file mode 100644 index c98653f13..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/expiring.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -typedef enum CONVO_EXPIRATION_MODE { - CONVO_EXPIRATION_NONE = 0, - CONVO_EXPIRATION_AFTER_SEND = 1, - CONVO_EXPIRATION_AFTER_READ = 2, -} CONVO_EXPIRATION_MODE; diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/expiring.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/expiring.hpp deleted file mode 100644 index 4c040b740..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/expiring.hpp +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once -#include - -namespace session::config { - -enum class expiration_mode : int8_t { none = 0, after_send = 1, after_read = 2 }; - -} diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/namespaces.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/namespaces.hpp deleted file mode 100644 index 394617c0c..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/namespaces.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include - -namespace session::config { - -enum class Namespace : std::int16_t { - UserProfile = 2, - Contacts = 3, - ConvoInfoVolatile = 4, - UserGroups = 5, -}; - -} // namespace session::config diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/notify.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/notify.h deleted file mode 100644 index 6dc825bf7..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/notify.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -typedef enum CONVO_NOTIFY_MODE { - CONVO_NOTIFY_DEFAULT = 0, - CONVO_NOTIFY_ALL = 1, - CONVO_NOTIFY_DISABLED = 2, - CONVO_NOTIFY_MENTIONS_ONLY = 3, -} CONVO_NOTIFY_MODE; diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/notify.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/notify.hpp deleted file mode 100644 index 5de1f5eec..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/notify.hpp +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -namespace session::config { - -enum class notify_mode { - defaulted = 0, - all = 1, - disabled = 2, - mentions_only = 3, // Only for groups; for DMs this becomes `all` -}; - -} diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/profile_pic.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/profile_pic.h deleted file mode 100644 index 204c87318..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/profile_pic.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -// Maximum length of the profile pic URL (not including the null terminator) -extern const size_t PROFILE_PIC_MAX_URL_LENGTH; - -typedef struct user_profile_pic { - // Null-terminated C string containing the uploaded URL of the pic. Will be length 0 if there - // is no profile pic. - char url[224]; - // The profile pic decryption key, in bytes. This is a byte buffer of length 32, *not* a - // null-terminated C string. This is only valid when there is a url (i.e. url has strlen > 0). - unsigned char key[32]; -} user_profile_pic; - -#ifdef __cplusplus -} -#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/profile_pic.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/profile_pic.hpp deleted file mode 100644 index d450d074f..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/profile_pic.hpp +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once - -#include - -#include "session/types.hpp" - -namespace session::config { - -// Profile pic info. -struct profile_pic { - static constexpr size_t MAX_URL_LENGTH = 223; - - std::string url; - ustring key; - - static void check_key(ustring_view key) { - if (!(key.empty() || key.size() == 32)) - throw std::invalid_argument{"Invalid profile pic key: 32 bytes required"}; - } - - // Default constructor, makes an empty profile pic - profile_pic() = default; - - // Constructs from a URL and key. Key must be empty or 32 bytes. - profile_pic(std::string_view url, ustring_view key) : url{url}, key{key} { - check_key(this->key); - } - - // Constructs from a string/ustring pair moved into the constructor - profile_pic(std::string&& url, ustring&& key) : url{std::move(url)}, key{std::move(key)} { - check_key(this->key); - } - - // Returns true if either url or key are empty (or invalid) - bool empty() const { return url.empty() || key.size() != 32; } - - // Clears the current url/key, if set. This is just a shortcut for calling `.clear()` on each - // of them. - void clear() { - url.clear(); - key.clear(); - } - - // The object in boolean context is true if url and key are both set, i.e. the opposite of - // `empty()`. - explicit operator bool() const { return !empty(); } - - // Sets and validates the key. The key can be empty, or 32 bytes. This is almost the same as - // just setting `.key` directly, except that it will throw if the provided key is invalid (i.e. - // neither empty nor 32 bytes). - void set_key(ustring new_key) { - check_key(new_key); - key = std::move(new_key); - } -}; - -} // namespace session::config diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.h deleted file mode 100644 index 5a2451d5f..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.h +++ /dev/null @@ -1,271 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include "base.h" -#include "notify.h" -#include "util.h" - -// Maximum length of a group name, in bytes -extern const size_t GROUP_NAME_MAX_LENGTH; - -/// Struct holding legacy group info; this struct owns allocated memory and *must* be freed via -/// either `ugroups_legacy_group_free()` or `user_groups_set_free_legacy_group()` when finished with -/// it. -typedef struct ugroups_legacy_group_info { - char session_id[67]; // in hex; 66 hex chars + null terminator. - - char name[101]; // Null-terminated C string (human-readable). Max length is 100 (plus 1 for - // null). Will always be set (even if an empty string). - - bool have_enc_keys; // Will be true if we have an encryption keypair, false if not. - unsigned char enc_pubkey[32]; // If `have_enc_keys`, this is the 32-byte pubkey (no NULL - // terminator). - unsigned char enc_seckey[32]; // If `have_enc_keys`, this is the 32-byte secret key (no NULL - // terminator). - - int64_t disappearing_timer; // Minutes. 0 == disabled. - int priority; // pinned message priority; 0 = unpinned, negative = hidden, positive = pinned - // (with higher meaning pinned higher). - int64_t joined_at; // unix timestamp when joined (or re-joined) - CONVO_NOTIFY_MODE notifications; // When the user wants notifications - int64_t mute_until; // Mute notifications until this timestamp (overrides `notifications` - // setting until the timestamp) - - // For members use the ugroups_legacy_group_members and associated calls. - - void* _internal; // Internal storage, do not touch. -} ugroups_legacy_group_info; - -typedef struct ugroups_community_info { - char base_url[268]; // null-terminated (max length 267), normalized (i.e. always lower-case, - // only has port if non-default, has trailing / removed) - char room[65]; // null-terminated (max length 64); this is case-preserving (i.e. can be - // "SomeRoom" instead of "someroom". Note this is different from volatile - // info (that one is always forced lower-cased). - unsigned char pubkey[32]; // 32 bytes (not terminated, can contain nulls) - - int priority; // pinned message priority; 0 = unpinned, negative = hidden, positive = pinned - // (with higher meaning pinned higher). - int64_t joined_at; // unix timestamp when joined (or re-joined) - CONVO_NOTIFY_MODE notifications; // When the user wants notifications - int64_t mute_until; // Mute notifications until this timestamp (overrides `notifications` - // setting until the timestamp) -} ugroups_community_info; - -int user_groups_init( - config_object** conf, - const unsigned char* ed25519_secretkey, - const unsigned char* dump, - size_t dumplen, - char* error) __attribute__((warn_unused_result)); - -/// Gets community conversation info into `comm`, if the community info was found. `base_url` and -/// `room` are null-terminated c strings; pubkey is 32 bytes. base_url will be -/// normalized/lower-cased; room is case-insensitive for the lookup: note that this may well return -/// a community info with a different room capitalization than the one provided to the call. -/// -/// Returns true if the community was found and `comm` populated; false otherwise. A false return -/// can either be because it didn't exist (`conf->last_error` will be NULL) or because of some error -/// (`last_error` will be set to an error string). -bool user_groups_get_community( - config_object* conf, - ugroups_community_info* comm, - const char* base_url, - const char* room) __attribute__((warn_unused_result)); - -/// Like the above, but if the community was not found, this constructs one that can be inserted. -/// `base_url` will be normalized in the returned object. `room` is a case-insensitive lookup key -/// for the room token. Note that it has subtle handling w.r.t its case: if an existing room is -/// found, you get back a record with the found case (which could differ in case from what you -/// provided). If you want to override to what you provided regardless of what is there you should -/// immediately set the name of the returned object to the case you prefer. If a *new* record is -/// constructed, however, it will match the room token case as given here. -/// -/// Note that this is all different from convo_info_volatile, which always forces the room token to -/// lower-case (because it does not preserve the case). -/// -/// Returns false (and sets `conf->last_error`) on error. -bool user_groups_get_or_construct_community( - config_object* conf, - ugroups_community_info* comm, - const char* base_url, - const char* room, - unsigned const char* pubkey) __attribute__((warn_unused_result)); - -/// Returns a ugroups_legacy_group_info pointer containing the conversation info for a given legacy -/// group ID (specified as a null-terminated hex string), if the conversation exists. If the -/// conversation does not exist, returns NULL. Sets conf->last_error on error. -/// -/// The returned pointer *must* be freed either by calling `ugroups_legacy_group_free()` when done -/// with it, or by passing it to `user_groups_set_free_legacy_group()`. -ugroups_legacy_group_info* user_groups_get_legacy_group(config_object* conf, const char* id) - __attribute__((warn_unused_result)); - -/// Same as the above except that when the conversation does not exist, this sets all the group -/// fields to defaults and loads it with the given id. -/// -/// Returns a ugroups_legacy_group_info as long as it is given a valid legacy group id (i.e. same -/// format as a session id); it will return NULL only if the given id is invalid (and so the caller -/// needs to either pre-validate the id, or post-validate the return value). -/// -/// The returned pointer *must* be freed either by calling `ugroups_legacy_group_free()` when done -/// with it, or by passing it to `user_groups_set_free_legacy_group()`. -/// -/// This is the method that should usually be used to create or update a conversation, followed by -/// setting fields in the group, and then giving it to user_groups_set(). -/// -/// On error, this returns NULL and sets `conf->last_error`. -ugroups_legacy_group_info* user_groups_get_or_construct_legacy_group( - config_object* conf, const char* id) __attribute__((warn_unused_result)); - -/// Properly frees memory associated with a ugroups_legacy_group_info pointer (as returned by -/// get_legacy_group/get_or_construct_legacy_group). -void ugroups_legacy_group_free(ugroups_legacy_group_info* group); - -/// Adds or updates a community conversation from the given group info -void user_groups_set_community(config_object* conf, const ugroups_community_info* group); - -/// Adds or updates a legacy group conversation from the into. This version of the method should -/// only be used when you explicitly want the `group` to remain valid; if the set is the last thing -/// you need to do with it (which is common) it is more efficient to call the freeing version, -/// below. -void user_groups_set_legacy_group(config_object* conf, const ugroups_legacy_group_info* group); - -/// Same as above, except that this also frees the pointer for you, which is commonly what is wanted -/// when updating fields. This is equivalent to, but more efficient than, setting and then freeing. -void user_groups_set_free_legacy_group(config_object* conf, ugroups_legacy_group_info* group); - -/// Erases a conversation from the conversation list. Returns true if the conversation was found -/// and removed, false if the conversation was not present. You must not call this during -/// iteration; see details below. -bool user_groups_erase_community(config_object* conf, const char* base_url, const char* room); -bool user_groups_erase_legacy_group(config_object* conf, const char* group_id); - -typedef struct ugroups_legacy_members_iterator ugroups_legacy_members_iterator; - -/// Group member iteration; this lets you walk through the full group member list. Example usage: -/// -/// const char* session_id; -/// bool admin; -/// ugroups_legacy_members_iterator* it = ugroups_legacy_members_begin(legacy_info); -/// while (ugroups_legacy_members_next(it, &session_id, &admin)) { -/// if (admin) -/// printf("ADMIN: %s", session_id); -/// } -/// ugroups_legacy_members_free(it); -/// -ugroups_legacy_members_iterator* ugroups_legacy_members_begin(ugroups_legacy_group_info* group); -bool ugroups_legacy_members_next( - ugroups_legacy_members_iterator* it, const char** session_id, bool* admin); -void ugroups_legacy_members_free(ugroups_legacy_members_iterator* it); - -/// This erases the group member at the current iteration location during a member iteration, -/// allowing iteration to continue. -/// -/// Example: -/// -/// while (ugroups_legacy_members_next(it, &sid, &admin)) { -/// if (should_remove(sid)) -/// ugroups_legacy_members_erase(it); -/// } -void ugroups_legacy_members_erase(ugroups_legacy_members_iterator* it); - -/// Adds a member (by session id and admin status) to this group. Returns true if the member was -/// inserted or had the admin status changed, false if the member already existed with the given -/// status, or if the session_id is not valid. -bool ugroups_legacy_member_add( - ugroups_legacy_group_info* group, const char* session_id, bool admin); - -/// Removes a member (including admins) from the group given the member's session id. This is not -/// safe to use on the current member during member iteration; for that see the above method -/// instead. Returns true if the session id was found and removed, false if not found. -bool ugroups_legacy_member_remove(ugroups_legacy_group_info* group, const char* session_id); - -/// Accesses the number of members in the group. The overall number is returned (both admins and -/// non-admins); if the given variables are not NULL, they will be populated with the individual -/// counts of members/admins. -size_t ugroups_legacy_members_count( - const ugroups_legacy_group_info* group, size_t* members, size_t* admins); - -/// Returns the number of conversations. -size_t user_groups_size(const config_object* conf); -/// Returns the number of conversations of the specific type. -size_t user_groups_size_communities(const config_object* conf); -size_t user_groups_size_legacy_groups(const config_object* conf); - -/// Functions for iterating through the entire conversation list. Intended use is: -/// -/// ugroups_community_info c2; -/// ugroups_legacy_group_info c3; -/// user_groups_iterator *it = user_groups_iterator_new(my_groups); -/// for (; !user_groups_iterator_done(it); user_groups_iterator_advance(it)) { -/// if (user_groups_it_is_community(it, &c2)) { -/// // use c2.whatever -/// } else if (user_groups_it_is_legacy_group(it, &c3)) { -/// // use c3.whatever -/// } -/// } -/// user_groups_iterator_free(it); -/// -/// It is permitted to modify records (e.g. with a call to one of the `user_groups_set_*` -/// functions) and add records while iterating. -/// -/// If you need to remove while iterating then usage is slightly different: you must advance the -/// iteration by calling either user_groups_iterator_advance if not deleting, or -/// user_groups_iterator_erase to erase and advance. Usage looks like this: -/// -/// ugroups_community_info comm; -/// ugroups_iterator *it = ugroups_iterator_new(my_groups); -/// while (!user_groups_iterator_done(it)) { -/// if (user_groups_it_is_community(it, &comm)) { -/// bool should_delete = /* ... */; -/// if (should_delete) -/// user_groups_iterator_erase(it); -/// else -/// user_groups_iterator_advance(it); -/// } else { -/// user_groups_iterator_advance(it); -/// } -/// } -/// user_groups_iterator_free(it); -/// - -typedef struct user_groups_iterator user_groups_iterator; - -// Starts a new iterator that iterates over all conversations. -user_groups_iterator* user_groups_iterator_new(const config_object* conf); - -// The same as `user_groups_iterator_new` except that this iterates *only* over one type of -// conversation. You still need to use `user_groups_it_is_community` (or the alternatives) -// to load the data in each pass of the loop. (You can, however, safely ignore the bool return -// value of the `it_is_whatever` function: it will always be true for the particular type being -// iterated over). -user_groups_iterator* user_groups_iterator_new_communities(const config_object* conf); -user_groups_iterator* user_groups_iterator_new_legacy_groups(const config_object* conf); - -// Frees an iterator once no longer needed. -void user_groups_iterator_free(user_groups_iterator* it); - -// Returns true if iteration has reached the end. -bool user_groups_iterator_done(user_groups_iterator* it); - -// Advances the iterator. -void user_groups_iterator_advance(user_groups_iterator* it); - -// If the current iterator record is a community conversation this sets the details into `c` and -// returns true. Otherwise it returns false. -bool user_groups_it_is_community(user_groups_iterator* it, ugroups_community_info* c); - -// If the current iterator record is a legacy group conversation this sets the details into -// `c` and returns true. Otherwise it returns false. -bool user_groups_it_is_legacy_group(user_groups_iterator* it, ugroups_legacy_group_info* c); - -// Erases the current group while advancing the iterator to the next group in the iteration. -void user_groups_iterator_erase(config_object* conf, user_groups_iterator* it); - -#ifdef __cplusplus -} // extern "C" -#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.hpp deleted file mode 100644 index 9ab69117e..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.hpp +++ /dev/null @@ -1,366 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "base.hpp" -#include "community.hpp" -#include "namespaces.hpp" -#include "notify.hpp" - -extern "C" { -struct ugroups_legacy_group_info; -struct ugroups_community_info; -} - -namespace session::config { - -/// keys used in this config, either currently or in the past (so that we don't reuse): -/// -/// C - dict of legacy groups; within this dict each key is the group pubkey (binary, 33 bytes) and -/// value is a dict containing keys: -/// -/// n - name (string). Always set, even if empty. -/// k - encryption public key (32 bytes). Optional. -/// K - encryption secret key (32 bytes). Optional. -/// m - set of member session ids (each 33 bytes). -/// a - set of admin session ids (each 33 bytes). -/// E - disappearing messages duration, in seconds, > 0. Omitted if disappearing messages is -/// disabled. (Note that legacy groups only support expire after-read) -/// @ - notification setting (int). Omitted = use default setting; 1 = all, 2 = disabled, 3 = -/// mentions-only. -/// ! - mute timestamp: if set then don't show notifications for this contact's messages until -/// this unix timestamp (i.e. overriding the current notification setting until the given -/// time). -/// + - the conversation priority, for pinned/hidden messages. Integer. Omitted means not -/// pinned; -1 means hidden, and a positive value is a pinned message for which higher -/// priority values means the conversation is meant to appear earlier in the pinned -/// conversation list. -/// j - joined at unix timestamp. Omitted if 0. -/// -/// o - dict of communities (AKA open groups); within this dict (which deliberately has the same -/// layout as convo_info_volatile) each key is the SOGS base URL (in canonical form), and value -/// is a dict of: -/// -/// # - server pubkey -/// R - dict of rooms on the server. Each key is the *lower-case* room name; each value is: -/// n - the room name as is commonly used, i.e. with possible capitalization (if -/// appropriate). For instance, a room name SudokuSolvers would be "sudokusolvers" in -/// the outer key, with the capitalization variation in use ("SudokuSolvers") in this -/// key. This key is *always* present (to keep the room dict non-empty). -/// @ - notification setting (see above). -/// ! - mute timestamp (see above). -/// + - the conversation priority, for pinned messages. Omitted means not pinned; -1 means -/// hidden; otherwise an integer value >0, where a higher priority means the -/// conversation is meant to appear earlier in the pinned conversation list. -/// j - joined at unix timestamp. Omitted if 0. -/// -/// c - reserved for future storage of new-style group info. - -/// Common base type with fields shared by all the groups -struct base_group_info { - int priority = 0; // The priority; 0 means unpinned, -1 means hidden, positive means - // pinned higher (i.e. higher priority conversations come first). - int64_t joined_at = 0; // unix timestamp (seconds) when the group was joined (or re-joined) - notify_mode notifications = notify_mode::defaulted; // When the user wants notifications - int64_t mute_until = 0; // unix timestamp (seconds) until which notifications are disabled - - protected: - void load(const dict& info_dict); -}; - -/// Struct containing legacy group info (aka "closed groups"). -struct legacy_group_info : base_group_info { - static constexpr size_t NAME_MAX_LENGTH = 100; // in bytes; name will be truncated if exceeded - - std::string session_id; // The legacy group "session id" (33 bytes). - std::string name; // human-readable; this should normally always be set, but in theory could be - // set to an empty string. - ustring enc_pubkey; // bytes (32 or empty) - ustring enc_seckey; // bytes (32 or empty) - std::chrono::seconds disappearing_timer{0}; // 0 == disabled. - - /// Constructs a new legacy group info from an id (which must look like a session_id). Throws - /// if id is invalid. - explicit legacy_group_info(std::string sid); - - // Accesses the session ids (in hex) of members of this group. The key is the hex session_id; - // the value indicates whether the member is an admin (true) or not (false). - const std::map& members() const { return members_; } - - // Returns a pair of the number of admins, and regular members of this group. (If all you want - // is the overall number just use `.members().size()` instead). - std::pair counts() const; - - // Adds a member (by session id and admin status) to this group. Returns true if the member was - // inserted or changed admin status, false if the member already existed. Throws - // std::invalid_argument if the given session id is invalid. - bool insert(std::string session_id, bool admin); - - // Removes a member (by session id) from this group. Returns true if the member was - // removed, false if the member was not present. - bool erase(const std::string& session_id); - - // Internal ctor/method for C API implementations: - legacy_group_info(const struct ugroups_legacy_group_info& c); // From c struct - legacy_group_info(struct ugroups_legacy_group_info&& c); // From c struct - void into(struct ugroups_legacy_group_info& c) const&; // Copy into c struct - void into(struct ugroups_legacy_group_info& c) &&; // Move into c struct - - private: - // session_id => (is admin) - std::map members_; - - friend class UserGroups; - - // Private implementations of the to/from C struct methods - struct impl_t {}; - static constexpr inline impl_t impl{}; - legacy_group_info(const struct ugroups_legacy_group_info& c, impl_t); - void into(struct ugroups_legacy_group_info& c, impl_t) const; - - void load(const dict& info_dict); -}; - -/// Community (aka open group) info -struct community_info : base_group_info, community { - // Note that *changing* url/room/pubkey and then doing a set inserts a new room under the given - // url/room/pubkey, it does *not* update an existing room. - - // See community_base (comm_base.hpp) for common constructors - using community::community; - - // Internal ctor/method for C API implementations: - community_info(const struct ugroups_community_info& c); // From c struct - void into(ugroups_community_info& c) const; // Into c struct - - private: - void load(const dict& info_dict); - - friend class UserGroups; - friend class comm_iterator_helper; -}; - -using any_group_info = std::variant; - -class UserGroups : public ConfigBase { - - public: - // No default constructor - UserGroups() = delete; - - /// Constructs a user group list from existing data (stored from `dump()`) and the user's - /// secret key for generating the data encryption key. To construct a blank list (i.e. with no - /// pre-existing dumped data to load) pass `std::nullopt` as the second argument. - /// - /// \param ed25519_secretkey - contains the libsodium secret key used to encrypt/decrypt the - /// data when pushing/pulling from the swarm. This can either be the full 64-byte value (which - /// is technically the 32-byte seed followed by the 32-byte pubkey), or just the 32-byte seed of - /// the secret key. - /// - /// \param dumped - either `std::nullopt` to construct a new, empty object; or binary state data - /// that was previously dumped from an instance of this class by calling `dump()`. - UserGroups(ustring_view ed25519_secretkey, std::optional dumped); - - Namespace storage_namespace() const override { return Namespace::UserGroups; } - - const char* encryption_domain() const override { return "UserGroups"; } - - /// Looks up and returns a community (aka open group) conversation. Takes the base URL and room - /// token (case insensitive). Retuns nullopt if the open group was not found, otherwise a - /// filled out `community_info`. Note that the `room` argument here is case-insensitive, but - /// the returned value will be the room as stored in the object (i.e. it may have a different - /// case from the requested `room` value). - std::optional get_community( - std::string_view base_url, std::string_view room) const; - - /// Looks up a community from a full URL. It is permitted for the URL to omit the pubkey (it - /// is not used or needed by this call). - std::optional get_community(std::string_view partial_url) const; - - /// Looks up and returns a legacy group by group ID (hex, looks like a Session ID). Returns - /// nullopt if the group was not found, otherwise returns a filled out `legacy_group_info`. - std::optional get_legacy_group(std::string_view pubkey_hex) const; - - /// Same as `get_community`, except if the community isn't found a new blank one is created for - /// you, prefilled with the url/room/pubkey. - /// - /// Note that `room` and `pubkey` have special handling: - /// - `room` is case-insensitive for the lookup: if a matching room is found then the returned - /// value reflects the room case of the existing record, which is not necessarily the same as - /// the `room` argument given here (to force a case change, set it within the returned - /// object). - /// - `pubkey` is not used to find an existing community, but if the community found has a - /// *different* pubkey from the one given then the returned record has its pubkey updated in - /// the return instance (note that this changed value is not committed to storage, however, - /// until the instance is passed to `set()`). For the string_view version the pubkey is - /// accepted as hex, base32z, or base64. - community_info get_or_construct_community( - std::string_view base_url, - std::string_view room, - std::string_view pubkey_encoded) const; - community_info get_or_construct_community( - std::string_view base_url, std::string_view room, ustring_view pubkey) const; - /// Shortcut to pass the url through community::parse_full_url, then call the above. - community_info get_or_construct_community(std::string_view full_url) const; - - /// Gets or constructs a blank legacy_group_info for the given group id. - legacy_group_info get_or_construct_legacy_group(std::string_view pubkey_hex) const; - - /// Inserts or replaces existing group info. For example, to update the info for a community - /// you would do: - /// - /// auto info = conversations.get_or_construct_community(some_session_id); - /// info.last_read = new_unix_timestamp; - /// conversations.set(info); - /// - void set(const community_info& info); - void set(const legacy_group_info& info); - /// Takes a variant of either group type to set: - void set(const any_group_info& info); - - protected: - // Drills into the nested dicts to access open group details - DictFieldProxy community_field( - const community_info& og, ustring_view* get_pubkey = nullptr) const; - - void set_base(const base_group_info& bg, DictFieldProxy& info) const; - - public: - /// Removes a community group. Returns true if found and removed, false if not present. - /// Arguments are the same as `get_community`. - bool erase_community(std::string_view base_url, std::string_view room); - - /// Removes a legacy group conversation. Returns true if found and removed, false if not - /// present. - bool erase_legacy_group(std::string_view pubkey_hex); - - /// Removes a conversation taking the community_info or legacy_group_info instance (rather than - /// the pubkey/url) for convenience. - bool erase(const community_info& g); - bool erase(const legacy_group_info& c); - bool erase(const any_group_info& info); - - struct iterator; - - /// This works like erase, but takes an iterator to the group to remove. The element is removed - /// and the iterator to the next element after the removed one is returned. This is intended - /// for use where elements are to be removed during iteration: see below for an example. - iterator erase(iterator it); - - /// Returns the number of groups (of any type). - size_t size() const; - - /// Returns the number of communities - size_t size_communities() const; - - /// Returns the number of legacy groups - size_t size_legacy_groups() const; - - /// Returns true if the group list is empty. - bool empty() const { return size() == 0; } - - /// Iterators for iterating through all groups. Typically you access this implicit via a - /// for loop over the `UserGroups` object: - /// - /// for (auto& group : usergroups) { - /// if (auto* comm = std::get_if(&group)) { - /// // use comm->name, comm->priority, etc. - /// } else if (auto* lg = std::get_if(&convo)) { - /// // use lg->session_id, lg->priority, etc. - /// } - /// } - /// - /// This iterates through all groups in sorted order (sorted first by convo type, then by - /// id within the type). - /// - /// It is permitted to modify and add records while iterating (e.g. by modifying one of the - /// `comm`/`lg` objects and then calling set()). - /// - /// If you need to erase the current conversation during iteration then care is required: you - /// need to advance the iterator via the iterator version of erase when erasing an element - /// rather than incrementing it regularly. For example: - /// - /// for (auto it = conversations.begin(); it != conversations.end(); ) { - /// if (should_remove(*it)) - /// it = converations.erase(it); - /// else - /// ++it; - /// } - /// - /// Alternatively, you can use the first version with two loops: the first loop through all - /// converations doesn't erase but just builds a vector of IDs to erase, then the second loops - /// through that vector calling `erase_1to1()`/`erase_open()`/`erase_legacy_group()` for each - /// one. - /// - iterator begin() const { return iterator{data}; } - iterator end() const { return iterator{}; } - - template - struct subtype_iterator; - - /// Returns an iterator that iterates only through one type of conversations. (The regular - /// `.end()` iterator is valid for testing the end of these iterations). - subtype_iterator begin_communities() const { return {data}; } - subtype_iterator begin_legacy_groups() const { return {data}; } - - using iterator_category = std::input_iterator_tag; - using value_type = std::variant; - using reference = value_type&; - using pointer = value_type*; - using difference_type = std::ptrdiff_t; - - struct iterator { - protected: - std::shared_ptr _val; - std::optional _it_comm; - std::optional _it_legacy, _end_legacy; - void _load_val(); - iterator() = default; // Constructs an end tombstone - explicit iterator( - const DictFieldRoot& data, bool communities = true, bool legacy_closed = true); - friend class UserGroups; - - public: - bool operator==(const iterator& other) const; - bool operator!=(const iterator& other) const { return !(*this == other); } - bool done() const; // Equivalent to comparing against the end iterator - any_group_info& operator*() const { return *_val; } - any_group_info* operator->() const { return _val.get(); } - iterator& operator++(); - iterator operator++(int) { - auto copy{*this}; - ++*this; - return copy; - } - }; - - template - struct subtype_iterator : iterator { - protected: - subtype_iterator(const DictFieldRoot& data) : - iterator( - data, - std::is_same_v, - std::is_same_v) {} - friend class UserGroups; - - public: - GroupType& operator*() const { return std::get(*_val); } - GroupType* operator->() const { return &std::get(*_val); } - subtype_iterator& operator++() { - iterator::operator++(); - return *this; - } - subtype_iterator operator++(int) { - auto copy{*this}; - ++*this; - return copy; - } - }; -}; - -} // namespace session::config diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.h deleted file mode 100644 index b7fb66842..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.h +++ /dev/null @@ -1,63 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include "base.h" -#include "profile_pic.h" - -/// Constructs a user profile config object and sets a pointer to it in `conf`. -/// -/// \param ed25519_secretkey must be the 32-byte secret key seed value. (You can also pass the -/// pointer to the beginning of the 64-byte value libsodium calls the "secret key" as the first 32 -/// bytes of that are the seed). This field cannot be null. -/// -/// \param dump - if non-NULL this restores the state from the dumped byte string produced by a past -/// instantiation's call to `dump()`. To construct a new, empty profile this should be NULL. -/// -/// \param dumplen - the length of `dump` when restoring from a dump, or 0 when `dump` is NULL. -/// -/// \param error - the pointer to a buffer in which we will write an error string if an error -/// occurs; error messages are discarded if this is given as NULL. If non-NULL this must be a -/// buffer of at least 256 bytes. -/// -/// Returns 0 on success; returns a non-zero error code and write the exception message as a -/// C-string into `error` (if not NULL) on failure. -/// -/// When done with the object the `config_object` must be destroyed by passing the pointer to -/// config_free() (in `session/config/base.h`). -int user_profile_init( - config_object** conf, - const unsigned char* ed25519_secretkey, - const unsigned char* dump, - size_t dumplen, - char* error) __attribute__((warn_unused_result)); - -/// Returns a pointer to the currently-set name (null-terminated), or NULL if there is no name at -/// all. Should be copied right away as the pointer may not remain valid beyond other API calls. -const char* user_profile_get_name(const config_object* conf); - -/// Sets the user profile name to the null-terminated C string. Returns 0 on success, non-zero on -/// error (and sets the config_object's error string). -int user_profile_set_name(config_object* conf, const char* name); - -// Obtains the current profile pic. The pointers in the returned struct will be NULL if a profile -// pic is not currently set, and otherwise should be copied right away (they will not be valid -// beyond other API calls on this config object). -user_profile_pic user_profile_get_pic(const config_object* conf); - -// Sets a user profile -int user_profile_set_pic(config_object* conf, user_profile_pic pic); - -// Gets the current note-to-self priority level. Will be negative for hidden, 0 for unpinned, and > -// 0 for pinned (with higher value = higher priority). -int user_profile_get_nts_priority(const config_object* conf); - -// Sets the current note-to-self priority level. Set to -1 for hidden; 0 for unpinned, and > 0 for -// higher priority in the conversation list. -void user_profile_set_nts_priority(config_object* conf, int priority); - -#ifdef __cplusplus -} // extern "C" -#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.hpp deleted file mode 100644 index 2ce3ca697..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.hpp +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once - -#include -#include - -#include "base.hpp" -#include "namespaces.hpp" -#include "profile_pic.hpp" - -namespace session::config { - -/// keys used in this config, either currently or in the past (so that we don't reuse): -/// -/// n - user profile name -/// p - user profile url -/// q - user profile decryption key (binary) -/// + - the priority value for the "Note to Self" pseudo-conversation (higher = higher in the -/// conversation list). Omitted when 0. -1 means hidden. - -class UserProfile final : public ConfigBase { - - public: - // No default constructor - UserProfile() = delete; - - /// Constructs a user profile from existing data (stored from `dump()`) and the user's secret - /// key for generating the data encryption key. To construct a blank profile (i.e. with no - /// pre-existing dumped data to load) pass `std::nullopt` as the second argument. - /// - /// \param ed25519_secretkey - contains the libsodium secret key used to encrypt/decrypt the - /// data when pushing/pulling from the swarm. This can either be the full 64-byte value (which - /// is technically the 32-byte seed followed by the 32-byte pubkey), or just the 32-byte seed of - /// the secret key. - /// - /// \param dumped - either `std::nullopt` to construct a new, empty object; or binary state data - /// that was previously dumped from an instance of this class by calling `dump()`. - UserProfile(ustring_view ed25519_secretkey, std::optional dumped); - - Namespace storage_namespace() const override { return Namespace::UserProfile; } - - const char* encryption_domain() const override { return "UserProfile"; } - - /// Returns the user profile name, or std::nullopt if there is no profile name set. - std::optional get_name() const; - - /// Sets the user profile name; if given an empty string then the name is removed. - void set_name(std::string_view new_name); - - /// Gets the user's current profile pic URL and decryption key. The returned object will - /// evaluate as false if the URL and/or key are not set. - profile_pic get_profile_pic() const; - - /// Sets the user's current profile pic to a new URL and decryption key. Clears both if either - /// one is empty. - void set_profile_pic(std::string_view url, ustring_view key); - void set_profile_pic(profile_pic pic); - - /// Gets the Note-to-self conversation priority. Negative means hidden; 0 means unpinned; - /// higher means higher priority (i.e. hidden in the convo list). - int get_nts_priority() const; - - /// Sets the Note-to-self conversation priority. -1 for hidden, 0 for unpinned, higher for - /// pinned higher. - void set_nts_priority(int priority); -}; - -} // namespace session::config diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/util.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/util.h deleted file mode 100644 index 6ae2c890b..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/util.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -/// Returns true if session_id has the right form (66 hex digits). This is a quick check, not a -/// robust one: it does not check the leading byte prefix, nor the cryptographic properties of the -/// pubkey for actual validity. -bool session_id_is_valid(const char* session_id); - -#ifdef __cplusplus -} -#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/export.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/export.h deleted file mode 100644 index ab307f6f2..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/export.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -#if defined(_WIN32) || defined(WIN32) -#define LIBSESSION_EXPORT __declspec(dllexport) -#else -#define LIBSESSION_EXPORT __attribute__((visibility("default"))) -#endif -#define LIBSESSION_C_API extern "C" LIBSESSION_EXPORT diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/fields.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/fields.hpp deleted file mode 100644 index 6ca71a245..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/fields.hpp +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace session { - -using namespace std::literals; - -/// An uploaded file is its URL + decryption key -struct Uploaded { - std::string url; - std::string key; -}; - -/// A conversation disappearing messages setting -struct Disappearing { - /// The possible modes of a disappearing messages setting. - enum class Mode : int { None = 0, AfterSend = 1, AfterRead = 2 }; - - /// The mode itself - Mode mode = Mode::None; - - /// The timer value; this is only used when mode is not None. - std::chrono::seconds timer = 0s; -}; - -/// A Session ID: an x25519 pubkey, with a 05 identifying prefix. On the wire we send just the -/// 32-byte pubkey value (i.e. not hex, without the prefix). -struct SessionID { - /// The fixed session netid, 0x05 - static constexpr unsigned char netid = 0x05; - - /// The raw x25519 pubkey, as bytes - std::array pubkey; - - /// Returns the full pubkey in hex, including the netid prefix. - std::string hex() const; -}; - -} // namespace session diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/types.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/types.hpp deleted file mode 100644 index d63fe470e..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/types.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace session { - -using ustring = std::basic_string; -using ustring_view = std::basic_string_view; - -namespace config { - - using seqno_t = std::int64_t; - -} // namespace config - -} // namespace session diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/util.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/util.hpp deleted file mode 100644 index 6cfa77e2b..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/util.hpp +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once -#include "types.hpp" - -namespace session { - -// Helper function to go to/from char pointers to unsigned char pointers: -inline const unsigned char* to_unsigned(const char* x) { - return reinterpret_cast(x); -} -inline unsigned char* to_unsigned(char* x) { - return reinterpret_cast(x); -} -inline const char* from_unsigned(const unsigned char* x) { - return reinterpret_cast(x); -} -inline char* from_unsigned(unsigned char* x) { - return reinterpret_cast(x); -} -// Helper function to switch between string_view and ustring_view -inline ustring_view to_unsigned_sv(std::string_view v) { - return {to_unsigned(v.data()), v.size()}; -} -inline std::string_view from_unsigned_sv(ustring_view v) { - return {from_unsigned(v.data()), v.size()}; -} - -/// Returns true if the first string is equal to the second string, compared case-insensitively. -inline bool string_iequal(std::string_view s1, std::string_view s2) { - return std::equal(s1.begin(), s1.end(), s2.begin(), s2.end(), [](char a, char b) { - return std::tolower(static_cast(a)) == - std::tolower(static_cast(b)); - }); -} - -// C++20 starts_/ends_with backport -inline constexpr bool starts_with(std::string_view str, std::string_view prefix) { - return str.size() >= prefix.size() && str.substr(prefix.size()) == prefix; -} - -inline constexpr bool end_with(std::string_view str, std::string_view suffix) { - return str.size() >= suffix.size() && str.substr(str.size() - suffix.size()) == suffix; -} - -} // namespace session diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/version.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/version.h deleted file mode 100644 index 5574fdecc..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/version.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifdef __cplusplus -extern "C" { -#endif - -#include - -/// libsession-util version triplet (major, minor, patch) -extern const uint16_t LIBSESSION_UTIL_VERSION[3]; - -/// Printable full libsession-util name and version string, such as `libsession-util v0.1.2-release` -/// for a tagged release or `libsession-util v0.1.2-7f144eb5` for an untagged build. -extern const char* LIBSESSION_UTIL_VERSION_FULL; - -/// Just the version component as a string, e.g. `v0.1.2-release`. -extern const char* LIBSESSION_UTIL_VERSION_STR; - -#ifdef __cplusplus -} // extern "C" -#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/xed25519.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/xed25519.h deleted file mode 100644 index 5348dafa0..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/xed25519.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -/// XEd25519-signed a message given a curve25519 privkey and message. Writes the 64-byte signature -/// to `sig` on success and returns 0. Returns non-zero on failure. -__attribute__((warn_unused_result)) int session_xed25519_sign( - unsigned char* signature /* 64 byte buffer */, - const unsigned char* curve25519_privkey /* 32 bytes */, - const unsigned char* msg, - const unsigned int msg_len); - -/// Verifies an XEd25519-signed message given a 64-byte signature, 32-byte curve25519 pubkey, and -/// message. Returns 0 if the signature verifies successfully, non-zero on failure. -__attribute__((warn_unused_result)) int session_xed25519_verify( - const unsigned char* signature /* 64 bytes */, - const unsigned char* pubkey /* 32-bytes */, - const unsigned char* msg, - const unsigned int msg_len); - -/// Given a curve25519 pubkey, this writes the associated XEd25519-derived Ed25519 pubkey into -/// ed25519_pubkey. Note, however, that there are *two* possible Ed25519 pubkeys that could result -/// in a given curve25519 pubkey: this always returns the positive value. You can get the other -/// possibility (the negative) by flipping the sign bit, i.e. `returned_pubkey[31] |= 0x80`. -/// Returns 0 on success, non-0 on failure. -__attribute__((warn_unused_result)) int session_xed25519_pubkey( - unsigned char* ed25519_pubkey /* 32-byte output buffer */, - const unsigned char* curve25519_pubkey /* 32 bytes */); - -#ifdef __cplusplus -} -#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/xed25519.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/xed25519.hpp deleted file mode 100644 index 9889113c2..000000000 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/xed25519.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once -#include -#include -#include - -namespace session::xed25519 { - -using ustring_view = std::basic_string_view; - -/// XEd25519-signs a message given the curve25519 privkey and message. -std::array sign( - ustring_view curve25519_privkey /* 32 bytes */, ustring_view msg); - -/// "Softer" version that takes and returns strings of regular chars -std::string sign(std::string_view curve25519_privkey /* 32 bytes */, std::string_view msg); - -/// Verifies a curve25519 message allegedly signed by the given curve25519 pubkey -[[nodiscard]] bool verify( - ustring_view signature /* 64 bytes */, - ustring_view curve25519_pubkey /* 32 bytes */, - ustring_view msg); - -/// "Softer" version that takes strings of regular chars -[[nodiscard]] bool verify( - std::string_view signature /* 64 bytes */, - std::string_view curve25519_pubkey /* 32 bytes */, - std::string_view msg); - -/// Given a curve25519 pubkey, this returns the associated XEd25519-derived Ed25519 pubkey. Note, -/// however, that there are *two* possible Ed25519 pubkeys that could result in a given curve25519 -/// pubkey: this always returns the positive value. You can get the other possibility (the -/// negative) by flipping the sign bit, i.e. `returned_pubkey[31] |= 0x80`. -std::array pubkey(ustring_view curve25519_pubkey); - -/// "Softer" version that takes/returns strings of regular chars -std::string pubkey(std::string_view curve25519_pubkey); - -} // namespace session::xed25519 diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift index cb7a9675e..d2814eeea 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift @@ -8,7 +8,8 @@ import SessionUtilitiesKit extension MessageReceiver { internal static func handleLegacyConfigurationMessage(_ db: Database, message: ConfigurationMessage) throws { - guard !Features.useSharedUtilForUserConfig else { + // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent + guard !SessionUtil.userConfigsEnabled else { TopBannerController.show(warning: .outdatedUserConfig) return } diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift similarity index 100% rename from SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift rename to SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift similarity index 99% rename from SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift rename to SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift index 1a834a168..ff4fb4eac 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift @@ -260,7 +260,7 @@ internal extension SessionUtil { } convo_info_volatile_set_community(conf, &community) - case .group: return // TODO: Need to add when the type is added to the lib + case .group: return } } } @@ -419,7 +419,7 @@ public extension SessionUtil { return (convoCommunity.last_read > timestampMs) - case .group: return false // TODO: Need to add when the type is added to the lib + case .group: return false } } .defaulting(to: false) // If we don't have a config then just assume it's unread diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Shared.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift similarity index 97% rename from SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Shared.swift rename to SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift index d2d03a8e3..93e494262 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Shared.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift @@ -42,11 +42,7 @@ internal extension SessionUtil { change: (UnsafeMutablePointer?) throws -> () ) throws { // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard Features.useSharedUtilForUserConfig else { return } - - // If we haven't completed the required migrations then do nothing (assume that - // this is called from a migration change and we won't miss a change) - guard SessionUtil.requiredMigrationsCompleted(db) else { return } + guard SessionUtil.userConfigsEnabled(db, ignoreRequirementsForRunningMigrations: true) else { return } // Since we are doing direct memory manipulation we are using an `Atomic` // type which has blocking access in it's `mutate` closure @@ -307,7 +303,7 @@ public extension SessionUtil { threadVariant: SessionThread.Variant ) -> Bool { // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard Features.useSharedUtilForUserConfig else { return true } + guard SessionUtil.userConfigsEnabled else { return true } let configVariant: ConfigDump.Variant = { switch threadVariant { diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserGroups.swift similarity index 99% rename from SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift rename to SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserGroups.swift index edeb4a472..2958b133c 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserGroups.swift @@ -354,7 +354,7 @@ internal extension SessionUtil { } // MARK: -- Handle Group Changes - // TODO: Add this + } fileprivate static func memberInfo(in legacyGroup: UnsafeMutablePointer) -> [String: Bool] { @@ -708,6 +708,7 @@ public extension SessionUtil { static func remove(_ db: Database, groupIds: [String]) throws { guard !groupIds.isEmpty else { return } + } } diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserProfile.swift similarity index 100% rename from SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift rename to SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserProfile.swift diff --git a/SessionMessagingKit/LibSessionUtil/Database/QueryInterfaceRequest+Utilities.swift b/SessionMessagingKit/SessionUtil/Database/QueryInterfaceRequest+Utilities.swift similarity index 97% rename from SessionMessagingKit/LibSessionUtil/Database/QueryInterfaceRequest+Utilities.swift rename to SessionMessagingKit/SessionUtil/Database/QueryInterfaceRequest+Utilities.swift index dbfeb8645..a8be039fe 100644 --- a/SessionMessagingKit/LibSessionUtil/Database/QueryInterfaceRequest+Utilities.swift +++ b/SessionMessagingKit/SessionUtil/Database/QueryInterfaceRequest+Utilities.swift @@ -93,7 +93,7 @@ public extension QueryInterfaceRequest where RowDecoder: FetchableRecord & Table // Then check if any of the changes could affect the config guard // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - Features.useSharedUtilForUserConfig, + SessionUtil.userConfigsEnabled(db, ignoreRequirementsForRunningMigrations: true), SessionUtil.assignmentsRequireConfigUpdate(assignments) else { return updatedData } diff --git a/SessionMessagingKit/LibSessionUtil/SessionUtil.swift b/SessionMessagingKit/SessionUtil/SessionUtil.swift similarity index 90% rename from SessionMessagingKit/LibSessionUtil/SessionUtil.swift rename to SessionMessagingKit/SessionUtil/SessionUtil.swift index d20e50253..16c7cf5a8 100644 --- a/SessionMessagingKit/LibSessionUtil/SessionUtil.swift +++ b/SessionMessagingKit/SessionUtil/SessionUtil.swift @@ -50,7 +50,7 @@ public enum SessionUtil { /// loaded yet (eg. fresh install) public static var needsSync: Bool { // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard Features.useSharedUtilForUserConfig else { return false } + guard SessionUtil.userConfigsEnabled else { return false } return configStore .wrappedValue @@ -63,16 +63,44 @@ public enum SessionUtil { public static var libSessionVersion: String { String(cString: LIBSESSION_UTIL_VERSION_STR) } - private static var hasCompletedRequiredMigrations: Bool = false + private static let requiredMigrationsCompleted: Atomic = Atomic(false) + private static let requiredMigrationIdentifiers: Set = [ + TargetMigrations.Identifier.messagingKit.key(with: _013_SessionUtilChanges.self), + TargetMigrations.Identifier.messagingKit.key(with: _014_GenerateInitialUserConfigDumps.self) + ] - internal static func requiredMigrationsCompleted(_ db: Database) -> Bool { - guard !hasCompletedRequiredMigrations else { return true } + public static var userConfigsEnabled: Bool { + Features.useSharedUtilForUserConfig && + requiredMigrationsCompleted.wrappedValue + } + + internal static func userConfigsEnabled( + _ db: Database, + ignoreRequirementsForRunningMigrations: Bool + ) -> Bool { + // First check if we are enabled regardless of what we want to ignore + guard + Features.useSharedUtilForUserConfig, + !requiredMigrationsCompleted.wrappedValue, + !refreshingUserConfigsEnabled(db), + ignoreRequirementsForRunningMigrations, + let currentlyRunningMigration: (identifier: TargetMigrations.Identifier, migration: Migration.Type) = Storage.shared.currentlyRunningMigration + else { return true } + + let nonIgnoredMigrationIdentifiers: Set = SessionUtil.requiredMigrationIdentifiers + .removing(currentlyRunningMigration.identifier.key(with: currentlyRunningMigration.migration)) return Storage.appliedMigrationIdentifiers(db) - .isSuperset(of: [ - _013_SessionUtilChanges.identifier, - _014_GenerateInitialUserConfigDumps.identifier - ]) + .isSuperset(of: nonIgnoredMigrationIdentifiers) + } + + @discardableResult public static func refreshingUserConfigsEnabled(_ db: Database) -> Bool { + let result: Bool = Storage.appliedMigrationIdentifiers(db) + .isSuperset(of: SessionUtil.requiredMigrationIdentifiers) + + requiredMigrationsCompleted.mutate { $0 = result } + + return result } internal static func lastError(_ conf: UnsafeMutablePointer?) -> String { @@ -86,9 +114,6 @@ public enum SessionUtil { userPublicKey: String, ed25519SecretKey: [UInt8]? ) { - // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard Features.useSharedUtilForUserConfig else { return } - // Ensure we have the ed25519 key and that we haven't already loaded the state before // we continue guard @@ -104,6 +129,9 @@ public enum SessionUtil { return } + // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent + guard SessionUtil.userConfigsEnabled(db, ignoreRequirementsForRunningMigrations: true) else { return } + // Retrieve the existing dumps from the database let existingDumps: Set = ((try? ConfigDump.fetchSet(db)) ?? []) let existingDumpVariants: Set = existingDumps @@ -300,7 +328,7 @@ public enum SessionUtil { public static func configHashes(for publicKey: String) -> [String] { // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard Features.useSharedUtilForUserConfig else { return [] } + guard SessionUtil.userConfigsEnabled else { return [] } return Storage.shared .read { db -> [String] in @@ -347,7 +375,7 @@ public enum SessionUtil { publicKey: String ) throws { // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard Features.useSharedUtilForUserConfig else { return } + guard SessionUtil.userConfigsEnabled else { return } guard !messages.isEmpty else { return } guard !publicKey.isEmpty else { throw MessageReceiverError.noThread } diff --git a/SessionMessagingKit/LibSessionUtil/SessionUtilError.swift b/SessionMessagingKit/SessionUtil/SessionUtilError.swift similarity index 100% rename from SessionMessagingKit/LibSessionUtil/SessionUtilError.swift rename to SessionMessagingKit/SessionUtil/SessionUtilError.swift diff --git a/SessionMessagingKit/LibSessionUtil/Utilities/TypeConversion+Utilities.swift b/SessionMessagingKit/SessionUtil/Utilities/TypeConversion+Utilities.swift similarity index 100% rename from SessionMessagingKit/LibSessionUtil/Utilities/TypeConversion+Utilities.swift rename to SessionMessagingKit/SessionUtil/Utilities/TypeConversion+Utilities.swift diff --git a/SessionMessagingKit/Utilities/AppReadiness.h b/SessionMessagingKit/Utilities/AppReadiness.h index 617892b00..2196cb68c 100755 --- a/SessionMessagingKit/Utilities/AppReadiness.h +++ b/SessionMessagingKit/Utilities/AppReadiness.h @@ -15,7 +15,8 @@ typedef void (^AppReadyBlock)(void); // This method can be called on any thread. + (BOOL)isAppReady; -// This method should only be called on the main thread. +// These methods should only be called on the main thread. ++ (void)invalidate; + (void)setAppIsReady; // If the app is ready, the block is called immediately; diff --git a/SessionMessagingKit/Utilities/AppReadiness.m b/SessionMessagingKit/Utilities/AppReadiness.m index 2d1ef8176..6698a777c 100755 --- a/SessionMessagingKit/Utilities/AppReadiness.m +++ b/SessionMessagingKit/Utilities/AppReadiness.m @@ -103,6 +103,16 @@ NS_ASSUME_NONNULL_BEGIN [self.appDidBecomeReadyBlocks addObject:block]; } ++ (void)invalidate +{ + [self.sharedManager invalidate]; +} + +- (void)invalidate +{ + self.isAppReady = NO; +} + + (void)setAppIsReady { [self.sharedManager setAppIsReady]; diff --git a/SessionMessagingKit/Utilities/Identity+Utilities.swift b/SessionMessagingKit/Utilities/Identity+Utilities.swift new file mode 100644 index 000000000..ca4d6e22b --- /dev/null +++ b/SessionMessagingKit/Utilities/Identity+Utilities.swift @@ -0,0 +1,19 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import GRDB +import SessionUtilitiesKit + +public extension Identity { + /// The user actually exists very early on during the onboarding process but there are also a few cases + /// where we want to know that the user is in a valid state (ie. has completed the proper onboarding + /// process), this value indicates that state + /// + /// One case which can happen is if the app crashed during onboarding the user can be left in an invalid + /// state (ie. with no display name) - the user would be asked to enter one on a subsequent launch to + /// resolve the invalid state + static func userCompletedRequiredOnboarding(_ db: Database? = nil) -> Bool { + Identity.userExists(db) && + !Profile.fetchOrCreateCurrentUser(db).name.isEmpty + } +} diff --git a/SessionMessagingKit/Utilities/ProfileManager.swift b/SessionMessagingKit/Utilities/ProfileManager.swift index 315896a46..14f748f9a 100644 --- a/SessionMessagingKit/Utilities/ProfileManager.swift +++ b/SessionMessagingKit/Utilities/ProfileManager.swift @@ -593,7 +593,7 @@ public struct ProfileManager { ) } // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - else if !Features.useSharedUtilForUserConfig { + else if !SessionUtil.userConfigsEnabled { // If we have a contact record for the profile (ie. it's a synced profile) then // should should send an updated config message, otherwise we should just update // the local state (the shared util has this logic build in to it's handling) diff --git a/SessionUtilitiesKit/Database/Storage.swift b/SessionUtilitiesKit/Database/Storage.swift index f2095bd52..4993be3f7 100644 --- a/SessionUtilitiesKit/Database/Storage.swift +++ b/SessionUtilitiesKit/Database/Storage.swift @@ -22,9 +22,15 @@ open class Storage { return true } + private let migrationsCompleted: Atomic = Atomic(false) + internal let internalCurrentlyRunningMigration: Atomic<(identifier: TargetMigrations.Identifier, migration: Migration.Type)?> = Atomic(nil) + public static let shared: Storage = Storage() public private(set) var isValid: Bool = false - public private(set) var hasCompletedMigrations: Bool = false + public var hasCompletedMigrations: Bool { migrationsCompleted.wrappedValue } + public var currentlyRunningMigration: (identifier: TargetMigrations.Identifier, migration: Migration.Type)? { + internalCurrentlyRunningMigration.wrappedValue + } public static let defaultPublisherScheduler: ValueObservationScheduler = .async(onQueue: .main) fileprivate var dbWriter: DatabaseWriter? @@ -186,7 +192,7 @@ open class Storage { // Store the logic to run when the migration completes let migrationCompleted: (Swift.Result) -> () = { [weak self] result in - self?.hasCompletedMigrations = true + self?.migrationsCompleted.mutate { $0 = true } self?.migrationProgressUpdater = nil SUKLegacy.clearLegacyDatabaseInstance() @@ -197,6 +203,12 @@ open class Storage { onComplete(result, needsConfigSync) } + // Update the 'migrationsCompleted' state (since we not support running migrations when + // returning from the background it's possible for this flag to transition back to false) + if unperformedMigrations.isEmpty { + self.migrationsCompleted.mutate { $0 = false } + } + // Note: The non-async migration should only be used for unit tests guard async else { do { try self.migrator?.migrate(dbWriter) } @@ -303,7 +315,7 @@ open class Storage { try? SUKLegacy.deleteLegacyDatabaseFilesAndKey() Storage.shared.isValid = false - Storage.shared.hasCompletedMigrations = false + Storage.shared.migrationsCompleted.mutate { $0 = false } Storage.shared.dbWriter = nil self.deleteDatabaseFiles() diff --git a/SessionUtilitiesKit/Database/Types/Migration.swift b/SessionUtilitiesKit/Database/Types/Migration.swift index 761525ea0..b0d87d187 100644 --- a/SessionUtilitiesKit/Database/Types/Migration.swift +++ b/SessionUtilitiesKit/Database/Types/Migration.swift @@ -16,7 +16,12 @@ public extension Migration { static func loggedMigrate(_ targetIdentifier: TargetMigrations.Identifier) -> ((_ db: Database) throws -> ()) { return { (db: Database) in SNLogNotTests("[Migration Info] Starting \(targetIdentifier.key(with: self))") - try migrate(db) + Storage.shared.internalCurrentlyRunningMigration.mutate { $0 = (targetIdentifier, self) } + do { try migrate(db) } + catch { + Storage.shared.internalCurrentlyRunningMigration.mutate { $0 = nil } + throw error + } SNLogNotTests("[Migration Info] Completed \(targetIdentifier.key(with: self))") } } diff --git a/SignalUtilitiesKit/Utilities/AppSetup.swift b/SignalUtilitiesKit/Utilities/AppSetup.swift index 641e7726a..9e89ed175 100644 --- a/SignalUtilitiesKit/Utilities/AppSetup.swift +++ b/SignalUtilitiesKit/Utilities/AppSetup.swift @@ -7,16 +7,16 @@ import SessionUtilitiesKit import SessionUIKit public enum AppSetup { - private static var hasRun: Bool = false + private static let hasRun: Atomic = Atomic(false) public static func setupEnvironment( appSpecificBlock: @escaping () -> (), migrationProgressChanged: ((CGFloat, TimeInterval) -> ())? = nil, migrationsCompletion: @escaping (Result, Bool) -> () ) { - guard !AppSetup.hasRun else { return } + guard !AppSetup.hasRun.wrappedValue else { return } - AppSetup.hasRun = true + AppSetup.hasRun.mutate { $0 = true } var backgroundTask: OWSBackgroundTask? = OWSBackgroundTask(labelStr: #function) @@ -84,6 +84,12 @@ public enum AppSetup { ) } + // Refresh the migration state for 'SessionUtil' so it's logic can start running + // correctly when called (doing this here instead of automatically via the + // `SessionUtil.userConfigsEnabled` property to avoid having to use the correct + // method when calling within a database read/write closure) + Storage.shared.read { db in SessionUtil.refreshingUserConfigsEnabled(db) } + DispatchQueue.main.async { migrationsCompletion(result, (needsConfigSync || SessionUtil.needsSync)) From 8cf2cef05009e4b19d0b5beef2c885e66bac99c1 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 21 Apr 2023 16:37:29 +1000 Subject: [PATCH 051/135] Fixed an issue where a migration could incorrectly hide the 'Note to Self' conversation --- .../Database/Migrations/_013_SessionUtilChanges.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/SessionMessagingKit/Database/Migrations/_013_SessionUtilChanges.swift b/SessionMessagingKit/Database/Migrations/_013_SessionUtilChanges.swift index b537933ca..8445948f9 100644 --- a/SessionMessagingKit/Database/Migrations/_013_SessionUtilChanges.swift +++ b/SessionMessagingKit/Database/Migrations/_013_SessionUtilChanges.swift @@ -217,8 +217,10 @@ enum _013_SessionUtilChanges: Migration { /// **Note:** Since migrations are run when running tests creating a random SessionThread will result in unexpected thread /// counts so don't do this when running tests (this logic is the same as in `MainAppContext.isRunningTests` if ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] == nil { - try SessionThread - .fetchOrCreate(db, id: userPublicKey, variant: .contact, shouldBeVisible: false) + if (try SessionThread.exists(db, id: userPublicKey)) == false { + try SessionThread + .fetchOrCreate(db, id: userPublicKey, variant: .contact, shouldBeVisible: false) + } } Storage.update(progress: 1, for: self, in: target) // In case this is the last migration From 56164ab7f47dd234597a5c692bd738da047a0e7f Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 26 Apr 2023 11:57:30 +1000 Subject: [PATCH 052/135] Fixed a build issue --- ...ThreadDisappearingMessagesSettingsViewModel.swift | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift b/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift index 26bb6e0e3..685247ce4 100644 --- a/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift +++ b/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift @@ -97,7 +97,7 @@ class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel [SectionModel] in let userPublicKey: String = getUserHexEncodedPublicKey(db, dependencies: dependencies) let maybeThreadViewModel: SessionThreadViewModel? = try SessionThreadViewModel @@ -115,7 +115,10 @@ class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel Date: Wed, 26 Apr 2023 15:13:39 +1000 Subject: [PATCH 053/135] Fixed an accessibility regression --- Session.xcodeproj/project.pbxproj | 8 +-- Session/Closed Groups/NewClosedGroupVC.swift | 2 +- .../ConversationVC+Interaction.swift | 11 ++-- .../Settings/ThreadSettingsViewModel.swift | 36 +++++++------ .../Settings/PrivacySettingsViewModel.swift | 4 +- Session/Settings/SettingsViewModel.swift | 6 +-- .../Types/SessionCell+Accessibility.swift | 18 ------- .../Shared/Types/SessionCell+Accessory.swift | 20 +++---- Session/Shared/Types/SessionCell+Info.swift | 14 ++--- Session/Shared/UserSelectionVC.swift | 2 +- .../UIContextualAction+Utilities.swift | 12 +++++ .../Components/ConfirmationModal.swift | 54 +++++++++---------- SessionUIKit/Types/Accessibility.swift | 16 ++++++ 13 files changed, 107 insertions(+), 96 deletions(-) delete mode 100644 Session/Shared/Types/SessionCell+Accessibility.swift create mode 100644 SessionUIKit/Types/Accessibility.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index eaa0f30e1..e483f8464 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -688,7 +688,6 @@ FD71164628E2CC1300B47552 /* SessionHighlightingBackgroundLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD71164528E2CC1300B47552 /* SessionHighlightingBackgroundLabel.swift */; }; FD71164828E2CE8700B47552 /* SessionCell+AccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD71164728E2CE8700B47552 /* SessionCell+AccessoryView.swift */; }; FD71164A28E3EA5B00B47552 /* DismissType.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD71164928E3EA5B00B47552 /* DismissType.swift */; }; - FD71164C28E3F5AA00B47552 /* SessionCell+Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD71164B28E3F5AA00B47552 /* SessionCell+Accessibility.swift */; }; FD71164E28E3F8CC00B47552 /* SessionCell+Info.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD71164D28E3F8CC00B47552 /* SessionCell+Info.swift */; }; FD71165028E3F9FA00B47552 /* SessionTableViewModel+NavItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD71164F28E3F9FA00B47552 /* SessionTableViewModel+NavItem.swift */; }; FD71165228E410BE00B47552 /* SessionTableSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD71165128E410BE00B47552 /* SessionTableSection.swift */; }; @@ -822,6 +821,7 @@ FDDF074429C3E3D000E5E8B5 /* FetchRequest+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDDF074329C3E3D000E5E8B5 /* FetchRequest+Utilities.swift */; }; FDE658A129418C7900A33BC1 /* CryptoKit+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE658A029418C7900A33BC1 /* CryptoKit+Utilities.swift */; }; FDE658A329418E2F00A33BC1 /* KeyPair.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE658A229418E2F00A33BC1 /* KeyPair.swift */; }; + FDE6E99829F8E63A00F93C5D /* Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE6E99729F8E63A00F93C5D /* Accessibility.swift */; }; FDE77F6B280FEB28002CFC5D /* ControlMessageProcessRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE77F6A280FEB28002CFC5D /* ControlMessageProcessRecord.swift */; }; FDED2E3C282E1B5D00B2CD2A /* UICollectionView+ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDED2E3B282E1B5D00B2CD2A /* UICollectionView+ReusableView.swift */; }; FDF0B73C27FFD3D6004C14C5 /* LinkPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B73B27FFD3D6004C14C5 /* LinkPreview.swift */; }; @@ -1824,7 +1824,6 @@ FD71164528E2CC1300B47552 /* SessionHighlightingBackgroundLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionHighlightingBackgroundLabel.swift; sourceTree = ""; }; FD71164728E2CE8700B47552 /* SessionCell+AccessoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionCell+AccessoryView.swift"; sourceTree = ""; }; FD71164928E3EA5B00B47552 /* DismissType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DismissType.swift; sourceTree = ""; }; - FD71164B28E3F5AA00B47552 /* SessionCell+Accessibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionCell+Accessibility.swift"; sourceTree = ""; }; FD71164D28E3F8CC00B47552 /* SessionCell+Info.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionCell+Info.swift"; sourceTree = ""; }; FD71164F28E3F9FA00B47552 /* SessionTableViewModel+NavItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionTableViewModel+NavItem.swift"; sourceTree = ""; }; FD71165128E410BE00B47552 /* SessionTableSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionTableSection.swift; sourceTree = ""; }; @@ -1954,6 +1953,7 @@ FDDF074329C3E3D000E5E8B5 /* FetchRequest+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FetchRequest+Utilities.swift"; sourceTree = ""; }; FDE658A029418C7900A33BC1 /* CryptoKit+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CryptoKit+Utilities.swift"; sourceTree = ""; }; FDE658A229418E2F00A33BC1 /* KeyPair.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyPair.swift; sourceTree = ""; }; + FDE6E99729F8E63A00F93C5D /* Accessibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Accessibility.swift; sourceTree = ""; }; FDE7214F287E50D50093DF33 /* ProtoWrappers.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = ProtoWrappers.py; sourceTree = ""; }; FDE72150287E50D50093DF33 /* LintLocalizableStrings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LintLocalizableStrings.swift; sourceTree = ""; }; FDE77F68280F9EDA002CFC5D /* JobRunnerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JobRunnerError.swift; sourceTree = ""; }; @@ -3967,6 +3967,7 @@ children = ( FD71162B28E1451400B47552 /* Position.swift */, FD71163128E2C42A00B47552 /* IconSize.swift */, + FDE6E99729F8E63A00F93C5D /* Accessibility.swift */, ); path = Types; sourceTree = ""; @@ -3989,7 +3990,6 @@ FD71163328E2C48400B47552 /* TransitionType.swift */, FD71164D28E3F8CC00B47552 /* SessionCell+Info.swift */, FD71164328E2CB8A00B47552 /* SessionCell+Accessory.swift */, - FD71164B28E3F5AA00B47552 /* SessionCell+Accessibility.swift */, FDF848F429413EEC007DCAE5 /* SessionCell+Styling.swift */, FD71165128E410BE00B47552 /* SessionTableSection.swift */, FD71164F28E3F9FA00B47552 /* SessionTableViewModel+NavItem.swift */, @@ -5426,6 +5426,7 @@ C331FFE92558FB0000070591 /* Separator.swift in Sources */, FD71163228E2C42A00B47552 /* IconSize.swift in Sources */, C33100282559000A00070591 /* UIView+Utilities.swift in Sources */, + FDE6E99829F8E63A00F93C5D /* Accessibility.swift in Sources */, FD37E9CA28A1E4BD003AE748 /* Theme+ClassicLight.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -5899,7 +5900,6 @@ B8CCF63723961D6D0091D419 /* NewDMVC.swift in Sources */, FDFDE12A282D056B0098B17F /* MediaZoomAnimationController.swift in Sources */, 4C1885D2218F8E1C00B67051 /* PhotoGridViewCell.swift in Sources */, - FD71164C28E3F5AA00B47552 /* SessionCell+Accessibility.swift in Sources */, 34D1F0501F7D45A60066283D /* GifPickerCell.swift in Sources */, 7BFA8AE32831D0D4001876F3 /* ContextMenuVC+EmojiReactsView.swift in Sources */, C3E5C2FA251DBABB0040DFFC /* EditClosedGroupVC.swift in Sources */, diff --git a/Session/Closed Groups/NewClosedGroupVC.swift b/Session/Closed Groups/NewClosedGroupVC.swift index f7c943b41..bdc06afb0 100644 --- a/Session/Closed Groups/NewClosedGroupVC.swift +++ b/Session/Closed Groups/NewClosedGroupVC.swift @@ -214,7 +214,7 @@ final class NewClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegate self?.selectedContacts.contains(profile.id) == true }), styling: SessionCell.StyleInfo(backgroundStyle: .edgeToEdge), - accessibility: SessionCell.Accessibility( + accessibility: Accessibility( identifier: "Contact" ) ) diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 462018a08..75cfbe147 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -71,8 +71,7 @@ extension ConversationVC: title: "modal_call_permission_request_title".localized(), explanation: "modal_call_permission_request_explanation".localized(), confirmTitle: "vc_settings_title".localized(), - confirmAccessibilityLabel: "Settings", - cancelAccessibilityLabel: "Cancel", + confirmAccessibility: Accessibility(identifier: "Settings"), dismissOnConfirm: false // Custom dismissal logic ) { [weak self] _ in self?.dismiss(animated: true) { @@ -139,8 +138,8 @@ extension ConversationVC: range: (message as NSString).range(of: self.viewModel.threadData.displayName) ), confirmTitle: "modal_blocked_button_title".localized(), - confirmAccessibilityLabel: "Confirm block", - cancelAccessibilityLabel: "Cancel block", + confirmAccessibility: Accessibility(identifier: "Confirm block"), + cancelAccessibility: Accessibility(identifier: "Cancel block"), dismissOnConfirm: false // Custom dismissal logic ) { [weak self] _ in self?.viewModel.unblockContact() @@ -912,8 +911,8 @@ extension ConversationVC: range: (message as NSString).range(of: cellViewModel.authorName) ), confirmTitle: "modal_download_button_title".localized(), - confirmAccessibilityLabel: "Download media", - cancelAccessibilityLabel: "Don't download media", + confirmAccessibility: Accessibility(identifier: "Download media"), + cancelAccessibility: Accessibility(identifier: "Don't download media"), dismissOnConfirm: false // Custom dismissal logic ) { [weak self] _ in self?.viewModel.trustContact() diff --git a/Session/Conversations/Settings/ThreadSettingsViewModel.swift b/Session/Conversations/Settings/ThreadSettingsViewModel.swift index c6b577a21..a55f4d7c7 100644 --- a/Session/Conversations/Settings/ThreadSettingsViewModel.swift +++ b/Session/Conversations/Settings/ThreadSettingsViewModel.swift @@ -285,7 +285,7 @@ class ThreadSettingsViewModel: SessionTableViewModel Void ) case toggle( DataSource, - accessibility: SessionCell.Accessibility? + accessibility: Accessibility? ) case dropDown( DataSource, - accessibility: SessionCell.Accessibility? + accessibility: Accessibility? ) case radio( size: RadioSize, isSelected: () -> Bool, storedSelection: Bool, - accessibility: SessionCell.Accessibility? + accessibility: Accessibility? ) case highlightingBackgroundLabel( title: String, - accessibility: SessionCell.Accessibility? + accessibility: Accessibility? ) case profile( id: String, @@ -49,18 +49,18 @@ extension SessionCell { profile: Profile?, additionalProfile: Profile?, cornerIcon: UIImage?, - accessibility: SessionCell.Accessibility? + accessibility: Accessibility? ) case search( placeholder: String, - accessibility: SessionCell.Accessibility?, + accessibility: Accessibility?, searchTermChanged: (String?) -> Void ) case button( style: SessionButton.Style, title: String, - accessibility: SessionCell.Accessibility?, + accessibility: Accessibility?, run: (SessionButton?) -> Void ) case customView( @@ -283,7 +283,7 @@ extension SessionCell.Accessory { return .icon(image, size: .medium, customTint: nil, shouldFill: shouldFill, accessibility: nil) } - public static func icon(_ image: UIImage?, accessibility: SessionCell.Accessibility) -> SessionCell.Accessory { + public static func icon(_ image: UIImage?, accessibility: Accessibility) -> SessionCell.Accessory { return .icon(image, size: .medium, customTint: nil, shouldFill: false, accessibility: accessibility) } diff --git a/Session/Shared/Types/SessionCell+Info.swift b/Session/Shared/Types/SessionCell+Info.swift index 35cd764ac..35d63cb03 100644 --- a/Session/Shared/Types/SessionCell+Info.swift +++ b/Session/Shared/Types/SessionCell+Info.swift @@ -14,7 +14,7 @@ extension SessionCell { let rightAccessory: SessionCell.Accessory? let styling: StyleInfo let isEnabled: Bool - let accessibility: SessionCell.Accessibility? + let accessibility: Accessibility? let confirmationInfo: ConfirmationModal.Info? let onTap: (() -> Void)? let onTapView: ((UIView?) -> Void)? @@ -37,7 +37,7 @@ extension SessionCell { rightAccessory: SessionCell.Accessory? = nil, styling: StyleInfo = StyleInfo(), isEnabled: Bool = true, - accessibility: SessionCell.Accessibility? = nil, + accessibility: Accessibility? = nil, confirmationInfo: ConfirmationModal.Info? = nil, onTap: (() -> Void)? = nil, onTapView: ((UIView?) -> Void)? = nil @@ -119,7 +119,7 @@ public extension SessionCell.Info { accessory: SessionCell.Accessory, styling: SessionCell.StyleInfo = SessionCell.StyleInfo(), isEnabled: Bool = true, - accessibility: SessionCell.Accessibility? = nil, + accessibility: Accessibility? = nil, confirmationInfo: ConfirmationModal.Info? = nil, onTap: (() -> Void)? = nil ) { @@ -146,7 +146,7 @@ public extension SessionCell.Info { rightAccessory: SessionCell.Accessory, styling: SessionCell.StyleInfo = SessionCell.StyleInfo(), isEnabled: Bool = true, - accessibility: SessionCell.Accessibility? = nil, + accessibility: Accessibility? = nil, confirmationInfo: ConfirmationModal.Info? = nil ) { self.id = id @@ -173,7 +173,7 @@ public extension SessionCell.Info { rightAccessory: SessionCell.Accessory? = nil, styling: SessionCell.StyleInfo = SessionCell.StyleInfo(), isEnabled: Bool = true, - accessibility: SessionCell.Accessibility? = nil, + accessibility: Accessibility? = nil, confirmationInfo: ConfirmationModal.Info? = nil, onTap: (() -> Void)? = nil ) { @@ -201,7 +201,7 @@ public extension SessionCell.Info { rightAccessory: SessionCell.Accessory? = nil, styling: SessionCell.StyleInfo = SessionCell.StyleInfo(), isEnabled: Bool = true, - accessibility: SessionCell.Accessibility? = nil, + accessibility: Accessibility? = nil, confirmationInfo: ConfirmationModal.Info? = nil, onTap: (() -> Void)? = nil ) { @@ -230,7 +230,7 @@ public extension SessionCell.Info { rightAccessory: SessionCell.Accessory? = nil, styling: SessionCell.StyleInfo = SessionCell.StyleInfo(), isEnabled: Bool = true, - accessibility: SessionCell.Accessibility? = nil, + accessibility: Accessibility? = nil, confirmationInfo: ConfirmationModal.Info? = nil, onTap: (() -> Void)? = nil, onTapView: ((UIView?) -> Void)? = nil diff --git a/Session/Shared/UserSelectionVC.swift b/Session/Shared/UserSelectionVC.swift index 17a5633cf..8e823ffbe 100644 --- a/Session/Shared/UserSelectionVC.swift +++ b/Session/Shared/UserSelectionVC.swift @@ -75,7 +75,7 @@ final class UserSelectionVC: BaseVC, UITableViewDataSource, UITableViewDelegate self?.selectedUsers.contains(profile.id) == true }), styling: SessionCell.StyleInfo(backgroundStyle: .edgeToEdge), - accessibility: SessionCell.Accessibility(identifier: "Contact") + accessibility: Accessibility(identifier: "Contact") ) ) diff --git a/Session/Utilities/UIContextualAction+Utilities.swift b/Session/Utilities/UIContextualAction+Utilities.swift index 934b595ea..75f18f96a 100644 --- a/Session/Utilities/UIContextualAction+Utilities.swift +++ b/Session/Utilities/UIContextualAction+Utilities.swift @@ -151,6 +151,9 @@ public extension UIContextualAction { title: "hide_note_to_self_confirmation_alert_title".localized(), attributedExplanation: confirmationModalExplanation, confirmTitle: "TXT_HIDE_TITLE".localized(), + confirmAccessibility: Accessibility( + identifier: "Hide" + ), confirmStyle: .danger, cancelStyle: .alert_text, dismissOnConfirm: true, @@ -337,6 +340,9 @@ public extension UIContextualAction { info: ConfirmationModal.Info( title: "MESSAGE_REQUESTS_BLOCK_CONFIRMATION_ACTON".localized(), confirmTitle: "BLOCK_LIST_BLOCK_BUTTON".localized(), + confirmAccessibility: Accessibility( + identifier: "Block" + ), confirmStyle: .danger, cancelStyle: .alert_text, dismissOnConfirm: true, @@ -398,6 +404,9 @@ public extension UIContextualAction { title: confirmationModalTitle, attributedExplanation: confirmationModalExplanation, confirmTitle: "LEAVE_BUTTON_TITLE".localized(), + confirmAccessibility: Accessibility( + identifier: "Leave" + ), confirmStyle: .danger, cancelStyle: .alert_text, dismissOnConfirm: true, @@ -493,6 +502,9 @@ public extension UIContextualAction { title: confirmationModalTitle, attributedExplanation: confirmationModalExplanation, confirmTitle: "TXT_DELETE_TITLE".localized(), + confirmAccessibility: Accessibility( + identifier: "Delete" + ), confirmStyle: .danger, cancelStyle: .alert_text, dismissOnConfirm: true, diff --git a/SessionUIKit/Components/ConfirmationModal.swift b/SessionUIKit/Components/ConfirmationModal.swift index 2ffe5947a..032d60e32 100644 --- a/SessionUIKit/Components/ConfirmationModal.swift +++ b/SessionUIKit/Components/ConfirmationModal.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit +import SessionUtilitiesKit public class ConfirmationModal: Modal { public struct Info: Equatable, Hashable { @@ -21,14 +22,13 @@ public class ConfirmationModal: Modal { let title: String let explanation: String? let attributedExplanation: NSAttributedString? - let accessibilityLabel: String? - let accessibilityIdentifier: String? + let accessibility: Accessibility? public let stateToShow: State let confirmTitle: String? - let confirmAccessibilityLabel: String? + let confirmAccessibility: Accessibility? let confirmStyle: ThemeValue let cancelTitle: String - let cancelAccessibilityLabel: String? + let cancelAccessibility: Accessibility? let cancelStyle: ThemeValue let dismissOnConfirm: Bool let onConfirm: ((UIViewController) -> ())? @@ -40,14 +40,15 @@ public class ConfirmationModal: Modal { title: String, explanation: String? = nil, attributedExplanation: NSAttributedString? = nil, - accessibilityLabel: String? = nil, - accessibilityId: String? = nil, + accessibility: Accessibility? = nil, stateToShow: State = .always, confirmTitle: String? = nil, - confirmAccessibilityLabel: String? = nil, + confirmAccessibility: Accessibility? = nil, confirmStyle: ThemeValue = .alert_text, cancelTitle: String = "TXT_CANCEL_TITLE".localized(), - cancelAccessibilityLabel: String? = nil, + cancelAccessibility: Accessibility? = Accessibility( + identifier: "Cancel" + ), cancelStyle: ThemeValue = .danger, dismissOnConfirm: Bool = true, onConfirm: ((UIViewController) -> ())? = nil, @@ -56,14 +57,13 @@ public class ConfirmationModal: Modal { self.title = title self.explanation = explanation self.attributedExplanation = attributedExplanation - self.accessibilityLabel = accessibilityLabel - self.accessibilityIdentifier = accessibilityId + self.accessibility = accessibility self.stateToShow = stateToShow self.confirmTitle = confirmTitle - self.confirmAccessibilityLabel = confirmAccessibilityLabel + self.confirmAccessibility = confirmAccessibility self.confirmStyle = confirmStyle self.cancelTitle = cancelTitle - self.cancelAccessibilityLabel = cancelAccessibilityLabel + self.cancelAccessibility = cancelAccessibility self.cancelStyle = cancelStyle self.dismissOnConfirm = dismissOnConfirm self.onConfirm = onConfirm @@ -80,13 +80,13 @@ public class ConfirmationModal: Modal { title: self.title, explanation: self.explanation, attributedExplanation: self.attributedExplanation, - accessibilityLabel: self.accessibilityLabel, + accessibility: self.accessibility, stateToShow: self.stateToShow, confirmTitle: self.confirmTitle, - confirmAccessibilityLabel: self.confirmAccessibilityLabel, + confirmAccessibility: self.confirmAccessibility, confirmStyle: self.confirmStyle, cancelTitle: self.cancelTitle, - cancelAccessibilityLabel: self.cancelAccessibilityLabel, + cancelAccessibility: self.cancelAccessibility, cancelStyle: self.cancelStyle, dismissOnConfirm: self.dismissOnConfirm, onConfirm: (onConfirm ?? self.onConfirm), @@ -101,13 +101,13 @@ public class ConfirmationModal: Modal { lhs.title == rhs.title && lhs.explanation == rhs.explanation && lhs.attributedExplanation == rhs.attributedExplanation && - lhs.accessibilityLabel == rhs.accessibilityLabel && + lhs.accessibility == rhs.accessibility && lhs.stateToShow == rhs.stateToShow && lhs.confirmTitle == rhs.confirmTitle && - lhs.confirmAccessibilityLabel == rhs.confirmAccessibilityLabel && + lhs.confirmAccessibility == rhs.confirmAccessibility && lhs.confirmStyle == rhs.confirmStyle && lhs.cancelTitle == rhs.cancelTitle && - lhs.cancelAccessibilityLabel == rhs.cancelAccessibilityLabel && + lhs.cancelAccessibility == rhs.cancelAccessibility && lhs.cancelStyle == rhs.cancelStyle && lhs.dismissOnConfirm == rhs.dismissOnConfirm ) @@ -117,13 +117,13 @@ public class ConfirmationModal: Modal { title.hash(into: &hasher) explanation.hash(into: &hasher) attributedExplanation.hash(into: &hasher) - accessibilityLabel.hash(into: &hasher) + accessibility.hash(into: &hasher) stateToShow.hash(into: &hasher) confirmTitle.hash(into: &hasher) - confirmAccessibilityLabel.hash(into: &hasher) + confirmAccessibility.hash(into: &hasher) confirmStyle.hash(into: &hasher) cancelTitle.hash(into: &hasher) - cancelAccessibilityLabel.hash(into: &hasher) + cancelAccessibility.hash(into: &hasher) cancelStyle.hash(into: &hasher) dismissOnConfirm.hash(into: &hasher) } @@ -229,20 +229,20 @@ public class ConfirmationModal: Modal { info.explanation == nil && info.attributedExplanation == nil ) - confirmButton.accessibilityLabel = info.confirmAccessibilityLabel - confirmButton.accessibilityIdentifier = info.confirmAccessibilityLabel + confirmButton.accessibilityLabel = info.confirmAccessibility?.label + confirmButton.accessibilityIdentifier = info.confirmAccessibility?.identifier confirmButton.isAccessibilityElement = true confirmButton.setTitle(info.confirmTitle, for: .normal) confirmButton.setThemeTitleColor(info.confirmStyle, for: .normal) confirmButton.isHidden = (info.confirmTitle == nil) - cancelButton.accessibilityLabel = info.cancelAccessibilityLabel - cancelButton.accessibilityIdentifier = info.cancelAccessibilityLabel + cancelButton.accessibilityLabel = info.cancelAccessibility?.label + cancelButton.accessibilityIdentifier = info.cancelAccessibility?.identifier cancelButton.isAccessibilityElement = true cancelButton.setTitle(info.cancelTitle, for: .normal) cancelButton.setThemeTitleColor(info.cancelStyle, for: .normal) - self.contentView.accessibilityLabel = info.accessibilityLabel - self.contentView.accessibilityIdentifier = info.accessibilityIdentifier + contentView.accessibilityLabel = info.accessibility?.label + contentView.accessibilityIdentifier = info.accessibility?.identifier } required init?(coder: NSCoder) { diff --git a/SessionUIKit/Types/Accessibility.swift b/SessionUIKit/Types/Accessibility.swift new file mode 100644 index 000000000..ef1d2ff02 --- /dev/null +++ b/SessionUIKit/Types/Accessibility.swift @@ -0,0 +1,16 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public struct Accessibility: Hashable, Equatable { + public let identifier: String? + public let label: String? + + public init( + identifier: String? = nil, + label: String? = nil + ) { + self.identifier = identifier + self.label = label + } +} From a9afb2d1d10ec53ac56a14a8aafbafc587218a99 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 26 Apr 2023 16:18:36 +1000 Subject: [PATCH 054/135] Accessibility and copy tweaks --- Session/Conversations/ConversationVC.swift | 4 ++-- Session/Meta/Translations/de.lproj/Localizable.strings | 1 - Session/Meta/Translations/en.lproj/Localizable.strings | 1 - Session/Meta/Translations/es.lproj/Localizable.strings | 1 - Session/Meta/Translations/fi.lproj/Localizable.strings | 1 - Session/Meta/Translations/fr.lproj/Localizable.strings | 1 - Session/Meta/Translations/hi.lproj/Localizable.strings | 1 - Session/Meta/Translations/hr.lproj/Localizable.strings | 1 - Session/Meta/Translations/id-ID.lproj/Localizable.strings | 1 - Session/Meta/Translations/it.lproj/Localizable.strings | 1 - Session/Meta/Translations/ja.lproj/Localizable.strings | 1 - Session/Meta/Translations/nl.lproj/Localizable.strings | 1 - Session/Meta/Translations/pl.lproj/Localizable.strings | 1 - Session/Meta/Translations/pt_BR.lproj/Localizable.strings | 1 - Session/Meta/Translations/ru.lproj/Localizable.strings | 1 - Session/Meta/Translations/si.lproj/Localizable.strings | 1 - Session/Meta/Translations/sk.lproj/Localizable.strings | 1 - Session/Meta/Translations/sv.lproj/Localizable.strings | 1 - Session/Meta/Translations/th.lproj/Localizable.strings | 1 - Session/Meta/Translations/vi-VN.lproj/Localizable.strings | 1 - Session/Meta/Translations/zh-Hant.lproj/Localizable.strings | 1 - Session/Meta/Translations/zh_CN.lproj/Localizable.strings | 1 - Session/Utilities/UIContextualAction+Utilities.swift | 2 +- 23 files changed, 3 insertions(+), 24 deletions(-) diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index 7ee477e53..274d3679a 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -325,10 +325,10 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers private lazy var messageRequestDeleteButton: UIButton = { let result: SessionButton = SessionButton(style: .destructive, size: .medium) - result.accessibilityLabel = "Decline message request" + result.accessibilityLabel = "Delete message request" result.isAccessibilityElement = true result.translatesAutoresizingMaskIntoConstraints = false - result.setTitle("TXT_DECLINE_TITLE".localized(), for: .normal) + result.setTitle("TXT_DELETE_TITLE".localized(), for: .normal) result.addTarget(self, action: #selector(deleteMessageRequest), for: .touchUpInside) return result diff --git a/Session/Meta/Translations/de.lproj/Localizable.strings b/Session/Meta/Translations/de.lproj/Localizable.strings index d3ed55f01..8833e453f 100644 --- a/Session/Meta/Translations/de.lproj/Localizable.strings +++ b/Session/Meta/Translations/de.lproj/Localizable.strings @@ -407,7 +407,6 @@ "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "TXT_HIDE_TITLE" = "Hide"; "TXT_DELETE_ACCEPT" = "Accept"; -"TXT_DECLINE_TITLE" = "Decline"; "TXT_BLOCK_USER_TITLE" = "Block User"; "ALERT_ERROR_TITLE" = "Fehler"; "modal_call_permission_request_title" = "Call Permissions Required"; diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index 74cef4b39..26c888e98 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -407,7 +407,6 @@ "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "TXT_HIDE_TITLE" = "Hide"; "TXT_DELETE_ACCEPT" = "Accept"; -"TXT_DECLINE_TITLE" = "Decline"; "TXT_BLOCK_USER_TITLE" = "Block User"; "ALERT_ERROR_TITLE" = "Error"; "modal_call_permission_request_title" = "Call Permissions Required"; diff --git a/Session/Meta/Translations/es.lproj/Localizable.strings b/Session/Meta/Translations/es.lproj/Localizable.strings index ce988d646..94d8cb40e 100644 --- a/Session/Meta/Translations/es.lproj/Localizable.strings +++ b/Session/Meta/Translations/es.lproj/Localizable.strings @@ -407,7 +407,6 @@ "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "TXT_HIDE_TITLE" = "Hide"; "TXT_DELETE_ACCEPT" = "Accept"; -"TXT_DECLINE_TITLE" = "Decline"; "TXT_BLOCK_USER_TITLE" = "Block User"; "ALERT_ERROR_TITLE" = "Fallo"; "modal_call_permission_request_title" = "Call Permissions Required"; diff --git a/Session/Meta/Translations/fi.lproj/Localizable.strings b/Session/Meta/Translations/fi.lproj/Localizable.strings index d3abeb90d..7480fcb7d 100644 --- a/Session/Meta/Translations/fi.lproj/Localizable.strings +++ b/Session/Meta/Translations/fi.lproj/Localizable.strings @@ -407,7 +407,6 @@ "MESSAGE_REQUESTS_NOTIFICATION" = "Sinulla on uusi viestipyyntö"; "TXT_HIDE_TITLE" = "Piilota"; "TXT_DELETE_ACCEPT" = "Hyväksy"; -"TXT_DECLINE_TITLE" = "Decline"; "TXT_BLOCK_USER_TITLE" = "Block User"; "ALERT_ERROR_TITLE" = "Virhe"; "modal_call_permission_request_title" = "Call Permissions Required"; diff --git a/Session/Meta/Translations/fr.lproj/Localizable.strings b/Session/Meta/Translations/fr.lproj/Localizable.strings index 00a2401da..2de5b60dc 100644 --- a/Session/Meta/Translations/fr.lproj/Localizable.strings +++ b/Session/Meta/Translations/fr.lproj/Localizable.strings @@ -407,7 +407,6 @@ "MESSAGE_REQUESTS_NOTIFICATION" = "Vous avez une nouvelle demande de message"; "TXT_HIDE_TITLE" = "Masquer"; "TXT_DELETE_ACCEPT" = "Accepter"; -"TXT_DECLINE_TITLE" = "Decline"; "TXT_BLOCK_USER_TITLE" = "Block User"; "ALERT_ERROR_TITLE" = "Erreur"; "modal_call_permission_request_title" = "Autorisations d'appel requises"; diff --git a/Session/Meta/Translations/hi.lproj/Localizable.strings b/Session/Meta/Translations/hi.lproj/Localizable.strings index 7df908ba8..727d53c93 100644 --- a/Session/Meta/Translations/hi.lproj/Localizable.strings +++ b/Session/Meta/Translations/hi.lproj/Localizable.strings @@ -407,7 +407,6 @@ "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "TXT_HIDE_TITLE" = "Hide"; "TXT_DELETE_ACCEPT" = "Accept"; -"TXT_DECLINE_TITLE" = "Decline"; "TXT_BLOCK_USER_TITLE" = "Block User"; "ALERT_ERROR_TITLE" = "Error"; "modal_call_permission_request_title" = "Call Permissions Required"; diff --git a/Session/Meta/Translations/hr.lproj/Localizable.strings b/Session/Meta/Translations/hr.lproj/Localizable.strings index 13e448075..c3f7d04bd 100644 --- a/Session/Meta/Translations/hr.lproj/Localizable.strings +++ b/Session/Meta/Translations/hr.lproj/Localizable.strings @@ -407,7 +407,6 @@ "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "TXT_HIDE_TITLE" = "Hide"; "TXT_DELETE_ACCEPT" = "Accept"; -"TXT_DECLINE_TITLE" = "Decline"; "TXT_BLOCK_USER_TITLE" = "Block User"; "ALERT_ERROR_TITLE" = "Greška"; "modal_call_permission_request_title" = "Call Permissions Required"; diff --git a/Session/Meta/Translations/id-ID.lproj/Localizable.strings b/Session/Meta/Translations/id-ID.lproj/Localizable.strings index 3ad886270..43c2f55e9 100644 --- a/Session/Meta/Translations/id-ID.lproj/Localizable.strings +++ b/Session/Meta/Translations/id-ID.lproj/Localizable.strings @@ -407,7 +407,6 @@ "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "TXT_HIDE_TITLE" = "Hide"; "TXT_DELETE_ACCEPT" = "Accept"; -"TXT_DECLINE_TITLE" = "Decline"; "TXT_BLOCK_USER_TITLE" = "Block User"; "ALERT_ERROR_TITLE" = "Galat"; "modal_call_permission_request_title" = "Call Permissions Required"; diff --git a/Session/Meta/Translations/it.lproj/Localizable.strings b/Session/Meta/Translations/it.lproj/Localizable.strings index cb679a67e..c0747756b 100644 --- a/Session/Meta/Translations/it.lproj/Localizable.strings +++ b/Session/Meta/Translations/it.lproj/Localizable.strings @@ -407,7 +407,6 @@ "MESSAGE_REQUESTS_NOTIFICATION" = "Hai una nuova richiesta di messaggio"; "TXT_HIDE_TITLE" = "Nascondi"; "TXT_DELETE_ACCEPT" = "Accetta"; -"TXT_DECLINE_TITLE" = "Decline"; "TXT_BLOCK_USER_TITLE" = "Block User"; "ALERT_ERROR_TITLE" = "Errore"; "modal_call_permission_request_title" = "Permessi Di Chiamata Richiesti"; diff --git a/Session/Meta/Translations/ja.lproj/Localizable.strings b/Session/Meta/Translations/ja.lproj/Localizable.strings index b8b24667a..96c28bfe7 100644 --- a/Session/Meta/Translations/ja.lproj/Localizable.strings +++ b/Session/Meta/Translations/ja.lproj/Localizable.strings @@ -407,7 +407,6 @@ "MESSAGE_REQUESTS_NOTIFICATION" = "新しいリクエストがあります"; "TXT_HIDE_TITLE" = "非表示"; "TXT_DELETE_ACCEPT" = "許可"; -"TXT_DECLINE_TITLE" = "Decline"; "TXT_BLOCK_USER_TITLE" = "Block User"; "ALERT_ERROR_TITLE" = "エラー"; "modal_call_permission_request_title" = "許可が必要です"; diff --git a/Session/Meta/Translations/nl.lproj/Localizable.strings b/Session/Meta/Translations/nl.lproj/Localizable.strings index e15af72a9..6036e56a9 100644 --- a/Session/Meta/Translations/nl.lproj/Localizable.strings +++ b/Session/Meta/Translations/nl.lproj/Localizable.strings @@ -407,7 +407,6 @@ "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "TXT_HIDE_TITLE" = "Hide"; "TXT_DELETE_ACCEPT" = "Accept"; -"TXT_DECLINE_TITLE" = "Decline"; "TXT_BLOCK_USER_TITLE" = "Block User"; "ALERT_ERROR_TITLE" = "Fout"; "modal_call_permission_request_title" = "Call Permissions Required"; diff --git a/Session/Meta/Translations/pl.lproj/Localizable.strings b/Session/Meta/Translations/pl.lproj/Localizable.strings index 50c5895f1..0e4b181ea 100644 --- a/Session/Meta/Translations/pl.lproj/Localizable.strings +++ b/Session/Meta/Translations/pl.lproj/Localizable.strings @@ -407,7 +407,6 @@ "MESSAGE_REQUESTS_NOTIFICATION" = "Masz nowe żądanie wiadomości"; "TXT_HIDE_TITLE" = "Ukryj"; "TXT_DELETE_ACCEPT" = "Zaakceptuj"; -"TXT_DECLINE_TITLE" = "Decline"; "TXT_BLOCK_USER_TITLE" = "Block User"; "ALERT_ERROR_TITLE" = "Błąd"; "modal_call_permission_request_title" = "Wymagane uprawnienia połączenia"; diff --git a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings index e4435383d..45b6eaac3 100644 --- a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings +++ b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings @@ -407,7 +407,6 @@ "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "TXT_HIDE_TITLE" = "Hide"; "TXT_DELETE_ACCEPT" = "Accept"; -"TXT_DECLINE_TITLE" = "Decline"; "TXT_BLOCK_USER_TITLE" = "Block User"; "ALERT_ERROR_TITLE" = "Erro"; "modal_call_permission_request_title" = "Call Permissions Required"; diff --git a/Session/Meta/Translations/ru.lproj/Localizable.strings b/Session/Meta/Translations/ru.lproj/Localizable.strings index f3cda8946..4095c858d 100644 --- a/Session/Meta/Translations/ru.lproj/Localizable.strings +++ b/Session/Meta/Translations/ru.lproj/Localizable.strings @@ -407,7 +407,6 @@ "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "TXT_HIDE_TITLE" = "Скрыть"; "TXT_DELETE_ACCEPT" = "Accept"; -"TXT_DECLINE_TITLE" = "Decline"; "TXT_BLOCK_USER_TITLE" = "Block User"; "ALERT_ERROR_TITLE" = "Ошибка"; "modal_call_permission_request_title" = "Call Permissions Required"; diff --git a/Session/Meta/Translations/si.lproj/Localizable.strings b/Session/Meta/Translations/si.lproj/Localizable.strings index f20d67f22..73fe4b973 100644 --- a/Session/Meta/Translations/si.lproj/Localizable.strings +++ b/Session/Meta/Translations/si.lproj/Localizable.strings @@ -407,7 +407,6 @@ "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "TXT_HIDE_TITLE" = "Hide"; "TXT_DELETE_ACCEPT" = "Accept"; -"TXT_DECLINE_TITLE" = "Decline"; "TXT_BLOCK_USER_TITLE" = "Block User"; "ALERT_ERROR_TITLE" = "Error"; "modal_call_permission_request_title" = "Call Permissions Required"; diff --git a/Session/Meta/Translations/sk.lproj/Localizable.strings b/Session/Meta/Translations/sk.lproj/Localizable.strings index e9e1c61c7..aca8b82b1 100644 --- a/Session/Meta/Translations/sk.lproj/Localizable.strings +++ b/Session/Meta/Translations/sk.lproj/Localizable.strings @@ -407,7 +407,6 @@ "MESSAGE_REQUESTS_NOTIFICATION" = "Máte novú žiadosť o správu"; "TXT_HIDE_TITLE" = "Skryť"; "TXT_DELETE_ACCEPT" = "Accept"; -"TXT_DECLINE_TITLE" = "Decline"; "TXT_BLOCK_USER_TITLE" = "Block User"; "ALERT_ERROR_TITLE" = "Error"; "modal_call_permission_request_title" = "Call Permissions Required"; diff --git a/Session/Meta/Translations/sv.lproj/Localizable.strings b/Session/Meta/Translations/sv.lproj/Localizable.strings index eab9e754e..f55527c47 100644 --- a/Session/Meta/Translations/sv.lproj/Localizable.strings +++ b/Session/Meta/Translations/sv.lproj/Localizable.strings @@ -407,7 +407,6 @@ "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "TXT_HIDE_TITLE" = "Hide"; "TXT_DELETE_ACCEPT" = "Accept"; -"TXT_DECLINE_TITLE" = "Decline"; "TXT_BLOCK_USER_TITLE" = "Block User"; "ALERT_ERROR_TITLE" = "Fel"; "modal_call_permission_request_title" = "Call Permissions Required"; diff --git a/Session/Meta/Translations/th.lproj/Localizable.strings b/Session/Meta/Translations/th.lproj/Localizable.strings index cba1fe5f1..e626b036d 100644 --- a/Session/Meta/Translations/th.lproj/Localizable.strings +++ b/Session/Meta/Translations/th.lproj/Localizable.strings @@ -407,7 +407,6 @@ "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "TXT_HIDE_TITLE" = "Hide"; "TXT_DELETE_ACCEPT" = "Accept"; -"TXT_DECLINE_TITLE" = "Decline"; "TXT_BLOCK_USER_TITLE" = "Block User"; "ALERT_ERROR_TITLE" = "ข้อผิดพลาด"; "modal_call_permission_request_title" = "Call Permissions Required"; diff --git a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings index 00440530d..ba08816f3 100644 --- a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings +++ b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings @@ -407,7 +407,6 @@ "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "TXT_HIDE_TITLE" = "Hide"; "TXT_DELETE_ACCEPT" = "Accept"; -"TXT_DECLINE_TITLE" = "Decline"; "TXT_BLOCK_USER_TITLE" = "Block User"; "ALERT_ERROR_TITLE" = "Error"; "modal_call_permission_request_title" = "Call Permissions Required"; diff --git a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings index 3f4b33302..ba5d76ba1 100644 --- a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings @@ -407,7 +407,6 @@ "MESSAGE_REQUESTS_NOTIFICATION" = "You have a new message request"; "TXT_HIDE_TITLE" = "Hide"; "TXT_DELETE_ACCEPT" = "Accept"; -"TXT_DECLINE_TITLE" = "Decline"; "TXT_BLOCK_USER_TITLE" = "Block User"; "ALERT_ERROR_TITLE" = "Error"; "modal_call_permission_request_title" = "Call Permissions Required"; diff --git a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings index ba2f7dd0c..a75f4c4cc 100644 --- a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings @@ -407,7 +407,6 @@ "MESSAGE_REQUESTS_NOTIFICATION" = "您有一个新的消息请求"; "TXT_HIDE_TITLE" = "隐藏"; "TXT_DELETE_ACCEPT" = "接受"; -"TXT_DECLINE_TITLE" = "Decline"; "TXT_BLOCK_USER_TITLE" = "Block User"; "ALERT_ERROR_TITLE" = "错误"; "modal_call_permission_request_title" = "请授予通话权限"; diff --git a/Session/Utilities/UIContextualAction+Utilities.swift b/Session/Utilities/UIContextualAction+Utilities.swift index 75f18f96a..b5ce85911 100644 --- a/Session/Utilities/UIContextualAction+Utilities.swift +++ b/Session/Utilities/UIContextualAction+Utilities.swift @@ -503,7 +503,7 @@ public extension UIContextualAction { attributedExplanation: confirmationModalExplanation, confirmTitle: "TXT_DELETE_TITLE".localized(), confirmAccessibility: Accessibility( - identifier: "Delete" + identifier: "Confirm delete" ), confirmStyle: .danger, cancelStyle: .alert_text, From 6685dc0572eca95f799b6bcf5d2bc03c390f1549 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Mon, 8 May 2023 11:30:36 +1000 Subject: [PATCH 055/135] Dropping messages which shouldn't be processed without a thread Removed an extra localised string value --- Session/Conversations/ConversationVC.swift | 5 +++-- .../Translations/fa.lproj/Localizable.strings | 1 - Session/Shared/Views/SessionCell.swift | 2 ++ SessionMessagingKit/Messages/Message.swift | 20 +++++++++++++++++++ .../Errors/MessageReceiverError.swift | 6 ++++-- .../Sending & Receiving/MessageReceiver.swift | 7 +++++++ .../Config Handling/SessionUtil+Shared.swift | 7 ++++--- 7 files changed, 40 insertions(+), 8 deletions(-) diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index 274d3679a..e9b28fa65 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -549,9 +549,10 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers if viewModel.threadData.threadIsNoteToSelf == false && viewModel.threadData.threadShouldBeVisible == false && - !SessionUtil.conversationVisibleInConfig( + !SessionUtil.conversationInConfig( threadId: threadId, - threadVariant: viewModel.threadData.threadVariant + threadVariant: viewModel.threadData.threadVariant, + visibleOnly: true ) { Storage.shared.writeAsync { db in diff --git a/Session/Meta/Translations/fa.lproj/Localizable.strings b/Session/Meta/Translations/fa.lproj/Localizable.strings index 47ba1eba3..eaefc0060 100644 --- a/Session/Meta/Translations/fa.lproj/Localizable.strings +++ b/Session/Meta/Translations/fa.lproj/Localizable.strings @@ -407,7 +407,6 @@ "MESSAGE_REQUESTS_NOTIFICATION" = "شما درخواست پیام جدیدی دارید"; "TXT_HIDE_TITLE" = "پنهان کردن"; "TXT_DELETE_ACCEPT" = "پذیرفتن"; -"TXT_DECLINE_TITLE" = "رد کردن"; "TXT_BLOCK_USER_TITLE" = "مسدود کردن کاربر"; "ALERT_ERROR_TITLE" = "خطا"; "modal_call_permission_request_title" = "دسترسی تلفن مورد نیاز است"; diff --git a/Session/Shared/Views/SessionCell.swift b/Session/Shared/Views/SessionCell.swift index 90407d4c6..912fb37a9 100644 --- a/Session/Shared/Views/SessionCell.swift +++ b/Session/Shared/Views/SessionCell.swift @@ -345,6 +345,8 @@ public class SessionCell: UITableViewCell { titleTextField.textAlignment = (info.title?.textAlignment ?? .left) titleTextField.placeholder = info.title?.editingPlaceholder titleTextField.isHidden = (info.title == nil) + titleTextField.accessibilityIdentifier = info.accessibility?.identifier + titleTextField.accessibilityLabel = info.accessibility?.label subtitleLabel.isUserInteractionEnabled = (info.subtitle?.interaction == .copy) subtitleLabel.font = info.subtitle?.font subtitleLabel.text = info.subtitle?.text diff --git a/SessionMessagingKit/Messages/Message.swift b/SessionMessagingKit/Messages/Message.swift index c0171149e..715023e30 100644 --- a/SessionMessagingKit/Messages/Message.swift +++ b/SessionMessagingKit/Messages/Message.swift @@ -163,6 +163,26 @@ public extension Message { } } + static func requiresExistingConversation(message: Message, threadVariant: SessionThread.Variant) -> Bool { + switch threadVariant { + case .contact, .community: return false + + case .legacyGroup: + switch message { + case let controlMessage as ClosedGroupControlMessage: + switch controlMessage.kind { + case .new: return false + default: return true + } + + default: return true + } + + case .group: + return false + } + } + static func shouldSync(message: Message) -> Bool { switch message { case is VisibleMessage: return true diff --git a/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift b/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift index b5d43eed6..5a89819cb 100644 --- a/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift +++ b/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift @@ -20,13 +20,14 @@ public enum MessageReceiverError: LocalizedError { case invalidGroupPublicKey case noGroupKeyPair case invalidSharedConfigMessageHandling + case requiredThreadNotInConfig public var isRetryable: Bool { switch self { case .duplicateMessage, .duplicateMessageNewSnode, .duplicateControlMessage, .invalidMessage, .unknownMessage, .unknownEnvelopeType, .invalidSignature, .noData, .senderBlocked, .noThread, .selfSend, .decryptionFailed, - .invalidSharedConfigMessageHandling: + .invalidSharedConfigMessageHandling, .requiredThreadNotInConfig: return false default: return true @@ -54,7 +55,8 @@ public enum MessageReceiverError: LocalizedError { case .invalidGroupPublicKey: return "Invalid group public key." case .noGroupKeyPair: return "Missing group key pair." - case .invalidSharedConfigMessageHandling: return "Invalid handling of a shared config message" + case .invalidSharedConfigMessageHandling: return "Invalid handling of a shared config message." + case .requiredThreadNotInConfig: return "Required thread not in config." } } } diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index 68674434e..a524634df 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -185,6 +185,13 @@ public enum MessageReceiver { associatedWithProto proto: SNProtoContent, dependencies: SMKDependencies = SMKDependencies() ) throws { + // Check if the message requires an existing conversation (if it does and the conversation isn't in + // the config then the message will be dropped) + guard + !Message.requiresExistingConversation(message: message, threadVariant: threadVariant) || + SessionUtil.conversationInConfig(threadId: threadId, threadVariant: threadVariant, visibleOnly: false) + else { throw MessageReceiverError.requiredThreadNotInConfig } + switch message { case let message as ReadReceipt: try MessageReceiver.handleReadReceipt( diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift index 93e494262..6444f8744 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift @@ -298,9 +298,10 @@ internal extension SessionUtil { // MARK: - External Outgoing Changes public extension SessionUtil { - static func conversationVisibleInConfig( + static func conversationInConfig( threadId: String, - threadVariant: SessionThread.Variant + threadVariant: SessionThread.Variant, + visibleOnly: Bool ) -> Bool { // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent guard SessionUtil.userConfigsEnabled else { return true } @@ -327,7 +328,7 @@ public extension SessionUtil { /// If the user opens a conversation with an existing contact but doesn't send them a message /// then the one-to-one conversation should remain hidden so we want to delete the `SessionThread` /// when leaving the conversation - return SessionUtil.shouldBeVisible(priority: contact.priority) + return (!visibleOnly || SessionUtil.shouldBeVisible(priority: contact.priority)) case .community: let maybeUrlInfo: OpenGroupUrlInfo? = Storage.shared From 977c2051ed6e78775fcb2a7025468120e0fe3862 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 10 May 2023 17:27:36 +1000 Subject: [PATCH 056/135] Fixed a few bugs uncovered with further testing Added some more logs to libSession build script and tweaked the stdout location Added shadow threads to the GarbageCollectionJob Changed the seed node retries to 2 because it's likely we will swap to another seed node pretty quickly which could resolve the issue Fixed a bug where the user could get kicked from a draft conversation if they get a contacts update before sending a message Fixed a bug where message status or media message download statuses would trigger the conversation to jump to the bottom --- Scripts/build_libSession_util.sh | 29 +++++++++++++++---- Session/Conversations/ConversationVC.swift | 26 +++++++++++------ Session/Shared/SessionTableViewModel.swift | 4 --- .../Jobs/Types/GarbageCollectionJob.swift | 29 +++++++++++++++++++ SessionMessagingKit/Messages/Message.swift | 22 ++++++++++++++ .../Open Groups/OpenGroupManager.swift | 6 ++++ .../Sending & Receiving/MessageSender.swift | 12 ++------ .../SessionUtil+Contacts.swift | 25 ++++++++++++++-- .../SessionThreadViewModel.swift | 2 -- SessionSnodeKit/Networking/SnodeAPI.swift | 2 +- 10 files changed, 124 insertions(+), 33 deletions(-) diff --git a/Scripts/build_libSession_util.sh b/Scripts/build_libSession_util.sh index cfd934ac6..bb3cb671b 100755 --- a/Scripts/build_libSession_util.sh +++ b/Scripts/build_libSession_util.sh @@ -25,19 +25,32 @@ # Need to set the path or we won't find cmake PATH=${PATH}:/usr/local/bin:/opt/homebrew/bin:/sbin/md5 -# Direct the output to a log file -exec > "${TARGET_BUILD_DIR}/libsession_util_output.log" 2>&1 +# Ensure the build directory exists (in case we need it before XCode creates it) +mkdir -p "${TARGET_BUILD_DIR}" # Remove any old build errors rm -rf "${TARGET_BUILD_DIR}/libsession_util_error.log" # First ensure cmake is installed (store the error in a log and exit with a success status - xcode will output the error) +echo "info: Validating build requirements" + if ! which cmake > /dev/null; then - echo "error: cmake is required to build, please install (can install via homebrew with 'brew install cmake')." > "${TARGET_BUILD_DIR}/error.log" + touch "${TARGET_BUILD_DIR}/libsession_util_error.log" + echo "error: cmake is required to build, please install (can install via homebrew with 'brew install cmake')." + echo "error: cmake is required to build, please install (can install via homebrew with 'brew install cmake')." > "${TARGET_BUILD_DIR}/libsession_util_error.log" exit 0 fi +if [ ! -d "${SRCROOT}/LibSession-Util" ] || [ ! -d "${SRCROOT}/LibSession-Util/src" ]; then + touch "${TARGET_BUILD_DIR}/libsession_util_error.log" + echo "error: Need to fetch LibSession-Util submodule." + echo "error: Need to fetch LibSession-Util submodule." > "${TARGET_BUILD_DIR}/libsession_util_error.log" + exit 1 +fi + # Generate a hash of the libSession-util source files and check if they differ from the last hash +echo "info: Checking for changes to source" + NEW_SOURCE_HASH=$(find "${SRCROOT}/LibSession-Util/src" -type f -exec md5 {} + | awk '{print $NF}' | sort | md5 | awk '{print $NF}') NEW_HEADER_HASH=$(find "${SRCROOT}/LibSession-Util/include" -type f -exec md5 {} + | awk '{print $NF}' | sort | md5 | awk '{print $NF}') @@ -62,13 +75,15 @@ if [ "${NEW_SOURCE_HASH}" != "${OLD_SOURCE_HASH}" ] || [ "${NEW_HEADER_HASH}" != rm -rf "${TARGET_BUILD_DIR}/libsession-util.a" rm -rf "${TARGET_BUILD_DIR}/libsession-util.xcframework" rm -rf "${BUILD_DIR}/libsession-util.xcframework" - + # Trigger the new build cd "${SRCROOT}/LibSession-Util" result=$(./utils/ios.sh "libsession-util" false) if [ $? -ne 0 ]; then - echo "error: Failed to build libsession-util (See details in '${TARGET_BUILD_DIR}/pre-action-output.log')." > "${TARGET_BUILD_DIR}/error.log" + touch "${TARGET_BUILD_DIR}/libsession_util_error.log" + echo "error: Failed to build libsession-util (See details in '${TARGET_BUILD_DIR}/pre-action-output.log')." + echo "error: Failed to build libsession-util (See details in '${TARGET_BUILD_DIR}/pre-action-output.log')." > "${TARGET_BUILD_DIR}/libsession_util_error.log" exit 0 fi @@ -76,6 +91,10 @@ if [ "${NEW_SOURCE_HASH}" != "${OLD_SOURCE_HASH}" ] || [ "${NEW_HEADER_HASH}" != echo "${NEW_SOURCE_HASH}" > "${TARGET_BUILD_DIR}/libsession_util_source_hash.log" echo "${NEW_HEADER_HASH}" > "${TARGET_BUILD_DIR}/libsession_util_header_hash.log" echo "${ARCHS[*]}" > "${TARGET_BUILD_DIR}/libsession_util_archs.log" + echo "" + echo "info: Build complete" +else + echo "info: Build is up-to-date" fi # Move the target-specific libSession-util build to the parent build directory (so XCode can have a reference to a single build) diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index e9b28fa65..9932102b8 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -734,6 +734,11 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers initialIsBlocked: (viewModel.threadData.threadIsBlocked == true) ) + messageRequestDescriptionLabel.text = (updatedThreadData.threadRequiresApproval == false ? + "MESSAGE_REQUESTS_INFO".localized() : + "MESSAGE_REQUEST_PENDING_APPROVAL_INFO".localized() + ) + let messageRequestsViewWasVisible: Bool = ( messageRequestStackView.isHidden == false ) @@ -865,15 +870,18 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers self.viewModel.updateInteractionData(updatedData) self.tableView.reloadData() - // We need to dispatch to the next run loop because it seems trying to scroll immediately after - // triggering a 'reloadData' doesn't work - DispatchQueue.main.async { [weak self] in - self?.scrollToBottom(isAnimated: false) - - // Note: The scroll button alpha won't get set correctly in this case so we forcibly set it to - // have an alpha of 0 to stop it appearing buggy - self?.scrollButton.alpha = 0 - self?.unreadCountView.alpha = 0 + // If we just sent a message then we want to jump to the bottom of the conversation instantly + if didSendMessageBeforeUpdate { + // We need to dispatch to the next run loop because it seems trying to scroll immediately after + // triggering a 'reloadData' doesn't work + DispatchQueue.main.async { [weak self] in + self?.scrollToBottom(isAnimated: false) + + // Note: The scroll button alpha won't get set correctly in this case so we forcibly set it to + // have an alpha of 0 to stop it appearing buggy + self?.scrollButton.alpha = 0 + self?.unreadCountView.alpha = 0 + } } return } diff --git a/Session/Shared/SessionTableViewModel.swift b/Session/Shared/SessionTableViewModel.swift index 9c97082da..cfd772876 100644 --- a/Session/Shared/SessionTableViewModel.swift +++ b/Session/Shared/SessionTableViewModel.swift @@ -41,10 +41,6 @@ class SessionTableViewModel { Just(nil).eraseToAnyPublisher() } - open var settingsData: [SectionModel] { preconditionFailure("abstract class - override in subclass") } - open var observableSettingsData: ObservableData { - preconditionFailure("abstract class - override in subclass") - } open var footerView: AnyPublisher { Just(nil).eraseToAnyPublisher() } open var footerButtonInfo: AnyPublisher { Just(nil).eraseToAnyPublisher() diff --git a/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift b/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift index f5022cf36..5649183fe 100644 --- a/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift +++ b/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift @@ -296,6 +296,34 @@ public enum GarbageCollectionJob: JobExecutor { .filter(PendingReadReceipt.Columns.serverExpirationTimestamp <= timestampNow) .deleteAll(db) } + + if finalTypesToCollect.contains(.shadowThreads) { + // Shadow threads are thread records which were created to start a conversation that + // didn't actually get turned into conversations (ie. the app was closed or crashed + // before the user sent a message) + let thread: TypedTableAlias = TypedTableAlias() + let contact: TypedTableAlias = TypedTableAlias() + let openGroup: TypedTableAlias = TypedTableAlias() + let closedGroup: TypedTableAlias = TypedTableAlias() + + try db.execute(literal: """ + DELETE FROM \(SessionThread.self) + WHERE \(Column.rowID) IN ( + SELECT \(thread.alias[Column.rowID]) + FROM \(SessionThread.self) + LEFT JOIN \(Contact.self) ON \(contact[.id]) = \(thread[.id]) + LEFT JOIN \(OpenGroup.self) ON \(openGroup[.threadId]) = \(thread[.id]) + LEFT JOIN \(ClosedGroup.self) ON \(closedGroup[.threadId]) = \(thread[.id]) + WHERE ( + \(contact[.id]) IS NULL AND + \(openGroup[.threadId]) IS NULL AND + \(closedGroup[.threadId]) IS NULL AND + \(thread[.shouldBeVisible]) = false AND + \(SQL("\(thread[.id]) != \(getUserHexEncodedPublicKey(db))")) + ) + ) + """) + } }, completion: { _, _ in // Dispatch async so we can swap from the write queue to a read one (we are done writing) @@ -450,6 +478,7 @@ extension GarbageCollectionJob { case orphanedAttachmentFiles case orphanedProfileAvatars case expiredPendingReadReceipts + case shadowThreads } public struct Details: Codable { diff --git a/SessionMessagingKit/Messages/Message.swift b/SessionMessagingKit/Messages/Message.swift index 715023e30..97d6198e9 100644 --- a/SessionMessagingKit/Messages/Message.swift +++ b/SessionMessagingKit/Messages/Message.swift @@ -206,6 +206,28 @@ public extension Message { } } + static func threadId(forMessage message: Message, destination: Message.Destination) -> String { + switch destination { + case .contact(let publicKey): + // Extract the 'syncTarget' value if there is one + let maybeSyncTarget: String? + + switch message { + case let message as VisibleMessage: maybeSyncTarget = message.syncTarget + case let message as ExpirationTimerUpdate: maybeSyncTarget = message.syncTarget + default: maybeSyncTarget = nil + } + + return (maybeSyncTarget ?? publicKey) + + case .closedGroup(let groupPublicKey): return groupPublicKey + case .openGroup(let roomToken, let server, _, _, _): + return OpenGroup.idFor(roomToken: roomToken, server: server) + + case .openGroupInbox(_, _, let blindedPublicKey): return blindedPublicKey + } + } + static func processRawReceivedMessage( _ db: Database, rawMessage: SnodeReceivedMessage diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index 3f33981da..3716460df 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -382,6 +382,12 @@ public final class OpenGroupManager { .filter(id: openGroupId) .deleteAll(db) + // Remove any MessageProcessRecord entries (we will want to reprocess all OpenGroup messages + // if they get re-added) + _ = try? ControlMessageProcessRecord + .filter(ControlMessageProcessRecord.Columns.threadId == openGroupId) + .deleteAll(db) + // Remove the open group (no foreign key to the thread so it won't auto-delete) if server?.lowercased() != OpenGroupAPI.defaultServer.lowercased() { _ = try? OpenGroup diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index 0f620b89f..1da13d3ec 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -961,16 +961,8 @@ public final class MessageSender { } } - let threadId: String = { - switch destination { - case .contact(let publicKey): return publicKey - case .closedGroup(let groupPublicKey): return groupPublicKey - case .openGroup(let roomToken, let server, _, _, _): - return OpenGroup.idFor(roomToken: roomToken, server: server) - - case .openGroupInbox(_, _, let blindedPublicKey): return blindedPublicKey - } - }() + // Extract the threadId from the message + let threadId: String = Message.threadId(forMessage: message, destination: destination) // Prevent ControlMessages from being handled multiple times if not supported try? ControlMessageProcessRecord( diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift index 283afe901..c4465cdeb 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift @@ -210,7 +210,7 @@ internal extension SessionUtil { } } - // Delete any contact/thread records which aren't in the config message + /// Delete any contact/thread records which aren't in the config message let syncedContactIds: [String] = targetContactData .map { $0.key } .appending(userPublicKey) @@ -226,7 +226,28 @@ internal extension SessionUtil { .select(.id) .asRequest(of: String.self) .fetchAll(db) - let combinedIds: [String] = contactIdsToRemove.appending(contentsOf: threadIdsToRemove) + + /// When the user opens a brand new conversation this creates a "draft conversation" which has a hidden thread but no + /// contact record, when we receive a contact update this "draft conversation" would be included in the + /// `threadIdsToRemove` which would result in the user getting kicked from the screen and the thread removed, we + /// want to avoid this (as it's essentially a bug) so find any conversations in this state and remove them from the list that + /// will be pruned + let threadT: TypedTableAlias = TypedTableAlias() + let contactT: TypedTableAlias = TypedTableAlias() + let draftConversationIds: [String] = try SQLRequest(""" + SELECT \(threadT[.id]) + FROM \(SessionThread.self) + LEFT JOIN \(Contact.self) ON \(contactT[.id]) = \(threadT[.id]) + WHERE ( + \(SQL("\(threadT[.id]) IN \(threadIdsToRemove)")) AND + \(contactT[.id]) IS NULL + ) + """).fetchAll(db) + + /// Consolidate the ids which should be removed + let combinedIds: [String] = contactIdsToRemove + .appending(contentsOf: threadIdsToRemove) + .filter { !draftConversationIds.contains($0) } if !combinedIds.isEmpty { SessionUtil.kickFromConversationUIIfNeeded(removedThreadIds: combinedIds) diff --git a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift index 817ff6582..c70d8388d 100644 --- a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift +++ b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift @@ -784,7 +784,6 @@ public extension SessionThreadViewModel { static func homeFilterSQL(userPublicKey: String) -> SQL { let thread: TypedTableAlias = TypedTableAlias() let contact: TypedTableAlias = TypedTableAlias() - let interaction: TypedTableAlias = TypedTableAlias() return """ \(thread[.shouldBeVisible]) = true AND ( @@ -848,7 +847,6 @@ public extension SessionThreadViewModel { let interaction: TypedTableAlias = TypedTableAlias() let aggregateInteractionLiteral: SQL = SQL(stringLiteral: "aggregateInteraction") - let timestampMsColumnLiteral: SQL = SQL(stringLiteral: Interaction.Columns.timestampMs.name) let closedGroupUserCountTableLiteral: SQL = SQL(stringLiteral: "\(ViewModel.closedGroupUserCountString)_table") let groupMemberGroupIdColumnLiteral: SQL = SQL(stringLiteral: GroupMember.Columns.groupId.name) let profileIdColumnLiteral: SQL = SQL(stringLiteral: Profile.Columns.id.name) diff --git a/SessionSnodeKit/Networking/SnodeAPI.swift b/SessionSnodeKit/Networking/SnodeAPI.swift index 58d33734e..39cf4d59e 100644 --- a/SessionSnodeKit/Networking/SnodeAPI.swift +++ b/SessionSnodeKit/Networking/SnodeAPI.swift @@ -1105,7 +1105,7 @@ public final class SnodeAPI { .compactMap { $0.value } .asSet() } - .retry(4) + .retry(2) .handleEvents( receiveCompletion: { result in switch result { From c134acdc9099cb1b1d894c1eadbea78954d5c1b2 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 16 May 2023 13:46:09 +1000 Subject: [PATCH 057/135] Updated to the latest version of libSession --- LibSession-Util | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LibSession-Util b/LibSession-Util index 53c824de0..97084c69f 160000 --- a/LibSession-Util +++ b/LibSession-Util @@ -1 +1 @@ -Subproject commit 53c824de0d514307f3bad6a62449166bd10da6f8 +Subproject commit 97084c69f86e67c675095b48efacc86113ccebb0 From db67e36acb7fd76d005ae66bb0f57a1204beda9a Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 18 May 2023 16:32:49 +1000 Subject: [PATCH 058/135] Updated the feature flag so it will continue to use User Config if the migrations have already been run --- SessionMessagingKit/Configuration.swift | 2 +- ...essageReceiver+ConfigurationMessages.swift | 2 +- .../Sending & Receiving/MessageReceiver.swift | 2 +- .../Config Handling/SessionUtil+Shared.swift | 3 +- .../SessionUtil/SessionUtil.swift | 44 ++++++++++++++++--- .../Utilities/ProfileManager.swift | 2 +- SessionUtilitiesKit/General/Features.swift | 2 - 7 files changed, 44 insertions(+), 13 deletions(-) diff --git a/SessionMessagingKit/Configuration.swift b/SessionMessagingKit/Configuration.swift index 0be9fa4f0..de6575edb 100644 --- a/SessionMessagingKit/Configuration.swift +++ b/SessionMessagingKit/Configuration.swift @@ -33,7 +33,7 @@ public enum SNMessagingKit { // Just to make the external API nice // Wait until the feature is turned on before doing the migration that generates // the config dump data // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - (Features.useSharedUtilForUserConfig ? + (Features.useSharedUtilForUserConfig() ? _014_GenerateInitialUserConfigDumps.self : (nil as Migration.Type?) ) diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift index d2814eeea..92385940f 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift @@ -9,7 +9,7 @@ import SessionUtilitiesKit extension MessageReceiver { internal static func handleLegacyConfigurationMessage(_ db: Database, message: ConfigurationMessage) throws { // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard !SessionUtil.userConfigsEnabled else { + guard !SessionUtil.userConfigsEnabled(db) else { TopBannerController.show(warning: .outdatedUserConfig) return } diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index a524634df..d4ab2e3c7 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -189,7 +189,7 @@ public enum MessageReceiver { // the config then the message will be dropped) guard !Message.requiresExistingConversation(message: message, threadVariant: threadVariant) || - SessionUtil.conversationInConfig(threadId: threadId, threadVariant: threadVariant, visibleOnly: false) + SessionUtil.conversationInConfig(db, threadId: threadId, threadVariant: threadVariant, visibleOnly: false) else { throw MessageReceiverError.requiredThreadNotInConfig } switch message { diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift index 6444f8744..4a8c7e9d2 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift @@ -299,12 +299,13 @@ internal extension SessionUtil { public extension SessionUtil { static func conversationInConfig( + _ db: Database? = nil, threadId: String, threadVariant: SessionThread.Variant, visibleOnly: Bool ) -> Bool { // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard SessionUtil.userConfigsEnabled else { return true } + guard SessionUtil.userConfigsEnabled(db) else { return true } let configVariant: ConfigDump.Variant = { switch threadVariant { diff --git a/SessionMessagingKit/SessionUtil/SessionUtil.swift b/SessionMessagingKit/SessionUtil/SessionUtil.swift index 16c7cf5a8..a5ffd851b 100644 --- a/SessionMessagingKit/SessionUtil/SessionUtil.swift +++ b/SessionMessagingKit/SessionUtil/SessionUtil.swift @@ -6,6 +6,28 @@ import SessionSnodeKit import SessionUtil import SessionUtilitiesKit +// MARK: - Features + +public extension Features { + static func useSharedUtilForUserConfig(_ db: Database? = nil) -> Bool { + // TODO: Need to set this timestamp to the correct date + guard Date().timeIntervalSince1970 < 1893456000 else { return true } + guard !SessionUtil.hasCheckedMigrationsCompleted.wrappedValue else { + return SessionUtil.userConfigsEnabledIgnoringFeatureFlag + } + + if let db: Database = db { + return SessionUtil.refreshingUserConfigsEnabled(db) + } + + return Storage.shared + .read { db in SessionUtil.refreshingUserConfigsEnabled(db) } + .defaulting(to: false) + } +} + +// MARK: - SessionUtil + public enum SessionUtil { public struct ConfResult { let needsPush: Bool @@ -63,6 +85,7 @@ public enum SessionUtil { public static var libSessionVersion: String { String(cString: LIBSESSION_UTIL_VERSION_STR) } + fileprivate static let hasCheckedMigrationsCompleted: Atomic = Atomic(false) private static let requiredMigrationsCompleted: Atomic = Atomic(false) private static let requiredMigrationIdentifiers: Set = [ TargetMigrations.Identifier.messagingKit.key(with: _013_SessionUtilChanges.self), @@ -70,8 +93,16 @@ public enum SessionUtil { ] public static var userConfigsEnabled: Bool { - Features.useSharedUtilForUserConfig && - requiredMigrationsCompleted.wrappedValue + return userConfigsEnabled(nil) + } + + public static func userConfigsEnabled(_ db: Database?) -> Bool { + Features.useSharedUtilForUserConfig(db) && + SessionUtil.userConfigsEnabledIgnoringFeatureFlag + } + + public static var userConfigsEnabledIgnoringFeatureFlag: Bool { + SessionUtil.requiredMigrationsCompleted.wrappedValue } internal static func userConfigsEnabled( @@ -80,9 +111,9 @@ public enum SessionUtil { ) -> Bool { // First check if we are enabled regardless of what we want to ignore guard - Features.useSharedUtilForUserConfig, - !requiredMigrationsCompleted.wrappedValue, - !refreshingUserConfigsEnabled(db), + Features.useSharedUtilForUserConfig(db), + !SessionUtil.requiredMigrationsCompleted.wrappedValue, + !SessionUtil.refreshingUserConfigsEnabled(db), ignoreRequirementsForRunningMigrations, let currentlyRunningMigration: (identifier: TargetMigrations.Identifier, migration: Migration.Type) = Storage.shared.currentlyRunningMigration else { return true } @@ -99,6 +130,7 @@ public enum SessionUtil { .isSuperset(of: SessionUtil.requiredMigrationIdentifiers) requiredMigrationsCompleted.mutate { $0 = result } + hasCheckedMigrationsCompleted.mutate { $0 = true } return result } @@ -375,7 +407,7 @@ public enum SessionUtil { publicKey: String ) throws { // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard SessionUtil.userConfigsEnabled else { return } + guard SessionUtil.userConfigsEnabled(db) else { return } guard !messages.isEmpty else { return } guard !publicKey.isEmpty else { throw MessageReceiverError.noThread } diff --git a/SessionMessagingKit/Utilities/ProfileManager.swift b/SessionMessagingKit/Utilities/ProfileManager.swift index 14f748f9a..259e928d3 100644 --- a/SessionMessagingKit/Utilities/ProfileManager.swift +++ b/SessionMessagingKit/Utilities/ProfileManager.swift @@ -593,7 +593,7 @@ public struct ProfileManager { ) } // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - else if !SessionUtil.userConfigsEnabled { + else if !SessionUtil.userConfigsEnabled(db) { // If we have a contact record for the profile (ie. it's a synced profile) then // should should send an updated config message, otherwise we should just update // the local state (the shared util has this logic build in to it's handling) diff --git a/SessionUtilitiesKit/General/Features.swift b/SessionUtilitiesKit/General/Features.swift index 3cc3d8e83..970315b2d 100644 --- a/SessionUtilitiesKit/General/Features.swift +++ b/SessionUtilitiesKit/General/Features.swift @@ -5,6 +5,4 @@ import Foundation public final class Features { public static let useOnionRequests: Bool = true public static let useTestnet: Bool = false - - public static let useSharedUtilForUserConfig: Bool = true // TODO: Base this off a timestamp } From 9799297e1519df66b47cdc3c13e214521b0371cb Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 18 May 2023 17:45:04 +1000 Subject: [PATCH 059/135] Fixed build issues from merge --- Session.xcodeproj/project.pbxproj | 8 +------- Session/Closed Groups/NewClosedGroupVC.swift | 2 +- Session/Conversations/ConversationViewModel.swift | 12 ++++++------ .../Settings/ThreadSettingsViewModel.swift | 4 ++-- Session/Home/New Conversation/NewDMVC.swift | 4 ++-- Session/Settings/SettingsViewModel.swift | 2 +- Session/Utilities/UIContextualAction+Utilities.swift | 2 +- 7 files changed, 14 insertions(+), 20 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 591bbbfd4..a09fceb75 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -578,9 +578,6 @@ FD245C6B2850667400B966DD /* VisibleMessage+Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C300A5B12554AF9800555489 /* VisibleMessage+Profile.swift */; }; FD245C6C2850669200B966DD /* MessageReceiveJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = C352A31225574F5200338F3E /* MessageReceiveJob.swift */; }; FD245C6D285066A400B966DD /* NotifyPushServerJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = C352A32E2557549C00338F3E /* NotifyPushServerJob.swift */; }; - FD26FA5E291CAFF9005801D8 /* (null) in Sources */ = {isa = PBXBuildFile; }; - FD26FA6D291DADAE005801D8 /* (null) in Sources */ = {isa = PBXBuildFile; }; - FD26FA7B291DF8F3005801D8 /* (null) in Sources */ = {isa = PBXBuildFile; }; FD2AAAED28ED3E1000A49611 /* MockGeneralCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */; }; FD2AAAEE28ED3E1100A49611 /* MockGeneralCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */; }; FD2AAAF028ED57B500A49611 /* SynchronousStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2AAAEF28ED57B500A49611 /* SynchronousStorage.swift */; }; @@ -4604,8 +4601,8 @@ isa = PBXNativeTarget; buildConfigurationList = C3C2A6F925539DE700C340D1 /* Build configuration list for PBXNativeTarget "SessionMessagingKit" */; buildPhases = ( - FDFC4E1729F14F7A00992FB6 /* Validate pre-build actions */, 2014435DF351DF6C60122751 /* [CP] Check Pods Manifest.lock */, + FDFC4E1729F14F7A00992FB6 /* Validate pre-build actions */, C3C2A6EB25539DE700C340D1 /* Headers */, C3C2A6EC25539DE700C340D1 /* Sources */, C3C2A6ED25539DE700C340D1 /* Frameworks */, @@ -5547,7 +5544,6 @@ FDF848D129405C5B007DCAE5 /* SnodeSwarmItem.swift in Sources */, FDF848DD29405C5B007DCAE5 /* LegacySendMessageRequest.swift in Sources */, FDF848BD29405C5A007DCAE5 /* GetMessagesRequest.swift in Sources */, - FD26FA7B291DF8F3005801D8 /* (null) in Sources */, FDF848DB29405C5B007DCAE5 /* DeleteMessagesResponse.swift in Sources */, FDF848E629405D6E007DCAE5 /* OnionRequestAPIDestination.swift in Sources */, FDF848CC29405C5B007DCAE5 /* SnodeReceivedMessage.swift in Sources */, @@ -5557,7 +5553,6 @@ FDF848D229405C5B007DCAE5 /* LegacyGetMessagesRequest.swift in Sources */, FDF848CB29405C5B007DCAE5 /* SnodePoolResponse.swift in Sources */, FDF848C429405C5A007DCAE5 /* RevokeSubkeyResponse.swift in Sources */, - FD26FA6D291DADAE005801D8 /* (null) in Sources */, FDF848E529405D6E007DCAE5 /* SnodeAPIError.swift in Sources */, FDF848D529405C5B007DCAE5 /* DeleteAllMessagesResponse.swift in Sources */, FDF848E329405D6E007DCAE5 /* OnionRequestAPIVersion.swift in Sources */, @@ -5691,7 +5686,6 @@ FDF8488929405B27007DCAE5 /* Data+Utilities.swift in Sources */, FD09797227FAA2F500936362 /* Optional+Utilities.swift in Sources */, FD7162DB281B6C440060647B /* TypedTableAlias.swift in Sources */, - FD26FA5E291CAFF9005801D8 /* (null) in Sources */, FD7115F828C8151C00B47552 /* DisposableBarButtonItem.swift in Sources */, FD17D7E727F6A16700122BE0 /* _003_YDBToGRDBMigration.swift in Sources */, ); diff --git a/Session/Closed Groups/NewClosedGroupVC.swift b/Session/Closed Groups/NewClosedGroupVC.swift index 35d48b090..3970e52c2 100644 --- a/Session/Closed Groups/NewClosedGroupVC.swift +++ b/Session/Closed Groups/NewClosedGroupVC.swift @@ -349,7 +349,7 @@ final class NewClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegate targetView: self?.view, info: ConfirmationModal.Info( title: "GROUP_CREATION_ERROR_TITLE".localized(), - explanation: "GROUP_CREATION_ERROR_MESSAGE".localized(), + body: .text("GROUP_CREATION_ERROR_MESSAGE".localized()), cancelTitle: "BUTTON_OK".localized(), cancelStyle: .alert_text ) diff --git a/Session/Conversations/ConversationViewModel.swift b/Session/Conversations/ConversationViewModel.swift index b6ce9b14d..9b891ce49 100644 --- a/Session/Conversations/ConversationViewModel.swift +++ b/Session/Conversations/ConversationViewModel.swift @@ -67,7 +67,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { // If we have a specified 'focusedInteractionInfo' then use that, otherwise retrieve the oldest // unread interaction and start focused around that one - let targetInteractionInfo: Int64? = (focusedInteractionInfo != nil ? focusedInteractionInfo : + let targetInteractionInfo: Interaction.TimestampInfo? = (focusedInteractionInfo != nil ? focusedInteractionInfo : try Interaction .select(.id, .timestampMs) .filter(interaction[.wasRead] == false) @@ -76,7 +76,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { .asRequest(of: Interaction.TimestampInfo.self) .fetchOne(db) ) - let threadIsBlocked: Bool= (threadVariant != .contact ? false : + let threadIsBlocked: Bool = (threadVariant != .contact ? false : try Contact .filter(id: threadId) .select(.isBlocked) @@ -85,7 +85,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { .defaulting(to: false) ) let currentUserIsClosedGroupMember: Bool? = (![.legacyGroup, .group].contains(threadVariant) ? nil : - try GroupMember + GroupMember .filter(groupMember[.groupId] == threadId) .filter(groupMember[.profileId] == getUserHexEncodedPublicKey(db)) .filter(groupMember[.role] == GroupMember.Role.standard) @@ -115,12 +115,12 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { self.threadId = threadId self.initialThreadVariant = threadVariant - self.focusedInteractionInfo = targetInteractionInfo + self.focusedInteractionInfo = initialData?.targetInteractionInfo self.threadData = SessionThreadViewModel( threadId: threadId, threadVariant: threadVariant, threadIsNoteToSelf: (self.threadId == getUserHexEncodedPublicKey()), - threadIsBlocked: threadIsBlocked, + threadIsBlocked: initialData?.threadIsBlocked, currentUserIsClosedGroupMember: initialData?.currentUserIsClosedGroupMember, openGroupPermissions: initialData?.openGroupPermissions ).populatingCurrentUserBlindedKey(currentUserBlindedPublicKeyForThisThread: initialData?.blindedKey) @@ -143,7 +143,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { DispatchQueue.global(qos: .userInitiated).async { [weak self] in // If we don't have a `initialFocusedInfo` then default to `.pageBefore` (it'll query // from a `0` offset) - guard let initialFocusedInfo: Interaction.TimestampInfo = targetInteractionInfo else { + guard let initialFocusedInfo: Interaction.TimestampInfo = initialData?.targetInteractionInfo else { self?.pagedDataObserver?.load(.pageBefore) return } diff --git a/Session/Conversations/Settings/ThreadSettingsViewModel.swift b/Session/Conversations/Settings/ThreadSettingsViewModel.swift index 308859e08..486644d3c 100644 --- a/Session/Conversations/Settings/ThreadSettingsViewModel.swift +++ b/Session/Conversations/Settings/ThreadSettingsViewModel.swift @@ -818,11 +818,11 @@ class ThreadSettingsViewModel: SessionTableViewModel Date: Fri, 19 May 2023 14:27:51 +1000 Subject: [PATCH 060/135] Added some accessibility info Fixed an issue where the Display Picture update modal wouldn't rendering animated images actually animating --- Session/Settings/SettingsViewModel.swift | 34 +++++++-- .../SessionUtil/SessionUtil.swift | 4 +- .../Components/ConfirmationModal.swift | 76 +++++++++++++++---- 3 files changed, 88 insertions(+), 26 deletions(-) diff --git a/Session/Settings/SettingsViewModel.swift b/Session/Settings/SettingsViewModel.swift index 9f0178bf7..f38e36281 100644 --- a/Session/Settings/SettingsViewModel.swift +++ b/Session/Settings/SettingsViewModel.swift @@ -3,6 +3,7 @@ import Foundation import Combine import GRDB +import YYImage import DifferenceKit import SessionUIKit import SessionMessagingKit @@ -155,9 +156,7 @@ class SettingsViewModel: SessionTableViewModel { - let userSessionId: String = self.userSessionId - - return navState + navState .map { [weak self] navState -> [NavItem] in switch navState { case .standard: @@ -488,21 +487,31 @@ class SettingsViewModel: SessionTableViewModel Bool { - // TODO: Need to set this timestamp to the correct date - guard Date().timeIntervalSince1970 < 1893456000 else { return true } + // TODO: Need to set this timestamp to the correct date (currently start of 2030) +// guard Date().timeIntervalSince1970 < 1893456000 else { return true } guard !SessionUtil.hasCheckedMigrationsCompleted.wrappedValue else { return SessionUtil.userConfigsEnabledIgnoringFeatureFlag } diff --git a/SessionUIKit/Components/ConfirmationModal.swift b/SessionUIKit/Components/ConfirmationModal.swift index 8d76907cb..60389c909 100644 --- a/SessionUIKit/Components/ConfirmationModal.swift +++ b/SessionUIKit/Components/ConfirmationModal.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit +import YYImage import SessionUtilitiesKit // FIXME: Refactor as part of the Groups Rebuild @@ -41,6 +42,12 @@ public class ConfirmationModal: Modal { let result: UIView = UIView() result.isHidden = true + let gestureRecogniser: UITapGestureRecognizer = UITapGestureRecognizer( + target: self, + action: #selector(imageViewTapped) + ) + result.addGestureRecognizer(gestureRecogniser) + return result }() @@ -50,6 +57,18 @@ public class ConfirmationModal: Modal { result.contentMode = .scaleAspectFill result.set(.width, to: ConfirmationModal.imageSize) result.set(.height, to: ConfirmationModal.imageSize) + result.isHidden = true + + return result + }() + + private lazy var animatedImageView: YYAnimatedImageView = { + let result: YYAnimatedImageView = YYAnimatedImageView() + result.clipsToBounds = true + result.contentMode = .scaleAspectFill + result.set(.width, to: ConfirmationModal.imageSize) + result.set(.height, to: ConfirmationModal.imageSize) + result.isHidden = true return result }() @@ -84,12 +103,6 @@ public class ConfirmationModal: Modal { right: Values.largeSpacing ) - let gestureRecogniser: UITapGestureRecognizer = UITapGestureRecognizer( - target: self, - action: #selector(bodyTapped) - ) - result.addGestureRecognizer(gestureRecogniser) - return result }() @@ -115,6 +128,9 @@ public class ConfirmationModal: Modal { bottom: 6, right: 6 ) + result.isAccessibilityElement = true + result.accessibilityIdentifier = "Close button" + result.accessibilityLabel = "Close button" result.set(.width, to: ConfirmationModal.closeSize) result.set(.height, to: ConfirmationModal.closeSize) result.addTarget(self, action: #selector(close), for: .touchUpInside) @@ -146,6 +162,11 @@ public class ConfirmationModal: Modal { imageView.pin(.top, to: .top, of: imageViewContainer, withInset: 15) imageView.pin(.bottom, to: .bottom, of: imageViewContainer, withInset: -15) + imageViewContainer.addSubview(animatedImageView) + animatedImageView.center(.horizontal, in: imageViewContainer) + animatedImageView.pin(.top, to: .top, of: imageViewContainer, withInset: 15) + animatedImageView.pin(.bottom, to: .bottom, of: imageViewContainer, withInset: -15) + mainStackView.pin(to: contentView) closeButton.pin(.top, to: .top, of: contentView, withInset: 8) closeButton.pin(.right, to: .right, of: contentView, withInset: -8) @@ -185,15 +206,32 @@ public class ConfirmationModal: Modal { explanationLabel.attributedText = attributedText explanationLabel.isHidden = false - case .image(let placeholder, let value, let style, let onClick): + case .image(let placeholder, let value, let animatedValue, let style, let accessibility, let onClick): + imageViewContainer.isAccessibilityElement = (accessibility != nil) + imageViewContainer.accessibilityIdentifier = accessibility?.identifier + imageViewContainer.accessibilityLabel = accessibility?.label mainStackView.spacing = 0 - imageView.image = (value ?? placeholder) - imageView.layer.cornerRadius = (style == .circular ? - (ConfirmationModal.imageSize / 2) : - 0 - ) imageViewContainer.isHidden = false internalOnBodyTap = onClick + + if let animatedValue: YYImage = animatedValue { + imageView.isHidden = true + animatedImageView.image = animatedValue + animatedImageView.isHidden = false + animatedImageView.layer.cornerRadius = (style == .circular ? + (ConfirmationModal.imageSize / 2) : + 0 + ) + } + else { + animatedImageView.isHidden = true + imageView.image = (value ?? placeholder) + imageView.isHidden = false + imageView.layer.cornerRadius = (style == .circular ? + (ConfirmationModal.imageSize / 2) : + 0 + ) + } } confirmButton.accessibilityLabel = info.confirmAccessibility?.label @@ -219,7 +257,7 @@ public class ConfirmationModal: Modal { // MARK: - Interaction - @objc private func bodyTapped() { + @objc private func imageViewTapped() { internalOnBodyTap?() } @@ -407,7 +445,9 @@ public extension ConfirmationModal.Info { case image( placeholder: UIImage?, value: UIImage?, + animatedValue: YYImage?, style: ImageStyle, + accessibility: Accessibility?, onClick: (() -> ()) ) @@ -431,11 +471,13 @@ public extension ConfirmationModal.Info { // lhsOptions.map { "\($0.0)-\($0.1)" } == rhsValue.map { "\($0.0)-\($0.1)" } // ) - case (.image(let lhsPlaceholder, let lhsValue, let lhsStyle, _), .image(let rhsPlaceholder, let rhsValue, let rhsStyle, _)): + case (.image(let lhsPlaceholder, let lhsValue, let lhsAnimatedValue, let lhsStyle, let lhsAccessibility, _), .image(let rhsPlaceholder, let rhsValue, let rhsAnimatedValue, let rhsStyle, let rhsAccessibility, _)): return ( lhsPlaceholder == rhsPlaceholder && lhsValue == rhsValue && - lhsStyle == rhsStyle + lhsAnimatedValue == rhsAnimatedValue && + lhsStyle == rhsStyle && + lhsAccessibility == rhsAccessibility ) default: return false @@ -448,10 +490,12 @@ public extension ConfirmationModal.Info { case .text(let text): text.hash(into: &hasher) case .attributedText(let text): text.hash(into: &hasher) - case .image(let placeholder, let value, let style, _): + case .image(let placeholder, let value, let animatedValue, let style, let accessibility, _): placeholder.hash(into: &hasher) value.hash(into: &hasher) + animatedValue.hash(into: &hasher) style.hash(into: &hasher) + accessibility.hash(into: &hasher) } } } From 19eddd79a10e8223942322a98b6ba2c340b9284e Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 19 May 2023 14:55:35 +1000 Subject: [PATCH 061/135] Fixed an issue where non-visible messages were causing their conversations to appear Fixed a crash when trying to unsend a message --- Podfile | 1 + Podfile.lock | 2 +- .../Conversations/ConversationVC+Interaction.swift | 6 ++++-- .../Jobs/Types/ConfigurationSyncJob.swift | 2 +- .../Sending & Receiving/MessageReceiver.swift | 14 +++++++++++++- 5 files changed, 20 insertions(+), 5 deletions(-) diff --git a/Podfile b/Podfile index 1f8b96f72..d9f7d9613 100644 --- a/Podfile +++ b/Podfile @@ -94,6 +94,7 @@ abstract_target 'GlobalDependencies' do target 'SessionUIKit' do pod 'GRDB.swift/SQLCipher' pod 'DifferenceKit' + pod 'YYImage/libwebp', git: 'https://github.com/signalapp/YYImage' end end diff --git a/Podfile.lock b/Podfile.lock index 0d70b673b..051db4dda 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -212,6 +212,6 @@ SPEC CHECKSUMS: YYImage: f1ddd15ac032a58b78bbed1e012b50302d318331 ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: f461937f78a0482496fea6fc4b2bb5d1351fe044 +PODFILE CHECKSUM: 5a4993725a7d48be883663a52df2a5de45d2d099 COCOAPODS: 1.11.3 diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index ab105ca12..bdf4a4399 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -1791,8 +1791,10 @@ extension ConversationVC: // Show a loading indicator Deferred { Future { resolver in - ModalActivityIndicatorViewController.present(fromViewController: viewController, canCancel: false) { _ in - resolver(Result.success(())) + DispatchQueue.main.async { + ModalActivityIndicatorViewController.present(fromViewController: viewController, canCancel: false) { _ in + resolver(Result.success(())) + } } } } diff --git a/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift b/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift index bd7e183e1..7be4b4a3a 100644 --- a/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift +++ b/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift @@ -175,7 +175,7 @@ public extension ConfigurationSyncJob { static func enqueue(_ db: Database, publicKey: String) { // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - guard SessionUtil.userConfigsEnabled else { + guard SessionUtil.userConfigsEnabled(db) else { // If we don't have a userKeyPair (or name) yet then there is no need to sync the // configuration as the user doesn't fully exist yet (this will get triggered on // the first launch of a fresh install due to the migrations getting run and a few diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index d4ab2e3c7..ba5f4263b 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -282,10 +282,22 @@ public enum MessageReceiver { threadId: String, message: Message ) throws { - // When handling any non-typing indicator message we want to make sure the thread becomes + // When handling any message type which has related UI we want to make sure the thread becomes // visible (the only other spot this flag gets set is when sending messages) switch message { + case is ReadReceipt: break case is TypingIndicator: break + case is ConfigurationMessage: break + case is UnsendRequest: break + + case let message as ClosedGroupControlMessage: + // Only re-show a legacy group conversation if we are going to add a control text message + switch message.kind { + case .new, .encryptionKeyPair, .encryptionKeyPairRequest: return + default: break + } + + fallthrough default: // Only update the `shouldBeVisible` flag if the thread is currently not visible From e2384e464f36c906ef43867a6a46b98c5dcff27a Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Fri, 19 May 2023 17:02:56 +1000 Subject: [PATCH 062/135] use snippet function in sql query for message search --- SessionMessagingKit/Shared Models/SessionThreadViewModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift index 793c07aaf..84de62d1f 100644 --- a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift +++ b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift @@ -1075,7 +1075,7 @@ public extension SessionThreadViewModel { \(interaction[.id]) AS \(ViewModel.interactionIdKey), \(interaction[.variant]) AS \(ViewModel.interactionVariantKey), \(interaction[.timestampMs]) AS \(ViewModel.interactionTimestampMsKey), - \(interaction[.body]) AS \(ViewModel.interactionBodyKey), + snippet(\(interactionFullTextSearch), -1, '', '', '...', 15) AS \(ViewModel.interactionBodyKey), \(interaction[.authorId]), IFNULL(\(profile[.nickname]), \(profile[.name])) AS \(ViewModel.authorNameInternalKey), From 66d7226d80941177560a3bf34ea8ef0c44c5b1e4 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 22 May 2023 11:07:22 +1000 Subject: [PATCH 063/135] get rid of truncating calculation and guard constant number --- Session/Shared/FullConversationCell.swift | 62 ++--------------------- 1 file changed, 3 insertions(+), 59 deletions(-) diff --git a/Session/Shared/FullConversationCell.swift b/Session/Shared/FullConversationCell.swift index c2de9ffa3..6d2c22b5f 100644 --- a/Session/Shared/FullConversationCell.swift +++ b/Session/Shared/FullConversationCell.swift @@ -8,11 +8,6 @@ import SessionMessagingKit public final class FullConversationCell: UITableViewCell { public static let unreadCountViewSize: CGFloat = 20 private static let statusIndicatorSize: CGFloat = 14 - // If a message is much too long, it will take forever to calculate its width and - // cause the app to be frozen. So if a search result string is longer than 100 - // characters, we assume it cannot be shown within one line and need to be truncated - // to avoid the calculation. - private static let maxApproxCharactersCanBeShownInOneLine: Int = 100 // MARK: - UI @@ -620,69 +615,18 @@ public final class FullConversationCell: UITableViewCell { } } - // We then want to truncate the content so the first matching term is visible - let startOfSnippet: String.Index = ( - firstMatchRange.map { - max( - mentionReplacedContent.startIndex, - mentionReplacedContent - .index( - $0.lowerBound, - offsetBy: -10, - limitedBy: mentionReplacedContent.startIndex - ) - .defaulting(to: mentionReplacedContent.startIndex) - ) - } ?? - mentionReplacedContent.startIndex - ) - - // This method determines if the content is probably too long and returns the truncated or untruncated - // content accordingly - func truncatingIfNeeded(approxWidth: CGFloat, content: NSAttributedString) -> NSAttributedString { - let approxFullWidth: CGFloat = (approxWidth + profilePictureView.size + (Values.mediumSpacing * 3)) - - guard ((bounds.width - approxFullWidth) < 0) else { return content } - - return content.attributedSubstring( - from: NSRange(startOfSnippet.. NSAttributedString? in guard !authorName.isEmpty else { return nil } let authorPrefix: NSAttributedString = NSAttributedString( - string: "\(authorName): ...", + string: "\(authorName): ", attributes: [ .foregroundColor: textColor ] ) - return authorPrefix - .appending( - truncatingIfNeeded( - approxWidth: ( - authorPrefix.size().width + - ( - result.length > Self.maxApproxCharactersCanBeShownInOneLine ? - bounds.width : - result.size().width - ) - ), - content: result - ) - ) + return authorPrefix.appending(result) } - .defaulting( - to: truncatingIfNeeded( - approxWidth: ( - result.length > Self.maxApproxCharactersCanBeShownInOneLine ? - bounds.width : - result.size().width - ), - content: result - ) - ) + .defaulting(to: result) } } From ffb3f0dd90830a9fb8bfa918eecaf3e040e3d451 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 22 May 2023 11:21:07 +1000 Subject: [PATCH 064/135] adjust snippet function parameters to get a good looking result --- SessionMessagingKit/Shared Models/SessionThreadViewModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift index 84de62d1f..aba1a1a42 100644 --- a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift +++ b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift @@ -1075,7 +1075,7 @@ public extension SessionThreadViewModel { \(interaction[.id]) AS \(ViewModel.interactionIdKey), \(interaction[.variant]) AS \(ViewModel.interactionVariantKey), \(interaction[.timestampMs]) AS \(ViewModel.interactionTimestampMsKey), - snippet(\(interactionFullTextSearch), -1, '', '', '...', 15) AS \(ViewModel.interactionBodyKey), + snippet(\(interactionFullTextSearch), -1, '', '', '...', 6) AS \(ViewModel.interactionBodyKey), \(interaction[.authorId]), IFNULL(\(profile[.nickname]), \(profile[.name])) AS \(ViewModel.authorNameInternalKey), From 37894175e5731e7e8bb8cbbbd00d7a0f87075687 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Mon, 22 May 2023 12:06:12 +1000 Subject: [PATCH 065/135] Fixed a couple of bugs Fixed a bug where reacting to an incoming attachment message in a one-to-one conversation would result in the attachment being re-uploaded Fixed a bug where concurrent queues in the JobRunner wouldn't start newly added jobs until current jobs had finished executing --- SessionMessagingKit/Jobs/Types/MessageSendJob.swift | 11 ++++++++++- SessionUtilitiesKit/JobRunner/JobRunner.swift | 5 +++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift index eac85ddfb..d0e062153 100644 --- a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift +++ b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift @@ -31,7 +31,16 @@ public enum MessageSendJob: JobExecutor { // so extract them from any associated attachments var messageFileIds: [String] = [] - if details.message is VisibleMessage { + /// Ensure any associated attachments have already been uploaded before sending the message + /// + /// **Note:** Reactions reference their original message so we need to ignore this logic for reaction messages to ensure we don't + /// incorrectly re-upload incoming attachments that the user reacted to, we also want to exclude "sync" messages since they should + /// already have attachments in a valid state + if + details.message is VisibleMessage, + (details.message as? VisibleMessage)?.reaction == nil && + details.isSyncMessage == false + { guard let jobId: Int64 = job.id, let interactionId: Int64 = job.interactionId diff --git a/SessionUtilitiesKit/JobRunner/JobRunner.swift b/SessionUtilitiesKit/JobRunner/JobRunner.swift index 25d352a1d..948fc9d80 100644 --- a/SessionUtilitiesKit/JobRunner/JobRunner.swift +++ b/SessionUtilitiesKit/JobRunner/JobRunner.swift @@ -528,6 +528,11 @@ private final class JobQueue { } queue.mutate { $0.append(job) } + + // If this is a concurrent queue then we should immediately start the next job + guard executionType == .concurrent else { return } + + runNextJob() } /// Upsert a job onto the queue, if the queue isn't currently running and 'canStartJob' is true then this will start From 3f362a71f3f5e1026d71a3ea85d03d239c6647c6 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 23 May 2023 09:42:10 +1000 Subject: [PATCH 066/135] Fixed a couple of QA issues Forced the user config feature to be on (for testing) Fixed a bug where triggering the 'Delete for everyone' functionality would incorrectly try to delete from the recipient swarm (not possible) Fixed a bug where the 'profileNamePublisher' could only be set once resulting in potential issues if you try to restore different accounts within the same session Re-added the limit to the number of reactions to display before collapsing to make it consistent with the designs and other platforms Updated the SnodeAPI to ensure that when it retries it will actually select a new snode --- .../ConversationVC+Interaction.swift | 31 ++- .../Content Views/ReactionContainerView.swift | 12 +- Session/Meta/SessionApp.swift | 3 +- Session/Onboarding/LinkDeviceVC.swift | 6 + Session/Onboarding/Onboarding.swift | 221 +++++++++++------- Session/Onboarding/PNModeVC.swift | 4 +- Session/Onboarding/RegisterVC.swift | 6 + Session/Onboarding/RestoreVC.swift | 7 + Session/Utilities/BackgroundPoller.swift | 6 +- .../Database/Models/Interaction.swift | 5 +- .../Jobs/Types/MessageSendJob.swift | 11 +- .../SessionUtil/SessionUtil.swift | 10 + SessionSnodeKit/Networking/SnodeAPI.swift | 168 ++++++------- .../Combine/Publisher+Utilities.swift | 4 +- .../General/Set+Utilities.swift | 7 + SessionUtilitiesKit/JobRunner/JobRunner.swift | 5 + .../Networking/BatchResponse.swift | 2 +- 17 files changed, 301 insertions(+), 207 deletions(-) diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index bdf4a4399..2b8ce7cf0 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -1921,6 +1921,10 @@ extension ConversationVC: } case .contact, .legacyGroup, .group: + let targetPublicKey: String = (cellViewModel.threadVariant == .contact ? + userPublicKey : + cellViewModel.threadId + ) let serverHash: String? = Storage.shared.read { db -> String? in try Interaction .select(.serverHash) @@ -1996,16 +2000,7 @@ extension ConversationVC: accessibilityIdentifier: "Delete for everyone", style: .destructive ) { [weak self] _ in - deleteRemotely( - from: self, - request: SnodeAPI - .deleteMessages( - publicKey: cellViewModel.threadId, - serverHashes: [serverHash] - ) - .map { _ in () } - .eraseToAnyPublisher() - ) { [weak self] in + let completeServerDeletion = { [weak self] in Storage.shared.writeAsync { db in try MessageSender .send( @@ -2019,6 +2014,22 @@ extension ConversationVC: self?.showInputAccessoryView() } + + // We can only delete messages on the server for `contact` and `group` conversations + guard cellViewModel.threadVariant == .contact || cellViewModel.threadVariant == .group else { + return completeServerDeletion() + } + + deleteRemotely( + from: self, + request: SnodeAPI + .deleteMessages( + publicKey: targetPublicKey, + serverHashes: [serverHash] + ) + .map { _ in () } + .eraseToAnyPublisher() + ) { completeServerDeletion() } }) actionSheet.addAction(UIAlertAction.init(title: "TXT_CANCEL_TITLE".localized(), style: .cancel) { [weak self] _ in diff --git a/Session/Conversations/Message Cells/Content Views/ReactionContainerView.swift b/Session/Conversations/Message Cells/Content Views/ReactionContainerView.swift index 94f52b6d5..1f2cd628f 100644 --- a/Session/Conversations/Message Cells/Content Views/ReactionContainerView.swift +++ b/Session/Conversations/Message Cells/Content Views/ReactionContainerView.swift @@ -9,6 +9,13 @@ final class ReactionContainerView: UIView { private static let arrowSize: CGSize = CGSize(width: 15, height: 13) private static let arrowSpacing: CGFloat = Values.verySmallSpacing + // We have explicit limits on the number of emoji which should be displayed before they + // automatically get collapsed, these values are consistent across platforms so are set + // here (even though the logic will automatically calculate and limit to a single line + // of reactions dynamically for the size of the view) + private static let numCollapsedEmoji: Int = 4 + private static let maxEmojiBeforeCollapse: Int = 6 + private var maxWidth: CGFloat = 0 private var collapsedCount: Int = 0 private var showingAllReactions: Bool = false @@ -173,7 +180,10 @@ final class ReactionContainerView: UIView { numReactions += 1 } - return numReactions + return (numReactions > ReactionContainerView.maxEmojiBeforeCollapse ? + ReactionContainerView.numCollapsedEmoji : + numReactions + ) }() self.showNumbers = showNumbers self.reactionViews = [] diff --git a/Session/Meta/SessionApp.swift b/Session/Meta/SessionApp.swift index c8c5e1eed..2adb9eb7c 100644 --- a/Session/Meta/SessionApp.swift +++ b/Session/Meta/SessionApp.swift @@ -71,7 +71,8 @@ public struct SessionApp { // This _should_ be wiped out below. Logger.error("") DDLog.flushLog() - + + SessionUtil.clearMemoryState() Storage.resetAllStorage() ProfileManager.resetProfileStorage() Attachment.resetAttachmentStorage() diff --git a/Session/Onboarding/LinkDeviceVC.swift b/Session/Onboarding/LinkDeviceVC.swift index faf0bc6c6..0c4b0af0a 100644 --- a/Session/Onboarding/LinkDeviceVC.swift +++ b/Session/Onboarding/LinkDeviceVC.swift @@ -86,6 +86,12 @@ final class LinkDeviceVC: BaseVC, UIPageViewControllerDataSource, UIPageViewCont scanQRCodePlaceholderVC.constrainHeight(to: height) } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + Onboarding.Flow.register.unregister() + } + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) tabBarTopConstraint.constant = navigationController!.navigationBar.height() diff --git a/Session/Onboarding/Onboarding.swift b/Session/Onboarding/Onboarding.swift index 902143c9c..8c664c045 100644 --- a/Session/Onboarding/Onboarding.swift +++ b/Session/Onboarding/Onboarding.swift @@ -8,111 +8,152 @@ import SessionUtilitiesKit import SessionMessagingKit enum Onboarding { - private static let profileNameRetrievalPublisher: Atomic> = { + private static let profileNameRetrievalIdentifier: Atomic = Atomic(nil) + private static let profileNameRetrievalPublisher: Atomic?> = Atomic(nil) + public static var profileNamePublisher: AnyPublisher { + guard let existingPublisher: AnyPublisher = profileNameRetrievalPublisher.wrappedValue else { + return profileNameRetrievalPublisher.mutate { value in + let requestId: UUID = UUID() + let result: AnyPublisher = createProfileNameRetrievalPublisher(requestId) + + value = result + profileNameRetrievalIdentifier.mutate { $0 = requestId } + return result + } + } + + return existingPublisher + } + + private static func createProfileNameRetrievalPublisher(_ requestId: UUID) -> AnyPublisher { // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent guard SessionUtil.userConfigsEnabled else { - return Atomic( - Just(nil) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - ) + return Just(nil) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } let userPublicKey: String = getUserHexEncodedPublicKey() - return Atomic( - SnodeAPI.getSwarm(for: userPublicKey) - .subscribe(on: DispatchQueue.global(qos: .userInitiated)) - .tryFlatMap { swarm -> AnyPublisher in - guard let snode = swarm.randomElement() else { throw SnodeAPIError.generic } - - return CurrentUserPoller - .poll( - namespaces: [.configUserProfile], - from: snode, - for: userPublicKey, - on: DispatchQueue.global(qos: .userInitiated), - // Note: These values mean the received messages will be - // processed immediately rather than async as part of a Job - calledFromBackgroundPoller: true, - isBackgroundPollValid: { true } - ) - .tryFlatMap { receivedMessageTypes -> AnyPublisher in - // FIXME: Remove this entire 'tryFlatMap' once the updated user config has been released for long enough - guard receivedMessageTypes.isEmpty else { - return Just(()) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - } - - SNLog("Onboarding failed to retrieve user config, checking for legacy config") - - return CurrentUserPoller - .poll( - namespaces: [.default], - from: snode, - for: userPublicKey, - on: DispatchQueue.global(qos: .userInitiated), - // Note: These values mean the received messages will be - // processed immediately rather than async as part of a Job - calledFromBackgroundPoller: true, - isBackgroundPollValid: { true } - ) - .tryMap { receivedMessageTypes -> Void in - guard - let message: ConfigurationMessage = receivedMessageTypes - .last(where: { $0 is ConfigurationMessage }) - .asType(ConfigurationMessage.self), - let displayName: String = message.displayName - else { return () } - - // Handle user profile changes - Storage.shared.write { db in - try ProfileManager.updateProfileIfNeeded( - db, - publicKey: userPublicKey, - name: displayName, - avatarUpdate: { - guard - let profilePictureUrl: String = message.profilePictureUrl, - let profileKey: Data = message.profileKey - else { return .none } - - return .updateTo( - url: profilePictureUrl, - key: profileKey, - fileName: nil - ) - }(), - sentTimestamp: TimeInterval((message.sentTimestamp ?? 0) / 1000), - calledFromConfigHandling: false - ) - } - return () - } + return SnodeAPI.getSwarm(for: userPublicKey) + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) + .tryFlatMapWithRandomSnode { snode -> AnyPublisher in + CurrentUserPoller + .poll( + namespaces: [.configUserProfile], + from: snode, + for: userPublicKey, + on: DispatchQueue.global(qos: .userInitiated), + // Note: These values mean the received messages will be + // processed immediately rather than async as part of a Job + calledFromBackgroundPoller: true, + isBackgroundPollValid: { true } + ) + .tryFlatMap { receivedMessageTypes -> AnyPublisher in + // FIXME: Remove this entire 'tryFlatMap' once the updated user config has been released for long enough + guard + receivedMessageTypes.isEmpty, + requestId == profileNameRetrievalIdentifier.wrappedValue + else { + return Just(()) + .setFailureType(to: Error.self) .eraseToAnyPublisher() } - } - .flatMap { _ -> AnyPublisher in - Storage.shared.readPublisher { db in - try Profile - .filter(id: userPublicKey) - .select(.name) - .asRequest(of: String.self) - .fetchOne(db) + + SNLog("Onboarding failed to retrieve user config, checking for legacy config") + + return CurrentUserPoller + .poll( + namespaces: [.default], + from: snode, + for: userPublicKey, + on: DispatchQueue.global(qos: .userInitiated), + // Note: These values mean the received messages will be + // processed immediately rather than async as part of a Job + calledFromBackgroundPoller: true, + isBackgroundPollValid: { true } + ) + .tryMap { receivedMessageTypes -> Void in + guard + let message: ConfigurationMessage = receivedMessageTypes + .last(where: { $0 is ConfigurationMessage }) + .asType(ConfigurationMessage.self), + let displayName: String = message.displayName, + requestId == profileNameRetrievalIdentifier.wrappedValue + else { return () } + + // Handle user profile changes + Storage.shared.write { db in + try ProfileManager.updateProfileIfNeeded( + db, + publicKey: userPublicKey, + name: displayName, + avatarUpdate: { + guard + let profilePictureUrl: String = message.profilePictureUrl, + let profileKey: Data = message.profileKey + else { return .none } + + return .updateTo( + url: profilePictureUrl, + key: profileKey, + fileName: nil + ) + }(), + sentTimestamp: TimeInterval((message.sentTimestamp ?? 0) / 1000), + calledFromConfigHandling: false + ) + } + return () + } + .eraseToAnyPublisher() } + } + .map { _ -> String? in + guard requestId == profileNameRetrievalIdentifier.wrappedValue else { + return nil } - .shareReplay(1) - .eraseToAnyPublisher() - ) - }() - public static var profileNamePublisher: AnyPublisher { - profileNameRetrievalPublisher.wrappedValue + + return Storage.shared.read { db in + try Profile + .filter(id: userPublicKey) + .select(.name) + .asRequest(of: String.self) + .fetchOne(db) + } + } + .shareReplay(1) + .eraseToAnyPublisher() } enum Flow { case register, recover, link + /// If the user returns to an earlier screen during Onboarding we might need to clear out a partially created + /// account (eg. returning from the PN setting screen to the seed entry screen when linking a device) + func unregister() { + // Clear the in-memory state from SessionUtil + SessionUtil.clearMemoryState() + + // Clear any data which gets set during Onboarding + Storage.shared.write { db in + db[.hasViewedSeed] = false + + try SessionThread.deleteAll(db) + try Profile.deleteAll(db) + try Contact.deleteAll(db) + try Identity.deleteAll(db) + try ConfigDump.deleteAll(db) + try SnodeReceivedMessageInfo.deleteAll(db) + } + + // Clear the profile name retrieve publisher + profileNameRetrievalIdentifier.mutate { $0 = nil } + profileNameRetrievalPublisher.mutate { $0 = nil } + + UserDefaults.standard[.hasSyncedInitialConfiguration] = false + } + func preregister(with seed: Data, ed25519KeyPair: KeyPair, x25519KeyPair: KeyPair) { let x25519PublicKey = x25519KeyPair.hexEncodedPublicKey diff --git a/Session/Onboarding/PNModeVC.swift b/Session/Onboarding/PNModeVC.swift index 6bc15d8d2..8c60ab51e 100644 --- a/Session/Onboarding/PNModeVC.swift +++ b/Session/Onboarding/PNModeVC.swift @@ -174,7 +174,7 @@ final class PNModeVC: BaseVC, OptionViewDelegate { } // If we don't have one then show a loading indicator and try to retrieve the existing name - ModalActivityIndicatorViewController.present(fromViewController: self) { viewController in + ModalActivityIndicatorViewController.present(fromViewController: self) { [weak self, flow = self.flow] viewController in Onboarding.profileNamePublisher .timeout(.seconds(15), scheduler: DispatchQueue.main, customError: { HTTPError.timeout }) .catch { _ -> AnyPublisher in @@ -185,7 +185,7 @@ final class PNModeVC: BaseVC, OptionViewDelegate { } .receive(on: DispatchQueue.main) .sinkUntilComplete( - receiveValue: { [weak self, flow = self.flow] value in + receiveValue: { value in // Hide the loading indicator viewController.dismiss(animated: true) diff --git a/Session/Onboarding/RegisterVC.swift b/Session/Onboarding/RegisterVC.swift index 56979231f..52cc441a6 100644 --- a/Session/Onboarding/RegisterVC.swift +++ b/Session/Onboarding/RegisterVC.swift @@ -151,6 +151,12 @@ final class RegisterVC : BaseVC { updateSeed() } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + Onboarding.Flow.register.unregister() + } + // MARK: General @objc private func enableCopyButton() { copyPublicKeyButton.isUserInteractionEnabled = true diff --git a/Session/Onboarding/RestoreVC.swift b/Session/Onboarding/RestoreVC.swift index abddf58f6..e196c5f06 100644 --- a/Session/Onboarding/RestoreVC.swift +++ b/Session/Onboarding/RestoreVC.swift @@ -128,8 +128,15 @@ final class RestoreVC: BaseVC { notificationCenter.addObserver(self, selector: #selector(handleKeyboardWillHideNotification(_:)), name: UIResponder.keyboardWillHideNotification, object: nil) } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + Onboarding.Flow.register.unregister() + } + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) + // On small screens we hide the legal label when the keyboard is up, but it's important that the user sees it so // in those instances we don't make the keyboard come up automatically if !isIPhone5OrSmaller { diff --git a/Session/Utilities/BackgroundPoller.swift b/Session/Utilities/BackgroundPoller.swift index d468e8c0c..e00e46f94 100644 --- a/Session/Utilities/BackgroundPoller.swift +++ b/Session/Utilities/BackgroundPoller.swift @@ -67,10 +67,8 @@ public final class BackgroundPoller { return SnodeAPI.getSwarm(for: userPublicKey) .subscribeOnMain(immediately: true) .receiveOnMain(immediately: true) - .tryFlatMap { swarm -> AnyPublisher<[Message], Error> in - guard let snode = swarm.randomElement() else { throw SnodeAPIError.generic } - - return CurrentUserPoller.poll( + .tryFlatMapWithRandomSnode { snode -> AnyPublisher<[Message], Error> in + CurrentUserPoller.poll( namespaces: CurrentUserPoller.namespaces, from: snode, for: userPublicKey, diff --git a/SessionMessagingKit/Database/Models/Interaction.swift b/SessionMessagingKit/Database/Models/Interaction.swift index 6abfd584e..21fdd5456 100644 --- a/SessionMessagingKit/Database/Models/Interaction.swift +++ b/SessionMessagingKit/Database/Models/Interaction.swift @@ -846,10 +846,9 @@ public extension Interaction { .asRequest(of: Attachment.DescriptionInfo.self) .fetchOne(db), attachmentCount: try? attachments.fetchCount(db), - isOpenGroupInvitation: (try? linkPreview + isOpenGroupInvitation: linkPreview .filter(LinkPreview.Columns.variant == LinkPreview.Variant.openGroupInvitation) - .isNotEmpty(db)) - .defaulting(to: false) + .isNotEmpty(db) ) case .infoMediaSavedNotification, .infoScreenshotNotification, .infoCall: diff --git a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift index 8a9e3e218..50a462f9a 100644 --- a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift +++ b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift @@ -31,7 +31,16 @@ public enum MessageSendJob: JobExecutor { // so extract them from any associated attachments var messageFileIds: [String] = [] - if details.message is VisibleMessage { + /// Ensure any associated attachments have already been uploaded before sending the message + /// + /// **Note:** Reactions reference their original message so we need to ignore this logic for reaction messages to ensure we don't + /// incorrectly re-upload incoming attachments that the user reacted to, we also want to exclude "sync" messages since they should + /// already have attachments in a valid state + if + details.message is VisibleMessage, + (details.message as? VisibleMessage)?.reaction == nil && + details.isSyncMessage == false + { guard let jobId: Int64 = job.id, let interactionId: Int64 = job.interactionId diff --git a/SessionMessagingKit/SessionUtil/SessionUtil.swift b/SessionMessagingKit/SessionUtil/SessionUtil.swift index bcc1e67aa..1735f6b6c 100644 --- a/SessionMessagingKit/SessionUtil/SessionUtil.swift +++ b/SessionMessagingKit/SessionUtil/SessionUtil.swift @@ -10,6 +10,7 @@ import SessionUtilitiesKit public extension Features { static func useSharedUtilForUserConfig(_ db: Database? = nil) -> Bool { + return true // TODO: Need to set this timestamp to the correct date (currently start of 2030) // guard Date().timeIntervalSince1970 < 1893456000 else { return true } guard !SessionUtil.hasCheckedMigrationsCompleted.wrappedValue else { @@ -141,6 +142,15 @@ public enum SessionUtil { // MARK: - Loading + public static func clearMemoryState() { + // Ensure we have a loaded state before we continue + guard !SessionUtil.configStore.wrappedValue.isEmpty else { return } + + SessionUtil.configStore.mutate { confStore in + confStore.removeAll() + } + } + public static func loadState( _ db: Database? = nil, userPublicKey: String, diff --git a/SessionSnodeKit/Networking/SnodeAPI.swift b/SessionSnodeKit/Networking/SnodeAPI.swift index 39cf4d59e..b0d7112bd 100644 --- a/SessionSnodeKit/Networking/SnodeAPI.swift +++ b/SessionSnodeKit/Networking/SnodeAPI.swift @@ -639,10 +639,8 @@ public final class SnodeAPI { } return getSwarm(for: publicKey) - .tryFlatMap { swarm -> AnyPublisher<(ResponseInfoType, SendMessagesResponse), Error> in - guard let snode: Snode = swarm.randomElement() else { throw SnodeAPIError.generic } - - return try sendMessage(to: snode) + .tryFlatMapWithRandomSnode(retry: maxRetryCount) { snode -> AnyPublisher<(ResponseInfoType, SendMessagesResponse), Error> in + try sendMessage(to: snode) .tryMap { info, response -> (ResponseInfoType, SendMessagesResponse) in try response.validateResultMap( sodium: sodium.wrappedValue, @@ -651,11 +649,8 @@ public final class SnodeAPI { return (info, response) } - .retry(maxRetryCount) .eraseToAnyPublisher() } - .retry(maxRetryCount) - .eraseToAnyPublisher() } public static func sendConfigMessages( @@ -732,10 +727,8 @@ public final class SnodeAPI { let responseTypes = requests.map { $0.responseType } return getSwarm(for: publicKey) - .tryFlatMap { swarm -> AnyPublisher in - guard let snode: Snode = swarm.randomElement() else { throw SnodeAPIError.generic } - - return SnodeAPI + .tryFlatMapWithRandomSnode(retry: maxRetryCount) { snode -> AnyPublisher in + SnodeAPI .send( request: SnodeRequest( endpoint: .sequence, @@ -749,8 +742,6 @@ public final class SnodeAPI { .decoded(as: responseTypes, requireAllResults: false, using: dependencies) .eraseToAnyPublisher() } - .retry(maxRetryCount) - .eraseToAnyPublisher() } // MARK: - Edit @@ -768,10 +759,8 @@ public final class SnodeAPI { return getSwarm(for: publicKey) .subscribe(on: Threading.workQueue) - .tryFlatMap { swarm -> AnyPublisher<[String: [(hash: String, expiry: UInt64)]], Error> in - guard let snode: Snode = swarm.randomElement() else { throw SnodeAPIError.generic } - - return SnodeAPI + .tryFlatMapWithRandomSnode(retry: maxRetryCount) { snode -> AnyPublisher<[String: [(hash: String, expiry: UInt64)]], Error> in + SnodeAPI .send( request: SnodeRequest( endpoint: .expire, @@ -796,11 +785,8 @@ public final class SnodeAPI { validationData: serverHashes ) } - .retry(maxRetryCount) .eraseToAnyPublisher() } - .retry(maxRetryCount) - .eraseToAnyPublisher() } public static func revokeSubkey( @@ -815,10 +801,8 @@ public final class SnodeAPI { return getSwarm(for: publicKey) .subscribe(on: Threading.workQueue) - .tryFlatMap { swarm -> AnyPublisher in - guard let snode: Snode = swarm.randomElement() else { throw SnodeAPIError.generic } - - return SnodeAPI + .tryFlatMapWithRandomSnode(retry: maxRetryCount) { snode -> AnyPublisher in + SnodeAPI .send( request: SnodeRequest( endpoint: .revokeSubkey, @@ -843,11 +827,8 @@ public final class SnodeAPI { return () } - .retry(maxRetryCount) .eraseToAnyPublisher() } - .retry(maxRetryCount) - .eraseToAnyPublisher() } // MARK: Delete @@ -866,61 +847,46 @@ public final class SnodeAPI { return getSwarm(for: publicKey) .subscribe(on: Threading.workQueue) - .flatMap { swarm -> AnyPublisher<[String: Bool], Error> in - Just(()) - .setFailureType(to: Error.self) - .tryMap { _ -> Snode in - guard let snode: Snode = swarm.randomElement() else { throw SnodeAPIError.generic } - - return snode - } - .flatMap { snode -> AnyPublisher<[String: Bool], Error> in - SnodeAPI - .send( - request: SnodeRequest( - endpoint: .deleteMessages, - body: DeleteMessagesRequest( - messageHashes: serverHashes, - requireSuccessfulDeletion: false, - pubkey: userX25519PublicKey, - ed25519PublicKey: userED25519KeyPair.publicKey, - ed25519SecretKey: userED25519KeyPair.secretKey - ) - ), - to: snode, - associatedWith: publicKey, - using: dependencies + .tryFlatMapWithRandomSnode(retry: maxRetryCount) { snode -> AnyPublisher<[String: Bool], Error> in + SnodeAPI + .send( + request: SnodeRequest( + endpoint: .deleteMessages, + body: DeleteMessagesRequest( + messageHashes: serverHashes, + requireSuccessfulDeletion: false, + pubkey: userX25519PublicKey, + ed25519PublicKey: userED25519KeyPair.publicKey, + ed25519SecretKey: userED25519KeyPair.secretKey ) - .subscribe(on: Threading.workQueue) - .eraseToAnyPublisher() - .decoded(as: DeleteMessagesResponse.self, using: dependencies) - .tryMap { _, response -> [String: Bool] in - let validResultMap: [String: Bool] = try response.validResultMap( - sodium: sodium.wrappedValue, - userX25519PublicKey: userX25519PublicKey, - validationData: serverHashes - ) - - // If `validResultMap` didn't throw then at least one service node - // deleted successfully so we should mark the hash as invalid so we - // don't try to fetch updates using that hash going forward (if we - // do we would end up re-fetching all old messages) - Storage.shared.writeAsync { db in - try? SnodeReceivedMessageInfo.handlePotentialDeletedOrInvalidHash( - db, - potentiallyInvalidHashes: serverHashes - ) - } - - return validResultMap - } - .retry(maxRetryCount) - .eraseToAnyPublisher() + ), + to: snode, + associatedWith: publicKey, + using: dependencies + ) + .decoded(as: DeleteMessagesResponse.self, using: dependencies) + .tryMap { _, response -> [String: Bool] in + let validResultMap: [String: Bool] = try response.validResultMap( + sodium: sodium.wrappedValue, + userX25519PublicKey: userX25519PublicKey, + validationData: serverHashes + ) + + // If `validResultMap` didn't throw then at least one service node + // deleted successfully so we should mark the hash as invalid so we + // don't try to fetch updates using that hash going forward (if we + // do we would end up re-fetching all old messages) + Storage.shared.writeAsync { db in + try? SnodeReceivedMessageInfo.handlePotentialDeletedOrInvalidHash( + db, + potentiallyInvalidHashes: serverHashes + ) + } + + return validResultMap } .eraseToAnyPublisher() } - .retry(maxRetryCount) - .eraseToAnyPublisher() } /// Clears all the user's data from their swarm. Returns a dictionary of snode public key to deletion confirmation. @@ -937,10 +903,8 @@ public final class SnodeAPI { return getSwarm(for: userX25519PublicKey) .subscribe(on: Threading.workQueue) - .tryFlatMap { swarm -> AnyPublisher<[String: Bool], Error> in - guard let snode: Snode = swarm.randomElement() else { throw SnodeAPIError.generic } - - return getNetworkTime(from: snode) + .tryFlatMapWithRandomSnode(retry: maxRetryCount) { snode -> AnyPublisher<[String: Bool], Error> in + getNetworkTime(from: snode) .flatMap { timestampMs -> AnyPublisher<[String: Bool], Error> in SnodeAPI .send( @@ -968,11 +932,8 @@ public final class SnodeAPI { } .eraseToAnyPublisher() } - .retry(maxRetryCount) .eraseToAnyPublisher() } - .retry(maxRetryCount) - .eraseToAnyPublisher() } /// Clears all the user's data from their swarm. Returns a dictionary of snode public key to deletion confirmation. @@ -990,10 +951,8 @@ public final class SnodeAPI { return getSwarm(for: userX25519PublicKey) .subscribe(on: Threading.workQueue) - .tryFlatMap { swarm -> AnyPublisher<[String: Bool], Error> in - guard let snode: Snode = swarm.randomElement() else { throw SnodeAPIError.generic } - - return getNetworkTime(from: snode) + .tryFlatMapWithRandomSnode(retry: maxRetryCount) { snode -> AnyPublisher<[String: Bool], Error> in + getNetworkTime(from: snode) .flatMap { timestampMs -> AnyPublisher<[String: Bool], Error> in SnodeAPI .send( @@ -1022,11 +981,8 @@ public final class SnodeAPI { } .eraseToAnyPublisher() } - .retry(maxRetryCount) .eraseToAnyPublisher() } - .retry(maxRetryCount) - .eraseToAnyPublisher() } // MARK: - Internal API @@ -1377,3 +1333,31 @@ public final class SNSnodeAPI: NSObject { return UInt64(SnodeAPI.currentOffsetTimestampMs()) } } + +// MARK: - Convenience + +public extension Publisher where Output == Set { + func tryFlatMapWithRandomSnode( + maxPublishers: Subscribers.Demand = .unlimited, + retry retries: Int = 0, + _ transform: @escaping (Snode) throws -> P + ) -> AnyPublisher where T == P.Output, P: Publisher, P.Failure == Error { + return self + .mapError { $0 } + .flatMap(maxPublishers: maxPublishers) { swarm -> AnyPublisher in + var remainingSnodes: Set = swarm + + return Just(()) + .setFailureType(to: Error.self) + .tryFlatMap(maxPublishers: maxPublishers) { _ -> AnyPublisher in + let snode: Snode = try remainingSnodes.popRandomElement() ?? { throw SnodeAPIError.generic }() + + return try transform(snode) + .eraseToAnyPublisher() + } + .retry(retries) + .eraseToAnyPublisher() + } + .eraseToAnyPublisher() + } +} diff --git a/SessionUtilitiesKit/Combine/Publisher+Utilities.swift b/SessionUtilitiesKit/Combine/Publisher+Utilities.swift index a49341ae4..4df5f143f 100644 --- a/SessionUtilitiesKit/Combine/Publisher+Utilities.swift +++ b/SessionUtilitiesKit/Combine/Publisher+Utilities.swift @@ -137,7 +137,7 @@ public extension AnyPublisher { // MARK: - Data Decoding -public extension AnyPublisher where Output == Data, Failure == Error { +public extension Publisher where Output == Data, Failure == Error { func decoded( as type: R.Type, using dependencies: Dependencies = Dependencies() @@ -148,7 +148,7 @@ public extension AnyPublisher where Output == Data, Failure == Error { } } -public extension AnyPublisher where Output == (ResponseInfoType, Data?), Failure == Error { +public extension Publisher where Output == (ResponseInfoType, Data?), Failure == Error { func decoded( as type: R.Type, using dependencies: Dependencies = Dependencies() diff --git a/SessionUtilitiesKit/General/Set+Utilities.swift b/SessionUtilitiesKit/General/Set+Utilities.swift index 5fb2d416b..c15f96f6a 100644 --- a/SessionUtilitiesKit/General/Set+Utilities.swift +++ b/SessionUtilitiesKit/General/Set+Utilities.swift @@ -29,4 +29,11 @@ public extension Set { return updatedSet } + + mutating func popRandomElement() -> Element? { + guard let value: Element = randomElement() else { return nil } + + self.remove(value) + return value + } } diff --git a/SessionUtilitiesKit/JobRunner/JobRunner.swift b/SessionUtilitiesKit/JobRunner/JobRunner.swift index 616ab687f..6364f8d77 100644 --- a/SessionUtilitiesKit/JobRunner/JobRunner.swift +++ b/SessionUtilitiesKit/JobRunner/JobRunner.swift @@ -537,6 +537,11 @@ private final class JobQueue { } queue.mutate { $0.append(job) } + + // If this is a concurrent queue then we should immediately start the next job + guard executionType == .concurrent else { return } + + runNextJob() } /// Upsert a job onto the queue, if the queue isn't currently running and 'canStartJob' is true then this will start diff --git a/SessionUtilitiesKit/Networking/BatchResponse.swift b/SessionUtilitiesKit/Networking/BatchResponse.swift index 78575a775..4b0e244e8 100644 --- a/SessionUtilitiesKit/Networking/BatchResponse.swift +++ b/SessionUtilitiesKit/Networking/BatchResponse.swift @@ -78,7 +78,7 @@ public extension Decodable { } } -public extension AnyPublisher where Output == (ResponseInfoType, Data?), Failure == Error { +public extension Publisher where Output == (ResponseInfoType, Data?), Failure == Error { func decoded( as types: HTTP.BatchResponseTypes, requireAllResults: Bool = true, From 44824c8127a61328ef1865fc1285767be4bdea6a Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 23 May 2023 14:02:12 +1000 Subject: [PATCH 067/135] Added a bunch of logs to the Jobs to better track the startup state --- Session/Notifications/SyncPushTokensJob.swift | 7 ++++++- .../Jobs/Types/AttachmentUploadJob.swift | 4 ++++ .../Jobs/Types/ConfigurationSyncJob.swift | 7 +++++-- .../Jobs/Types/DisappearingMessagesJob.swift | 12 +++++++++--- .../Jobs/Types/FailedAttachmentDownloadsJob.swift | 7 ++++--- .../Jobs/Types/FailedMessageSendsJob.swift | 10 ++++++---- SessionMessagingKit/Jobs/Types/GroupLeavingJob.swift | 4 +++- SessionMessagingKit/Jobs/Types/MessageSendJob.swift | 11 ++++++++--- .../Jobs/Types/NotifyPushServerJob.swift | 1 + .../Types/RetrieveDefaultOpenGroupRoomsJob.swift | 9 +++++++-- .../Jobs/Types/UpdateProfilePictureJob.swift | 6 +++++- SessionSnodeKit/Jobs/GetSnodePoolJob.swift | 10 ++++++++-- 12 files changed, 66 insertions(+), 22 deletions(-) diff --git a/Session/Notifications/SyncPushTokensJob.swift b/Session/Notifications/SyncPushTokensJob.swift index 4ac93c7a8..c11ec93d3 100644 --- a/Session/Notifications/SyncPushTokensJob.swift +++ b/Session/Notifications/SyncPushTokensJob.swift @@ -26,6 +26,7 @@ public enum SyncPushTokensJob: JobExecutor { (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false), Identity.userCompletedRequiredOnboarding() else { + SNLog("[SyncPushTokensJob] Deferred due to incomplete registration") deferred(job) // Don't need to do anything if it's not the main app return } @@ -62,6 +63,7 @@ public enum SyncPushTokensJob: JobExecutor { !UIApplication.shared.isRegisteredForRemoteNotifications || Date().timeIntervalSince(lastPushNotificationSync) >= SyncPushTokensJob.maxFrequency else { + SNLog("[SyncPushTokensJob] Deferred due to Fast Mode disabled or recent-enough registration") deferred(job) // Don't need to do anything if push notifications are already registered return } @@ -86,9 +88,12 @@ public enum SyncPushTokensJob: JobExecutor { .handleEvents( receiveCompletion: { result in switch result { - case .failure: break + case .failure(let error): + SNLog("[SyncPushTokensJob] Failed to register due to error: \(error)") + case .finished: Logger.warn("Recording push tokens locally. pushToken: \(redact(pushToken)), voipToken: \(redact(voipToken))") + SNLog("[SyncPushTokensJob] Completed") Storage.shared.write { db in db[.lastRecordedPushToken] = pushToken diff --git a/SessionMessagingKit/Jobs/Types/AttachmentUploadJob.swift b/SessionMessagingKit/Jobs/Types/AttachmentUploadJob.swift index 47981542d..9d0f14ee4 100644 --- a/SessionMessagingKit/Jobs/Types/AttachmentUploadJob.swift +++ b/SessionMessagingKit/Jobs/Types/AttachmentUploadJob.swift @@ -31,6 +31,7 @@ public enum AttachmentUploadJob: JobExecutor { return (attachment, try OpenGroup.fetchOne(db, id: threadId)) }) else { + SNLog("[AttachmentUploadJob] Failed due to missing details") failure(job, JobRunnerError.missingRequiredDetails, true) return } @@ -38,12 +39,14 @@ public enum AttachmentUploadJob: JobExecutor { // If the original interaction no longer exists then don't bother uploading the attachment (ie. the // message was deleted before it even got sent) guard Storage.shared.read({ db in try Interaction.exists(db, id: interactionId) }) == true else { + SNLog("[AttachmentUploadJob] Failed due to missing interaction") failure(job, StorageError.objectNotFound, true) return } // If the attachment is still pending download the hold off on running this job guard attachment.state != .pendingDownload && attachment.state != .downloading else { + SNLog("[AttachmentUploadJob] Deferred as attachment is still being downloaded") deferred(job) return } @@ -98,6 +101,7 @@ public enum AttachmentUploadJob: JobExecutor { ) } + SNLog("[AttachmentUploadJob] Failed due to error: \(error)") failure(job, error, false) case .finished: success(job, false) diff --git a/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift b/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift index 7be4b4a3a..7dbde8b4a 100644 --- a/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift +++ b/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift @@ -49,6 +49,7 @@ public enum ConfigurationSyncJob: JobExecutor { // If there are no pending changes then the job can just complete (next time something // is updated we want to try and run immediately so don't scuedule another run in this case) guard !pendingConfigChanges.isEmpty else { + SNLog("[ConfigurationSyncJob] Completed with no pending changes") success(job, true) return } @@ -118,8 +119,10 @@ public enum ConfigurationSyncJob: JobExecutor { .sinkUntilComplete( receiveCompletion: { result in switch result { - case .finished: break - case .failure(let error): failure(job, error, false) + case .finished: SNLog("[ConfigurationSyncJob] Completed") + case .failure(let error): + SNLog("[ConfigurationSyncJob] Failed due to error: \(error)") + failure(job, error, false) } }, receiveValue: { (configDumps: [ConfigDump]) in diff --git a/SessionMessagingKit/Jobs/Types/DisappearingMessagesJob.swift b/SessionMessagingKit/Jobs/Types/DisappearingMessagesJob.swift index d294c27b3..777c7fad8 100644 --- a/SessionMessagingKit/Jobs/Types/DisappearingMessagesJob.swift +++ b/SessionMessagingKit/Jobs/Types/DisappearingMessagesJob.swift @@ -20,9 +20,10 @@ public enum DisappearingMessagesJob: JobExecutor { // The 'backgroundTask' gets captured and cleared within the 'completion' block let timestampNowMs: TimeInterval = TimeInterval(SnodeAPI.currentOffsetTimestampMs()) var backgroundTask: OWSBackgroundTask? = OWSBackgroundTask(label: #function) + var numDeleted: Int = -1 let updatedJob: Job? = Storage.shared.write { db in - _ = try Interaction + numDeleted = try Interaction .filter(Interaction.Columns.expiresStartedAtMs != nil) .filter((Interaction.Columns.expiresStartedAtMs + (Interaction.Columns.expiresInSeconds * 1000)) <= timestampNowMs) .deleteAll(db) @@ -35,6 +36,7 @@ public enum DisappearingMessagesJob: JobExecutor { .saved(db) } + SNLog("[DisappearingMessagesJob] Deleted \(numDeleted) expired messages") success(updatedJob ?? job, false) // The 'if' is only there to prevent the "variable never read" warning from showing @@ -58,12 +60,16 @@ public extension DisappearingMessagesJob { .asRequest(of: Double.self) .fetchOne(db) - guard let nextExpirationTimestampMs: Double = nextExpirationTimestampMs else { return nil } + guard let nextExpirationTimestampMs: Double = nextExpirationTimestampMs else { + SNLog("[DisappearingMessagesJob] No remaining expiring messages") + return nil + } /// The `expiresStartedAtMs` timestamp is now based on the `SnodeAPI.currentOffsetTimestampMs()` value /// so we need to make sure offset the `nextRunTimestamp` accordingly to ensure it runs at the correct local time let clockOffsetMs: Int64 = SnodeAPI.clockOffsetMs.wrappedValue + SNLog("[DisappearingMessagesJob] Scheduled future message expiration") return try? Job .filter(Job.Columns.variant == Job.Variant.disappearingMessages) .fetchOne(db)? @@ -103,7 +109,7 @@ public extension DisappearingMessagesJob { return updateNextRunIfNeeded(db, interactionIds: [interactionId], startedAtMs: startedAtMs) } catch { - SNLog("Failed to update the expiring messages timer on an interaction") + SNLog("[DisappearingMessagesJob] Failed to update the expiring messages timer on an interaction") return nil } } diff --git a/SessionMessagingKit/Jobs/Types/FailedAttachmentDownloadsJob.swift b/SessionMessagingKit/Jobs/Types/FailedAttachmentDownloadsJob.swift index a2d921eee..747664c9b 100644 --- a/SessionMessagingKit/Jobs/Types/FailedAttachmentDownloadsJob.swift +++ b/SessionMessagingKit/Jobs/Types/FailedAttachmentDownloadsJob.swift @@ -17,15 +17,16 @@ public enum FailedAttachmentDownloadsJob: JobExecutor { failure: @escaping (Job, Error?, Bool) -> (), deferred: @escaping (Job) -> () ) { + var changeCount: Int = -1 + // Update all 'sending' message states to 'failed' Storage.shared.write { db in - let changeCount: Int = try Attachment + changeCount = try Attachment .filter(Attachment.Columns.state == Attachment.State.downloading) .updateAll(db, Attachment.Columns.state.set(to: Attachment.State.failedDownload)) - - Logger.debug("Marked \(changeCount) attachments as failed") } + SNLog("[FailedAttachmentDownloadsJob] Marked \(changeCount) attachments as failed") success(job, false) } } diff --git a/SessionMessagingKit/Jobs/Types/FailedMessageSendsJob.swift b/SessionMessagingKit/Jobs/Types/FailedMessageSendsJob.swift index 48298d73c..31d382cc7 100644 --- a/SessionMessagingKit/Jobs/Types/FailedMessageSendsJob.swift +++ b/SessionMessagingKit/Jobs/Types/FailedMessageSendsJob.swift @@ -16,6 +16,9 @@ public enum FailedMessageSendsJob: JobExecutor { failure: @escaping (Job, Error?, Bool) -> (), deferred: @escaping (Job) -> () ) { + var changeCount: Int = -1 + var attachmentChangeCount: Int = -1 + // Update all 'sending' message states to 'failed' Storage.shared.write { db in let sendChangeCount: Int = try RecipientState @@ -24,14 +27,13 @@ public enum FailedMessageSendsJob: JobExecutor { let syncChangeCount: Int = try RecipientState .filter(RecipientState.Columns.state == RecipientState.State.syncing) .updateAll(db, RecipientState.Columns.state.set(to: RecipientState.State.failedToSync)) - let attachmentChangeCount: Int = try Attachment + attachmentChangeCount = try Attachment .filter(Attachment.Columns.state == Attachment.State.uploading) .updateAll(db, Attachment.Columns.state.set(to: Attachment.State.failedUpload)) - let changeCount: Int = (sendChangeCount + syncChangeCount) - - SNLog("Marked \(changeCount) message\(changeCount == 1 ? "" : "s") as failed (\(attachmentChangeCount) upload\(attachmentChangeCount == 1 ? "" : "s") cancelled)") + changeCount = (sendChangeCount + syncChangeCount) } + SNLog("[FailedMessageSendsJob] Marked \(changeCount) message\(changeCount == 1 ? "" : "s") as failed (\(attachmentChangeCount) upload\(attachmentChangeCount == 1 ? "" : "s") cancelled)") success(job, false) } } diff --git a/SessionMessagingKit/Jobs/Types/GroupLeavingJob.swift b/SessionMessagingKit/Jobs/Types/GroupLeavingJob.swift index af9918392..a66935fac 100644 --- a/SessionMessagingKit/Jobs/Types/GroupLeavingJob.swift +++ b/SessionMessagingKit/Jobs/Types/GroupLeavingJob.swift @@ -25,6 +25,7 @@ public enum GroupLeavingJob: JobExecutor { let threadId: String = job.threadId, let interactionId: Int64 = job.interactionId else { + SNLog("[GroupLeavingJob] Failed due to missing details") failure(job, JobRunnerError.missingRequiredDetails, true) return } @@ -34,10 +35,11 @@ public enum GroupLeavingJob: JobExecutor { Storage.shared .writePublisher { db in guard (try? SessionThread.exists(db, id: threadId)) == true else { - SNLog("Can't update nonexistent closed group.") + SNLog("[GroupLeavingJob] Failed due to non-existent group conversation") throw MessageSenderError.noThread } guard (try? ClosedGroup.exists(db, id: threadId)) == true else { + SNLog("[GroupLeavingJob] Failed due to non-existent group") throw MessageSenderError.invalidClosedGroupUpdate } diff --git a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift index 50a462f9a..8e97addcd 100644 --- a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift +++ b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift @@ -23,6 +23,7 @@ public enum MessageSendJob: JobExecutor { let detailsData: Data = job.details, let details: Details = try? JSONDecoder().decode(Details.self, from: detailsData) else { + SNLog("[MessageSendJob] Failing due to missing details") failure(job, JobRunnerError.missingRequiredDetails, true) return } @@ -45,6 +46,7 @@ public enum MessageSendJob: JobExecutor { let jobId: Int64 = job.id, let interactionId: Int64 = job.interactionId else { + SNLog("[MessageSendJob] Failing due to missing details") failure(job, JobRunnerError.missingRequiredDetails, true) return } @@ -52,6 +54,7 @@ public enum MessageSendJob: JobExecutor { // If the original interaction no longer exists then don't bother sending the message (ie. the // message was deleted before it even got sent) guard Storage.shared.read({ db in try Interaction.exists(db, id: interactionId) }) == true else { + SNLog("[MessageSendJob] Failing due to missing interaction") failure(job, StorageError.objectNotFound, true) return } @@ -150,12 +153,14 @@ public enum MessageSendJob: JobExecutor { // Note: If we have gotten to this point then any dependant attachment upload // jobs will have permanently failed so this message send should also do so guard attachmentState?.shouldFail == false else { + SNLog("[MessageSendJob] Failing due to failed attachment upload") failure(job, AttachmentError.notUploaded, true) return } // Defer the job if we found incomplete uploads guard attachmentState?.shouldDefer == false else { + SNLog("[MessageSendJob] Deferring pending attachment uploads") deferred(job) return } @@ -190,7 +195,7 @@ public enum MessageSendJob: JobExecutor { switch result { case .finished: success(job, false) case .failure(let error): - SNLog("Couldn't send message due to error: \(error).") + SNLog("[MessageSendJob] Couldn't send message due to error: \(error).") switch error { case let senderError as MessageSenderError where !senderError.isRetryable: @@ -200,11 +205,11 @@ public enum MessageSendJob: JobExecutor { failure(job, error, true) case SnodeAPIError.clockOutOfSync: - SNLog("\(originalSentTimestamp != nil ? "Permanently Failing" : "Failing") to send \(type(of: details.message)) due to clock out of sync issue.") + SNLog("[MessageSendJob] \(originalSentTimestamp != nil ? "Permanently Failing" : "Failing") to send \(type(of: details.message)) due to clock out of sync issue.") failure(job, error, (originalSentTimestamp != nil)) default: - SNLog("Failed to send \(type(of: details.message)).") + SNLog("[MessageSendJob] Failed to send \(type(of: details.message)).") if details.message is VisibleMessage { guard diff --git a/SessionMessagingKit/Jobs/Types/NotifyPushServerJob.swift b/SessionMessagingKit/Jobs/Types/NotifyPushServerJob.swift index 121aa0d47..fa2e1fdb4 100644 --- a/SessionMessagingKit/Jobs/Types/NotifyPushServerJob.swift +++ b/SessionMessagingKit/Jobs/Types/NotifyPushServerJob.swift @@ -21,6 +21,7 @@ public enum NotifyPushServerJob: JobExecutor { let detailsData: Data = job.details, let details: Details = try? JSONDecoder().decode(Details.self, from: detailsData) else { + SNLog("[NotifyPushServerJob] Failing due to missing details") failure(job, JobRunnerError.missingRequiredDetails, true) return } diff --git a/SessionMessagingKit/Jobs/Types/RetrieveDefaultOpenGroupRoomsJob.swift b/SessionMessagingKit/Jobs/Types/RetrieveDefaultOpenGroupRoomsJob.swift index ba029b0cb..f1629925a 100644 --- a/SessionMessagingKit/Jobs/Types/RetrieveDefaultOpenGroupRoomsJob.swift +++ b/SessionMessagingKit/Jobs/Types/RetrieveDefaultOpenGroupRoomsJob.swift @@ -47,8 +47,13 @@ public enum RetrieveDefaultOpenGroupRoomsJob: JobExecutor { .sinkUntilComplete( receiveCompletion: { result in switch result { - case .finished: success(job, false) - case .failure(let error): failure(job, error, false) + case .finished: + SNLog("[RetrieveDefaultOpenGroupRoomsJob] Successfully retrieved default Community rooms") + success(job, false) + + case .failure(let error): + SNLog("[RetrieveDefaultOpenGroupRoomsJob] Failed to get default Community rooms") + failure(job, error, false) } } ) diff --git a/SessionMessagingKit/Jobs/Types/UpdateProfilePictureJob.swift b/SessionMessagingKit/Jobs/Types/UpdateProfilePictureJob.swift index 43bab45c1..be6dcb321 100644 --- a/SessionMessagingKit/Jobs/Types/UpdateProfilePictureJob.swift +++ b/SessionMessagingKit/Jobs/Types/UpdateProfilePictureJob.swift @@ -55,10 +55,14 @@ public enum UpdateProfilePictureJob: JobExecutor { // issue as it will write to the database and this closure is already called within // another database write queue.async { + SNLog("[UpdateProfilePictureJob] Profile successfully updated") success(job, false) } }, - failure: { error in failure(job, error, false) } + failure: { error in + SNLog("[UpdateProfilePictureJob] Failed to update profile") + failure(job, error, false) + } ) } } diff --git a/SessionSnodeKit/Jobs/GetSnodePoolJob.swift b/SessionSnodeKit/Jobs/GetSnodePoolJob.swift index 365ef1773..2d0c5e827 100644 --- a/SessionSnodeKit/Jobs/GetSnodePoolJob.swift +++ b/SessionSnodeKit/Jobs/GetSnodePoolJob.swift @@ -23,6 +23,7 @@ public enum GetSnodePoolJob: JobExecutor { // to block if we have no Snode pool and prevent other jobs from failing but avoids having to // wait if we already have a potentially valid snode pool guard !SnodeAPI.hasCachedSnodesInclusingExpired() else { + SNLog("[GetSnodePoolJob] Has valid cached pool, running async instead") SnodeAPI .getSnodePool() .subscribe(on: DispatchQueue.global(qos: .default)) @@ -40,8 +41,13 @@ public enum GetSnodePoolJob: JobExecutor { .sinkUntilComplete( receiveCompletion: { result in switch result { - case .finished: success(job, false) - case .failure(let error): failure(job, error, false) + case .finished: + SNLog("[GetSnodePoolJob] Completed") + success(job, false) + + case .failure(let error): + SNLog("[GetSnodePoolJob] Failed due to error: \(error)") + failure(job, error, false) } } ) From 9c2ec4755751089385f75f57b52159f94651e66d Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 23 May 2023 14:53:48 +1000 Subject: [PATCH 068/135] Added more logs for debugging --- SessionMessagingKit/Database/Models/Interaction.swift | 1 + SessionMessagingKit/Jobs/Types/MessageSendJob.swift | 1 + .../Sending & Receiving/MessageSender+Convenience.swift | 2 +- SessionMessagingKit/Sending & Receiving/MessageSender.swift | 2 +- SessionUtilitiesKit/JobRunner/JobRunner.swift | 2 +- 5 files changed, 5 insertions(+), 3 deletions(-) diff --git a/SessionMessagingKit/Database/Models/Interaction.swift b/SessionMessagingKit/Database/Models/Interaction.swift index 21fdd5456..d89430dd9 100644 --- a/SessionMessagingKit/Database/Models/Interaction.swift +++ b/SessionMessagingKit/Database/Models/Interaction.swift @@ -421,6 +421,7 @@ public struct Interaction: Codable, Identifiable, Equatable, FetchableRecord, Mu } public mutating func didInsert(_ inserted: InsertionSuccess) { + SNLog("[MessageSendDebugging] didInsert interaction for id: \(inserted.rowID)") self.id = inserted.rowID } } diff --git a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift index 8e97addcd..70ff6d98b 100644 --- a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift +++ b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift @@ -55,6 +55,7 @@ public enum MessageSendJob: JobExecutor { // message was deleted before it even got sent) guard Storage.shared.read({ db in try Interaction.exists(db, id: interactionId) }) == true else { SNLog("[MessageSendJob] Failing due to missing interaction") + SNLog("[MessageSendDebugging] Failed with interactionId \(interactionId)") failure(job, StorageError.objectNotFound, true) return } diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift b/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift index 4c6421b34..0373b0b4f 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift @@ -69,7 +69,7 @@ extension MessageSender { ) return } - + SNLog("[MessageSendDebugging] Add send job for interactionId: \(interactionId)\n\(Thread.callStackSymbols)") JobRunner.add( db, job: Job( diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index 1da13d3ec..4385d8e41 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -1073,7 +1073,7 @@ public final class MessageSender { { if let message = message as? VisibleMessage { message.syncTarget = publicKey } if let message = message as? ExpirationTimerUpdate { message.syncTarget = publicKey } - + SNLog("[MessageSendDebugging] Send sync message for interactionId \(interactionId)") JobRunner.add( db, job: Job( diff --git a/SessionUtilitiesKit/JobRunner/JobRunner.swift b/SessionUtilitiesKit/JobRunner/JobRunner.swift index 6364f8d77..7a56a24be 100644 --- a/SessionUtilitiesKit/JobRunner/JobRunner.swift +++ b/SessionUtilitiesKit/JobRunner/JobRunner.swift @@ -1104,7 +1104,7 @@ private final class JobQueue { updatedFailureCount <= maxFailureCount ) else { - SNLog("[JobRunner] \(queueContext) \(job.variant) failed permanently\(maxFailureCount >= 0 ? "; too many retries" : "")") + SNLog("[JobRunner] \(queueContext) \(job.variant) failed permanently\(maxFailureCount >= 0 && updatedFailureCount > maxFailureCount ? "; too many retries" : "")") // If the job permanently failed or we have performed all of our retry attempts // then delete the job and all of it's dependant jobs (it'll probably never succeed) From a5329763333ffdea596fd341439b45fb2666c0f5 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 23 May 2023 15:02:37 +1000 Subject: [PATCH 069/135] Updated the JobRunner to wait until the current transaction is done before running a job --- SessionUtilitiesKit/JobRunner/JobRunner.swift | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/SessionUtilitiesKit/JobRunner/JobRunner.swift b/SessionUtilitiesKit/JobRunner/JobRunner.swift index 7a56a24be..7906fd532 100644 --- a/SessionUtilitiesKit/JobRunner/JobRunner.swift +++ b/SessionUtilitiesKit/JobRunner/JobRunner.swift @@ -138,7 +138,7 @@ public final class JobRunner { return } - queues.mutate { $0[updatedJob.variant]?.add(updatedJob, canStartJob: canStartJob) } + queues.wrappedValue[updatedJob.variant]?.add(db, job: updatedJob, canStartJob: canStartJob) // Don't start the queue if the job can't be started guard canStartJob else { return } @@ -161,7 +161,7 @@ public final class JobRunner { return } - queues.wrappedValue[job.variant]?.upsert(job, canStartJob: canStartJob) + queues.wrappedValue[job.variant]?.upsert(db, job: job, canStartJob: canStartJob) // Don't start the queue if the job can't be started guard canStartJob else { return } @@ -524,7 +524,7 @@ private final class JobQueue { // MARK: - Execution - fileprivate func add(_ job: Job, canStartJob: Bool = true) { + fileprivate func add(_ db: Database, job: Job, canStartJob: Bool = true) { // Check if the job should be added to the queue guard canStartJob, @@ -541,7 +541,11 @@ private final class JobQueue { // If this is a concurrent queue then we should immediately start the next job guard executionType == .concurrent else { return } - runNextJob() + // Ensure that the database commit has completed and then trigger the next job to run (need + // to ensure any interactions have been correctly inserted first) + db.afterNextTransactionNestedOnce(dedupeId: "JobRunner-Add: \(job.variant)") { [weak self] _ in + self?.runNextJob() + } } /// Upsert a job onto the queue, if the queue isn't currently running and 'canStartJob' is true then this will start @@ -549,7 +553,7 @@ private final class JobQueue { /// /// **Note:** If the job has a `behaviour` of `runOnceNextLaunch` or the `nextRunTimestamp` /// is in the future then the job won't be started - fileprivate func upsert(_ job: Job, canStartJob: Bool = true) { + fileprivate func upsert(_ db: Database, job: Job, canStartJob: Bool = true) { guard let jobId: Int64 = job.id else { SNLog("[JobRunner] Prevented attempt to upsert \(job.variant) job without id to queue") return @@ -572,7 +576,7 @@ private final class JobQueue { // If we didn't update an existing job then we need to add it to the queue guard !didUpdateExistingJob else { return } - add(job, canStartJob: canStartJob) + add(db, job: job, canStartJob: canStartJob) } fileprivate func insert(_ job: Job, before otherJob: Job) { From b277056a624c8a84db267de03e57208555e6a2b2 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 23 May 2023 15:09:11 +1000 Subject: [PATCH 070/135] Removed the extra debug logs for message sending --- Session/Conversations/ConversationVC+Interaction.swift | 4 +--- SessionMessagingKit/Database/Models/Interaction.swift | 1 - SessionMessagingKit/Jobs/Types/MessageSendJob.swift | 1 - .../Sending & Receiving/MessageSender+Convenience.swift | 2 +- SessionMessagingKit/Sending & Receiving/MessageSender.swift | 2 +- 5 files changed, 3 insertions(+), 7 deletions(-) diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 2b8ce7cf0..715a4ab34 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -608,9 +608,7 @@ extension ConversationVC: .fetchOne(db) ).inserted(db) - guard let interactionId: Int64 = interaction.id else { - return - } + guard let interactionId: Int64 = interaction.id else { return } // Prepare any attachments try Attachment.prepare( diff --git a/SessionMessagingKit/Database/Models/Interaction.swift b/SessionMessagingKit/Database/Models/Interaction.swift index d89430dd9..21fdd5456 100644 --- a/SessionMessagingKit/Database/Models/Interaction.swift +++ b/SessionMessagingKit/Database/Models/Interaction.swift @@ -421,7 +421,6 @@ public struct Interaction: Codable, Identifiable, Equatable, FetchableRecord, Mu } public mutating func didInsert(_ inserted: InsertionSuccess) { - SNLog("[MessageSendDebugging] didInsert interaction for id: \(inserted.rowID)") self.id = inserted.rowID } } diff --git a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift index 70ff6d98b..8e97addcd 100644 --- a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift +++ b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift @@ -55,7 +55,6 @@ public enum MessageSendJob: JobExecutor { // message was deleted before it even got sent) guard Storage.shared.read({ db in try Interaction.exists(db, id: interactionId) }) == true else { SNLog("[MessageSendJob] Failing due to missing interaction") - SNLog("[MessageSendDebugging] Failed with interactionId \(interactionId)") failure(job, StorageError.objectNotFound, true) return } diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift b/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift index 0373b0b4f..4c6421b34 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift @@ -69,7 +69,7 @@ extension MessageSender { ) return } - SNLog("[MessageSendDebugging] Add send job for interactionId: \(interactionId)\n\(Thread.callStackSymbols)") + JobRunner.add( db, job: Job( diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index 4385d8e41..1da13d3ec 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -1073,7 +1073,7 @@ public final class MessageSender { { if let message = message as? VisibleMessage { message.syncTarget = publicKey } if let message = message as? ExpirationTimerUpdate { message.syncTarget = publicKey } - SNLog("[MessageSendDebugging] Send sync message for interactionId \(interactionId)") + JobRunner.add( db, job: Job( From 2bb68ccbcf21ee5649dc11e117588815261c9a30 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 24 May 2023 09:43:06 +1000 Subject: [PATCH 071/135] Fixed a few issues found during QA Fixed an issue where you could be left on the settings screen after deleting/leaving a conversation Fixed an issue where deleting the last contact/group/community from a device would result in linked devices not deleting the relevant conversation Fixed an issue where leaving a group created a race condition where linked devices wouldn't remove the group --- .../Settings/ThreadSettingsViewModel.swift | 7 ++++++- .../MessageReceiver+ClosedGroups.swift | 2 +- .../Config Handling/SessionUtil+Contacts.swift | 3 --- .../SessionUtil+ConvoInfoVolatile.swift | 3 --- .../Config Handling/SessionUtil+UserGroups.swift | 3 --- .../Shared Models/SessionThreadViewModel.swift | 13 +++---------- 6 files changed, 10 insertions(+), 21 deletions(-) diff --git a/Session/Conversations/Settings/ThreadSettingsViewModel.swift b/Session/Conversations/Settings/ThreadSettingsViewModel.swift index 486644d3c..2a1592866 100644 --- a/Session/Conversations/Settings/ThreadSettingsViewModel.swift +++ b/Session/Conversations/Settings/ThreadSettingsViewModel.swift @@ -201,7 +201,12 @@ class ThreadSettingsViewModel: SessionTableViewModel () = { + // Only make this change if needed (want to avoid triggering a thread update + // if not needed) guard threadWasMarkedUnread == true else { return } Storage.shared.writeAsync { db in @@ -289,16 +291,7 @@ public struct SessionThreadViewModel: FetchableRecordWithRowId, Decodable, Equat let threadIsMessageRequest: Bool? = self.threadIsMessageRequest Storage.shared.writeAsync { db in - // Only make this change if needed (want to avoid triggering a thread update - // if not needed) - if threadWasMarkedUnread == true { - try SessionThread - .filter(id: threadId) - .updateAllAndConfig( - db, - SessionThread.Columns.markedAsUnread.set(to: false) - ) - } + markThreadAsReadIfNeeded() try Interaction.markAsRead( db, From 2d792e4e3e9858e8a30373d7fc024af16cede380 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 24 May 2023 13:33:53 +1000 Subject: [PATCH 072/135] Updated the profile picture modal and standardised the ProfilePictureView sizes Fixed an issue where 'CurrentAppContext().isMainAppAndActive' wasn't called on the main thread Updated the ProfilePictureView to have the updated icon UI --- .../Call Management/SessionCallManager.swift | 4 +- .../Views & Modals/IncomingCallBanner.swift | 10 +- Session/Conversations/ConversationVC.swift | 6 +- .../Conversations/Input View/InputView.swift | 3 +- .../Input View/MentionSelectionView.swift | 21 +- .../Message Cells/VisibleMessageCell.swift | 30 +- Session/Home/HomeVC.swift | 6 +- .../Views/MessageRequestsCell.swift | 7 +- .../Settings/Views/BlockedContactCell.swift | 5 +- Session/Shared/FullConversationCell.swift | 10 +- Session/Shared/Views/SessionAvatarCell.swift | 6 +- .../Views/SessionCell+AccessoryView.swift | 6 +- .../SimplifiedConversationCell.swift | 6 +- .../Themes/Theme+ClassicDark.swift | 7 +- .../Themes/Theme+ClassicLight.swift | 7 +- .../Style Guide/Themes/Theme+OceanDark.swift | 7 +- .../Style Guide/Themes/Theme+OceanLight.swift | 7 +- SessionUIKit/Style Guide/Themes/Theme.swift | 5 + SessionUIKit/Style Guide/Values.swift | 5 - .../Profile Pictures/ProfilePictureView.swift | 330 +++++++++++++++--- 20 files changed, 344 insertions(+), 144 deletions(-) diff --git a/Session/Calls/Call Management/SessionCallManager.swift b/Session/Calls/Call Management/SessionCallManager.swift index b33177ff7..cf7ac6184 100644 --- a/Session/Calls/Call Management/SessionCallManager.swift +++ b/Session/Calls/Call Management/SessionCallManager.swift @@ -205,9 +205,9 @@ public final class SessionCallManager: NSObject, CallManagerProtocol { return } - guard CurrentAppContext().isMainAppAndActive else { return } - DispatchQueue.main.async { + guard CurrentAppContext().isMainAppAndActive else { return } + guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() // FIXME: Handle more gracefully } diff --git a/Session/Calls/Views & Modals/IncomingCallBanner.swift b/Session/Calls/Views & Modals/IncomingCallBanner.swift index cecb6c01b..8ed425011 100644 --- a/Session/Calls/Views & Modals/IncomingCallBanner.swift +++ b/Session/Calls/Views & Modals/IncomingCallBanner.swift @@ -4,6 +4,7 @@ import UIKit import WebRTC import SessionUIKit import SessionMessagingKit +import SignalUtilitiesKit final class IncomingCallBanner: UIView, UIGestureRecognizerDelegate { private static let swipeToOperateThreshold: CGFloat = 60 @@ -20,14 +21,7 @@ final class IncomingCallBanner: UIView, UIGestureRecognizerDelegate { return result }() - private lazy var profilePictureView: ProfilePictureView = { - let result = ProfilePictureView() - let size: CGFloat = 60 - result.size = size - result.set(.width, to: size) - result.set(.height, to: size) - return result - }() + private lazy var profilePictureView: ProfilePictureView = ProfilePictureView(size: .list) private lazy var displayNameLabel: UILabel = { let result = UILabel() diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index 17ab87f7d..f521f6f01 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -1104,15 +1104,13 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl switch threadData.threadVariant { case .contact: - let profilePictureView = ProfilePictureView() - profilePictureView.size = Values.verySmallProfilePictureSize + let profilePictureView = ProfilePictureView(size: .navigation) profilePictureView.update( publicKey: threadData.threadId, // Contact thread uses the contactId profile: threadData.profile, threadVariant: threadData.threadVariant ) - profilePictureView.set(.width, to: (44 - 16)) // Width of the standard back button - profilePictureView.set(.height, to: Values.verySmallProfilePictureSize) + profilePictureView.customWidth = (44 - 16) // Width of the standard back button let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(openSettings)) profilePictureView.addGestureRecognizer(tapGestureRecognizer) diff --git a/Session/Conversations/Input View/InputView.swift b/Session/Conversations/Input View/InputView.swift index 4727b53fa..a2020e8be 100644 --- a/Session/Conversations/Input View/InputView.swift +++ b/Session/Conversations/Input View/InputView.swift @@ -4,6 +4,7 @@ import UIKit import SessionUIKit import SessionMessagingKit import SessionUtilitiesKit +import SignalUtilitiesKit final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, MentionSelectionViewDelegate { // MARK: - Variables @@ -491,7 +492,7 @@ final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, M func showMentionsUI(for candidates: [MentionInfo]) { mentionsView.candidates = candidates - let mentionCellHeight = (Values.smallProfilePictureSize + 2 * Values.smallSpacing) + let mentionCellHeight = (ProfilePictureView.Size.message.viewSize + 2 * Values.smallSpacing) mentionsViewHeightConstraint.constant = CGFloat(min(3, candidates.count)) * mentionCellHeight layoutIfNeeded() diff --git a/Session/Conversations/Input View/MentionSelectionView.swift b/Session/Conversations/Input View/MentionSelectionView.swift index 881059cc3..b499af0b9 100644 --- a/Session/Conversations/Input View/MentionSelectionView.swift +++ b/Session/Conversations/Input View/MentionSelectionView.swift @@ -111,9 +111,7 @@ private extension MentionSelectionView { final class Cell: UITableViewCell { // MARK: - UI - private lazy var profilePictureView: ProfilePictureView = ProfilePictureView() - - private lazy var moderatorIconImageView: UIImageView = UIImageView(image: #imageLiteral(resourceName: "Crown")) + private lazy var profilePictureView: ProfilePictureView = ProfilePictureView(size: .message) private lazy var displayNameLabel: UILabel = { let result: UILabel = UILabel() @@ -155,18 +153,12 @@ private extension MentionSelectionView { selectedBackgroundView.themeBackgroundColor = .highlighted(.settings_tabBackground) self.selectedBackgroundView = selectedBackgroundView - // Profile picture image view - let profilePictureViewSize = Values.smallProfilePictureSize - profilePictureView.set(.width, to: profilePictureViewSize) - profilePictureView.set(.height, to: profilePictureViewSize) - profilePictureView.size = profilePictureViewSize - // Main stack view let mainStackView = UIStackView(arrangedSubviews: [ profilePictureView, displayNameLabel ]) mainStackView.axis = .horizontal mainStackView.alignment = .center mainStackView.spacing = Values.mediumSpacing - mainStackView.set(.height, to: profilePictureViewSize) + mainStackView.set(.height, to: ProfilePictureView.Size.message.viewSize) contentView.addSubview(mainStackView) mainStackView.pin(.leading, to: .leading, of: contentView, withInset: Values.mediumSpacing) mainStackView.pin(.top, to: .top, of: contentView, withInset: Values.smallSpacing) @@ -174,13 +166,6 @@ private extension MentionSelectionView { contentView.pin(.bottom, to: .bottom, of: mainStackView, withInset: Values.smallSpacing) mainStackView.set(.width, to: UIScreen.main.bounds.width - 2 * Values.mediumSpacing) - // Moderator icon image view - moderatorIconImageView.set(.width, to: 20) - moderatorIconImageView.set(.height, to: 20) - contentView.addSubview(moderatorIconImageView) - moderatorIconImageView.pin(.trailing, to: .trailing, of: profilePictureView, withInset: 1) - moderatorIconImageView.pin(.bottom, to: .bottom, of: profilePictureView, withInset: 4.5) - // Separator addSubview(separator) separator.pin(.leading, to: .leading, of: self) @@ -200,9 +185,9 @@ private extension MentionSelectionView { profilePictureView.update( publicKey: profile.id, profile: profile, + icon: (isUserModeratorOrAdmin ? .crown : .none), threadVariant: threadVariant ) - moderatorIconImageView.isHidden = !isUserModeratorOrAdmin separator.isHidden = isLast } } diff --git a/Session/Conversations/Message Cells/VisibleMessageCell.swift b/Session/Conversations/Message Cells/VisibleMessageCell.swift index fc1c853a3..df081981c 100644 --- a/Session/Conversations/Message Cells/VisibleMessageCell.swift +++ b/Session/Conversations/Message Cells/VisibleMessageCell.swift @@ -22,7 +22,6 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { private lazy var authorLabelTopConstraint = authorLabel.pin(.top, to: .top, of: self) private lazy var authorLabelHeightConstraint = authorLabel.set(.height, to: 0) private lazy var profilePictureViewLeadingConstraint = profilePictureView.pin(.leading, to: .leading, of: self, withInset: VisibleMessageCell.groupThreadHSpacing) - private lazy var profilePictureViewWidthConstraint = profilePictureView.set(.width, to: Values.verySmallProfilePictureSize) private lazy var contentViewLeadingConstraint1 = snContentView.pin(.leading, to: .trailing, of: profilePictureView, withInset: VisibleMessageCell.groupThreadHSpacing) private lazy var contentViewLeadingConstraint2 = snContentView.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor, constant: VisibleMessageCell.gutterSize) private lazy var contentViewTopConstraint = snContentView.pin(.top, to: .bottom, of: authorLabel, withInset: VisibleMessageCell.authorLabelBottomSpacing) @@ -51,22 +50,13 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { private lazy var viewsToMoveForReply: [UIView] = [ snContentView, profilePictureView, - moderatorIconImageView, replyButton, timerView, messageStatusImageView, reactionContainerView ] - private lazy var profilePictureView: ProfilePictureView = { - let result: ProfilePictureView = ProfilePictureView() - result.set(.height, to: Values.verySmallProfilePictureSize) - result.size = Values.verySmallProfilePictureSize - - return result - }() - - private lazy var moderatorIconImageView = UIImageView(image: #imageLiteral(resourceName: "Crown")) + private lazy var profilePictureView: ProfilePictureView = ProfilePictureView(size: .message) lazy var bubbleBackgroundView: UIView = { let result = UIView() @@ -176,7 +166,6 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { private static let messageStatusImageViewSize: CGFloat = 12 private static let authorLabelBottomSpacing: CGFloat = 4 private static let groupThreadHSpacing: CGFloat = 12 - private static let profilePictureSize = Values.verySmallProfilePictureSize private static let authorLabelInset: CGFloat = 12 private static let replyButtonSize: CGFloat = 24 private static let maxBubbleTranslationX: CGFloat = 40 @@ -186,7 +175,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { static let contactThreadHSpacing = Values.mediumSpacing static var gutterSize: CGFloat = { - var result = groupThreadHSpacing + profilePictureSize + groupThreadHSpacing + var result = groupThreadHSpacing + ProfilePictureView.Size.message.viewSize + groupThreadHSpacing if UIDevice.current.isIPad { result += 168 @@ -195,7 +184,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { return result }() - static var leftGutterSize: CGFloat { groupThreadHSpacing + profilePictureSize + groupThreadHSpacing } + static var leftGutterSize: CGFloat { groupThreadHSpacing + ProfilePictureView.Size.message.viewSize + groupThreadHSpacing } // MARK: Direction & Position @@ -214,21 +203,13 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { // Profile picture view addSubview(profilePictureView) profilePictureViewLeadingConstraint.isActive = true - profilePictureViewWidthConstraint.isActive = true - - // Moderator icon image view - moderatorIconImageView.set(.width, to: 20) - moderatorIconImageView.set(.height, to: 20) - addSubview(moderatorIconImageView) - moderatorIconImageView.pin(.trailing, to: .trailing, of: profilePictureView, withInset: 1) - moderatorIconImageView.pin(.bottom, to: .bottom, of: profilePictureView, withInset: 4.5) // Content view addSubview(snContentView) contentViewLeadingConstraint1.isActive = true contentViewTopConstraint.isActive = true contentViewTrailingConstraint1.isActive = true - snContentView.pin(.bottom, to: .bottom, of: profilePictureView, withInset: -1) + snContentView.pin(.bottom, to: .bottom, of: profilePictureView) // Bubble background view bubbleBackgroundView.addSubview(bubbleView) @@ -317,14 +298,13 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { // Profile picture view profilePictureViewLeadingConstraint.constant = (isGroupThread ? VisibleMessageCell.groupThreadHSpacing : 0) - profilePictureViewWidthConstraint.constant = (isGroupThread ? VisibleMessageCell.profilePictureSize : 0) profilePictureView.isHidden = (!cellViewModel.shouldShowProfile || cellViewModel.profile == nil) profilePictureView.update( publicKey: cellViewModel.authorId, profile: cellViewModel.profile, + icon: (cellViewModel.isSenderOpenGroupModerator ? .crown : .none), threadVariant: cellViewModel.threadVariant ) - moderatorIconImageView.isHidden = (!cellViewModel.isSenderOpenGroupModerator || !cellViewModel.shouldShowProfile) // Bubble view contentViewLeadingConstraint1.isActive = ( diff --git a/Session/Home/HomeVC.swift b/Session/Home/HomeVC.swift index 3475539da..1abd0f503 100644 --- a/Session/Home/HomeVC.swift +++ b/Session/Home/HomeVC.swift @@ -477,19 +477,15 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, SeedRemi private func updateNavBarButtons() { // Profile picture view - let profilePictureSize = Values.verySmallProfilePictureSize - let profilePictureView = ProfilePictureView() + let profilePictureView = ProfilePictureView(size: .navigation) profilePictureView.accessibilityIdentifier = "User settings" profilePictureView.accessibilityLabel = "User settings" profilePictureView.isAccessibilityElement = true - profilePictureView.size = profilePictureSize profilePictureView.update( publicKey: getUserHexEncodedPublicKey(), profile: Profile.fetchOrCreateCurrentUser(), threadVariant: .contact ) - profilePictureView.set(.width, to: profilePictureSize) - profilePictureView.set(.height, to: profilePictureSize) let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(openSettings)) profilePictureView.addGestureRecognizer(tapGestureRecognizer) diff --git a/Session/Home/Message Requests/Views/MessageRequestsCell.swift b/Session/Home/Message Requests/Views/MessageRequestsCell.swift index 5141500eb..61feef7b3 100644 --- a/Session/Home/Message Requests/Views/MessageRequestsCell.swift +++ b/Session/Home/Message Requests/Views/MessageRequestsCell.swift @@ -2,6 +2,7 @@ import UIKit import SessionUIKit +import SignalUtilitiesKit class MessageRequestsCell: UITableViewCell { static let reuseIdentifier = "MessageRequestsCell" @@ -29,7 +30,7 @@ class MessageRequestsCell: UITableViewCell { result.translatesAutoresizingMaskIntoConstraints = false result.clipsToBounds = true result.themeBackgroundColor = .conversationButton_unreadBubbleBackground - result.layer.cornerRadius = (Values.mediumProfilePictureSize / 2) + result.layer.cornerRadius = (ProfilePictureView.Size.list.viewSize / 2) return result }() @@ -100,8 +101,8 @@ class MessageRequestsCell: UITableViewCell { constant: (Values.accentLineThickness + Values.mediumSpacing) ), iconContainerView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), - iconContainerView.widthAnchor.constraint(equalToConstant: Values.mediumProfilePictureSize), - iconContainerView.heightAnchor.constraint(equalToConstant: Values.mediumProfilePictureSize), + iconContainerView.widthAnchor.constraint(equalToConstant: ProfilePictureView.Size.list.viewSize), + iconContainerView.heightAnchor.constraint(equalToConstant: ProfilePictureView.Size.list.viewSize), iconImageView.centerXAnchor.constraint(equalTo: iconContainerView.centerXAnchor), iconImageView.centerYAnchor.constraint(equalTo: iconContainerView.centerYAnchor), diff --git a/Session/Settings/Views/BlockedContactCell.swift b/Session/Settings/Views/BlockedContactCell.swift index 3cf838fb8..261de3719 100644 --- a/Session/Settings/Views/BlockedContactCell.swift +++ b/Session/Settings/Views/BlockedContactCell.swift @@ -8,7 +8,7 @@ import SignalUtilitiesKit class BlockedContactCell: UITableViewCell { // MARK: - Components - private lazy var profilePictureView: ProfilePictureView = ProfilePictureView() + private lazy var profilePictureView: ProfilePictureView = ProfilePictureView(size: .list) private let selectionView: RadioButton = { let result: RadioButton = RadioButton(size: .medium) @@ -61,9 +61,6 @@ class BlockedContactCell: UITableViewCell { .constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: -Values.mediumSpacing) .isActive = true profilePictureView.pin(.left, to: .left, of: contentView, withInset: Values.veryLargeSpacing) - profilePictureView.set(.width, to: Values.mediumProfilePictureSize) - profilePictureView.set(.height, to: Values.mediumProfilePictureSize) - profilePictureView.size = Values.mediumProfilePictureSize selectionView.center(.vertical, in: contentView) selectionView.topAnchor diff --git a/Session/Shared/FullConversationCell.swift b/Session/Shared/FullConversationCell.swift index c2de9ffa3..611ac20af 100644 --- a/Session/Shared/FullConversationCell.swift +++ b/Session/Shared/FullConversationCell.swift @@ -18,7 +18,7 @@ public final class FullConversationCell: UITableViewCell { private let accentLineView: UIView = UIView() - private lazy var profilePictureView: ProfilePictureView = ProfilePictureView() + private lazy var profilePictureView: ProfilePictureView = ProfilePictureView(size: .list) private lazy var displayNameLabel: UILabel = { let result: UILabel = UILabel() @@ -159,12 +159,6 @@ public final class FullConversationCell: UITableViewCell { accentLineView.set(.width, to: Values.accentLineThickness) accentLineView.set(.height, to: cellHeight) - // Profile picture view - let profilePictureViewSize = Values.mediumProfilePictureSize - profilePictureView.set(.width, to: profilePictureViewSize) - profilePictureView.set(.height, to: profilePictureViewSize) - profilePictureView.size = profilePictureViewSize - // Unread count view unreadCountView.addSubview(unreadCountLabel) unreadCountLabel.setCompressionResistanceHigh() @@ -640,7 +634,7 @@ public final class FullConversationCell: UITableViewCell { // This method determines if the content is probably too long and returns the truncated or untruncated // content accordingly func truncatingIfNeeded(approxWidth: CGFloat, content: NSAttributedString) -> NSAttributedString { - let approxFullWidth: CGFloat = (approxWidth + profilePictureView.size + (Values.mediumSpacing * 3)) + let approxFullWidth: CGFloat = (approxWidth + profilePictureView.size.viewSize + (Values.mediumSpacing * 3)) guard ((bounds.width - approxFullWidth) < 0) else { return content } diff --git a/Session/Shared/Views/SessionAvatarCell.swift b/Session/Shared/Views/SessionAvatarCell.swift index b7f827946..9dee6402a 100644 --- a/Session/Shared/Views/SessionAvatarCell.swift +++ b/Session/Shared/Views/SessionAvatarCell.swift @@ -51,11 +51,10 @@ class SessionAvatarCell: UITableViewCell { }() fileprivate let profilePictureView: ProfilePictureView = { - let view: ProfilePictureView = ProfilePictureView() + let view: ProfilePictureView = ProfilePictureView(size: .hero) view.accessibilityLabel = "Profile picture" view.isAccessibilityElement = true view.translatesAutoresizingMaskIntoConstraints = false - view.size = Values.largeProfilePictureSize return view }() @@ -148,9 +147,6 @@ class SessionAvatarCell: UITableViewCell { private func setupLayout() { stackView.pin(to: contentView) - profilePictureView.set(.width, to: profilePictureView.size) - profilePictureView.set(.height, to: profilePictureView.size) - displayNameLabel.pin(to: displayNameContainer) displayNameTextField.center(in: displayNameContainer) displayNameTextField.widthAnchor diff --git a/Session/Shared/Views/SessionCell+AccessoryView.swift b/Session/Shared/Views/SessionCell+AccessoryView.swift index 5b43c9cd0..7a625410a 100644 --- a/Session/Shared/Views/SessionCell+AccessoryView.swift +++ b/Session/Shared/Views/SessionCell+AccessoryView.swift @@ -141,12 +141,8 @@ extension SessionCell { }() private lazy var profilePictureView: ProfilePictureView = { - let result: ProfilePictureView = ProfilePictureView() - result.translatesAutoresizingMaskIntoConstraints = false - result.size = Values.smallProfilePictureSize + let result: ProfilePictureView = ProfilePictureView(size: .list) result.isHidden = true - result.set(.width, to: Values.smallProfilePictureSize) - result.set(.height, to: Values.smallProfilePictureSize) return result }() diff --git a/SessionShareExtension/SimplifiedConversationCell.swift b/SessionShareExtension/SimplifiedConversationCell.swift index 3c2fa1e32..db7cf2c55 100644 --- a/SessionShareExtension/SimplifiedConversationCell.swift +++ b/SessionShareExtension/SimplifiedConversationCell.swift @@ -39,7 +39,7 @@ final class SimplifiedConversationCell: UITableViewCell { }() private lazy var profilePictureView: ProfilePictureView = { - let view: ProfilePictureView = ProfilePictureView() + let view: ProfilePictureView = ProfilePictureView(size: .list) view.translatesAutoresizingMaskIntoConstraints = false return view @@ -79,10 +79,6 @@ final class SimplifiedConversationCell: UITableViewCell { accentLineView.set(.width, to: Values.accentLineThickness) accentLineView.set(.height, to: 68) - profilePictureView.set(.width, to: Values.mediumProfilePictureSize) - profilePictureView.set(.height, to: Values.mediumProfilePictureSize) - profilePictureView.size = Values.mediumProfilePictureSize - stackView.pin(to: self) } diff --git a/SessionUIKit/Style Guide/Themes/Theme+ClassicDark.swift b/SessionUIKit/Style Guide/Themes/Theme+ClassicDark.swift index 69242e54b..b23d07000 100644 --- a/SessionUIKit/Style Guide/Themes/Theme+ClassicDark.swift +++ b/SessionUIKit/Style Guide/Themes/Theme+ClassicDark.swift @@ -108,6 +108,11 @@ internal enum Theme_ClassicDark: ThemeColors { .reactions_contextMoreBackground: .classicDark1, // NewConversation - .newConversation_background: .classicDark1 + .newConversation_background: .classicDark1, + + // Profile + .profileIcon: .primary, + .profileIcon_greenPrimaryColor: .black, + .profileIcon_background: .white ] } diff --git a/SessionUIKit/Style Guide/Themes/Theme+ClassicLight.swift b/SessionUIKit/Style Guide/Themes/Theme+ClassicLight.swift index f1e1a9d80..67f9f9ce1 100644 --- a/SessionUIKit/Style Guide/Themes/Theme+ClassicLight.swift +++ b/SessionUIKit/Style Guide/Themes/Theme+ClassicLight.swift @@ -108,6 +108,11 @@ internal enum Theme_ClassicLight: ThemeColors { .reactions_contextMoreBackground: .classicLight6, // NewConversation - .newConversation_background: .classicLight6 + .newConversation_background: .classicLight6, + + // Profile + .profileIcon: .primary, + .profileIcon_greenPrimaryColor: .primary, + .profileIcon_background: .black ] } diff --git a/SessionUIKit/Style Guide/Themes/Theme+OceanDark.swift b/SessionUIKit/Style Guide/Themes/Theme+OceanDark.swift index beb4cba54..78adeab56 100644 --- a/SessionUIKit/Style Guide/Themes/Theme+OceanDark.swift +++ b/SessionUIKit/Style Guide/Themes/Theme+OceanDark.swift @@ -108,6 +108,11 @@ internal enum Theme_OceanDark: ThemeColors { .reactions_contextMoreBackground: .oceanDark2, // NewConversation - .newConversation_background: .oceanDark3 + .newConversation_background: .oceanDark3, + + // Profile + .profileIcon: .primary, + .profileIcon_greenPrimaryColor: .black, + .profileIcon_background: .white ] } diff --git a/SessionUIKit/Style Guide/Themes/Theme+OceanLight.swift b/SessionUIKit/Style Guide/Themes/Theme+OceanLight.swift index c1e72799e..a71b96c11 100644 --- a/SessionUIKit/Style Guide/Themes/Theme+OceanLight.swift +++ b/SessionUIKit/Style Guide/Themes/Theme+OceanLight.swift @@ -108,6 +108,11 @@ internal enum Theme_OceanLight: ThemeColors { .reactions_contextMoreBackground: .oceanLight6, // NewConversation - .newConversation_background: .oceanLight7 + .newConversation_background: .oceanLight7, + + // Profile + .profileIcon: .primary, + .profileIcon_greenPrimaryColor: .primary, + .profileIcon_background: .oceanLight1 ] } diff --git a/SessionUIKit/Style Guide/Themes/Theme.swift b/SessionUIKit/Style Guide/Themes/Theme.swift index f2766cc6d..3e2f30e62 100644 --- a/SessionUIKit/Style Guide/Themes/Theme.swift +++ b/SessionUIKit/Style Guide/Themes/Theme.swift @@ -197,6 +197,11 @@ public indirect enum ThemeValue: Hashable { // NewConversation case newConversation_background + + // Profile + case profileIcon + case profileIcon_greenPrimaryColor + case profileIcon_background } // MARK: - ForcedThemeValue diff --git a/SessionUIKit/Style Guide/Values.swift b/SessionUIKit/Style Guide/Values.swift index a66d47801..fd7cc1012 100644 --- a/SessionUIKit/Style Guide/Values.swift +++ b/SessionUIKit/Style Guide/Values.swift @@ -25,11 +25,6 @@ public final class Values : NSObject { @objc public static let accentLineThickness = CGFloat(4) - @objc public static let verySmallProfilePictureSize = CGFloat(26) - @objc public static let smallProfilePictureSize = CGFloat(33) - @objc public static let mediumProfilePictureSize = CGFloat(45) - @objc public static let largeProfilePictureSize = CGFloat(75) - @objc public static let searchBarHeight = CGFloat(36) @objc public static var separatorThickness: CGFloat { return 1 / UIScreen.main.scale } diff --git a/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift b/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift index 1f87c047f..79f973472 100644 --- a/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift +++ b/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift @@ -7,14 +7,107 @@ import SessionUIKit import SessionMessagingKit public final class ProfilePictureView: UIView { - private var hasTappableProfilePicture: Bool = false - public var size: CGFloat = 0 + public enum Size { + case navigation + case message + case list + case hero + + public var viewSize: CGFloat { + switch self { + case .navigation, .message: return 26 + case .list: return 46 + case .hero: return 110 + } + } + + var imageSize: CGFloat { + switch self { + case .navigation, .message: return 26 + case .list: return 46 + case .hero: return 80 + } + } + + var multiImageSize: CGFloat { + switch self { + case .navigation, .message: return 18 // Shouldn't be used + case .list: return 32 + case .hero: return 80 + } + } + + var iconSize: CGFloat { + switch self { + case .navigation, .message: return 8 + case .list: return 16 + case .hero: return 24 + } + } + + var iconVerticalInset: CGFloat { + switch self { + case .navigation, .message: return 1 + case .list: return 3 + case .hero: return 5 + } + } + } - // Constraints + public enum ProfileIcon { + case none + case crown + case rightPlus + } + + public var size: Size { + didSet { + widthConstraint.constant = (customWidth ?? size.viewSize) + heightConstraint.constant = size.viewSize + profileIconTopConstraint.constant = size.iconVerticalInset + profileIconBottomConstraint.constant = -size.iconVerticalInset + profileIconBackgroundWidthConstraint.constant = size.iconSize + profileIconBackgroundHeightConstraint.constant = size.iconSize + additionalProfileIconTopConstraint.constant = size.iconVerticalInset + additionalProfileIconBottomConstraint.constant = -size.iconVerticalInset + additionalProfileIconBackgroundWidthConstraint.constant = size.iconSize + additionalProfileIconBackgroundHeightConstraint.constant = size.iconSize + + profileIconBackgroundView.layer.cornerRadius = (size.iconSize / 2) + additionalProfileIconBackgroundView.layer.cornerRadius = (size.iconSize / 2) + } + } + public var customWidth: CGFloat? { + didSet { + self.widthConstraint.constant = (customWidth ?? self.size.viewSize) + } + } + private var hasTappableProfilePicture: Bool = false + + // MARK: - Constraints + + private var widthConstraint: NSLayoutConstraint! + private var heightConstraint: NSLayoutConstraint! + private var imageViewTopConstraint: NSLayoutConstraint! + private var imageViewLeadingConstraint: NSLayoutConstraint! + private var imageViewCenterXConstraint: NSLayoutConstraint! + private var imageViewCenterYConstraint: NSLayoutConstraint! private var imageViewWidthConstraint: NSLayoutConstraint! private var imageViewHeightConstraint: NSLayoutConstraint! private var additionalImageViewWidthConstraint: NSLayoutConstraint! private var additionalImageViewHeightConstraint: NSLayoutConstraint! + private var profileIconTopConstraint: NSLayoutConstraint! + private var profileIconBottomConstraint: NSLayoutConstraint! + private var profileIconBackgroundLeftAlignConstraint: NSLayoutConstraint! + private var profileIconBackgroundRightAlignConstraint: NSLayoutConstraint! + private var profileIconBackgroundWidthConstraint: NSLayoutConstraint! + private var profileIconBackgroundHeightConstraint: NSLayoutConstraint! + private var additionalProfileIconTopConstraint: NSLayoutConstraint! + private var additionalProfileIconBottomConstraint: NSLayoutConstraint! + private var additionalProfileIconBackgroundLeftAlignConstraint: NSLayoutConstraint! + private var additionalProfileIconBackgroundRightAlignConstraint: NSLayoutConstraint! + private var additionalProfileIconBackgroundWidthConstraint: NSLayoutConstraint! + private var additionalProfileIconBackgroundHeightConstraint: NSLayoutConstraint! // MARK: - Components @@ -51,7 +144,6 @@ public final class ProfilePictureView: UIView { result.clipsToBounds = true result.themeBackgroundColor = .primary result.themeBorderColor = .backgroundPrimary - result.layer.cornerRadius = (Values.smallProfilePictureSize / 2) result.isHidden = true return result @@ -88,33 +180,72 @@ public final class ProfilePictureView: UIView { return result }() + private lazy var profileIconBackgroundView: UIView = { + let result: UIView = UIView() + result.isHidden = true + + return result + }() + + private lazy var profileIconImageView: UIImageView = { + let result: UIImageView = UIImageView() + result.contentMode = .scaleAspectFit + + return result + }() + + private lazy var additionalProfileIconBackgroundView: UIView = { + let result: UIView = UIView() + result.isHidden = true + + return result + }() + + private lazy var additionalProfileIconImageView: UIImageView = { + let result: UIImageView = UIImageView() + result.contentMode = .scaleAspectFit + + return result + }() + // MARK: - Lifecycle - public override init(frame: CGRect) { - super.init(frame: frame) + public init(size: Size) { + self.size = size + + super.init(frame: CGRect(x: 0, y: 0, width: size.viewSize, height: size.viewSize)) + setUpViewHierarchy() } public required init?(coder: NSCoder) { - super.init(coder: coder) - setUpViewHierarchy() + preconditionFailure("Use init(size:) instead.") } private func setUpViewHierarchy() { - let imageViewSize = CGFloat(Values.mediumProfilePictureSize) - let additionalImageViewSize = CGFloat(Values.smallProfilePictureSize) - addSubview(imageContainerView) + addSubview(profileIconBackgroundView) addSubview(additionalImageContainerView) + addSubview(additionalProfileIconBackgroundView) - imageContainerView.pin(.leading, to: .leading, of: self) - imageContainerView.pin(.top, to: .top, of: self) - imageViewWidthConstraint = imageContainerView.set(.width, to: imageViewSize) - imageViewHeightConstraint = imageContainerView.set(.height, to: imageViewSize) + profileIconBackgroundView.addSubview(profileIconImageView) + additionalProfileIconBackgroundView.addSubview(additionalProfileIconImageView) + + widthConstraint = self.set(.width, to: self.size.viewSize) + heightConstraint = self.set(.height, to: self.size.viewSize) + + imageViewTopConstraint = imageContainerView.pin(.top, to: .top, of: self) + imageViewLeadingConstraint = imageContainerView.pin(.leading, to: .leading, of: self) + imageViewCenterXConstraint = imageContainerView.center(.horizontal, in: self) + imageViewCenterXConstraint.isActive = false + imageViewCenterYConstraint = imageContainerView.center(.vertical, in: self) + imageViewCenterYConstraint.isActive = false + imageViewWidthConstraint = imageContainerView.set(.width, to: size.imageSize) + imageViewHeightConstraint = imageContainerView.set(.height, to: size.imageSize) additionalImageContainerView.pin(.trailing, to: .trailing, of: self) additionalImageContainerView.pin(.bottom, to: .bottom, of: self) - additionalImageViewWidthConstraint = additionalImageContainerView.set(.width, to: additionalImageViewSize) - additionalImageViewHeightConstraint = additionalImageContainerView.set(.height, to: additionalImageViewSize) + additionalImageViewWidthConstraint = additionalImageContainerView.set(.width, to: size.multiImageSize) + additionalImageViewHeightConstraint = additionalImageContainerView.set(.height, to: size.multiImageSize) imageContainerView.addSubview(imageView) imageContainerView.addSubview(animatedImageView) @@ -131,32 +262,137 @@ public final class ProfilePictureView: UIView { additionalProfilePlaceholderImageView.pin(.left, to: .left, of: additionalImageContainerView) additionalProfilePlaceholderImageView.pin(.right, to: .right, of: additionalImageContainerView) additionalProfilePlaceholderImageView.pin(.bottom, to: .bottom, of: additionalImageContainerView, withInset: 5) + + profileIconTopConstraint = profileIconImageView.pin( + .top, + to: .top, + of: profileIconBackgroundView, + withInset: size.iconVerticalInset + ) + profileIconImageView.pin(.left, to: .left, of: profileIconBackgroundView) + profileIconImageView.pin(.right, to: .right, of: profileIconBackgroundView) + profileIconBottomConstraint = profileIconImageView.pin( + .bottom, + to: .bottom, + of: profileIconBackgroundView, + withInset: -size.iconVerticalInset + ) + profileIconBackgroundLeftAlignConstraint = profileIconBackgroundView.pin(.leading, to: .leading, of: imageContainerView) + profileIconBackgroundRightAlignConstraint = profileIconBackgroundView.pin(.trailing, to: .trailing, of: imageContainerView) + profileIconBackgroundView.pin(.bottom, to: .bottom, of: imageContainerView) + profileIconBackgroundWidthConstraint = profileIconBackgroundView.set(.width, to: size.iconSize) + profileIconBackgroundHeightConstraint = profileIconBackgroundView.set(.height, to: size.iconSize) + profileIconBackgroundLeftAlignConstraint.isActive = false + profileIconBackgroundRightAlignConstraint.isActive = false + + additionalProfileIconTopConstraint = additionalProfileIconImageView.pin( + .top, + to: .top, + of: additionalProfileIconBackgroundView, + withInset: size.iconVerticalInset + ) + additionalProfileIconImageView.pin(.left, to: .left, of: additionalProfileIconBackgroundView) + additionalProfileIconImageView.pin(.right, to: .right, of: additionalProfileIconBackgroundView) + additionalProfileIconBottomConstraint = additionalProfileIconImageView.pin( + .bottom, + to: .bottom, + of: additionalProfileIconBackgroundView, + withInset: -size.iconVerticalInset + ) + additionalProfileIconBackgroundLeftAlignConstraint = additionalProfileIconBackgroundView.pin(.leading, to: .leading, of: additionalImageContainerView) + additionalProfileIconBackgroundRightAlignConstraint = additionalProfileIconBackgroundView.pin(.trailing, to: .trailing, of: additionalImageContainerView) + additionalProfileIconBackgroundView.pin(.bottom, to: .bottom, of: additionalImageContainerView) + additionalProfileIconBackgroundWidthConstraint = additionalProfileIconBackgroundView.set(.width, to: size.iconSize) + additionalProfileIconBackgroundHeightConstraint = additionalProfileIconBackgroundView.set(.height, to: size.iconSize) + additionalProfileIconBackgroundLeftAlignConstraint.isActive = false + additionalProfileIconBackgroundRightAlignConstraint.isActive = false + } + + // MARK: - Content + + private func updateIconView( + icon: ProfileIcon, + imageView: UIImageView, + backgroundView: UIView, + leftAlignConstraint: NSLayoutConstraint, + rightAlignConstraint: NSLayoutConstraint + ) { + backgroundView.isHidden = (icon == .none) + leftAlignConstraint.isActive = ( + icon == .none || + icon == .crown + ) + rightAlignConstraint.isActive = ( + icon == .rightPlus + ) + + switch icon { + case .none: imageView.image = nil + + case .crown: + imageView.image = UIImage(systemName: "crown.fill") + backgroundView.themeBackgroundColor = .profileIcon_background + + ThemeManager.onThemeChange(observer: imageView) { [weak imageView] _, primaryColor in + let targetColor: ThemeValue = (primaryColor == .green ? + .profileIcon_greenPrimaryColor : + .profileIcon + ) + + guard imageView?.themeTintColor != targetColor else { return } + + imageView?.themeTintColor = targetColor + } + + case .rightPlus: + imageView.image = UIImage(systemName: "plus") + imageView.themeTintColor = .black + backgroundView.themeBackgroundColor = .primary + } } public func update( publicKey: String = "", profile: Profile? = nil, + icon: ProfileIcon = .none, additionalProfile: Profile? = nil, + additionalIcon: ProfileIcon = .none, threadVariant: SessionThread.Variant, openGroupProfilePictureData: Data? = nil, useFallbackPicture: Bool = false, showMultiAvatarForClosedGroup: Bool = false ) { AssertIsOnMainThread() + + // Sort out the profile icon first + updateIconView( + icon: icon, + imageView: profileIconImageView, + backgroundView: profileIconBackgroundView, + leftAlignConstraint: profileIconBackgroundLeftAlignConstraint, + rightAlignConstraint: profileIconBackgroundRightAlignConstraint + ) + guard !useFallbackPicture else { switch self.size { - case Values.smallProfilePictureSize.. Date: Wed, 24 May 2023 17:42:43 +1000 Subject: [PATCH 073/135] Updated the profile picture modal Moved the ProfilePictureView into SessionUIKit Fixed a couple of minor ProfilePictureView bugs --- Session.xcodeproj/project.pbxproj | 40 +- .../Calls/Call Management/SessionCall.swift | 3 +- .../CropScaleImageViewController.swift | 15 +- .../Translations/de.lproj/Localizable.strings | 2 +- .../Translations/en.lproj/Localizable.strings | 2 +- .../Translations/es.lproj/Localizable.strings | 2 +- .../Translations/fa.lproj/Localizable.strings | 2 +- .../Translations/fi.lproj/Localizable.strings | 2 +- .../Translations/fr.lproj/Localizable.strings | 2 +- .../Translations/hi.lproj/Localizable.strings | 2 +- .../Translations/hr.lproj/Localizable.strings | 2 +- .../id-ID.lproj/Localizable.strings | 2 +- .../Translations/it.lproj/Localizable.strings | 2 +- .../Translations/ja.lproj/Localizable.strings | 2 +- .../Translations/nl.lproj/Localizable.strings | 2 +- .../Translations/pl.lproj/Localizable.strings | 2 +- .../pt_BR.lproj/Localizable.strings | 2 +- .../Translations/ru.lproj/Localizable.strings | 2 +- .../Translations/si.lproj/Localizable.strings | 2 +- .../Translations/sk.lproj/Localizable.strings | 2 +- .../Translations/sv.lproj/Localizable.strings | 2 +- .../Translations/th.lproj/Localizable.strings | 2 +- .../vi-VN.lproj/Localizable.strings | 2 +- .../zh-Hant.lproj/Localizable.strings | 2 +- .../zh_CN.lproj/Localizable.strings | 2 +- Session/Settings/SettingsViewModel.swift | 48 +- .../ProfilePictureView+Convenience.swift | 211 +++++++++ .../Components/ConfirmationModal.swift | 50 +- .../Components}/PlaceholderIcon.swift | 61 ++- .../Components}/ProfilePictureView.swift | 441 +++++++++--------- .../Profile Pictures/Identicon+ObjC.swift | 57 --- 31 files changed, 585 insertions(+), 385 deletions(-) create mode 100644 SessionMessagingKit/Utilities/ProfilePictureView+Convenience.swift rename {SignalUtilitiesKit/Profile Pictures => SessionUIKit/Components}/PlaceholderIcon.swift (54%) rename {SignalUtilitiesKit/Profile Pictures => SessionUIKit/Components}/ProfilePictureView.swift (55%) delete mode 100644 SignalUtilitiesKit/Profile Pictures/Identicon+ObjC.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 1f46f41af..50b28fed6 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -381,9 +381,6 @@ C38EF24C255B6D67007E1867 /* NSAttributedString+OWS.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF23F255B6D67007E1867 /* NSAttributedString+OWS.h */; settings = {ATTRIBUTES = (Public, ); }; }; 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 */; }; - 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 */; }; C38EF30C255B6DBF007E1867 /* ScreenLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2E2255B6DB9007E1867 /* ScreenLock.swift */; }; C38EF31C255B6DBF007E1867 /* Searcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2F2255B6DBC007E1867 /* Searcher.swift */; }; @@ -547,6 +544,9 @@ FD09C5E828264937000CE219 /* MediaDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09C5E728264937000CE219 /* MediaDetailViewController.swift */; }; FD09C5EA282A1BB2000CE219 /* ThreadTypingIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09C5E9282A1BB2000CE219 /* ThreadTypingIndicator.swift */; }; FD09C5EC282B8F18000CE219 /* AttachmentError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09C5EB282B8F17000CE219 /* AttachmentError.swift */; }; + FD16AB5B2A1DD7CA0083D849 /* PlaceholderIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2A3255B6D93007E1867 /* PlaceholderIcon.swift */; }; + FD16AB5F2A1DD98F0083D849 /* ProfilePictureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2A4255B6D93007E1867 /* ProfilePictureView.swift */; }; + FD16AB612A1DD9B60083D849 /* ProfilePictureView+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD16AB602A1DD9B60083D849 /* ProfilePictureView+Convenience.swift */; }; FD17D79927F40AB800122BE0 /* _003_YDBToGRDBMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79827F40AB800122BE0 /* _003_YDBToGRDBMigration.swift */; }; FD17D79C27F40B2E00122BE0 /* SMKLegacy.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79B27F40B2E00122BE0 /* SMKLegacy.swift */; }; FD17D7A027F40CC800122BE0 /* _001_InitialSetupMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79F27F40CC800122BE0 /* _001_InitialSetupMigration.swift */; }; @@ -1489,9 +1489,8 @@ 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; }; 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; }; - C38EF2A4255B6D93007E1867 /* ProfilePictureView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProfilePictureView.swift; path = "SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift"; sourceTree = SOURCE_ROOT; }; + C38EF2A3255B6D93007E1867 /* PlaceholderIcon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PlaceholderIcon.swift; path = SessionUIKit/Components/PlaceholderIcon.swift; sourceTree = SOURCE_ROOT; }; + C38EF2A4255B6D93007E1867 /* ProfilePictureView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProfilePictureView.swift; path = SessionUIKit/Components/ProfilePictureView.swift; sourceTree = SOURCE_ROOT; }; C38EF2B1255B6D9C007E1867 /* UIViewController+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIViewController+Utilities.swift"; path = "SignalUtilitiesKit/Utilities/UIViewController+Utilities.swift"; sourceTree = SOURCE_ROOT; }; C38EF2E2255B6DB9007E1867 /* ScreenLock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ScreenLock.swift; path = "SignalUtilitiesKit/Screen Lock/ScreenLock.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; }; @@ -1673,6 +1672,7 @@ FD09C5E728264937000CE219 /* MediaDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaDetailViewController.swift; sourceTree = ""; }; FD09C5E9282A1BB2000CE219 /* ThreadTypingIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadTypingIndicator.swift; sourceTree = ""; }; FD09C5EB282B8F17000CE219 /* AttachmentError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentError.swift; sourceTree = ""; }; + FD16AB602A1DD9B60083D849 /* ProfilePictureView+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfilePictureView+Convenience.swift"; sourceTree = ""; }; FD17D79527F3E04600122BE0 /* _001_InitialSetupMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _001_InitialSetupMigration.swift; sourceTree = ""; }; FD17D79827F40AB800122BE0 /* _003_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _003_YDBToGRDBMigration.swift; sourceTree = ""; }; FD17D79B27F40B2E00122BE0 /* SMKLegacy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMKLegacy.swift; sourceTree = ""; }; @@ -2857,6 +2857,8 @@ C38EF3EE255B6DF6007E1867 /* GradientView.swift */, B86BD08323399ACF000F5AE3 /* Modal.swift */, FD52090628B49738006098F6 /* ConfirmationModal.swift */, + C38EF2A3255B6D93007E1867 /* PlaceholderIcon.swift */, + C38EF2A4255B6D93007E1867 /* ProfilePictureView.swift */, FD71165A28E6DDBC00B47552 /* StyledNavigationController.swift */, ); path = Components; @@ -2867,7 +2869,7 @@ children = ( C33FD9B7255A54A300E217F9 /* Meta */, C36096ED25AD20FD008B62B2 /* Media Viewing & Editing */, - C36096EF25AD2268008B62B2 /* Profile Pictures */, + FD16AB5D2A1DD8E70083D849 /* Profile Pictures */, C36096EE25AD21BC008B62B2 /* Screen Lock */, C3851CD225624B060061EEB0 /* Shared Views */, C360970125AD22D3008B62B2 /* Shared View Controllers */, @@ -3046,16 +3048,6 @@ path = "Screen Lock"; sourceTree = ""; }; - C36096EF25AD2268008B62B2 /* Profile Pictures */ = { - isa = PBXGroup; - children = ( - C38EF2A3255B6D93007E1867 /* PlaceholderIcon.swift */, - C38EF2A4255B6D93007E1867 /* ProfilePictureView.swift */, - C38EF2A2255B6D93007E1867 /* Identicon+ObjC.swift */, - ); - path = "Profile Pictures"; - sourceTree = ""; - }; C360970125AD22D3008B62B2 /* Shared View Controllers */ = { isa = PBXGroup; children = ( @@ -3174,6 +3166,7 @@ FDC4386827B4E6B700C60D73 /* String+Utlities.swift */, FDC4387327B5BB9B00C60D73 /* Promise+Utilities.swift */, FD772899284AF1BD0018502F /* Sodium+Utilities.swift */, + FD16AB602A1DD9B60083D849 /* ProfilePictureView+Convenience.swift */, C3A3A170256E1D25004D228D /* SSKReachabilityManager.swift */, C3ECBF7A257056B700EA7FCE /* Threading.swift */, ); @@ -3558,6 +3551,13 @@ path = Models; sourceTree = ""; }; + FD16AB5D2A1DD8E70083D849 /* Profile Pictures */ = { + isa = PBXGroup; + children = ( + ); + path = "Profile Pictures"; + sourceTree = ""; + }; FD17D79427F3E03300122BE0 /* Migrations */ = { isa = PBXGroup; children = ( @@ -5171,6 +5171,7 @@ C331FFB92558FA8D00070591 /* UIView+Constraints.swift in Sources */, FD37E9F628A5F106003AE748 /* Configuration.swift in Sources */, C331FFE02558FB0000070591 /* SearchBar.swift in Sources */, + FD16AB5B2A1DD7CA0083D849 /* PlaceholderIcon.swift in Sources */, FD71162C28E1451400B47552 /* Position.swift in Sources */, FD52090328B4680F006098F6 /* RadioButton.swift in Sources */, C331FFE82558FB0000070591 /* TextView.swift in Sources */, @@ -5181,6 +5182,7 @@ FD37E9C328A1C6F3003AE748 /* ThemeManager.swift in Sources */, C331FF9A2558FA6B00070591 /* Values.swift in Sources */, FD37E9C628A1D4EC003AE748 /* Theme+ClassicDark.swift in Sources */, + FD16AB5F2A1DD98F0083D849 /* ProfilePictureView.swift in Sources */, C331FFE42558FB0000070591 /* SessionButton.swift in Sources */, C331FFE92558FB0000070591 /* Separator.swift in Sources */, FD71163228E2C42A00B47552 /* IconSize.swift in Sources */, @@ -5199,7 +5201,6 @@ C38EF247255B6D67007E1867 /* NSAttributedString+OWS.m in Sources */, C33FDD49255A582000E217F9 /* ParamParser.swift in Sources */, C38EF3C5255B6DE7007E1867 /* OWSViewController+ImageEditor.swift in Sources */, - C38EF2A5255B6D93007E1867 /* Identicon+ObjC.swift in Sources */, C38EF385255B6DD2007E1867 /* AttachmentTextToolbar.swift in Sources */, C33FDD23255A582000E217F9 /* FeatureFlags.swift in Sources */, FD71161E28D9772700B47552 /* UIViewController+OWS.swift in Sources */, @@ -5220,12 +5221,10 @@ C3F0A530255C80BC007BE2A3 /* NoopNotificationsManager.swift in Sources */, C33FDD8D255A582000E217F9 /* OWSSignalAddress.swift 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 */, - C38EF2A6255B6D93007E1867 /* PlaceholderIcon.swift in Sources */, C33FDD92255A582000E217F9 /* SignalIOS.pb.swift in Sources */, C33FDC45255A581F00E217F9 /* AppVersion.m in Sources */, C38EF3C7255B6DE7007E1867 /* ImageEditorCanvasView.swift in Sources */, @@ -5558,6 +5557,7 @@ C32C598A256D0664003C73A2 /* SNProtoEnvelope+Conversion.swift in Sources */, FDC438CB27BB7DB100C60D73 /* UpdateMessageRequest.swift in Sources */, C352A2FF25574B6300338F3E /* MessageSendJob.swift in Sources */, + FD16AB612A1DD9B60083D849 /* ProfilePictureView+Convenience.swift in Sources */, FDC438C327BB512200C60D73 /* SodiumProtocols.swift in Sources */, B8856D11256F112A001CE70E /* OWSAudioSession.swift in Sources */, C3DB66C3260ACCE6001EFC55 /* OpenGroupPoller.swift in Sources */, diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index de3f64f48..f174346c6 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -5,6 +5,7 @@ import CallKit import GRDB import WebRTC import PromiseKit +import SessionUIKit import SignalUtilitiesKit import SessionMessagingKit @@ -154,7 +155,7 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate { self.contactName = Profile.displayName(db, id: sessionId, threadVariant: .contact) self.profilePicture = ProfileManager.profileAvatar(db, id: sessionId) .map { UIImage(data: $0) } - .defaulting(to: Identicon.generatePlaceholderIcon(seed: sessionId, text: self.contactName, size: 300)) + .defaulting(to: PlaceholderIcon.generate(seed: sessionId, text: self.contactName, size: 300)) WebRTCSession.current = self.webRTCSession self.webRTCSession.delegate = self diff --git a/Session/Media Viewing & Editing/CropScaleImageViewController.swift b/Session/Media Viewing & Editing/CropScaleImageViewController.swift index f6ad5d8ff..4cf5f7da1 100644 --- a/Session/Media Viewing & Editing/CropScaleImageViewController.swift +++ b/Session/Media Viewing & Editing/CropScaleImageViewController.swift @@ -32,7 +32,7 @@ import SignalUtilitiesKit let srcImage: UIImage - let successCompletion: ((UIImage) -> Void) + let successCompletion: ((Data) -> Void) var imageView: UIView! @@ -79,7 +79,7 @@ import SignalUtilitiesKit notImplemented() } - @objc required init(srcImage: UIImage, successCompletion : @escaping (UIImage) -> Void) { + @objc required init(srcImage: UIImage, successCompletion : @escaping (Data) -> Void) { // normalized() can be slightly expensive but in practice this is fine. self.srcImage = srcImage.normalized() self.successCompletion = successCompletion @@ -487,10 +487,9 @@ import SignalUtilitiesKit @objc func donePressed(sender: UIButton) { let successCompletion = self.successCompletion dismiss(animated: true, completion: { - guard let dstImage = self.generateDstImage() else { - return - } - successCompletion(dstImage) + guard let dstImageData: Data = self.generateDstImageData() else { return } + + successCompletion(dstImageData) }) } @@ -517,4 +516,8 @@ import SignalUtilitiesKit UIGraphicsEndImageContext() return scaledImage } + + func generateDstImageData() -> Data? { + return generateDstImage().map { $0.pngData() } + } } diff --git a/Session/Meta/Translations/de.lproj/Localizable.strings b/Session/Meta/Translations/de.lproj/Localizable.strings index 1d590e0d3..35366179c 100644 --- a/Session/Meta/Translations/de.lproj/Localizable.strings +++ b/Session/Meta/Translations/de.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index cf97bdb51..b7a7bbc93 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/es.lproj/Localizable.strings b/Session/Meta/Translations/es.lproj/Localizable.strings index ee4801866..e5b1cf5d4 100644 --- a/Session/Meta/Translations/es.lproj/Localizable.strings +++ b/Session/Meta/Translations/es.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/fa.lproj/Localizable.strings b/Session/Meta/Translations/fa.lproj/Localizable.strings index 4b1b87715..3c002daa7 100644 --- a/Session/Meta/Translations/fa.lproj/Localizable.strings +++ b/Session/Meta/Translations/fa.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/fi.lproj/Localizable.strings b/Session/Meta/Translations/fi.lproj/Localizable.strings index a75e1070c..ef8fac68e 100644 --- a/Session/Meta/Translations/fi.lproj/Localizable.strings +++ b/Session/Meta/Translations/fi.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/fr.lproj/Localizable.strings b/Session/Meta/Translations/fr.lproj/Localizable.strings index f23971351..086da6b13 100644 --- a/Session/Meta/Translations/fr.lproj/Localizable.strings +++ b/Session/Meta/Translations/fr.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Êtes-vous sûr de vouloir supprimer votre conversation avec %@ ?"; "delete_conversation_confirmation_alert_title" = "Supprimer conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/hi.lproj/Localizable.strings b/Session/Meta/Translations/hi.lproj/Localizable.strings index ca058e22a..515ec2808 100644 --- a/Session/Meta/Translations/hi.lproj/Localizable.strings +++ b/Session/Meta/Translations/hi.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/hr.lproj/Localizable.strings b/Session/Meta/Translations/hr.lproj/Localizable.strings index e6cd0d2a3..1f41192b4 100644 --- a/Session/Meta/Translations/hr.lproj/Localizable.strings +++ b/Session/Meta/Translations/hr.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/id-ID.lproj/Localizable.strings b/Session/Meta/Translations/id-ID.lproj/Localizable.strings index c620b6ae6..9342b0e4c 100644 --- a/Session/Meta/Translations/id-ID.lproj/Localizable.strings +++ b/Session/Meta/Translations/id-ID.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/it.lproj/Localizable.strings b/Session/Meta/Translations/it.lproj/Localizable.strings index 2c1b9996f..48353cfb5 100644 --- a/Session/Meta/Translations/it.lproj/Localizable.strings +++ b/Session/Meta/Translations/it.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/ja.lproj/Localizable.strings b/Session/Meta/Translations/ja.lproj/Localizable.strings index cc28d1898..5ee36faf0 100644 --- a/Session/Meta/Translations/ja.lproj/Localizable.strings +++ b/Session/Meta/Translations/ja.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/nl.lproj/Localizable.strings b/Session/Meta/Translations/nl.lproj/Localizable.strings index 477156edf..8a864919c 100644 --- a/Session/Meta/Translations/nl.lproj/Localizable.strings +++ b/Session/Meta/Translations/nl.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/pl.lproj/Localizable.strings b/Session/Meta/Translations/pl.lproj/Localizable.strings index 69e08221d..b1e3863aa 100644 --- a/Session/Meta/Translations/pl.lproj/Localizable.strings +++ b/Session/Meta/Translations/pl.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings index 02587c0a2..345c6ed5b 100644 --- a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings +++ b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/ru.lproj/Localizable.strings b/Session/Meta/Translations/ru.lproj/Localizable.strings index d3f3b00b7..175222c59 100644 --- a/Session/Meta/Translations/ru.lproj/Localizable.strings +++ b/Session/Meta/Translations/ru.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/si.lproj/Localizable.strings b/Session/Meta/Translations/si.lproj/Localizable.strings index c33b738d8..aeaaafb52 100644 --- a/Session/Meta/Translations/si.lproj/Localizable.strings +++ b/Session/Meta/Translations/si.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/sk.lproj/Localizable.strings b/Session/Meta/Translations/sk.lproj/Localizable.strings index 4f924c6fe..5886328d6 100644 --- a/Session/Meta/Translations/sk.lproj/Localizable.strings +++ b/Session/Meta/Translations/sk.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/sv.lproj/Localizable.strings b/Session/Meta/Translations/sv.lproj/Localizable.strings index 2fa76cecb..00f1ebd2f 100644 --- a/Session/Meta/Translations/sv.lproj/Localizable.strings +++ b/Session/Meta/Translations/sv.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/th.lproj/Localizable.strings b/Session/Meta/Translations/th.lproj/Localizable.strings index 6aae143ff..a0bbeb02e 100644 --- a/Session/Meta/Translations/th.lproj/Localizable.strings +++ b/Session/Meta/Translations/th.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings index a146cdbf7..3a2fdaa77 100644 --- a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings +++ b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings index 8725b7d5f..68c0bfba5 100644 --- a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings index d4f9a737b..f537301d9 100644 --- a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings @@ -624,5 +624,5 @@ "delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?"; "delete_conversation_confirmation_alert_title" = "Delete Conversation"; "update_profile_modal_title" = "Set Display Picture"; -"update_profile_modal_upload" = "Upload"; +"update_profile_modal_save" = "Save"; "update_profile_modal_remove" = "Remove"; diff --git a/Session/Settings/SettingsViewModel.swift b/Session/Settings/SettingsViewModel.swift index 509adaee6..3278a099e 100644 --- a/Session/Settings/SettingsViewModel.swift +++ b/Session/Settings/SettingsViewModel.swift @@ -52,7 +52,7 @@ class SettingsViewModel: SessionTableViewModel ())? = nil @@ -37,22 +36,7 @@ public class ConfirmationModal: Modal { return result }() - private lazy var imageViewContainer: UIView = { - let result: UIView = UIView() - result.isHidden = true - - return result - }() - - private lazy var imageView: UIImageView = { - let result: UIImageView = UIImageView() - result.clipsToBounds = true - result.contentMode = .scaleAspectFill - result.set(.width, to: ConfirmationModal.imageSize) - result.set(.height, to: ConfirmationModal.imageSize) - - return result - }() + private lazy var profileView: ProfilePictureView = ProfilePictureView(size: .hero) private lazy var confirmButton: UIButton = { let result: UIButton = Modal.createButton( @@ -73,7 +57,7 @@ public class ConfirmationModal: Modal { }() private lazy var contentStackView: UIStackView = { - let result = UIStackView(arrangedSubviews: [ titleLabel, explanationLabel, imageViewContainer ]) + let result = UIStackView(arrangedSubviews: [ titleLabel, explanationLabel, profileView ]) result.axis = .vertical result.spacing = Values.smallSpacing result.isLayoutMarginsRelativeArrangement = true @@ -141,11 +125,6 @@ public class ConfirmationModal: Modal { contentView.addSubview(mainStackView) contentView.addSubview(closeButton) - imageViewContainer.addSubview(imageView) - imageView.center(.horizontal, in: imageViewContainer) - imageView.pin(.top, to: .top, of: imageViewContainer, withInset: 15) - imageView.pin(.bottom, to: .bottom, of: imageViewContainer, withInset: -15) - mainStackView.pin(to: contentView) closeButton.pin(.top, to: .top, of: contentView, withInset: 8) closeButton.pin(.right, to: .right, of: contentView, withInset: -8) @@ -185,14 +164,16 @@ public class ConfirmationModal: Modal { explanationLabel.attributedText = attributedText explanationLabel.isHidden = false - case .image(let placeholder, let value, let style, let onClick): + case .image(let placeholder, let value, let icon, let style, let onClick): mainStackView.spacing = 0 - imageView.image = (value ?? placeholder) - imageView.layer.cornerRadius = (style == .circular ? - (ConfirmationModal.imageSize / 2) : - 0 + profileView.clipsToBounds = (style == .circular) + profileView.update( + ProfilePictureView.Info( + imageData: (value ?? placeholder), + icon: icon + ) ) - imageViewContainer.isHidden = false + profileView.isHidden = false internalOnBodyTap = onClick } @@ -406,8 +387,9 @@ public extension ConfirmationModal.Info { // case input(placeholder: String, value: String?) // case radio(explanation: NSAttributedString?, options: [(title: String, selected: Bool)]) case image( - placeholder: UIImage?, - value: UIImage?, + placeholderData: Data?, + valueData: Data?, + icon: ProfilePictureView.ProfileIcon = .none, style: ImageStyle, onClick: (() -> ()) ) @@ -432,10 +414,11 @@ public extension ConfirmationModal.Info { // lhsOptions.map { "\($0.0)-\($0.1)" } == rhsValue.map { "\($0.0)-\($0.1)" } // ) - case (.image(let lhsPlaceholder, let lhsValue, let lhsStyle, _), .image(let rhsPlaceholder, let rhsValue, let rhsStyle, _)): + case (.image(let lhsPlaceholder, let lhsValue, let lhsIcon, let lhsStyle, _), .image(let rhsPlaceholder, let rhsValue, let rhsIcon, let rhsStyle, _)): return ( lhsPlaceholder == rhsPlaceholder && lhsValue == rhsValue && + lhsIcon == rhsIcon && lhsStyle == rhsStyle ) @@ -449,9 +432,10 @@ public extension ConfirmationModal.Info { case .text(let text): text.hash(into: &hasher) case .attributedText(let text): text.hash(into: &hasher) - case .image(let placeholder, let value, let style, _): + case .image(let placeholder, let value, let icon, let style, _): placeholder.hash(into: &hasher) value.hash(into: &hasher) + icon.hash(into: &hasher) style.hash(into: &hasher) } } diff --git a/SignalUtilitiesKit/Profile Pictures/PlaceholderIcon.swift b/SessionUIKit/Components/PlaceholderIcon.swift similarity index 54% rename from SignalUtilitiesKit/Profile Pictures/PlaceholderIcon.swift rename to SessionUIKit/Components/PlaceholderIcon.swift index 33246ebce..7b6e00e92 100644 --- a/SignalUtilitiesKit/Profile Pictures/PlaceholderIcon.swift +++ b/SessionUIKit/Components/PlaceholderIcon.swift @@ -2,14 +2,23 @@ import UIKit import CryptoSwift -import SessionUIKit +import SessionUtilitiesKit public class PlaceholderIcon { + private static let placeholderCache: Atomic> = { + let result = NSCache() + result.countLimit = 50 + + return Atomic(result) + }() + private let seed: Int // Colour palette private var colors: [UIColor] = Theme.PrimaryColor.allCases.map { $0.color } + // MARK: - Initialization + init(seed: Int, colors: [UIColor]? = nil) { self.seed = seed if let colors = colors { self.colors = colors } @@ -21,7 +30,7 @@ public class PlaceholderIcon { if (hash.matches("^[0-9A-Fa-f]+$") && hash.count >= 12) { hash = seed.sha512() } guard let number = Int(hash.substring(to: 12), radix: 16) else { - owsFailDebug("Failed to generate number from seed string: \(seed).") + SNLog("Failed to generate number from seed string: \(seed).") self.init(seed: 0, colors: colors) return } @@ -29,7 +38,53 @@ public class PlaceholderIcon { self.init(seed: number, colors: colors) } - public func generateLayer(with diameter: CGFloat, text: String) -> CALayer { + // MARK: - Convenience + + public static func generate(seed: String, text: String, size: CGFloat) -> UIImage { + let icon = PlaceholderIcon(seed: seed) + + var content: String = (text.hasSuffix("\(String(seed.suffix(4))))") ? + (text.split(separator: "(") + .first + .map { String($0) }) + .defaulting(to: text) : + text + ) + + if content.count > 2 && SessionId.Prefix(from: content) != nil { + content.removeFirst(2) + } + + let initials: String = content + .split(separator: " ") + .compactMap { word in word.first.map { String($0) } } + .joined() + let cacheKey: String = "\(content)-\(Int(floor(size)))" + + if let cachedIcon: UIImage = placeholderCache.wrappedValue.object(forKey: cacheKey as NSString) { + return cachedIcon + } + + let layer = icon.generateLayer( + with: size, + text: (initials.count >= 2 ? + initials.substring(to: 2).uppercased() : + content.substring(to: 2).uppercased() + ) + ) + + let rect = CGRect(origin: CGPoint.zero, size: layer.frame.size) + let renderer = UIGraphicsImageRenderer(size: rect.size) + let result = renderer.image { layer.render(in: $0.cgContext) } + + placeholderCache.mutate { $0.setObject(result, forKey: cacheKey as NSString) } + + return result + } + + // MARK: - Internal + + private func generateLayer(with diameter: CGFloat, text: String) -> CALayer { let color: UIColor = self.colors[seed % self.colors.count] let base: CALayer = getTextLayer(with: diameter, color: color, text: text) base.masksToBounds = true diff --git a/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift b/SessionUIKit/Components/ProfilePictureView.swift similarity index 55% rename from SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift rename to SessionUIKit/Components/ProfilePictureView.swift index 79f973472..60703d805 100644 --- a/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift +++ b/SessionUIKit/Components/ProfilePictureView.swift @@ -3,10 +3,36 @@ import UIKit import GRDB import YYImage -import SessionUIKit -import SessionMessagingKit public final class ProfilePictureView: UIView { + public struct Info { + let imageData: Data? + let renderingMode: UIImage.RenderingMode + let themeTintColor: ThemeValue? + let inset: UIEdgeInsets + let icon: ProfileIcon + let backgroundColor: ThemeValue? + let forcedBackgroundColor: ForcedThemeValue? + + public init( + imageData: Data?, + renderingMode: UIImage.RenderingMode = .automatic, + themeTintColor: ThemeValue? = nil, + inset: UIEdgeInsets = .zero, + icon: ProfileIcon = .none, + backgroundColor: ThemeValue? = nil, + forcedBackgroundColor: ForcedThemeValue? = nil + ) { + self.imageData = imageData + self.renderingMode = renderingMode + self.themeTintColor = themeTintColor + self.inset = inset + self.icon = icon + self.backgroundColor = backgroundColor + self.forcedBackgroundColor = forcedBackgroundColor + } + } + public enum Size { case navigation case message @@ -21,7 +47,7 @@ public final class ProfilePictureView: UIView { } } - var imageSize: CGFloat { + public var imageSize: CGFloat { switch self { case .navigation, .message: return 26 case .list: return 46 @@ -29,7 +55,7 @@ public final class ProfilePictureView: UIView { } } - var multiImageSize: CGFloat { + public var multiImageSize: CGFloat { switch self { case .navigation, .message: return 18 // Shouldn't be used case .list: return 32 @@ -44,32 +70,31 @@ public final class ProfilePictureView: UIView { case .hero: return 24 } } - - var iconVerticalInset: CGFloat { - switch self { - case .navigation, .message: return 1 - case .list: return 3 - case .hero: return 5 - } - } } - public enum ProfileIcon { + public enum ProfileIcon: Equatable, Hashable { case none case crown case rightPlus + + func iconVerticalInset(for size: Size) -> CGFloat { + switch (self, size) { + case (.crown, .navigation), (.crown, .message): return 1 + case (.crown, .list): return 3 + case (.crown, .hero): return 5 + + case (.rightPlus, _): return 3 + default: return 0 + } + } } public var size: Size { didSet { widthConstraint.constant = (customWidth ?? size.viewSize) heightConstraint.constant = size.viewSize - profileIconTopConstraint.constant = size.iconVerticalInset - profileIconBottomConstraint.constant = -size.iconVerticalInset profileIconBackgroundWidthConstraint.constant = size.iconSize profileIconBackgroundHeightConstraint.constant = size.iconSize - additionalProfileIconTopConstraint.constant = size.iconVerticalInset - additionalProfileIconBottomConstraint.constant = -size.iconVerticalInset additionalProfileIconBackgroundWidthConstraint.constant = size.iconSize additionalProfileIconBackgroundHeightConstraint.constant = size.iconSize @@ -82,7 +107,18 @@ public final class ProfilePictureView: UIView { self.widthConstraint.constant = (customWidth ?? self.size.viewSize) } } - private var hasTappableProfilePicture: Bool = false + override public var clipsToBounds: Bool { + didSet { + imageContainerView.clipsToBounds = clipsToBounds + additionalImageContainerView.clipsToBounds = clipsToBounds + + imageContainerView.layer.cornerRadius = (clipsToBounds ? + (additionalImageContainerView.isHidden ? (size.imageSize / 2) : (size.multiImageSize / 2)) : + 0 + ) + imageContainerView.layer.cornerRadius = (clipsToBounds ? (size.multiImageSize / 2) : 0) + } + } // MARK: - Constraints @@ -108,6 +144,26 @@ public final class ProfilePictureView: UIView { private var additionalProfileIconBackgroundRightAlignConstraint: NSLayoutConstraint! private var additionalProfileIconBackgroundWidthConstraint: NSLayoutConstraint! private var additionalProfileIconBackgroundHeightConstraint: NSLayoutConstraint! + private lazy var imageEdgeConstraints: [NSLayoutConstraint] = [ // MUST be in 'top, left, bottom, right' order + imageView.pin(.top, to: .top, of: imageContainerView, withInset: 0), + imageView.pin(.left, to: .left, of: imageContainerView, withInset: 0), + imageView.pin(.bottom, to: .bottom, of: imageContainerView, withInset: 0), + imageView.pin(.right, to: .right, of: imageContainerView, withInset: 0), + animatedImageView.pin(.top, to: .top, of: imageContainerView, withInset: 0), + animatedImageView.pin(.left, to: .left, of: imageContainerView, withInset: 0), + animatedImageView.pin(.bottom, to: .bottom, of: imageContainerView, withInset: 0), + animatedImageView.pin(.right, to: .right, of: imageContainerView, withInset: 0) + ] + private lazy var additionalImageEdgeConstraints: [NSLayoutConstraint] = [ // MUST be in 'top, left, bottom, right' order + additionalImageView.pin(.top, to: .top, of: additionalImageContainerView, withInset: 0), + additionalImageView.pin(.left, to: .left, of: additionalImageContainerView, withInset: 0), + additionalImageView.pin(.bottom, to: .bottom, of: additionalImageContainerView, withInset: 0), + additionalImageView.pin(.right, to: .right, of: additionalImageContainerView, withInset: 0), + additionalAnimatedImageView.pin(.top, to: .top, of: additionalImageContainerView, withInset: 0), + additionalAnimatedImageView.pin(.left, to: .left, of: additionalImageContainerView, withInset: 0), + additionalAnimatedImageView.pin(.bottom, to: .bottom, of: additionalImageContainerView, withInset: 0), + additionalAnimatedImageView.pin(.right, to: .right, of: additionalImageContainerView, withInset: 0) + ] // MARK: - Components @@ -144,18 +200,7 @@ public final class ProfilePictureView: UIView { result.clipsToBounds = true result.themeBackgroundColor = .primary result.themeBorderColor = .backgroundPrimary - result.isHidden = true - - return result - }() - - private lazy var additionalProfilePlaceholderImageView: UIImageView = { - let result: UIImageView = UIImageView( - image: UIImage(systemName: "person.fill")?.withRenderingMode(.alwaysTemplate) - ) - result.translatesAutoresizingMaskIntoConstraints = false - result.contentMode = .scaleAspectFill - result.themeTintColor = .textPrimary + result.layer.borderWidth = 1 result.isHidden = true return result @@ -215,6 +260,7 @@ public final class ProfilePictureView: UIView { super.init(frame: CGRect(x: 0, y: 0, width: size.viewSize, height: size.viewSize)) + clipsToBounds = true setUpViewHierarchy() } @@ -251,23 +297,16 @@ public final class ProfilePictureView: UIView { imageContainerView.addSubview(animatedImageView) additionalImageContainerView.addSubview(additionalImageView) additionalImageContainerView.addSubview(additionalAnimatedImageView) - additionalImageContainerView.addSubview(additionalProfilePlaceholderImageView) - imageView.pin(to: imageContainerView) - animatedImageView.pin(to: imageContainerView) - additionalImageView.pin(to: additionalImageContainerView) - additionalAnimatedImageView.pin(to: additionalImageContainerView) - - additionalProfilePlaceholderImageView.pin(.top, to: .top, of: additionalImageContainerView, withInset: 3) - additionalProfilePlaceholderImageView.pin(.left, to: .left, of: additionalImageContainerView) - additionalProfilePlaceholderImageView.pin(.right, to: .right, of: additionalImageContainerView) - additionalProfilePlaceholderImageView.pin(.bottom, to: .bottom, of: additionalImageContainerView, withInset: 5) + // Activate the image edge constraints + imageEdgeConstraints.forEach { $0.isActive = true } + additionalImageEdgeConstraints.forEach { $0.isActive = true } profileIconTopConstraint = profileIconImageView.pin( .top, to: .top, of: profileIconBackgroundView, - withInset: size.iconVerticalInset + withInset: 0 ) profileIconImageView.pin(.left, to: .left, of: profileIconBackgroundView) profileIconImageView.pin(.right, to: .right, of: profileIconBackgroundView) @@ -275,7 +314,7 @@ public final class ProfilePictureView: UIView { .bottom, to: .bottom, of: profileIconBackgroundView, - withInset: -size.iconVerticalInset + withInset: 0 ) profileIconBackgroundLeftAlignConstraint = profileIconBackgroundView.pin(.leading, to: .leading, of: imageContainerView) profileIconBackgroundRightAlignConstraint = profileIconBackgroundView.pin(.trailing, to: .trailing, of: imageContainerView) @@ -289,7 +328,7 @@ public final class ProfilePictureView: UIView { .top, to: .top, of: additionalProfileIconBackgroundView, - withInset: size.iconVerticalInset + withInset: 0 ) additionalProfileIconImageView.pin(.left, to: .left, of: additionalProfileIconBackgroundView) additionalProfileIconImageView.pin(.right, to: .right, of: additionalProfileIconBackgroundView) @@ -297,7 +336,7 @@ public final class ProfilePictureView: UIView { .bottom, to: .bottom, of: additionalProfileIconBackgroundView, - withInset: -size.iconVerticalInset + withInset: 0 ) additionalProfileIconBackgroundLeftAlignConstraint = additionalProfileIconBackgroundView.pin(.leading, to: .leading, of: additionalImageContainerView) additionalProfileIconBackgroundRightAlignConstraint = additionalProfileIconBackgroundView.pin(.trailing, to: .trailing, of: additionalImageContainerView) @@ -314,8 +353,10 @@ public final class ProfilePictureView: UIView { icon: ProfileIcon, imageView: UIImageView, backgroundView: UIView, + topConstraint: NSLayoutConstraint, leftAlignConstraint: NSLayoutConstraint, - rightAlignConstraint: NSLayoutConstraint + rightAlignConstraint: NSLayoutConstraint, + bottomConstraint: NSLayoutConstraint ) { backgroundView.isHidden = (icon == .none) leftAlignConstraint.isActive = ( @@ -325,6 +366,8 @@ public final class ProfilePictureView: UIView { rightAlignConstraint.isActive = ( icon == .rightPlus ) + topConstraint.constant = icon.iconVerticalInset(for: size) + bottomConstraint.constant = -icon.iconVerticalInset(for: size) switch icon { case .none: imageView.image = nil @@ -345,201 +388,159 @@ public final class ProfilePictureView: UIView { } case .rightPlus: - imageView.image = UIImage(systemName: "plus") + imageView.image = UIImage( + systemName: "plus", + withConfiguration: UIImage.SymbolConfiguration(weight: .semibold) + ) imageView.themeTintColor = .black - backgroundView.themeBackgroundColor = .primary + backgroundView.themeBackgroundColorForced = .primary(.green) } } - - public func update( - publicKey: String = "", - profile: Profile? = nil, - icon: ProfileIcon = .none, - additionalProfile: Profile? = nil, - additionalIcon: ProfileIcon = .none, - threadVariant: SessionThread.Variant, - openGroupProfilePictureData: Data? = nil, - useFallbackPicture: Bool = false, - showMultiAvatarForClosedGroup: Bool = false - ) { - AssertIsOnMainThread() + + // MARK: - Content + + private func prepareForReuse() { + imageView.contentMode = .scaleAspectFill + imageView.isHidden = true + animatedImageView.contentMode = .scaleAspectFill + animatedImageView.isHidden = true + imageContainerView.clipsToBounds = clipsToBounds + imageContainerView.themeBackgroundColor = .backgroundSecondary + additionalImageContainerView.isHidden = true + animatedImageView.image = nil + additionalImageView.image = nil + additionalAnimatedImageView.image = nil + additionalImageView.isHidden = true + additionalAnimatedImageView.isHidden = true + additionalImageContainerView.clipsToBounds = clipsToBounds - // Sort out the profile icon first + imageViewTopConstraint.isActive = false + imageViewLeadingConstraint.isActive = false + imageViewCenterXConstraint.isActive = true + imageViewCenterYConstraint.isActive = true + profileIconBackgroundView.isHidden = true + profileIconBackgroundLeftAlignConstraint.isActive = false + profileIconBackgroundRightAlignConstraint.isActive = false + additionalProfileIconBackgroundView.isHidden = true + additionalProfileIconBackgroundLeftAlignConstraint.isActive = false + additionalProfileIconBackgroundRightAlignConstraint.isActive = false + imageEdgeConstraints.forEach { $0.constant = 0 } + additionalImageEdgeConstraints.forEach { $0.constant = 0 } + } + + public func update( + _ info: Info, + additionalInfo: Info? = nil + ) { + prepareForReuse() + + // Sort out the icon first updateIconView( - icon: icon, + icon: info.icon, imageView: profileIconImageView, backgroundView: profileIconBackgroundView, + topConstraint: profileIconTopConstraint, leftAlignConstraint: profileIconBackgroundLeftAlignConstraint, - rightAlignConstraint: profileIconBackgroundRightAlignConstraint + rightAlignConstraint: profileIconBackgroundRightAlignConstraint, + bottomConstraint: profileIconBottomConstraint ) - guard !useFallbackPicture else { - switch self.size { - case .navigation, .message: imageView.image = #imageLiteral(resourceName: "SessionWhite16") - case .list: imageView.image = #imageLiteral(resourceName: "SessionWhite24") - case .hero: imageView.image = #imageLiteral(resourceName: "SessionWhite40") + // Populate the main imageView + switch info.imageData?.guessedImageFormat { + case .gif, .webp: animatedImageView.image = info.imageData.map { YYImage(data: $0) } + default: + imageView.image = info.imageData + .map { + guard info.renderingMode != .automatic else { return UIImage(data: $0) } + + return UIImage(data: $0)?.withRenderingMode(info.renderingMode) + } + } + + imageView.themeTintColor = info.themeTintColor + imageView.isHidden = (imageView.image == nil) + animatedImageView.themeTintColor = info.themeTintColor + animatedImageView.isHidden = (animatedImageView.image == nil) + imageContainerView.themeBackgroundColor = info.backgroundColor + imageContainerView.themeBackgroundColorForced = info.forcedBackgroundColor + profileIconBackgroundView.layer.cornerRadius = (size.iconSize / 2) + imageEdgeConstraints.enumerated().forEach { index, constraint in + switch index % 4 { + case 0: constraint.constant = info.inset.top + case 1: constraint.constant = info.inset.left + case 2: constraint.constant = -info.inset.bottom + case 3: constraint.constant = -info.inset.right + default: break } - - imageView.contentMode = .center - imageView.isHidden = false - animatedImageView.isHidden = true - imageContainerView.themeBackgroundColorForced = .theme(.classicDark, color: .borderSeparator) - imageContainerView.layer.cornerRadius = (self.size.imageSize / 2) - imageViewWidthConstraint.constant = self.size.imageSize - imageViewHeightConstraint.constant = self.size.imageSize - profileIconBackgroundWidthConstraint.constant = self.size.iconSize - profileIconBackgroundHeightConstraint.constant = self.size.iconSize - profileIconBackgroundView.layer.cornerRadius = (self.size.iconSize / 2) - additionalProfileIconBackgroundWidthConstraint.constant = self.size.iconSize - additionalProfileIconBackgroundHeightConstraint.constant = self.size.iconSize - additionalProfileIconBackgroundView.layer.cornerRadius = (self.size.iconSize / 2) - additionalImageContainerView.isHidden = true - animatedImageView.image = nil - additionalImageView.image = nil - additionalAnimatedImageView.image = nil - additionalImageView.isHidden = true - additionalAnimatedImageView.isHidden = true - additionalProfilePlaceholderImageView.isHidden = true + } + + // Check if there is a second image (if not then set the size and finish) + guard let additionalInfo: Info = additionalInfo else { + imageViewWidthConstraint.constant = size.imageSize + imageViewHeightConstraint.constant = size.imageSize + imageContainerView.layer.cornerRadius = (imageContainerView.clipsToBounds ? (size.imageSize / 2) : 0) return } - guard !publicKey.isEmpty || openGroupProfilePictureData != nil else { return } - func getProfilePicture(of size: CGFloat, for publicKey: String, profile: Profile?) -> (image: UIImage?, animatedImage: YYImage?, isTappable: Bool) { - if let profile: Profile = profile, let profileData: Data = ProfileManager.profileAvatar(profile: profile) { - let format: ImageFormat = profileData.guessedImageFormat - - let image: UIImage? = (format == .gif || format == .webp ? - nil : - UIImage(data: profileData) - ) - let animatedImage: YYImage? = (format != .gif && format != .webp ? - nil : - YYImage(data: profileData) - ) - - if image != nil || animatedImage != nil { - return (image, animatedImage, true) - } - } - - return ( - Identicon.generatePlaceholderIcon( - seed: publicKey, - text: (profile?.displayName(for: threadVariant)) - .defaulting(to: publicKey), - size: size - ), - nil, - false - ) - } + // Sort out the additional icon first + updateIconView( + icon: additionalInfo.icon, + imageView: additionalProfileIconImageView, + backgroundView: additionalProfileIconBackgroundView, + topConstraint: additionalProfileIconTopConstraint, + leftAlignConstraint: additionalProfileIconBackgroundLeftAlignConstraint, + rightAlignConstraint: additionalProfileIconBackgroundRightAlignConstraint, + bottomConstraint: additionalProfileIconBottomConstraint + ) - // Calulate the sizes (and set the additional image content) - let targetSize: CGFloat - - switch (threadVariant, showMultiAvatarForClosedGroup) { - case (.closedGroup, true): - targetSize = self.size.multiImageSize - additionalImageContainerView.isHidden = false - imageViewTopConstraint.isActive = true - imageViewLeadingConstraint.isActive = true - imageViewCenterXConstraint.isActive = false - imageViewCenterYConstraint.isActive = false - - // Sort out the additinoal profile icon if needed - updateIconView( - icon: additionalIcon, - imageView: additionalProfileIconImageView, - backgroundView: additionalProfileIconBackgroundView, - leftAlignConstraint: additionalProfileIconBackgroundLeftAlignConstraint, - rightAlignConstraint: additionalProfileIconBackgroundRightAlignConstraint - ) - - if let additionalProfile: Profile = additionalProfile { - let (image, animatedImage, _): (UIImage?, YYImage?, Bool) = getProfilePicture( - of: self.size.multiImageSize, - for: additionalProfile.id, - profile: additionalProfile - ) - - // Set the images and show the appropriate imageView (non-animated should be - // visible if there is no image) - additionalImageView.image = image - additionalAnimatedImageView.image = animatedImage - additionalImageView.isHidden = (animatedImage != nil) - additionalAnimatedImageView.isHidden = (animatedImage == nil) - additionalProfilePlaceholderImageView.isHidden = true - } - else { - additionalImageView.isHidden = true - additionalAnimatedImageView.isHidden = true - additionalProfilePlaceholderImageView.isHidden = false - } - + // Set the additional image content and reposition the image views correctly + switch additionalInfo.imageData?.guessedImageFormat { + case .gif, .webp: additionalAnimatedImageView.image = additionalInfo.imageData.map { YYImage(data: $0) } default: - targetSize = self.size.imageSize - - additionalImageContainerView.isHidden = true - additionalProfileIconBackgroundView.isHidden = true - additionalImageView.image = nil - additionalImageView.isHidden = true - additionalAnimatedImageView.image = nil - additionalAnimatedImageView.isHidden = true - additionalProfilePlaceholderImageView.isHidden = true - imageViewTopConstraint.isActive = false - imageViewLeadingConstraint.isActive = false - imageViewCenterXConstraint.isActive = true - imageViewCenterYConstraint.isActive = true + additionalImageView.image = additionalInfo.imageData + .map { + guard additionalInfo.renderingMode != .automatic else { return UIImage(data: $0) } + + return UIImage(data: $0)?.withRenderingMode(additionalInfo.renderingMode) + } } - // Set the image - if let openGroupProfilePictureData: Data = openGroupProfilePictureData { - let format: ImageFormat = openGroupProfilePictureData.guessedImageFormat - - let image: UIImage? = (format == .gif || format == .webp ? - nil : - UIImage(data: openGroupProfilePictureData) - ) - let animatedImage: YYImage? = (format != .gif && format != .webp ? - nil : - YYImage(data: openGroupProfilePictureData) - ) - - imageView.image = image - animatedImageView.image = animatedImage - imageView.isHidden = (animatedImage != nil) - animatedImageView.isHidden = (animatedImage == nil) - hasTappableProfilePicture = true - } - else { - let (image, animatedImage, isTappable): (UIImage?, YYImage?, Bool) = getProfilePicture( - of: targetSize, - for: publicKey, - profile: profile - ) - imageView.image = image - animatedImageView.image = animatedImage - imageView.isHidden = (animatedImage != nil) - animatedImageView.isHidden = (animatedImage == nil) - hasTappableProfilePicture = isTappable + additionalImageView.themeTintColor = additionalInfo.themeTintColor + additionalImageView.isHidden = (additionalImageView.image == nil) + additionalAnimatedImageView.themeTintColor = additionalInfo.themeTintColor + additionalAnimatedImageView.isHidden = (additionalAnimatedImageView.image == nil) + additionalImageContainerView.isHidden = false + + switch (info.backgroundColor, info.forcedBackgroundColor) { + case (_, .some(let color)): additionalImageContainerView.themeBackgroundColorForced = color + case (.some(let color), _): additionalImageContainerView.themeBackgroundColor = color + default: additionalImageContainerView.themeBackgroundColor = .primary } - imageView.contentMode = .scaleAspectFill - animatedImageView.contentMode = .scaleAspectFill - imageContainerView.themeBackgroundColor = .backgroundSecondary - imageViewWidthConstraint.constant = targetSize - imageViewHeightConstraint.constant = targetSize - imageContainerView.layer.cornerRadius = (targetSize / 2) - additionalImageViewWidthConstraint.constant = targetSize - additionalImageViewHeightConstraint.constant = targetSize - additionalImageContainerView.layer.cornerRadius = (targetSize / 2) - profileIconBackgroundView.layer.cornerRadius = (size.iconSize / 2) + additionalImageEdgeConstraints.enumerated().forEach { index, constraint in + switch index % 4 { + case 0: constraint.constant = additionalInfo.inset.top + case 1: constraint.constant = additionalInfo.inset.left + case 2: constraint.constant = -additionalInfo.inset.bottom + case 3: constraint.constant = -additionalInfo.inset.right + default: break + } + } + + imageViewTopConstraint.isActive = true + imageViewLeadingConstraint.isActive = true + imageViewCenterXConstraint.isActive = false + imageViewCenterYConstraint.isActive = false + + imageViewWidthConstraint.constant = size.multiImageSize + imageViewHeightConstraint.constant = size.multiImageSize + imageContainerView.layer.cornerRadius = (imageContainerView.clipsToBounds ? (size.multiImageSize / 2) : 0) + additionalImageViewWidthConstraint.constant = size.multiImageSize + additionalImageViewHeightConstraint.constant = size.multiImageSize + additionalImageContainerView.layer.cornerRadius = (additionalImageContainerView.clipsToBounds ? + (size.multiImageSize / 2) : + 0 + ) additionalProfileIconBackgroundView.layer.cornerRadius = (size.iconSize / 2) } - - // MARK: - Convenience - - @objc public func getProfilePicture() -> UIImage? { - return (hasTappableProfilePicture ? imageView.image : nil) - } } diff --git a/SignalUtilitiesKit/Profile Pictures/Identicon+ObjC.swift b/SignalUtilitiesKit/Profile Pictures/Identicon+ObjC.swift deleted file mode 100644 index 256438529..000000000 --- a/SignalUtilitiesKit/Profile Pictures/Identicon+ObjC.swift +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import UIKit -import SessionUIKit -import SessionUtilitiesKit - -@objc(LKIdenticon) -public final class Identicon: NSObject { - private static let placeholderCache: Atomic> = { - let result = NSCache() - result.countLimit = 50 - - return Atomic(result) - }() - - @objc public static func generatePlaceholderIcon(seed: String, text: String, size: CGFloat) -> UIImage { - let icon = PlaceholderIcon(seed: seed) - - var content: String = (text.hasSuffix("\(String(seed.suffix(4))))") ? - (text.split(separator: "(") - .first - .map { String($0) }) - .defaulting(to: text) : - text - ) - - if content.count > 2 && SessionId.Prefix(from: content) != nil { - content.removeFirst(2) - } - - let initials: String = content - .split(separator: " ") - .compactMap { word in word.first.map { String($0) } } - .joined() - let cacheKey: String = "\(content)-\(Int(floor(size)))" - - if let cachedIcon: UIImage = placeholderCache.wrappedValue.object(forKey: cacheKey as NSString) { - return cachedIcon - } - - let layer = icon.generateLayer( - with: size, - text: (initials.count >= 2 ? - initials.substring(to: 2).uppercased() : - content.substring(to: 2).uppercased() - ) - ) - - let rect = CGRect(origin: CGPoint.zero, size: layer.frame.size) - let renderer = UIGraphicsImageRenderer(size: rect.size) - let result = renderer.image { layer.render(in: $0.cgContext) } - - placeholderCache.mutate { $0.setObject(result, forKey: cacheKey as NSString) } - - return result - } -} From 22303f245858c5aaddbf4a7a0dbc7626357f54a9 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 25 May 2023 08:42:27 +1000 Subject: [PATCH 074/135] Colour tweak --- SessionUIKit/Components/ProfilePictureView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SessionUIKit/Components/ProfilePictureView.swift b/SessionUIKit/Components/ProfilePictureView.swift index 60703d805..e8b9350a6 100644 --- a/SessionUIKit/Components/ProfilePictureView.swift +++ b/SessionUIKit/Components/ProfilePictureView.swift @@ -393,7 +393,7 @@ public final class ProfilePictureView: UIView { withConfiguration: UIImage.SymbolConfiguration(weight: .semibold) ) imageView.themeTintColor = .black - backgroundView.themeBackgroundColorForced = .primary(.green) + backgroundView.themeBackgroundColor = .primary } } From 41ba692a03ec1d324e2f3c3c91dc7eaa5247b1d1 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 25 May 2023 09:14:01 +1000 Subject: [PATCH 075/135] Fixed an issue where you couldn't search for hidden contacts --- .../SessionThreadViewModel.swift | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift index 8d83302f7..d7d134ad0 100644 --- a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift +++ b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift @@ -1256,12 +1256,18 @@ public extension SessionThreadViewModel { /// - Closed group member name /// - Open group name /// - "Note to self" text match + /// - Hidden contact nickname + /// - Hidden contact name + /// + /// **Note 2:** Since the "Hidden Contact" records don't have associated threads the `rowId` value in the + /// returned results will always be `-1` for those results static func contactsAndGroupsQuery(userPublicKey: String, pattern: FTS5Pattern, searchTerm: String) -> AdaptedFetchRequest> { let thread: TypedTableAlias = TypedTableAlias() let closedGroup: TypedTableAlias = TypedTableAlias() let groupMember: TypedTableAlias = TypedTableAlias() let openGroup: TypedTableAlias = TypedTableAlias() let profile: TypedTableAlias = TypedTableAlias() + let contact: TypedTableAlias = TypedTableAlias() let profileIdColumnLiteral: SQL = SQL(stringLiteral: Profile.Columns.id.name) let profileNicknameColumnLiteral: SQL = SQL(stringLiteral: Profile.Columns.nickname.name) let profileNameColumnLiteral: SQL = SQL(stringLiteral: Profile.Columns.name.name) @@ -1572,6 +1578,83 @@ public extension SessionThreadViewModel { WHERE \(SQL("\(thread[.id]) = \(userPublicKey)")) """ + // MARK: --Contacts without threads + let hiddenContactQuery: SQL = """ + SELECT + IFNULL(\(Column.rank), 100) AS \(Column.rank), + + -1 AS \(ViewModel.rowIdKey), + \(contact[.id]) AS \(ViewModel.threadIdKey), + \(SQL("\(SessionThread.Variant.contact)")) AS \(ViewModel.threadVariantKey), + 0 AS \(ViewModel.threadCreationDateTimestampKey), + \(groupMemberInfoLiteral).\(ViewModel.threadMemberNamesKey), + + false AS \(ViewModel.threadIsNoteToSelfKey), + -1 AS \(ViewModel.threadPinnedPriorityKey), + + \(ViewModel.contactProfileKey).*, + \(ViewModel.closedGroupProfileFrontKey).*, + \(ViewModel.closedGroupProfileBackKey).*, + \(ViewModel.closedGroupProfileBackFallbackKey).*, + \(closedGroup[.name]) AS \(ViewModel.closedGroupNameKey), + \(openGroup[.name]) AS \(ViewModel.openGroupNameKey), + \(openGroup[.imageData]) AS \(ViewModel.openGroupProfilePictureDataKey), + + \(SQL("\(userPublicKey)")) AS \(ViewModel.currentUserPublicKeyKey) + + FROM \(Contact.self) + """ + let hiddenContactQueryCommonJoins: SQL = """ + JOIN \(Profile.self) AS \(ViewModel.contactProfileKey) ON \(ViewModel.contactProfileKey).\(profileIdColumnLiteral) = \(contact[.id]) + LEFT JOIN \(SessionThread.self) ON \(thread[.id]) = \(contact[.id]) + LEFT JOIN \(Profile.self) AS \(ViewModel.closedGroupProfileFrontKey) ON false + LEFT JOIN \(Profile.self) AS \(ViewModel.closedGroupProfileBackKey) ON false + LEFT JOIN \(Profile.self) AS \(ViewModel.closedGroupProfileBackFallbackKey) ON false + LEFT JOIN \(ClosedGroup.self) ON false + LEFT JOIN \(OpenGroup.self) ON false + LEFT JOIN ( + SELECT + \(groupMember[.groupId]), + '' AS \(ViewModel.threadMemberNamesKey) + FROM \(GroupMember.self) + ) AS \(groupMemberInfoLiteral) ON false + + WHERE \(thread[.id]) IS NULL + GROUP BY \(contact[.id]) + """ + + // Hidden contact by nickname + sqlQuery += """ + + UNION ALL + + """ + sqlQuery += hiddenContactQuery + sqlQuery += """ + + JOIN \(profileFullTextSearch) ON ( + \(profileFullTextSearch).rowid = \(ViewModel.contactProfileKey).rowid AND + \(profileFullTextSearch).\(profileNicknameColumnLiteral) MATCH \(pattern) + ) + """ + sqlQuery += hiddenContactQueryCommonJoins + + // Hidden contact by name + sqlQuery += """ + + UNION ALL + + """ + sqlQuery += hiddenContactQuery + sqlQuery += """ + + JOIN \(profileFullTextSearch) ON ( + \(profileFullTextSearch).rowid = \(ViewModel.contactProfileKey).rowid AND + \(profileFullTextSearch).\(profileNameColumnLiteral) MATCH \(pattern) + ) + """ + sqlQuery += hiddenContactQueryCommonJoins + // Group everything by 'threadId' (the same thread can be found in multiple queries due // to seaerching both nickname and name), then order everything by 'rank' (relevance) // first, 'Note to Self' second (want it to appear at the bottom of threads unless it From 4419d3107720398f433119a9d133db76dcc7594b Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 25 May 2023 09:24:20 +1000 Subject: [PATCH 076/135] Removed the ability to swipe-to-reply to sending/failed outgoing messages --- .../Context Menu/ContextMenuVC+Action.swift | 18 +++++++++++------- .../Message Cells/VisibleMessageCell.swift | 6 +++--- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/Session/Conversations/Context Menu/ContextMenuVC+Action.swift b/Session/Conversations/Context Menu/ContextMenuVC+Action.swift index 82d50360b..1c9265e90 100644 --- a/Session/Conversations/Context Menu/ContextMenuVC+Action.swift +++ b/Session/Conversations/Context Menu/ContextMenuVC+Action.swift @@ -131,6 +131,16 @@ extension ContextMenuVC { ) { delegate?.contextMenuDismissed() } } } + + static func viewModelCanReply(_ cellViewModel: MessageViewModel) -> Bool { + return ( + cellViewModel.variant == .standardIncoming || ( + cellViewModel.variant == .standardOutgoing && + cellViewModel.state != .failed && + cellViewModel.state != .sending + ) + ) + } static func actions( for cellViewModel: MessageViewModel, @@ -161,12 +171,6 @@ extension ContextMenuVC { ) ) ) - let canReply: Bool = ( - cellViewModel.variant != .standardOutgoing || ( - cellViewModel.state != .failed && - cellViewModel.state != .sending - ) - ) let canCopy: Bool = ( cellViewModel.cellType == .textOnlyMessage || ( ( @@ -219,7 +223,7 @@ extension ContextMenuVC { let generatedActions: [Action] = [ (canRetry ? Action.retry(cellViewModel, delegate) : nil), - (canReply ? Action.reply(cellViewModel, delegate) : nil), + (viewModelCanReply(cellViewModel) ? Action.reply(cellViewModel, delegate) : nil), (canCopy ? Action.copy(cellViewModel, delegate) : nil), (canSave ? Action.save(cellViewModel, delegate) : nil), (canCopySessionId ? Action.copySessionID(cellViewModel, delegate) : nil), diff --git a/Session/Conversations/Message Cells/VisibleMessageCell.swift b/Session/Conversations/Message Cells/VisibleMessageCell.swift index 8def1187d..0a6c010eb 100644 --- a/Session/Conversations/Message Cells/VisibleMessageCell.swift +++ b/Session/Conversations/Message Cells/VisibleMessageCell.swift @@ -400,11 +400,11 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { ) // Swipe to reply - if cellViewModel.variant == .standardIncomingDeleted || cellViewModel.variant == .infoCall { - removeGestureRecognizer(panGestureRecognizer) + if ContextMenuVC.viewModelCanReply(cellViewModel) { + addGestureRecognizer(panGestureRecognizer) } else { - addGestureRecognizer(panGestureRecognizer) + removeGestureRecognizer(panGestureRecognizer) } // Under bubble content From 8f2e09d125b7f323ed8af742ea5cf3451c13b5c9 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 26 May 2023 08:48:11 +1000 Subject: [PATCH 077/135] Added a missing filter to a db query --- .../Config Handling/SessionUtil+Contacts.swift | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift index 04e0d6713..63301fa5b 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift @@ -158,6 +158,7 @@ internal extension SessionUtil { /// If the contact's `hidden` flag doesn't match the visibility of their conversation then create/delete the /// associated contact conversation accordingly let threadInfo: PriorityVisibilityInfo? = try? SessionThread + .filter(id: sessionId) .select(.id, .variant, .pinnedPriority, .shouldBeVisible) .asRequest(of: PriorityVisibilityInfo.self) .fetchOne(db) @@ -166,12 +167,12 @@ internal extension SessionUtil { switch (updatedShouldBeVisible, threadExists) { case (false, true): - SessionUtil.kickFromConversationUIIfNeeded(removedThreadIds: [contact.id]) + SessionUtil.kickFromConversationUIIfNeeded(removedThreadIds: [sessionId]) try SessionThread .deleteOrLeave( db, - threadId: contact.id, + threadId: sessionId, threadVariant: .contact, groupLeaveType: .forced, calledFromConfigHandling: true @@ -179,7 +180,7 @@ internal extension SessionUtil { case (true, false): try SessionThread( - id: contact.id, + id: sessionId, variant: .contact, creationDateTimestamp: data.created, shouldBeVisible: true, @@ -197,7 +198,7 @@ internal extension SessionUtil { ].compactMap { $0 } try SessionThread - .filter(id: contact.id) + .filter(id: sessionId) .updateAll( // Handling a config update so don't use `updateAllAndConfig` db, changes From 6fcfffafe7e2abcc2f11800351ab09ce67d2b11b Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 26 May 2023 16:52:40 +1000 Subject: [PATCH 078/135] Added logic to include the commit hash in the logs for debug builds --- Session.xcodeproj/project.pbxproj | 22 ++++++++++++++++++++++ Session/Settings/HelpViewModel.swift | 14 +++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index a09fceb75..775572c89 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -4626,6 +4626,7 @@ D221A085169C9E5E00537ABF /* Sources */, D221A086169C9E5E00537ABF /* Frameworks */, D221A087169C9E5E00537ABF /* Resources */, + FDD82C422A2085B900425F05 /* Add Commit Hash To Build Info Plist */, 453518771FC635DD00210559 /* Embed Foundation Extensions */, 4535189F1FC63DBF00210559 /* Embed Frameworks */, 90DF4725BB1271EBA2C66A12 /* [CP] Embed Pods Frameworks */, @@ -5319,6 +5320,27 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + FDD82C422A2085B900425F05 /* Add Commit Hash To Build Info Plist */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + $BUILT_PRODUCTS_DIR/$INFOPLIST_PATH, + $TARGET_BUILD_DIR/$INFOPLIST_PATH, + ); + name = "Add Commit Hash To Build Info Plist"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "INFO_PLIST=\"${TARGET_BUILD_DIR}\"/\"${INFOPLIST_PATH}\"\n\n# Query and save the value; suppress any error message, if key not found.\nvalue=$(/usr/libexec/PlistBuddy -c 'print :GitCommitHash' \"${INFO_PLIST}\" 2>/dev/null)\n\n# Check if value is empty\nif [ -z \"$value\" ] \nthen\n /usr/libexec/PlistBuddy -c \"Add :GitCommitHash string\" \"${INFO_PLIST}\"\nfi\n\n/usr/libexec/PlistBuddy -c \"Set :GitCommitHash `git rev-parse --short=7 HEAD`\" \"${INFO_PLIST}\"\n"; + showEnvVarsInLog = 0; + }; FDE7214D287E50820093DF33 /* Lint Localizable.strings */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; diff --git a/Session/Settings/HelpViewModel.swift b/Session/Settings/HelpViewModel.swift index b93e0fcc4..1f8d6f89c 100644 --- a/Session/Settings/HelpViewModel.swift +++ b/Session/Settings/HelpViewModel.swift @@ -150,7 +150,19 @@ class HelpViewModel: SessionTableViewModel Date: Tue, 30 May 2023 12:13:20 +1000 Subject: [PATCH 079/135] Fixed a bug where the ProfilePictureView could incorrectly take up space when hidden --- SessionUIKit/Components/ProfilePictureView.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/SessionUIKit/Components/ProfilePictureView.swift b/SessionUIKit/Components/ProfilePictureView.swift index e8b9350a6..1ffe575a7 100644 --- a/SessionUIKit/Components/ProfilePictureView.swift +++ b/SessionUIKit/Components/ProfilePictureView.swift @@ -119,6 +119,12 @@ public final class ProfilePictureView: UIView { imageContainerView.layer.cornerRadius = (clipsToBounds ? (size.multiImageSize / 2) : 0) } } + public override var isHidden: Bool { + didSet { + widthConstraint.constant = (isHidden ? 0 : size.viewSize) + heightConstraint.constant = (isHidden ? 0 : size.viewSize) + } + } // MARK: - Constraints From 9794877692f2856e9d480ea57816e46823c26279 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 30 May 2023 14:20:35 +1000 Subject: [PATCH 080/135] Fixed an issue where jobs could run before their references are persisted --- SessionUtilitiesKit/JobRunner/JobRunner.swift | 54 +++++++++++-------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/SessionUtilitiesKit/JobRunner/JobRunner.swift b/SessionUtilitiesKit/JobRunner/JobRunner.swift index 7906fd532..c6b0bad56 100644 --- a/SessionUtilitiesKit/JobRunner/JobRunner.swift +++ b/SessionUtilitiesKit/JobRunner/JobRunner.swift @@ -138,13 +138,15 @@ public final class JobRunner { return } - queues.wrappedValue[updatedJob.variant]?.add(db, job: updatedJob, canStartJob: canStartJob) - - // Don't start the queue if the job can't be started - guard canStartJob else { return } - - // Start the job runner if needed - db.afterNextTransactionNestedOnce(dedupeId: "JobRunner-Start: \(updatedJob.variant)") { _ in + // Wait until the transaction has been completed before updating the queue (to ensure anything + // created during the transaction has been saved to the database before any corresponding jobs + // are run) + db.afterNextTransactionNested { _ in + queues.wrappedValue[updatedJob.variant]?.add(updatedJob, canStartJob: canStartJob) + + // Don't start the queue if the job can't be started + guard canStartJob else { return } + queues.wrappedValue[updatedJob.variant]?.start() } } @@ -161,17 +163,22 @@ public final class JobRunner { return } - queues.wrappedValue[job.variant]?.upsert(db, job: job, canStartJob: canStartJob) - - // Don't start the queue if the job can't be started - guard canStartJob else { return } - - // Start the job runner if needed - db.afterNextTransactionNestedOnce(dedupeId: "JobRunner-Start: \(job.variant)") { _ in + // Wait until the transaction has been completed before updating the queue (to ensure anything + // created during the transaction has been saved to the database before any corresponding jobs + // are run) + db.afterNextTransactionNested { _ in + queues.wrappedValue[job.variant]?.upsert(job, canStartJob: canStartJob) + + // Don't start the queue if the job can't be started + guard canStartJob else { return } + queues.wrappedValue[job.variant]?.start() } } + /// Insert a job before another job in the queue + /// + /// **Note:** This function assumes the relevant job queue is already running and as such **will not** start the queue if it isn't running @discardableResult public static func insert(_ db: Database, job: Job?, before otherJob: Job) -> (Int64, Job)? { switch job?.behaviour { case .recurringOnActive, .recurringOnLaunch, .runOnceNextLaunch: @@ -191,7 +198,12 @@ public final class JobRunner { return nil } - queues.wrappedValue[updatedJob.variant]?.insert(updatedJob, before: otherJob) + // Wait until the transaction has been completed before updating the queue (to ensure anything + // created during the transaction has been saved to the database before any corresponding jobs + // are run) + db.afterNextTransactionNested { _ in + queues.wrappedValue[updatedJob.variant]?.insert(updatedJob, before: otherJob) + } return (jobId, updatedJob) } @@ -524,7 +536,7 @@ private final class JobQueue { // MARK: - Execution - fileprivate func add(_ db: Database, job: Job, canStartJob: Bool = true) { + fileprivate func add(_ job: Job, canStartJob: Bool = true) { // Check if the job should be added to the queue guard canStartJob, @@ -541,11 +553,7 @@ private final class JobQueue { // If this is a concurrent queue then we should immediately start the next job guard executionType == .concurrent else { return } - // Ensure that the database commit has completed and then trigger the next job to run (need - // to ensure any interactions have been correctly inserted first) - db.afterNextTransactionNestedOnce(dedupeId: "JobRunner-Add: \(job.variant)") { [weak self] _ in - self?.runNextJob() - } + runNextJob() } /// Upsert a job onto the queue, if the queue isn't currently running and 'canStartJob' is true then this will start @@ -553,7 +561,7 @@ private final class JobQueue { /// /// **Note:** If the job has a `behaviour` of `runOnceNextLaunch` or the `nextRunTimestamp` /// is in the future then the job won't be started - fileprivate func upsert(_ db: Database, job: Job, canStartJob: Bool = true) { + fileprivate func upsert(_ job: Job, canStartJob: Bool = true) { guard let jobId: Int64 = job.id else { SNLog("[JobRunner] Prevented attempt to upsert \(job.variant) job without id to queue") return @@ -576,7 +584,7 @@ private final class JobQueue { // If we didn't update an existing job then we need to add it to the queue guard !didUpdateExistingJob else { return } - add(db, job: job, canStartJob: canStartJob) + add(job, canStartJob: canStartJob) } fileprivate func insert(_ job: Job, before otherJob: Job) { From 3b772b7f902f690f4d12f876a3d5c06ba0a07a14 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 30 May 2023 17:45:46 +1000 Subject: [PATCH 081/135] [WIP] started adding logic to ignore messages invalidated by config Created a ConfigMessageReceiveJob just to clean up the logs a bit Updated the poller to make any MessageReceive jobs dependant on any ConfigMessageReceive jobs which are created Updated legacy groups to delete the group content when you are removed Fixed an issue where the JobRunner wouldn't stop pending jobs while clearing data Fixed another issue with the profile view in the message cell --- Session.xcodeproj/project.pbxproj | 4 + .../Message Cells/VisibleMessageCell.swift | 8 +- Session/Settings/NukeDataModal.swift | 6 ++ SessionMessagingKit/Configuration.swift | 1 + .../Migrations/_013_SessionUtilChanges.swift | 2 + .../_014_GenerateInitialUserConfigDumps.swift | 13 ++- .../Database/Models/ConfigDump.swift | 8 +- .../Jobs/Types/ConfigMessageReceiveJob.swift | 63 ++++++++++++++ .../Jobs/Types/MessageReceiveJob.swift | 23 +++-- .../SharedConfigMessage.swift | 5 +- .../MessageReceiver+ClosedGroups.swift | 75 ++++++++++------- .../Sending & Receiving/Pollers/Poller.swift | 84 +++++++++++++++++-- .../Config Handling/SessionUtil+Shared.swift | 37 +++++++- .../SessionUtil+UserGroups.swift | 4 +- .../SessionUtil+UserProfile.swift | 4 +- .../SessionUtil/SessionUtil.swift | 34 +++++--- .../Shared Models/MessageViewModel.swift | 12 +++ SessionUtilitiesKit/Database/Models/Job.swift | 5 ++ SessionUtilitiesKit/JobRunner/JobRunner.swift | 11 ++- 19 files changed, 322 insertions(+), 77 deletions(-) create mode 100644 SessionMessagingKit/Jobs/Types/ConfigMessageReceiveJob.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index d515422c3..cfa0cdded 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -587,6 +587,7 @@ FD2B4AFD294688D000AB4848 /* SessionUtil+Contacts.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2B4AFC294688D000AB4848 /* SessionUtil+Contacts.swift */; }; FD2B4AFF2946C93200AB4848 /* ConfigurationSyncJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2B4AFE2946C93200AB4848 /* ConfigurationSyncJob.swift */; }; FD2B4B042949887A00AB4848 /* QueryInterfaceRequest+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2B4B032949887A00AB4848 /* QueryInterfaceRequest+Utilities.swift */; }; + FD3003662A25D5B300B5A5FB /* ConfigMessageReceiveJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3003652A25D5B300B5A5FB /* ConfigMessageReceiveJob.swift */; }; FD368A6829DE8F9C000DBF1E /* _012_AddFTSIfNeeded.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD368A6729DE8F9B000DBF1E /* _012_AddFTSIfNeeded.swift */; }; FD368A6A29DE9E30000DBF1E /* UIContextualAction+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD368A6929DE9E30000DBF1E /* UIContextualAction+Utilities.swift */; }; FD37E9C328A1C6F3003AE748 /* ThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9C228A1C6F3003AE748 /* ThemeManager.swift */; }; @@ -1725,6 +1726,7 @@ FD2B4AFC294688D000AB4848 /* SessionUtil+Contacts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionUtil+Contacts.swift"; sourceTree = ""; }; FD2B4AFE2946C93200AB4848 /* ConfigurationSyncJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationSyncJob.swift; sourceTree = ""; }; FD2B4B032949887A00AB4848 /* QueryInterfaceRequest+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QueryInterfaceRequest+Utilities.swift"; sourceTree = ""; }; + FD3003652A25D5B300B5A5FB /* ConfigMessageReceiveJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigMessageReceiveJob.swift; sourceTree = ""; }; FD368A6729DE8F9B000DBF1E /* _012_AddFTSIfNeeded.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _012_AddFTSIfNeeded.swift; sourceTree = ""; }; FD368A6929DE9E30000DBF1E /* UIContextualAction+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIContextualAction+Utilities.swift"; sourceTree = ""; }; FD37E9C228A1C6F3003AE748 /* ThemeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = ""; }; @@ -4296,6 +4298,7 @@ FDD2506F2837199200198BDA /* GarbageCollectionJob.swift */, C352A2FE25574B6300338F3E /* MessageSendJob.swift */, C352A31225574F5200338F3E /* MessageReceiveJob.swift */, + FD3003652A25D5B300B5A5FB /* ConfigMessageReceiveJob.swift */, C352A32E2557549C00338F3E /* NotifyPushServerJob.swift */, FDF0B74E28079E5E004C14C5 /* SendReadReceiptsJob.swift */, C352A348255781F400338F3E /* AttachmentDownloadJob.swift */, @@ -5729,6 +5732,7 @@ C3471ECB2555356A00297E91 /* MessageSender+Encryption.swift in Sources */, FDF40CDE2897A1BC006A0CC4 /* _004_RemoveLegacyYDB.swift in Sources */, FDF0B74928060D13004C14C5 /* QuotedReplyModel.swift in Sources */, + FD3003662A25D5B300B5A5FB /* ConfigMessageReceiveJob.swift in Sources */, 7B81682C28B72F480069F315 /* PendingChange.swift in Sources */, FD77289A284AF1BD0018502F /* Sodium+Utilities.swift in Sources */, FD5C7309285007920029977D /* BlindedIdLookup.swift in Sources */, diff --git a/Session/Conversations/Message Cells/VisibleMessageCell.swift b/Session/Conversations/Message Cells/VisibleMessageCell.swift index a89b3d80e..79b137e5d 100644 --- a/Session/Conversations/Message Cells/VisibleMessageCell.swift +++ b/Session/Conversations/Message Cells/VisibleMessageCell.swift @@ -299,8 +299,14 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { ) // Profile picture view (should always be handled as a standard 'contact' profile picture) + let profileShouldBeVisible: Bool = ( + cellViewModel.canHaveProfile && + cellViewModel.shouldShowProfile && + cellViewModel.profile != nil + ) profilePictureViewLeadingConstraint.constant = (isGroupThread ? VisibleMessageCell.groupThreadHSpacing : 0) - profilePictureView.isHidden = (!cellViewModel.shouldShowProfile || cellViewModel.profile == nil) + profilePictureView.isHidden = !cellViewModel.canHaveProfile + profilePictureView.alpha = (profileShouldBeVisible ? 1 : 0) profilePictureView.update( publicKey: cellViewModel.authorId, threadVariant: .contact, // Always show the display picture in 'contact' mode diff --git a/Session/Settings/NukeDataModal.swift b/Session/Settings/NukeDataModal.swift index 2d8ceeaff..1564f2441 100644 --- a/Session/Settings/NukeDataModal.swift +++ b/Session/Settings/NukeDataModal.swift @@ -229,6 +229,12 @@ final class NukeDataModal: Modal { PushNotificationAPI.unregister(data).sinkUntilComplete() } + /// Stop and cancel all current jobs (don't want to inadvertantly have a job store data after it's table has already been cleared) + /// + /// **Note:** This is file as long as this process kills the app, if it doesn't then we need an alternate mechanism to flag that + /// the `JobRunner` is allowed to start it's queues again + JobRunner.stopAndClearPendingJobs() + // Clear the app badge and notifications AppEnvironment.shared.notificationPresenter.clearAllNotifications() CurrentAppContext().setMainAppBadgeNumber(0) diff --git a/SessionMessagingKit/Configuration.swift b/SessionMessagingKit/Configuration.swift index de6575edb..c6d92487c 100644 --- a/SessionMessagingKit/Configuration.swift +++ b/SessionMessagingKit/Configuration.swift @@ -58,5 +58,6 @@ public enum SNMessagingKit { // Just to make the external API nice JobRunner.add(executor: GroupLeavingJob.self, for: .groupLeaving) JobRunner.add(executor: AttachmentDownloadJob.self, for: .attachmentDownload) JobRunner.add(executor: ConfigurationSyncJob.self, for: .configurationSync) + JobRunner.add(executor: ConfigMessageReceiveJob.self, for: .configMessageReceive) } } diff --git a/SessionMessagingKit/Database/Migrations/_013_SessionUtilChanges.swift b/SessionMessagingKit/Database/Migrations/_013_SessionUtilChanges.swift index 8445948f9..2412e0970 100644 --- a/SessionMessagingKit/Database/Migrations/_013_SessionUtilChanges.swift +++ b/SessionMessagingKit/Database/Migrations/_013_SessionUtilChanges.swift @@ -154,6 +154,8 @@ enum _013_SessionUtilChanges: Migration { .indexed() t.column(.data, .blob) .notNull() + t.column(.timestampMs, .integer) + .notNull() t.primaryKey([.variant, .publicKey]) } diff --git a/SessionMessagingKit/Database/Migrations/_014_GenerateInitialUserConfigDumps.swift b/SessionMessagingKit/Database/Migrations/_014_GenerateInitialUserConfigDumps.swift index 703acf6a1..3091e2531 100644 --- a/SessionMessagingKit/Database/Migrations/_014_GenerateInitialUserConfigDumps.swift +++ b/SessionMessagingKit/Database/Migrations/_014_GenerateInitialUserConfigDumps.swift @@ -23,6 +23,7 @@ enum _014_GenerateInitialUserConfigDumps: Migration { // Create the initial config state let userPublicKey: String = getUserHexEncodedPublicKey(db) + let timestampMs: Int64 = Int64(Date().timeIntervalSince1970 * 1000) SessionUtil.loadState(db, userPublicKey: userPublicKey, ed25519SecretKey: secretKey) @@ -56,7 +57,8 @@ enum _014_GenerateInitialUserConfigDumps: Migration { .createDump( conf: conf, for: .userProfile, - publicKey: userPublicKey + publicKey: userPublicKey, + timestampMs: timestampMs )? .save(db) } @@ -120,7 +122,8 @@ enum _014_GenerateInitialUserConfigDumps: Migration { .createDump( conf: conf, for: .contacts, - publicKey: userPublicKey + publicKey: userPublicKey, + timestampMs: timestampMs )? .save(db) } @@ -144,7 +147,8 @@ enum _014_GenerateInitialUserConfigDumps: Migration { .createDump( conf: conf, for: .convoInfoVolatile, - publicKey: userPublicKey + publicKey: userPublicKey, + timestampMs: timestampMs )? .save(db) } @@ -179,7 +183,8 @@ enum _014_GenerateInitialUserConfigDumps: Migration { .createDump( conf: conf, for: .userGroups, - publicKey: userPublicKey + publicKey: userPublicKey, + timestampMs: timestampMs )? .save(db) } diff --git a/SessionMessagingKit/Database/Models/ConfigDump.swift b/SessionMessagingKit/Database/Models/ConfigDump.swift index cca17d29f..8f20f1466 100644 --- a/SessionMessagingKit/Database/Models/ConfigDump.swift +++ b/SessionMessagingKit/Database/Models/ConfigDump.swift @@ -13,6 +13,7 @@ public struct ConfigDump: Codable, Equatable, Hashable, FetchableRecord, Persist case variant case publicKey case data + case timestampMs } public enum Variant: String, Codable, DatabaseValueConvertible { @@ -33,14 +34,19 @@ public struct ConfigDump: Codable, Equatable, Hashable, FetchableRecord, Persist /// The data for this dump public let data: Data + /// When the configDump was created in milliseconds since epoch + public let timestampMs: Int64 + internal init( variant: Variant, publicKey: String, - data: Data + data: Data, + timestampMs: Int64 ) { self.variant = variant self.publicKey = publicKey self.data = data + self.timestampMs = timestampMs } } diff --git a/SessionMessagingKit/Jobs/Types/ConfigMessageReceiveJob.swift b/SessionMessagingKit/Jobs/Types/ConfigMessageReceiveJob.swift new file mode 100644 index 000000000..c5f0ae21f --- /dev/null +++ b/SessionMessagingKit/Jobs/Types/ConfigMessageReceiveJob.swift @@ -0,0 +1,63 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import GRDB +import SessionUtilitiesKit + +public enum ConfigMessageReceiveJob: JobExecutor { + public static var maxFailureCount: Int = 0 + public static var requiresThreadId: Bool = true + public static let requiresInteractionId: Bool = false + + public static func run( + _ job: Job, + queue: DispatchQueue, + success: @escaping (Job, Bool) -> (), + failure: @escaping (Job, Error?, Bool) -> (), + deferred: @escaping (Job) -> () + ) { + guard + let detailsData: Data = job.details, + let details: Details = try? JSONDecoder().decode(Details.self, from: detailsData) + else { + failure(job, JobRunnerError.missingRequiredDetails, true) + return + } + + // Ensure no standard messages are sent through this job + guard !details.messages.contains(where: { $0.variant != .sharedConfigMessage }) else { + SNLog("[ConfigMessageReceiveJob] Standard messages incorrectly sent to the 'configMessageReceive' job") + failure(job, MessageReceiverError.invalidMessage, true) + return + } + + var lastError: Error? + let sharedConfigMessages: [SharedConfigMessage] = details.messages + .compactMap { $0.message as? SharedConfigMessage } + + Storage.shared.write { db in + // Send any SharedConfigMessages to the SessionUtil to handle it + do { + try SessionUtil.handleConfigMessages( + db, + messages: sharedConfigMessages, + publicKey: (job.threadId ?? "") + ) + } + catch { lastError = error } + } + + // Handle the result + switch lastError { + case let error as MessageReceiverError where !error.isRetryable: failure(job, error, true) + case .some(let error): failure(job, error, false) + case .none: success(job, false) + } + } +} + +// MARK: - ConfigMessageReceiveJob.Details + +extension ConfigMessageReceiveJob { + typealias Details = MessageReceiveJob.Details +} diff --git a/SessionMessagingKit/Jobs/Types/MessageReceiveJob.swift b/SessionMessagingKit/Jobs/Types/MessageReceiveJob.swift index 726624dcd..10e3fb15d 100644 --- a/SessionMessagingKit/Jobs/Types/MessageReceiveJob.swift +++ b/SessionMessagingKit/Jobs/Types/MessageReceiveJob.swift @@ -25,10 +25,17 @@ public enum MessageReceiveJob: JobExecutor { return } + // Ensure no config messages are sent through this job + guard !details.messages.contains(where: { $0.variant == .sharedConfigMessage }) else { + SNLog("[MessageReceiveJob] Config messages incorrectly sent to the 'messageReceive' job") + failure(job, MessageReceiverError.invalidSharedConfigMessageHandling, true) + return + } + var updatedJob: Job = job var lastError: Error? var remainingMessagesToProcess: [Details.MessageInfo] = [] - let nonConfigMessages: [(info: Details.MessageInfo, proto: SNProtoContent)] = details.messages + let messageData: [(info: Details.MessageInfo, proto: SNProtoContent)] = details.messages .filter { $0.variant != .sharedConfigMessage } .compactMap { messageInfo -> (info: Details.MessageInfo, proto: SNProtoContent)? in do { @@ -44,19 +51,9 @@ public enum MessageReceiveJob: JobExecutor { return nil } } - let sharedConfigMessages: [SharedConfigMessage] = details.messages - .compactMap { $0.message as? SharedConfigMessage } Storage.shared.write { db in - // Send any SharedConfigMessages to the SessionUtil to handle it - try SessionUtil.handleConfigMessages( - db, - messages: sharedConfigMessages, - publicKey: (job.threadId ?? "") - ) - - // Handle the remaining messages - for (messageInfo, protoContent) in nonConfigMessages { + for (messageInfo, protoContent) in messageData { do { try MessageReceiver.handle( db, @@ -98,6 +95,8 @@ public enum MessageReceiveJob: JobExecutor { // If any messages failed to process then we want to update the job to only include // those failed messages + guard !remainingMessagesToProcess.isEmpty else { return } + updatedJob = try job .with( details: Details( diff --git a/SessionMessagingKit/Messages/Control Messages/SharedConfigMessage.swift b/SessionMessagingKit/Messages/Control Messages/SharedConfigMessage.swift index 2e67ea96e..09aecc1c5 100644 --- a/SessionMessagingKit/Messages/Control Messages/SharedConfigMessage.swift +++ b/SessionMessagingKit/Messages/Control Messages/SharedConfigMessage.swift @@ -42,13 +42,14 @@ public final class SharedConfigMessage: ControlMessage { public init( kind: Kind, seqNo: Int64, - data: Data + data: Data, + sentTimestamp: UInt64? = nil ) { self.kind = kind self.seqNo = seqNo self.data = data - super.init() + super.init(sentTimestamp: sentTimestamp) } // MARK: - Codable diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift index ae9b3a53d..0fb862067 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift @@ -69,7 +69,15 @@ extension MessageReceiver { guard case let .new(publicKeyAsData, name, encryptionKeyPair, membersAsData, adminsAsData, expirationTimer) = message.kind else { return } - guard let sentTimestamp: UInt64 = message.sentTimestamp else { return } + guard + let sentTimestamp: UInt64 = message.sentTimestamp, + SessionUtil.canPerformChange( + db, + threadId: publicKeyAsData.toHexString(), + targetConfig: .userGroups, + changeTimestampMs: Int64(sentTimestamp) + ) + else { return SNLog("Ignoring outdated NEW legacy group message due to more recent config state") } try handleNewClosedGroup( db, @@ -473,16 +481,11 @@ extension MessageReceiver { let wasCurrentUserRemoved: Bool = !members.contains(userPublicKey) if wasCurrentUserRemoved { - ClosedGroupPoller.shared.stopPolling(for: threadId) - - _ = try closedGroup - .keyPairs - .deleteAll(db) - - let _ = PushNotificationAPI.performOperation( - .unsubscribe, - for: threadId, - publicKey: userPublicKey + try ClosedGroup.removeKeysAndUnsubscribe( + db, + threadId: threadId, + removeGroupData: true, + calledFromConfigHandling: false ) } } @@ -584,27 +587,35 @@ extension MessageReceiver { return SNLog("Ignoring group update for nonexistent group.") } - // Legacy groups used these control messages for making changes, new groups only use them - // for information purposes - switch threadVariant { - case .legacyGroup: - // Check that the message isn't from before the group was created - guard Double(message.sentTimestamp ?? 0) > closedGroup.formationTimestamp else { - return SNLog("Ignoring legacy group update from before thread was created.") - } - - // If these values are missing then we probably won't be able to validly handle the message - guard - let allMembers: [GroupMember] = try? closedGroup.allMembers.fetchAll(db), - allMembers.contains(where: { $0.profileId == sender }) - else { return SNLog("Ignoring legacy group update from non-member.") } - - try legacyGroupChanges(sender, closedGroup, allMembers) - - case .group: - break - - default: return // Ignore as invalid + let timestampMs: Int64 = ( + message.sentTimestamp.map { Int64($0) } ?? + SnodeAPI.currentOffsetTimestampMs() + ) + // Only actually make the change if SessionUtil says we can (we always want to insert the info + // message though) + if SessionUtil.canPerformChange(db, threadId: threadId, targetConfig: .userGroups, changeTimestampMs: timestampMs ) { + // Legacy groups used these control messages for making changes, new groups only use them + // for information purposes + switch threadVariant { + case .legacyGroup: + // Check that the message isn't from before the group was created + guard Double(message.sentTimestamp ?? 0) > closedGroup.formationTimestamp else { + return SNLog("Ignoring legacy group update from before thread was created.") + } + + // If these values are missing then we probably won't be able to validly handle the message + guard + let allMembers: [GroupMember] = try? closedGroup.allMembers.fetchAll(db), + allMembers.contains(where: { $0.profileId == sender }) + else { return SNLog("Ignoring legacy group update from non-member.") } + + try legacyGroupChanges(sender, closedGroup, allMembers) + + case .group: + break + + default: return // Ignore as invalid + } } // Insert the info message for this group control message diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift index 52fa670f1..8dbcbdd91 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift @@ -248,10 +248,11 @@ public class Poller { var messageCount: Int = 0 var processedMessages: [Message] = [] var hadValidHashUpdate: Bool = false - var jobsToRun: [Job] = [] + var configMessageJobsToRun: [Job] = [] + var standardMessageJobsToRun: [Job] = [] Storage.shared.write { db in - allMessages + let allProcessedMessages: [ProcessedMessage] = allMessages .compactMap { message -> ProcessedMessage? in do { return try Message.processRawReceivedMessage(db, rawMessage: message) @@ -284,6 +285,39 @@ public class Poller { return nil } } + + // Add a job to process the config messages first + let configJobIds: [Int64] = allProcessedMessages + .filter { $0.messageInfo.variant == .sharedConfigMessage } + .grouped { threadId, _, _, _ in threadId } + .compactMap { threadId, threadMessages in + messageCount += threadMessages.count + processedMessages += threadMessages.map { $0.messageInfo.message } + + let jobToRun: Job? = Job( + variant: .configMessageReceive, + behaviour: .runOnce, + threadId: threadId, + details: ConfigMessageReceiveJob.Details( + messages: threadMessages.map { $0.messageInfo }, + calledFromBackgroundPoller: calledFromBackgroundPoller + ) + ) + configMessageJobsToRun = configMessageJobsToRun.appending(jobToRun) + + // If we are force-polling then add to the JobRunner so they are + // persistent and will retry on the next app run if they fail but + // don't let them auto-start + let updatedJob: Job? = JobRunner + .add(db, job: jobToRun, canStartJob: !calledFromBackgroundPoller) + + return updatedJob?.id + } + + // Add jobs for processing non-config messages which are dependant on the config message + // processing jobs + allProcessedMessages + .filter { $0.messageInfo.variant != .sharedConfigMessage } .grouped { threadId, _, _, _ in threadId } .forEach { threadId, threadMessages in messageCount += threadMessages.count @@ -298,12 +332,29 @@ public class Poller { calledFromBackgroundPoller: calledFromBackgroundPoller ) ) - jobsToRun = jobsToRun.appending(jobToRun) + standardMessageJobsToRun = standardMessageJobsToRun.appending(jobToRun) // If we are force-polling then add to the JobRunner so they are // persistent and will retry on the next app run if they fail but // don't let them auto-start - JobRunner.add(db, job: jobToRun, canStartJob: !calledFromBackgroundPoller) + let updatedJob: Job? = JobRunner + .add(db, job: jobToRun, canStartJob: !calledFromBackgroundPoller) + + // Create the dependency between the jobs + if let updatedJobId: Int64 = updatedJob?.id { + do { + try configJobIds.forEach { configJobId in + try JobDependencies( + jobId: updatedJobId, + dependantId: configJobId + ) + .insert(db) + } + } + catch { + SNLog("Failed to add dependency between config processing and non-config processing messageReceive jobs.") + } + } } // Clean up message hashes and add some logs about the poll results @@ -334,11 +385,11 @@ public class Poller { // We want to try to handle the receive jobs immediately in the background return Publishers .MergeMany( - jobsToRun.map { job -> AnyPublisher in + configMessageJobsToRun.map { job -> AnyPublisher in Deferred { Future { resolver in // Note: In the background we just want jobs to fail silently - MessageReceiveJob.run( + ConfigMessageReceiveJob.run( job, queue: queue, success: { _, _ in resolver(Result.success(())) }, @@ -351,6 +402,27 @@ public class Poller { } ) .collect() + .flatMap { _ in + Publishers + .MergeMany( + standardMessageJobsToRun.map { job -> AnyPublisher in + Deferred { + Future { resolver in + // Note: In the background we just want jobs to fail silently + MessageReceiveJob.run( + job, + queue: queue, + success: { _, _ in resolver(Result.success(())) }, + failure: { _, _, _ in resolver(Result.success(())) }, + deferred: { _ in resolver(Result.success(())) } + ) + } + } + .eraseToAnyPublisher() + } + ) + .collect() + } .map { _ in processedMessages } .eraseToAnyPublisher() } diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift index 4a8c7e9d2..3ea0d9194 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift @@ -9,6 +9,11 @@ import SessionUtilitiesKit // MARK: - Convenience internal extension SessionUtil { + /// This is a buffer period within which we will process messages which would result in a config change, any message which would normally + /// result in a config change which was sent before `lastConfigMessage.timestamp - configChangeBufferPeriod` will not + /// actually have it's changes applied (info messages would still be inserted though) + static let configChangeBufferPeriod: TimeInterval = (2 * 60) + static let columnsRelatedToThreads: [ColumnExpression] = [ SessionThread.Columns.pinnedPriority, SessionThread.Columns.shouldBeVisible @@ -66,7 +71,8 @@ internal extension SessionUtil { try SessionUtil.createDump( conf: conf, for: variant, - publicKey: publicKey + publicKey: publicKey, + timestampMs: Int64(Date().timeIntervalSince1970 * 1000) )?.save(db) return config_needs_push(conf) @@ -293,6 +299,35 @@ internal extension SessionUtil { } } } + + static func canPerformChange( + _ db: Database, + threadId: String, + targetConfig: ConfigDump.Variant, + changeTimestampMs: Int64 + ) -> Bool { + // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent + guard SessionUtil.userConfigsEnabled(db) else { return true } + + let targetPublicKey: String = { + switch targetConfig { + default: return getUserHexEncodedPublicKey(db) + } + }() + + let configDumpTimestampMs: Int64 = (try? ConfigDump + .filter( + ConfigDump.Columns.variant == targetConfig && + ConfigDump.Columns.publicKey == targetPublicKey + ) + .select(.timestampMs) + .asRequest(of: Int64.self) + .fetchOne(db)) + .defaulting(to: 0) + + // Ensure the change occurred after the last config message was handled (minus the buffer period) + return (changeTimestampMs > (configDumpTimestampMs - Int64(SessionUtil.configChangeBufferPeriod * 1000))) + } } // MARK: - External Outgoing Changes diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserGroups.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserGroups.swift index 0ba663d29..3433ea5dd 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserGroups.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserGroups.swift @@ -29,7 +29,7 @@ internal extension SessionUtil { _ db: Database, in conf: UnsafeMutablePointer?, mergeNeedsDump: Bool, - latestConfigUpdateSentTimestamp: TimeInterval + latestConfigSentTimestampMs: Int64 ) throws { guard mergeNeedsDump else { return } guard conf != nil else { throw SessionUtilError.nilConfigObject } @@ -219,7 +219,7 @@ internal extension SessionUtil { .map { $0.profileId }, admins: updatedAdmins.map { $0.profileId }, expirationTimer: UInt32(group.disappearingConfig?.durationSeconds ?? 0), - formationTimestampMs: UInt64((group.joinedAt ?? Int64(latestConfigUpdateSentTimestamp)) * 1000), + formationTimestampMs: UInt64((group.joinedAt.map { $0 * 1000 } ?? latestConfigSentTimestampMs)), calledFromConfigHandling: true ) } diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserProfile.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserProfile.swift index 920eb3e47..2bbe3cc4d 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserProfile.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserProfile.swift @@ -18,7 +18,7 @@ internal extension SessionUtil { _ db: Database, in conf: UnsafeMutablePointer?, mergeNeedsDump: Bool, - latestConfigUpdateSentTimestamp: TimeInterval + latestConfigSentTimestampMs: Int64 ) throws { typealias ProfileData = (profileName: String, profilePictureUrl: String?, profilePictureKey: Data?) @@ -50,7 +50,7 @@ internal extension SessionUtil { fileName: nil ) }(), - sentTimestamp: latestConfigUpdateSentTimestamp, + sentTimestamp: (TimeInterval(latestConfigSentTimestampMs) / 1000), calledFromConfigHandling: true ) diff --git a/SessionMessagingKit/SessionUtil/SessionUtil.swift b/SessionMessagingKit/SessionUtil/SessionUtil.swift index 1735f6b6c..391a4a45a 100644 --- a/SessionMessagingKit/SessionUtil/SessionUtil.swift +++ b/SessionMessagingKit/SessionUtil/SessionUtil.swift @@ -260,7 +260,8 @@ public enum SessionUtil { internal static func createDump( conf: UnsafeMutablePointer?, for variant: ConfigDump.Variant, - publicKey: String + publicKey: String, + timestampMs: Int64 ) throws -> ConfigDump? { guard conf != nil else { throw SessionUtilError.nilConfigObject } @@ -279,7 +280,8 @@ public enum SessionUtil { return ConfigDump( variant: variant, publicKey: publicKey, - data: dumpData + data: dumpData, + timestampMs: timestampMs ) } @@ -363,7 +365,8 @@ public enum SessionUtil { return try? SessionUtil.createDump( conf: conf, for: message.kind.configDumpVariant, - publicKey: publicKey + publicKey: publicKey, + timestampMs: (message.sentTimestamp.map { Int64($0) } ?? 0) ) } } @@ -427,9 +430,7 @@ public enum SessionUtil { let needsPush: Bool = try groupedMessages .sorted { lhs, rhs in lhs.key.processingOrder < rhs.key.processingOrder } .reduce(false) { prevNeedsPush, next -> Bool in - let messageSentTimestamp: TimeInterval = TimeInterval( - (next.value.compactMap { $0.sentTimestamp }.max() ?? 0) / 1000 - ) + let latestConfigSentTimestampMs: Int64 = Int64(next.value.compactMap { $0.sentTimestamp }.max() ?? 0) let needsPush: Bool = try SessionUtil .config(for: next.key, publicKey: publicKey) .mutate { conf in @@ -453,7 +454,7 @@ public enum SessionUtil { db, in: conf, mergeNeedsDump: config_needs_dump(conf), - latestConfigUpdateSentTimestamp: messageSentTimestamp + latestConfigSentTimestampMs: latestConfigSentTimestampMs ) case .contacts: @@ -475,7 +476,7 @@ public enum SessionUtil { db, in: conf, mergeNeedsDump: config_needs_dump(conf), - latestConfigUpdateSentTimestamp: messageSentTimestamp + latestConfigSentTimestampMs: latestConfigSentTimestampMs ) } } @@ -486,12 +487,25 @@ public enum SessionUtil { // Need to check if the config needs to be dumped (this might have changed // after handling the merge changes) - guard config_needs_dump(conf) else { return config_needs_push(conf) } + guard config_needs_dump(conf) else { + try ConfigDump + .filter( + ConfigDump.Columns.variant == next.key && + ConfigDump.Columns.publicKey == publicKey + ) + .updateAll( + db, + ConfigDump.Columns.timestampMs.set(to: latestConfigSentTimestampMs) + ) + + return config_needs_push(conf) + } try SessionUtil.createDump( conf: conf, for: next.key, - publicKey: publicKey + publicKey: publicKey, + timestampMs: latestConfigSentTimestampMs )?.save(db) return config_needs_push(conf) diff --git a/SessionMessagingKit/Shared Models/MessageViewModel.swift b/SessionMessagingKit/Shared Models/MessageViewModel.swift index 880f9c0bb..177dcbbbb 100644 --- a/SessionMessagingKit/Shared Models/MessageViewModel.swift +++ b/SessionMessagingKit/Shared Models/MessageViewModel.swift @@ -34,6 +34,7 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable, public static let currentUserPublicKeyKey: SQL = SQL(stringLiteral: CodingKeys.currentUserPublicKey.stringValue) public static let cellTypeKey: SQL = SQL(stringLiteral: CodingKeys.cellType.stringValue) public static let authorNameKey: SQL = SQL(stringLiteral: CodingKeys.authorName.stringValue) + public static let canHaveProfileKey: SQL = SQL(stringLiteral: CodingKeys.canHaveProfile.stringValue) public static let shouldShowProfileKey: SQL = SQL(stringLiteral: CodingKeys.shouldShowProfile.stringValue) public static let shouldShowDateHeaderKey: SQL = SQL(stringLiteral: CodingKeys.shouldShowDateHeader.stringValue) public static let positionInClusterKey: SQL = SQL(stringLiteral: CodingKeys.positionInCluster.stringValue) @@ -115,6 +116,9 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable, /// **Note:** This will only be populated for incoming messages public let senderName: String? + /// A flag indicating whether the profile view can be displayed + public let canHaveProfile: Bool + /// A flag indicating whether the profile view should be displayed public let shouldShowProfile: Bool @@ -191,6 +195,7 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable, cellType: self.cellType, authorName: self.authorName, senderName: self.senderName, + canHaveProfile: self.canHaveProfile, shouldShowProfile: self.shouldShowProfile, shouldShowDateHeader: self.shouldShowDateHeader, containsOnlyEmoji: self.containsOnlyEmoji, @@ -393,6 +398,11 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable, return authorDisplayName }(), + canHaveProfile: ( + // Only group threads and incoming messages + isGroupThread && + self.variant == .standardIncoming + ), shouldShowProfile: ( // Only group threads isGroupThread && @@ -564,6 +574,7 @@ public extension MessageViewModel { self.cellType = cellType self.authorName = "" self.senderName = nil + self.canHaveProfile = false self.shouldShowProfile = false self.shouldShowDateHeader = false self.containsOnlyEmoji = nil @@ -733,6 +744,7 @@ public extension MessageViewModel { -- query from crashing when decoding we need to provide default values \(CellType.textOnlyMessage) AS \(ViewModel.cellTypeKey), '' AS \(ViewModel.authorNameKey), + false AS \(ViewModel.canHaveProfileKey), false AS \(ViewModel.shouldShowProfileKey), false AS \(ViewModel.shouldShowDateHeaderKey), \(Position.middle) AS \(ViewModel.positionInClusterKey), diff --git a/SessionUtilitiesKit/Database/Models/Job.swift b/SessionUtilitiesKit/Database/Models/Job.swift index 8fd845cba..d1c588c39 100644 --- a/SessionUtilitiesKit/Database/Models/Job.swift +++ b/SessionUtilitiesKit/Database/Models/Job.swift @@ -111,6 +111,11 @@ public struct Job: Codable, Equatable, Hashable, Identifiable, FetchableRecord, /// This is a job that runs once whenever the user config or a closed group config changes, it retrieves the /// state of all config objects and syncs any that are flagged as needing to be synced case configurationSync + + /// This is a job that runs once whenever a config message is received to attempt to decode it and update the + /// config state with the changes; this job will generally be scheduled along since a `messageReceive` job + /// and will block the standard message receive job + case configMessageReceive } public enum Behaviour: Int, Codable, DatabaseValueConvertible, CaseIterable { diff --git a/SessionUtilitiesKit/JobRunner/JobRunner.swift b/SessionUtilitiesKit/JobRunner/JobRunner.swift index c6b0bad56..19a750f26 100644 --- a/SessionUtilitiesKit/JobRunner/JobRunner.swift +++ b/SessionUtilitiesKit/JobRunner/JobRunner.swift @@ -80,7 +80,8 @@ public final class JobRunner { executionType: .serial, qos: .default, jobVariants: [ - jobVariants.remove(.messageReceive) + jobVariants.remove(.messageReceive), + jobVariants.remove(.configMessageReceive) ].compactMap { $0 } ) let attachmentDownloadQueue: JobQueue = JobQueue( @@ -127,15 +128,15 @@ public final class JobRunner { /// /// **Note:** If the job has a `behaviour` of `runOnceNextLaunch` or the `nextRunTimestamp` /// is in the future then the job won't be started - public static func add(_ db: Database, job: Job?, canStartJob: Bool = true) { + @discardableResult public static func add(_ db: Database, job: Job?, canStartJob: Bool = true) -> Job? { // Store the job into the database (getting an id for it) guard let updatedJob: Job = try? job?.inserted(db) else { SNLog("[JobRunner] Unable to add \(job.map { "\($0.variant)" } ?? "unknown") job") - return + return nil } guard !canStartJob || updatedJob.id != nil else { SNLog("[JobRunner] Not starting \(job.map { "\($0.variant)" } ?? "unknown") job due to missing id") - return + return nil } // Wait until the transaction has been completed before updating the queue (to ensure anything @@ -149,6 +150,8 @@ public final class JobRunner { queues.wrappedValue[updatedJob.variant]?.start() } + + return updatedJob } /// Upsert a job onto the queue, if the queue isn't currently running and 'canStartJob' is true then this will start From c455a13a7b53f2f25314cbd6ca6ef0729ff3d02d Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 31 May 2023 14:30:33 +1000 Subject: [PATCH 082/135] Finished adding logic to ignore messages invalidated by config Added timestamps to the Profile table to avoid overriding current profile info with older info Updated the MessageReceiver to ignore the rest of the messages invalidated by the config Updated to the latest libSession Updated the JobRunner to expose some more info about the currently running jobs Made some tweaks to the ConfigurationSyncJob to better support concurrent jobs running for different targets --- LibSession-Util | 2 +- Session/Onboarding/Onboarding.swift | 17 ++- Session/Utilities/MockDataGenerator.swift | 12 +- .../Migrations/_003_YDBToGRDBMigration.swift | 12 +- .../Migrations/_013_SessionUtilChanges.swift | 10 ++ .../Database/Models/Profile.swift | 29 +++- .../Jobs/Types/AttachmentDownloadJob.swift | 6 +- .../Jobs/Types/ConfigMessageReceiveJob.swift | 29 +++- .../Jobs/Types/ConfigurationSyncJob.swift | 51 ++++--- .../Errors/MessageReceiverError.swift | 4 +- .../MessageReceiver+Calls.swift | 25 +++- .../MessageReceiver+ClosedGroups.swift | 26 +++- ...eReceiver+DataExtractionNotification.swift | 42 +++++- .../MessageReceiver+ExpirationTimers.swift | 59 +++++--- .../MessageReceiver+MessageRequests.swift | 35 ++++- .../MessageReceiver+VisibleMessages.swift | 28 +++- .../SessionUtil+Contacts.swift | 32 +++- .../Config Handling/SessionUtil+Shared.swift | 2 +- .../SessionUtil/SessionUtil.swift | 3 +- .../Utilities/ProfileManager.swift | 34 ++--- .../Components/ProfilePictureView.swift | 2 +- .../Database/Models/JobDependencies.swift | 4 +- .../General/SNUserDefaults.swift | 2 - SessionUtilitiesKit/JobRunner/JobRunner.swift | 139 ++++++++++++++---- 24 files changed, 465 insertions(+), 140 deletions(-) diff --git a/LibSession-Util b/LibSession-Util index 97084c69f..9777b37e8 160000 --- a/LibSession-Util +++ b/LibSession-Util @@ -1 +1 @@ -Subproject commit 97084c69f86e67c675095b48efacc86113ccebb0 +Subproject commit 9777b37e8545febcc082578341352dba7433db21 diff --git a/Session/Onboarding/Onboarding.swift b/Session/Onboarding/Onboarding.swift index 8c664c045..914976728 100644 --- a/Session/Onboarding/Onboarding.swift +++ b/Session/Onboarding/Onboarding.swift @@ -219,11 +219,18 @@ enum Onboarding { } func completeRegistration() { - // Set the `lastDisplayNameUpdate` to the current date, so that we don't - // overwrite what the user set in the display name step with whatever we - // find in their swarm (otherwise the user could enter a display name and - // have it immediately overwritten due to the config request running slow) - UserDefaults.standard[.lastDisplayNameUpdate] = Date() + // Set the `lastNameUpdate` to the current date, so that we don't overwrite + // what the user set in the display name step with whatever we find in their + // swarm (otherwise the user could enter a display name and have it immediately + // overwritten due to the config request running slow) + Storage.shared.write { db in + try Profile + .filter(id: getUserHexEncodedPublicKey(db)) + .updateAllAndConfig( + db, + Profile.Columns.lastNameUpdate.set(to: Date().timeIntervalSince1970) + ) + } // Notify the app that registration is complete Identity.didRegister() diff --git a/Session/Utilities/MockDataGenerator.swift b/Session/Utilities/MockDataGenerator.swift index e20b9b1e9..d4ab94abc 100644 --- a/Session/Utilities/MockDataGenerator.swift +++ b/Session/Utilities/MockDataGenerator.swift @@ -158,7 +158,9 @@ enum MockDataGenerator { id: randomSessionId, name: (0.. = JobRunner - .defailsForCurrentlyRunningJobs(of: .attachmentDownload) + .infoForCurrentlyRunningJobs(of: .attachmentDownload) .filter { key, _ in key != job.id } .values - .compactMap { data -> String? in - guard let data: Data = data else { return nil } + .compactMap { info -> String? in + guard let data: Data = info.detailsData else { return nil } return (try? JSONDecoder().decode(Details.self, from: data))? .attachmentId diff --git a/SessionMessagingKit/Jobs/Types/ConfigMessageReceiveJob.swift b/SessionMessagingKit/Jobs/Types/ConfigMessageReceiveJob.swift index c5f0ae21f..29fe85ab0 100644 --- a/SessionMessagingKit/Jobs/Types/ConfigMessageReceiveJob.swift +++ b/SessionMessagingKit/Jobs/Types/ConfigMessageReceiveJob.swift @@ -16,17 +16,38 @@ public enum ConfigMessageReceiveJob: JobExecutor { failure: @escaping (Job, Error?, Bool) -> (), deferred: @escaping (Job) -> () ) { + /// When the `configMessageReceive` job fails we want to unblock any `messageReceive` jobs it was blocking + /// to ensure the user isn't losing any messages - this generally _shouldn't_ happen but if it does then having a temporary + /// "outdated" state due to standard messages which would have been invalidated by a config change incorrectly being + /// processed is less severe then dropping a bunch on messages just because they were processed in the same poll as + /// invalid config messages + let removeDependencyOnMessageReceiveJobs: () -> () = { + guard let jobId: Int64 = job.id else { return } + + Storage.shared.write { db in + try JobDependencies + .filter(JobDependencies.Columns.dependantId == jobId) + .joining( + required: JobDependencies.job + .filter(Job.Columns.variant == Job.Variant.messageReceive) + ) + .deleteAll(db) + } + } + guard let detailsData: Data = job.details, let details: Details = try? JSONDecoder().decode(Details.self, from: detailsData) else { + removeDependencyOnMessageReceiveJobs() failure(job, JobRunnerError.missingRequiredDetails, true) return } - + // Ensure no standard messages are sent through this job guard !details.messages.contains(where: { $0.variant != .sharedConfigMessage }) else { SNLog("[ConfigMessageReceiveJob] Standard messages incorrectly sent to the 'configMessageReceive' job") + removeDependencyOnMessageReceiveJobs() failure(job, MessageReceiverError.invalidMessage, true) return } @@ -49,8 +70,10 @@ public enum ConfigMessageReceiveJob: JobExecutor { // Handle the result switch lastError { - case let error as MessageReceiverError where !error.isRetryable: failure(job, error, true) - case .some(let error): failure(job, error, false) + case .some(let error): + removeDependencyOnMessageReceiveJobs() + failure(job, error, true) + case .none: success(job, false) } } diff --git a/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift b/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift index 7dbde8b4a..e7eee9f34 100644 --- a/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift +++ b/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift @@ -9,7 +9,7 @@ import SessionUtilitiesKit public enum ConfigurationSyncJob: JobExecutor { public static let maxFailureCount: Int = -1 - public static let requiresThreadId: Bool = false + public static let requiresThreadId: Bool = true public static let requiresInteractionId: Bool = false private static let maxRunFrequency: TimeInterval = 3 @@ -25,13 +25,29 @@ public enum ConfigurationSyncJob: JobExecutor { Identity.userCompletedRequiredOnboarding() else { return success(job, true) } - // On startup it's possible for multiple ConfigSyncJob's to run at the same time (which is - // redundant) so check if there is another job already running and, if so, defer this job - let jobDetails: [Int64: Data?] = JobRunner.defailsForCurrentlyRunningJobs(of: .configurationSync) - - guard jobDetails.setting(job.id, nil).count == 0 else { - deferred(job) // We will re-enqueue when needed - return + // It's possible for multiple ConfigSyncJob's with the same target (user/group) to try to run at the + // same time since as soon as one is started we will enqueue a second one, rather than adding dependencies + // between the jobs we just continue to defer the subsequent job while the first one is running in + // order to prevent multiple configurationSync jobs with the same target from running at the same time + guard + JobRunner + .infoForCurrentlyRunningJobs(of: .configurationSync) + .filter({ key, info in + key != job.id && // Exclude this job + info.threadId == job.threadId // Exclude jobs for different ids + }) + .isEmpty + else { + // Defer the job to run 'maxRunFrequency' from when this one ran (if we don't it'll try start + // it again immediately which is pointless) + let updatedJob: Job? = Storage.shared.write { db in + try job + .with(nextRunTimestamp: Date().timeIntervalSince1970 + maxRunFrequency) + .saved(db) + } + + SNLog("[ConfigurationSyncJob] For \(job.threadId ?? "UnknownId") deferred due to in progress job") + return deferred(updatedJob ?? job) } // If we don't have a userKeyPair yet then there is no need to sync the configuration @@ -42,16 +58,15 @@ public enum ConfigurationSyncJob: JobExecutor { let pendingConfigChanges: [SessionUtil.OutgoingConfResult] = Storage.shared .read({ db in try SessionUtil.pendingChanges(db, publicKey: publicKey) }) else { - failure(job, StorageError.generic, false) - return + SNLog("[ConfigurationSyncJob] For \(job.threadId ?? "UnknownId") failed due to invalid data") + return failure(job, StorageError.generic, false) } // If there are no pending changes then the job can just complete (next time something // is updated we want to try and run immediately so don't scuedule another run in this case) guard !pendingConfigChanges.isEmpty else { - SNLog("[ConfigurationSyncJob] Completed with no pending changes") - success(job, true) - return + SNLog("[ConfigurationSyncJob] For \(publicKey) completed with no pending changes") + return success(job, true) } // Identify the destination and merge all obsolete hashes into a single set @@ -63,6 +78,8 @@ public enum ConfigurationSyncJob: JobExecutor { .map { $0.obsoleteHashes } .reduce([], +) .asSet() + let jobStartTimestamp: TimeInterval = Date().timeIntervalSince1970 + SNLog("[ConfigurationSyncJob] For \(publicKey) started with \(pendingConfigChanges.count) change\(pendingConfigChanges.count == 1 ? "" : "s")") Storage.shared .readPublisher { db in @@ -119,9 +136,9 @@ public enum ConfigurationSyncJob: JobExecutor { .sinkUntilComplete( receiveCompletion: { result in switch result { - case .finished: SNLog("[ConfigurationSyncJob] Completed") + case .finished: SNLog("[ConfigurationSyncJob] For \(publicKey) completed") case .failure(let error): - SNLog("[ConfigurationSyncJob] Failed due to error: \(error)") + SNLog("[ConfigurationSyncJob] For \(publicKey) failed due to error: \(error)") failure(job, error, false) } }, @@ -137,7 +154,7 @@ public enum ConfigurationSyncJob: JobExecutor { // When we complete the 'ConfigurationSync' job we want to immediately schedule // another one with a 'nextRunTimestamp' set to the 'maxRunFrequency' value to // throttle the config sync requests - let nextRunTimestamp: TimeInterval = (Date().timeIntervalSince1970 + maxRunFrequency) + let nextRunTimestamp: TimeInterval = (jobStartTimestamp + maxRunFrequency) // If another 'ConfigurationSync' job was scheduled then update that one // to run at 'nextRunTimestamp' and make the current job stop @@ -146,6 +163,7 @@ public enum ConfigurationSyncJob: JobExecutor { .filter(Job.Columns.id != job.id) .filter(Job.Columns.variant == Job.Variant.configurationSync) .filter(Job.Columns.threadId == publicKey) + .order(Job.Columns.nextRunTimestamp.asc) .fetchOne(db) { // If the next job isn't currently running then delay it's start time @@ -175,7 +193,6 @@ public enum ConfigurationSyncJob: JobExecutor { // MARK: - Convenience public extension ConfigurationSyncJob { - static func enqueue(_ db: Database, publicKey: String) { // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent guard SessionUtil.userConfigsEnabled(db) else { diff --git a/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift b/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift index 5a89819cb..bcc097efe 100644 --- a/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift +++ b/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift @@ -21,13 +21,14 @@ public enum MessageReceiverError: LocalizedError { case noGroupKeyPair case invalidSharedConfigMessageHandling case requiredThreadNotInConfig + case outdatedMessage public var isRetryable: Bool { switch self { case .duplicateMessage, .duplicateMessageNewSnode, .duplicateControlMessage, .invalidMessage, .unknownMessage, .unknownEnvelopeType, .invalidSignature, .noData, .senderBlocked, .noThread, .selfSend, .decryptionFailed, - .invalidSharedConfigMessageHandling, .requiredThreadNotInConfig: + .invalidSharedConfigMessageHandling, .requiredThreadNotInConfig, .outdatedMessage: return false default: return true @@ -57,6 +58,7 @@ public enum MessageReceiverError: LocalizedError { case .invalidSharedConfigMessageHandling: return "Invalid handling of a shared config message." case .requiredThreadNotInConfig: return "Required thread not in config." + case .outdatedMessage: return "Message was sent before a config change which would have removed the message." } } } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift index 8737f2643..dafc634d2 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift @@ -13,8 +13,31 @@ extension MessageReceiver { threadVariant: SessionThread.Variant, message: CallMessage ) throws { + let timestampMs: Int64 = (message.sentTimestamp.map { Int64($0) } ?? SnodeAPI.currentOffsetTimestampMs()) + // Only support calls from contact threads - guard threadVariant == .contact else { return } + guard + threadVariant == .contact, + /// Only process the message if the thread `shouldBeVisible` or it was sent after the libSession buffer period + ( + SessionThread + .filter(id: threadId) + .filter(SessionThread.Columns.shouldBeVisible == true) + .isNotEmpty(db) || + SessionUtil.conversationInConfig( + db, + threadId: threadId, + threadVariant: threadVariant, + visibleOnly: true + ) || + SessionUtil.canPerformChange( + db, + threadId: threadId, + targetConfig: .contacts, + changeTimestampMs: timestampMs + ) + ) + else { return } switch message.kind { case .preOffer: try MessageReceiver.handleNewCallMessage(db, message: message) diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift index 0fb862067..e413db82c 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift @@ -77,7 +77,28 @@ extension MessageReceiver { targetConfig: .userGroups, changeTimestampMs: Int64(sentTimestamp) ) - else { return SNLog("Ignoring outdated NEW legacy group message due to more recent config state") } + else { + // If the closed group already exists then store the encryption keys (just in case - there can be + // some weird edge-cases where we don't have keys we need if we don't store them) + let groupPublicKey: String = publicKeyAsData.toHexString() + let receivedTimestamp: TimeInterval = (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) + let newKeyPair: ClosedGroupKeyPair = ClosedGroupKeyPair( + threadId: groupPublicKey, + publicKey: Data(encryptionKeyPair.publicKey), + secretKey: Data(encryptionKeyPair.secretKey), + receivedTimestamp: receivedTimestamp + ) + + guard + ClosedGroup.filter(id: groupPublicKey).isNotEmpty(db), + !ClosedGroupKeyPair + .filter(ClosedGroupKeyPair.Columns.threadKeyPairHash == newKeyPair.threadKeyPairHash) + .isNotEmpty(db) + else { return SNLog("Ignoring outdated NEW legacy group message due to more recent config state") } + + try newKeyPair.insert(db) + return + } try handleNewClosedGroup( db, @@ -591,9 +612,10 @@ extension MessageReceiver { message.sentTimestamp.map { Int64($0) } ?? SnodeAPI.currentOffsetTimestampMs() ) + // Only actually make the change if SessionUtil says we can (we always want to insert the info // message though) - if SessionUtil.canPerformChange(db, threadId: threadId, targetConfig: .userGroups, changeTimestampMs: timestampMs ) { + if SessionUtil.canPerformChange(db, threadId: threadId, targetConfig: .userGroups, changeTimestampMs: timestampMs) { // Legacy groups used these control messages for making changes, new groups only use them // for information purposes switch threadVariant { diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+DataExtractionNotification.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+DataExtractionNotification.swift index c1edbbe6a..e9397be26 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+DataExtractionNotification.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+DataExtractionNotification.swift @@ -3,6 +3,7 @@ import Foundation import GRDB import SessionSnodeKit +import SessionUtilitiesKit extension MessageReceiver { internal static func handleDataExtractionNotification( @@ -11,11 +12,45 @@ extension MessageReceiver { threadVariant: SessionThread.Variant, message: DataExtractionNotification ) throws { + let timestampMs: Int64 = ( + message.sentTimestamp.map { Int64($0) } ?? + SnodeAPI.currentOffsetTimestampMs() + ) + guard threadVariant == .contact, let sender: String = message.sender, let messageKind: DataExtractionNotification.Kind = message.kind - else { return } + else { throw MessageReceiverError.invalidMessage } + + /// Only process the message if the thread `shouldBeVisible` or it was sent after the libSession buffer period + guard + SessionThread + .filter(id: threadId) + .filter(SessionThread.Columns.shouldBeVisible == true) + .isNotEmpty(db) || + SessionUtil.conversationInConfig( + db, + threadId: threadId, + threadVariant: threadVariant, + visibleOnly: true + ) || + SessionUtil.canPerformChange( + db, + threadId: threadId, + targetConfig: { + switch threadVariant { + case .contact: + let currentUserPublicKey: String = getUserHexEncodedPublicKey(db) + + return (threadId == currentUserPublicKey ? .userProfile : .contacts) + + default: return .userGroups + } + }(), + changeTimestampMs: timestampMs + ) + else { throw MessageReceiverError.outdatedMessage } _ = try Interaction( serverHash: message.serverHash, @@ -27,10 +62,7 @@ extension MessageReceiver { case .mediaSaved: return .infoMediaSavedNotification } }(), - timestampMs: ( - message.sentTimestamp.map { Int64($0) } ?? - SnodeAPI.currentOffsetTimestampMs() - ) + timestampMs: timestampMs ).inserted(db) } } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ExpirationTimers.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ExpirationTimers.swift index 1d8ef1033..fbec8536b 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ExpirationTimers.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ExpirationTimers.swift @@ -15,9 +15,9 @@ extension MessageReceiver { // Only process these for contact and legacy groups (new groups handle it separately) (threadVariant == .contact || threadVariant == .legacyGroup), let sender: String = message.sender - else { return } + else { throw MessageReceiverError.invalidMessage } - // Update the configuration + // Generate an updated configuration // // Note: Messages which had been sent during the previous configuration will still // use it's settings (so if you enable, send a message and then disable disappearing @@ -34,18 +34,40 @@ extension MessageReceiver { DisappearingMessagesConfiguration.defaultDuration ) ) + let timestampMs: Int64 = Int64(message.sentTimestamp ?? 0) // Default to `0` if not set - // Legacy closed groups need to update the SessionUtil - switch threadVariant { - case .legacyGroup: - try SessionUtil - .update( - db, - groupPublicKey: threadId, - disappearingConfig: config - ) - - default: break + // Only actually make the change if SessionUtil says we can (we always want to insert the info + // message though) + let canPerformChange: Bool = SessionUtil.canPerformChange( + db, + threadId: threadId, + targetConfig: { + switch threadVariant { + case .contact: + let currentUserPublicKey: String = getUserHexEncodedPublicKey(db) + + return (threadId == currentUserPublicKey ? .userProfile : .contacts) + + default: return .userGroups + } + }(), + changeTimestampMs: timestampMs + ) + + // Only update libSession if we can perform the change + if canPerformChange { + // Legacy closed groups need to update the SessionUtil + switch threadVariant { + case .legacyGroup: + try SessionUtil + .update( + db, + groupPublicKey: threadId, + disappearingConfig: config + ) + + default: break + } } // Add an info message for the user @@ -60,11 +82,14 @@ extension MessageReceiver { nil ) ), - timestampMs: Int64(message.sentTimestamp ?? 0) // Default to `0` if not set + timestampMs: timestampMs ).inserted(db) - // Finally save the changes to the DisappearingMessagesConfiguration (If it's a duplicate - // then the interaction unique constraint will prevent the code from getting here) - try config.save(db) + // Only save the updated config if we can perform the change + if canPerformChange { + // Finally save the changes to the DisappearingMessagesConfiguration (If it's a duplicate + // then the interaction unique constraint will prevent the code from getting here) + try config.save(db) + } } } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift index 16968da4a..fbaad1124 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift @@ -13,11 +13,37 @@ extension MessageReceiver { dependencies: SMKDependencies ) throws { let userPublicKey = getUserHexEncodedPublicKey(db, dependencies: dependencies) + let timestampMs: Int64 = ( + message.sentTimestamp.map { Int64($0) } ?? + SnodeAPI.currentOffsetTimestampMs() + ) var blindedContactIds: [String] = [] // Ignore messages which were sent from the current user - guard message.sender != userPublicKey else { return } - guard let senderId: String = message.sender else { return } + guard + message.sender != userPublicKey, + let senderId: String = message.sender + else { throw MessageReceiverError.invalidMessage } + + /// Only process the message if the thread `shouldBeVisible` or it was sent after the libSession buffer period + guard + SessionThread + .filter(id: senderId) + .filter(SessionThread.Columns.shouldBeVisible == true) + .isNotEmpty(db) || + SessionUtil.conversationInConfig( + db, + threadId: senderId, + threadVariant: .contact, + visibleOnly: true + ) || + SessionUtil.canPerformChange( + db, + threadId: senderId, + targetConfig: .contacts, + changeTimestampMs: timestampMs + ) + else { throw MessageReceiverError.outdatedMessage } // Update profile if needed (want to do this regardless of whether the message exists or // not to ensure the profile info gets sync between a users devices at every chance) @@ -134,10 +160,7 @@ extension MessageReceiver { threadId: unblindedThread.id, authorId: senderId, variant: .infoMessageRequestAccepted, - timestampMs: ( - message.sentTimestamp.map { Int64($0) } ?? - SnodeAPI.currentOffsetTimestampMs() - ) + timestampMs: timestampMs ).inserted(db) } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift index a891bb3ba..d41cf4f40 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift @@ -3,6 +3,7 @@ import Foundation import GRDB import Sodium +import SessionSnodeKit import SessionUtilitiesKit extension MessageReceiver { @@ -22,6 +23,32 @@ extension MessageReceiver { // seconds to maintain the accuracy) let messageSentTimestamp: TimeInterval = (TimeInterval(message.sentTimestamp ?? 0) / 1000) let isMainAppActive: Bool = (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) + let currentUserPublicKey: String = getUserHexEncodedPublicKey(db, dependencies: dependencies) + + /// Only process the message if the thread `shouldBeVisible` or it was sent after the libSession buffer period + guard + SessionThread + .filter(id: threadId) + .filter(SessionThread.Columns.shouldBeVisible == true) + .isNotEmpty(db) || + SessionUtil.conversationInConfig( + db, + threadId: threadId, + threadVariant: threadVariant, + visibleOnly: true + ) || + SessionUtil.canPerformChange( + db, + threadId: threadId, + targetConfig: { + switch threadVariant { + case .contact: return (threadId == currentUserPublicKey ? .userProfile : .contacts) + default: return .userGroups + } + }(), + changeTimestampMs: (message.sentTimestamp.map { Int64($0) } ?? SnodeAPI.currentOffsetTimestampMs()) + ) + else { throw MessageReceiverError.outdatedMessage } // Update profile if needed (want to do this regardless of whether the message exists or // not to ensure the profile info gets sync between a users devices at every chance) @@ -63,7 +90,6 @@ extension MessageReceiver { } // Store the message variant so we can run variant-specific behaviours - let currentUserPublicKey: String = getUserHexEncodedPublicKey(db, dependencies: dependencies) let thread: SessionThread = try SessionThread .fetchOrCreate(db, id: threadId, variant: threadVariant, shouldBeVisible: nil) let maybeOpenGroup: OpenGroup? = { diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift index 63301fa5b..66fe34ec3 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift @@ -31,7 +31,8 @@ internal extension SessionUtil { static func handleContactsUpdate( _ db: Database, in conf: UnsafeMutablePointer?, - mergeNeedsDump: Bool + mergeNeedsDump: Bool, + latestConfigSentTimestampMs: Int64 ) throws { typealias ContactData = [ String: ( @@ -64,6 +65,7 @@ internal extension SessionUtil { let profileResult: Profile = Profile( id: contactId, name: String(libSessionVal: contact.name), + lastNameUpdate: (TimeInterval(latestConfigSentTimestampMs) / 1000), nickname: String(libSessionVal: contact.nickname, nullIfEmpty: true), profilePictureUrl: profilePictureUrl, profileEncryptionKey: (profilePictureUrl == nil ? nil : @@ -71,7 +73,8 @@ internal extension SessionUtil { libSessionVal: contact.profile_pic.key, count: ProfileManager.avatarAES256KeyByteLength ) - ) + ), + lastProfilePictureUpdate: (TimeInterval(latestConfigSentTimestampMs) / 1000) ) contactData[contactId] = ( @@ -99,12 +102,23 @@ internal extension SessionUtil { // observation system can't differ between update calls which do and don't change anything) let contact: Contact = Contact.fetchOrCreate(db, id: sessionId) let profile: Profile = Profile.fetchOrCreate(db, id: sessionId) + let profileNameShouldBeUpdated: Bool = ( + !data.profile.name.isEmpty && + profile.name != data.profile.name && + profile.lastNameUpdate < data.profile.lastNameUpdate + ) + let profilePictureShouldBeUpdated: Bool = ( + ( + profile.profilePictureUrl != data.profile.profilePictureUrl || + profile.profileEncryptionKey != data.profile.profileEncryptionKey + ) && + profile.lastProfilePictureUpdate < data.profile.lastProfilePictureUpdate + ) if - (!data.profile.name.isEmpty && profile.name != data.profile.name) || + profileNameShouldBeUpdated || profile.nickname != data.profile.nickname || - profile.profilePictureUrl != data.profile.profilePictureUrl || - profile.profileEncryptionKey != data.profile.profileEncryptionKey + profilePictureShouldBeUpdated { try profile.save(db) try Profile @@ -112,9 +126,12 @@ internal extension SessionUtil { .updateAll( // Handling a config update so don't use `updateAllAndConfig` db, [ - (data.profile.name.isEmpty || profile.name == data.profile.name ? nil : + (!profileNameShouldBeUpdated ? nil : Profile.Columns.name.set(to: data.profile.name) ), + (!profileNameShouldBeUpdated ? nil : + Profile.Columns.lastNameUpdate.set(to: data.profile.lastNameUpdate) + ), (profile.nickname == data.profile.nickname ? nil : Profile.Columns.nickname.set(to: data.profile.nickname) ), @@ -123,6 +140,9 @@ internal extension SessionUtil { ), (profile.profileEncryptionKey != data.profile.profileEncryptionKey ? nil : Profile.Columns.profileEncryptionKey.set(to: data.profile.profileEncryptionKey) + ), + (!profilePictureShouldBeUpdated ? nil : + Profile.Columns.lastProfilePictureUpdate.set(to: data.profile.lastProfilePictureUpdate) ) ].compactMap { $0 } ) diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift index 3ea0d9194..f6d6e74a5 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift @@ -326,7 +326,7 @@ internal extension SessionUtil { .defaulting(to: 0) // Ensure the change occurred after the last config message was handled (minus the buffer period) - return (changeTimestampMs > (configDumpTimestampMs - Int64(SessionUtil.configChangeBufferPeriod * 1000))) + return (changeTimestampMs >= (configDumpTimestampMs - Int64(SessionUtil.configChangeBufferPeriod * 1000))) } } diff --git a/SessionMessagingKit/SessionUtil/SessionUtil.swift b/SessionMessagingKit/SessionUtil/SessionUtil.swift index 391a4a45a..de5e22d45 100644 --- a/SessionMessagingKit/SessionUtil/SessionUtil.swift +++ b/SessionMessagingKit/SessionUtil/SessionUtil.swift @@ -461,7 +461,8 @@ public enum SessionUtil { try SessionUtil.handleContactsUpdate( db, in: conf, - mergeNeedsDump: config_needs_dump(conf) + mergeNeedsDump: config_needs_dump(conf), + latestConfigSentTimestampMs: latestConfigSentTimestampMs ) case .convoInfoVolatile: diff --git a/SessionMessagingKit/Utilities/ProfileManager.swift b/SessionMessagingKit/Utilities/ProfileManager.swift index 4b5708675..ffbff0aeb 100644 --- a/SessionMessagingKit/Utilities/ProfileManager.swift +++ b/SessionMessagingKit/Utilities/ProfileManager.swift @@ -495,47 +495,28 @@ public struct ProfileManager { // Name if let name: String = name, !name.isEmpty, name != profile.name { - let shouldUpdate: Bool = { - guard isCurrentUser else { return true } - - return UserDefaults.standard[.lastDisplayNameUpdate] - .map { sentTimestamp > $0.timeIntervalSince1970 } - .defaulting(to: true) - }() - - if shouldUpdate { - if isCurrentUser { - UserDefaults.standard[.lastDisplayNameUpdate] = Date(timeIntervalSince1970: sentTimestamp) - } - + // FIXME: Remove the `userConfigsEnabled` check once `useSharedUtilForUserConfig` is permanent + if sentTimestamp > profile.lastNameUpdate || (isCurrentUser && (calledFromConfigHandling || !SessionUtil.userConfigsEnabled(db))) { profileChanges.append(Profile.Columns.name.set(to: name)) + profileChanges.append(Profile.Columns.lastNameUpdate.set(to: sentTimestamp)) } } // Profile picture & profile key var avatarNeedsDownload: Bool = false var targetAvatarUrl: String? = nil - let shouldUpdateAvatar: Bool = { - guard isCurrentUser else { return true } - - return UserDefaults.standard[.lastProfilePictureUpdate] - .map { sentTimestamp > $0.timeIntervalSince1970 } - .defaulting(to: true) - }() - if shouldUpdateAvatar { + // FIXME: Remove the `userConfigsEnabled` check once `useSharedUtilForUserConfig` is permanent + if sentTimestamp > profile.lastProfilePictureUpdate || (isCurrentUser && (calledFromConfigHandling || !SessionUtil.userConfigsEnabled(db))) { switch avatarUpdate { case .none: break case .uploadImageData: preconditionFailure("Invalid options for this function") case .remove: - if isCurrentUser { - UserDefaults.standard[.lastProfilePictureUpdate] = Date(timeIntervalSince1970: sentTimestamp) - } - profileChanges.append(Profile.Columns.profilePictureUrl.set(to: nil)) profileChanges.append(Profile.Columns.profileEncryptionKey.set(to: nil)) profileChanges.append(Profile.Columns.profilePictureFileName.set(to: nil)) + profileChanges.append(Profile.Columns.lastProfilePictureUpdate.set(to: sentTimestamp)) case .updateTo(let url, let key, let fileName): if url != profile.profilePictureUrl { @@ -558,6 +539,9 @@ public struct ProfileManager { !ProfileManager.hasProfileImageData(with: fileName) ) } + + // Update the 'lastProfilePictureUpdate' timestamp for either external or local changes + profileChanges.append(Profile.Columns.lastProfilePictureUpdate.set(to: sentTimestamp)) } } diff --git a/SessionUIKit/Components/ProfilePictureView.swift b/SessionUIKit/Components/ProfilePictureView.swift index 1ffe575a7..b05e69606 100644 --- a/SessionUIKit/Components/ProfilePictureView.swift +++ b/SessionUIKit/Components/ProfilePictureView.swift @@ -65,7 +65,7 @@ public final class ProfilePictureView: UIView { var iconSize: CGFloat { switch self { - case .navigation, .message: return 8 + case .navigation, .message: return 10 // Intentionally not a multiple of 4 case .list: return 16 case .hero: return 24 } diff --git a/SessionUtilitiesKit/Database/Models/JobDependencies.swift b/SessionUtilitiesKit/Database/Models/JobDependencies.swift index 16201367b..bca762c5a 100644 --- a/SessionUtilitiesKit/Database/Models/JobDependencies.swift +++ b/SessionUtilitiesKit/Database/Models/JobDependencies.swift @@ -7,8 +7,8 @@ public struct JobDependencies: Codable, Equatable, Hashable, FetchableRecord, Pe public static var databaseTableName: String { "jobDependencies" } internal static let jobForeignKey = ForeignKey([Columns.jobId], to: [Job.Columns.id]) internal static let dependantForeignKey = ForeignKey([Columns.dependantId], to: [Job.Columns.id]) - internal static let job = belongsTo(Job.self, using: jobForeignKey) - internal static let dependant = hasOne(Job.self, using: Job.dependencyForeignKey) + public static let job = belongsTo(Job.self, using: jobForeignKey) + public static let dependant = hasOne(Job.self, using: Job.dependencyForeignKey) public typealias Columns = CodingKeys public enum CodingKeys: String, CodingKey, ColumnExpression { diff --git a/SessionUtilitiesKit/General/SNUserDefaults.swift b/SessionUtilitiesKit/General/SNUserDefaults.swift index 82f18bd64..bc55e8231 100644 --- a/SessionUtilitiesKit/General/SNUserDefaults.swift +++ b/SessionUtilitiesKit/General/SNUserDefaults.swift @@ -38,8 +38,6 @@ public enum SNUserDefaults { public enum Date: Swift.String { case lastConfigurationSync - case lastDisplayNameUpdate - case lastProfilePictureUpdate case lastProfilePictureUpload case lastOpenGroupImageUpdate case lastOpen diff --git a/SessionUtilitiesKit/JobRunner/JobRunner.swift b/SessionUtilitiesKit/JobRunner/JobRunner.swift index 19a750f26..48752c5bf 100644 --- a/SessionUtilitiesKit/JobRunner/JobRunner.swift +++ b/SessionUtilitiesKit/JobRunner/JobRunner.swift @@ -42,6 +42,12 @@ public final class JobRunner { case notFound } + public struct JobInfo { + public let threadId: String? + public let interactionId: Int64? + public let detailsData: Data? + } + private static let blockingQueue: Atomic = Atomic( JobQueue( type: .blocking, @@ -381,8 +387,8 @@ public final class JobRunner { return (queues.wrappedValue[job.variant]?.isCurrentlyRunning(jobId) == true) } - public static func defailsForCurrentlyRunningJobs(of variant: Job.Variant) -> [Int64: Data?] { - return (queues.wrappedValue[variant]?.detailsForAllCurrentlyRunningJobs()) + public static func infoForCurrentlyRunningJobs(of variant: Job.Variant) -> [Int64: JobInfo] { + return (queues.wrappedValue[variant]?.infoForAllCurrentlyRunningJobs()) .defaulting(to: [:]) } @@ -395,11 +401,24 @@ public final class JobRunner { queue.afterCurrentlyRunningJob(jobId, callback: callback) } - public static func hasPendingOrRunningJob(with variant: Job.Variant, details: T) -> Bool { + public static func hasPendingOrRunningJob( + with variant: Job.Variant, + threadId: String? = nil, + interactionId: Int64? = nil, + details: T? = nil + ) -> Bool { guard let targetQueue: JobQueue = queues.wrappedValue[variant] else { return false } - guard let detailsData: Data = try? JSONEncoder().encode(details) else { return false } - return targetQueue.hasPendingOrRunningJob(with: detailsData) + // Ensure we can encode the details (if provided) + let detailsData: Data? = details.map { try? JSONEncoder().encode($0) } + + guard details == nil || detailsData != nil else { return false } + + return targetQueue.hasPendingOrRunningJobWith( + threadId: threadId, + interactionId: interactionId, + detailsData: detailsData + ) } public static func removePendingJob(_ job: Job?) { @@ -513,9 +532,9 @@ private final class JobQueue { private var nextTrigger: Atomic = Atomic(nil) fileprivate var isRunning: Atomic = Atomic(false) private var queue: Atomic<[Job]> = Atomic([]) - private var jobsCurrentlyRunning: Atomic> = Atomic([]) private var jobCallbacks: Atomic<[Int64: [(JobRunner.JobResult) -> ()]]> = Atomic([:]) - private var detailsForCurrentlyRunningJobs: Atomic<[Int64: Data?]> = Atomic([:]) + private var currentlyRunningJobIds: Atomic> = Atomic([]) + private var currentlyRunningJobInfo: Atomic<[Int64: JobRunner.JobInfo]> = Atomic([:]) private var deferLoopTracker: Atomic<[Int64: (count: Int, times: [TimeInterval])]> = Atomic([:]) fileprivate var hasPendingJobs: Bool { !queue.wrappedValue.isEmpty } @@ -620,7 +639,7 @@ private final class JobQueue { } fileprivate func appDidBecomeActive(with jobs: [Job], canStart: Bool) { - let currentlyRunningJobIds: Set = jobsCurrentlyRunning.wrappedValue + let currentlyRunningJobIds: Set = currentlyRunningJobIds.wrappedValue queue.mutate { queue in // Avoid re-adding jobs to the queue that are already in it (this can @@ -642,11 +661,11 @@ private final class JobQueue { } fileprivate func isCurrentlyRunning(_ jobId: Int64) -> Bool { - return jobsCurrentlyRunning.wrappedValue.contains(jobId) + return currentlyRunningJobIds.wrappedValue.contains(jobId) } - fileprivate func detailsForAllCurrentlyRunningJobs() -> [Int64: Data?] { - return detailsForCurrentlyRunningJobs.wrappedValue + fileprivate func infoForAllCurrentlyRunningJobs() -> [Int64: JobRunner.JobInfo] { + return currentlyRunningJobInfo.wrappedValue } fileprivate func afterCurrentlyRunningJob(_ jobId: Int64, callback: @escaping (JobRunner.JobResult) -> ()) { @@ -660,14 +679,65 @@ private final class JobQueue { } } - fileprivate func hasPendingOrRunningJob(with detailsData: Data?) -> Bool { - guard let detailsData: Data = detailsData else { return false } - + fileprivate func hasPendingOrRunningJobWith( + threadId: String? = nil, + interactionId: Int64? = nil, + detailsData: Data? = nil + ) -> Bool { let pendingJobs: [Job] = queue.wrappedValue + let currentlyRunningJobInfo: [Int64: JobRunner.JobInfo] = currentlyRunningJobInfo.wrappedValue + var possibleJobIds: Set = Set(currentlyRunningJobInfo.keys) + .inserting(contentsOf: pendingJobs.compactMap { $0.id }.asSet()) - guard !pendingJobs.contains(where: { job in job.details == detailsData }) else { return true } + // Remove any which don't have the matching threadId (if provided) + if let targetThreadId: String = threadId { + let pendingJobIdsWithWrongThreadId: Set = pendingJobs + .filter { $0.threadId != targetThreadId } + .compactMap { $0.id } + .asSet() + let runningJobIdsWithWrongThreadId: Set = currentlyRunningJobInfo + .filter { _, info -> Bool in info.threadId != targetThreadId } + .map { key, _ in key } + .asSet() + + possibleJobIds = possibleJobIds + .subtracting(pendingJobIdsWithWrongThreadId) + .subtracting(runningJobIdsWithWrongThreadId) + } - return detailsForCurrentlyRunningJobs.wrappedValue.values.contains(detailsData) + // Remove any which don't have the matching interactionId (if provided) + if let targetInteractionId: Int64 = interactionId { + let pendingJobIdsWithWrongInteractionId: Set = pendingJobs + .filter { $0.interactionId != targetInteractionId } + .compactMap { $0.id } + .asSet() + let runningJobIdsWithWrongInteractionId: Set = currentlyRunningJobInfo + .filter { _, info -> Bool in info.interactionId != targetInteractionId } + .map { key, _ in key } + .asSet() + + possibleJobIds = possibleJobIds + .subtracting(pendingJobIdsWithWrongInteractionId) + .subtracting(runningJobIdsWithWrongInteractionId) + } + + // Remove any which don't have the matching details (if provided) + if let targetDetailsData: Data = detailsData { + let pendingJobIdsWithWrongDetailsData: Set = pendingJobs + .filter { $0.details != targetDetailsData } + .compactMap { $0.id } + .asSet() + let runningJobIdsWithWrongDetailsData: Set = currentlyRunningJobInfo + .filter { _, info -> Bool in info.detailsData != detailsData } + .map { key, _ in key } + .asSet() + + possibleJobIds = possibleJobIds + .subtracting(pendingJobIdsWithWrongDetailsData) + .subtracting(runningJobIdsWithWrongDetailsData) + } + + return !possibleJobIds.isEmpty } fileprivate func removePendingJob(_ jobId: Int64) { @@ -706,7 +776,7 @@ private final class JobQueue { } // Get any pending jobs - let jobIdsAlreadyRunning: Set = jobsCurrentlyRunning.wrappedValue + let jobIdsAlreadyRunning: Set = currentlyRunningJobIds.wrappedValue let jobsAlreadyInQueue: Set = queue.wrappedValue.compactMap { $0.id }.asSet() let jobsToRun: [Job] = Storage.shared.read { db in try Job @@ -765,7 +835,7 @@ private final class JobQueue { } guard let (nextJob, numJobsRemaining): (Job, Int) = queue.mutate({ queue in queue.popFirst().map { ($0, queue.count) } }) else { // If it's a serial queue, or there are no more jobs running then update the 'isRunning' flag - if executionType != .concurrent || jobsCurrentlyRunning.wrappedValue.isEmpty { + if executionType != .concurrent || currentlyRunningJobIds.wrappedValue.isEmpty { isRunning.mutate { $0 = false } } @@ -827,7 +897,7 @@ private final class JobQueue { /// /// **Note:** We don't add the current job back the the queue because it should only be re-added if it's dependencies /// are successfully completed - let currentlyRunningJobIds: [Int64] = Array(detailsForCurrentlyRunningJobs.wrappedValue.keys) + let currentlyRunningJobIds: [Int64] = Array(currentlyRunningJobIds.wrappedValue) let dependencyJobsNotCurrentlyRunning: [Job] = dependencyInfo.jobs .filter { job in !currentlyRunningJobIds.contains(job.id ?? -1) } .sorted { lhs, rhs in (lhs.id ?? -1) < (rhs.id ?? -1) } @@ -851,11 +921,20 @@ private final class JobQueue { trigger?.invalidate() // Need to invalidate to prevent a memory leak trigger = nil } - jobsCurrentlyRunning.mutate { jobsCurrentlyRunning in - jobsCurrentlyRunning = jobsCurrentlyRunning.inserting(nextJob.id) - numJobsRunning = jobsCurrentlyRunning.count + currentlyRunningJobIds.mutate { currentlyRunningJobIds in + currentlyRunningJobIds = currentlyRunningJobIds.inserting(nextJob.id) + numJobsRunning = currentlyRunningJobIds.count + } + currentlyRunningJobInfo.mutate { currentlyRunningJobInfo in + currentlyRunningJobInfo = currentlyRunningJobInfo.setting( + nextJob.id, + JobRunner.JobInfo( + threadId: nextJob.threadId, + interactionId: nextJob.interactionId, + detailsData: nextJob.details + ) + ) } - detailsForCurrentlyRunningJobs.mutate { $0 = $0.setting(nextJob.id, nextJob.details) } SNLog("[JobRunner] \(queueContext) started \(nextJob.variant) job (\(executionType == .concurrent ? "\(numJobsRunning) currently running, " : "")\(numJobsRemaining) remaining)") /// As it turns out Combine doesn't plat too nicely with concurrent Dispatch Queues, in Combine events are dispatched asynchronously to @@ -894,7 +973,7 @@ private final class JobQueue { } private func scheduleNextSoonestJob() { - let jobIdsAlreadyRunning: Set = jobsCurrentlyRunning.wrappedValue + let jobIdsAlreadyRunning: Set = currentlyRunningJobIds.wrappedValue let nextJobTimestamp: TimeInterval? = Storage.shared.read { db in try Job .filterPendingJobs( @@ -911,7 +990,7 @@ private final class JobQueue { // If there are no remaining jobs or the JobRunner isn't allowed to start any queues then trigger // the 'onQueueDrained' callback and stop guard let nextJobTimestamp: TimeInterval = nextJobTimestamp, JobRunner.canStartQueues.wrappedValue else { - if executionType != .concurrent || jobsCurrentlyRunning.wrappedValue.isEmpty { + if executionType != .concurrent || currentlyRunningJobIds.wrappedValue.isEmpty { self.onQueueDrained?() } return @@ -922,7 +1001,7 @@ private final class JobQueue { guard secondsUntilNextJob > 0 else { // Only log that the queue is getting restarted if this queue had actually been about to stop - if executionType != .concurrent || jobsCurrentlyRunning.wrappedValue.isEmpty { + if executionType != .concurrent || currentlyRunningJobIds.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" @@ -940,7 +1019,7 @@ private final class JobQueue { } // Only schedule a trigger if this queue has actually completed - guard executionType != .concurrent || jobsCurrentlyRunning.wrappedValue.isEmpty else { return } + guard executionType != .concurrent || currentlyRunningJobIds.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")") @@ -1029,7 +1108,7 @@ private final class JobQueue { /// **Note:** If any of these `dependantJobs` have other dependencies then when they attempt to start they will be /// removed from the queue, replaced by their dependencies if !dependantJobs.isEmpty { - let currentlyRunningJobIds: [Int64] = Array(detailsForCurrentlyRunningJobs.wrappedValue.keys) + let currentlyRunningJobIds: [Int64] = Array(currentlyRunningJobIds.wrappedValue) let dependantJobsNotCurrentlyRunning: [Job] = dependantJobs .filter { job in !currentlyRunningJobIds.contains(job.id ?? -1) } .sorted { lhs, rhs in (lhs.id ?? -1) < (rhs.id ?? -1) } @@ -1211,8 +1290,8 @@ private final class JobQueue { private func performCleanUp(for job: Job, result: JobRunner.JobResult, shouldTriggerCallbacks: Bool = true) { // The job is removed from the queue before it runs so all we need to to is remove it // from the 'currentlyRunning' set - jobsCurrentlyRunning.mutate { $0 = $0.removing(job.id) } - detailsForCurrentlyRunningJobs.mutate { $0 = $0.removingValue(forKey: job.id) } + currentlyRunningJobIds.mutate { $0 = $0.removing(job.id) } + currentlyRunningJobInfo.mutate { $0 = $0.removingValue(forKey: job.id) } guard shouldTriggerCallbacks else { return } From 5760c23cbcb7aeb7a33a234a21269fb30e1bcfc6 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 31 May 2023 15:46:41 +1000 Subject: [PATCH 083/135] Updated the LintLocalizableStrings to exclude built extension files --- Scripts/LintLocalizableStrings.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Scripts/LintLocalizableStrings.swift b/Scripts/LintLocalizableStrings.swift index 956822df3..a363d73f6 100755 --- a/Scripts/LintLocalizableStrings.swift +++ b/Scripts/LintLocalizableStrings.swift @@ -28,6 +28,7 @@ var localizableFiles: [String] = { .filter { $0.hasSuffix("Localizable.strings") && !$0.contains(".app/") && // Exclude Built Localizable.strings files + !$0.contains(".appex/") && // Exclude Built Localizable.strings extension files !$0.contains("Pods") // Exclude Pods } }() @@ -38,6 +39,7 @@ var executableFiles: [String] = { return pathFiles.filter { !$0.localizedCaseInsensitiveContains("test") && // Exclude test files !$0.contains(".app/") && // Exclude Built Localizable.strings files + !$0.contains(".appex/") && // Exclude Built Localizable.strings extension files !$0.contains("Pods") && // Exclude Pods ( NSString(string: $0).pathExtension == "swift" || From 6209f2b5c1f00552bcbdbc514a08a00e3fbb9fcd Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 31 May 2023 16:23:04 +1000 Subject: [PATCH 084/135] Updated LintLocalizableStrings to be a bit more exclusive Removed a bunch of unused hidden translation files --- Scripts/LintLocalizableStrings.swift | 39 ++++++++++-------- Session/Meta/Translations/.tx/config | 8 ---- .../an_translation | Bin 15408 -> 0 bytes .../ar_translation | Bin 15732 -> 0 bytes .../bg_BG_translation | Bin 15408 -> 0 bytes .../ca_translation | Bin 16282 -> 0 bytes .../cs_translation | Bin 15016 -> 0 bytes .../da_translation | Bin 15808 -> 0 bytes .../de_translation | Bin 16728 -> 0 bytes .../es_translation | Bin 16732 -> 0 bytes .../eu_translation | Bin 15834 -> 0 bytes .../fa_IR_translation | Bin 15408 -> 0 bytes .../fa_translation | Bin 15590 -> 0 bytes .../fi_translation | Bin 15888 -> 0 bytes .../fil_translation | Bin 17564 -> 0 bytes .../fr_translation | Bin 16810 -> 0 bytes .../he_translation | Bin 14686 -> 0 bytes .../hu_translation | Bin 15408 -> 0 bytes .../it_IT_translation | Bin 16542 -> 0 bytes .../ja_JP_translation | Bin 13480 -> 0 bytes .../lv_translation | Bin 15408 -> 0 bytes .../nb_translation | Bin 15632 -> 0 bytes .../nl_translation | Bin 16494 -> 0 bytes .../pl_translation | Bin 16004 -> 0 bytes .../pt_BR_translation | Bin 16586 -> 0 bytes .../ro_translation | Bin 15746 -> 0 bytes .../ru_translation | Bin 16678 -> 0 bytes .../sl_translation | Bin 16154 -> 0 bytes .../sq_translation | Bin 15408 -> 0 bytes .../sv_SE_translation | Bin 15660 -> 0 bytes .../ta_translation | Bin 15408 -> 0 bytes .../tr_TR_translation | Bin 15408 -> 0 bytes .../uk_translation | Bin 15408 -> 0 bytes .../zh_CN_translation | Bin 15408 -> 0 bytes .../zh_TW.Big5_translation | Bin 15408 -> 0 bytes .../zh_TW_translation | Bin 15408 -> 0 bytes 36 files changed, 21 insertions(+), 26 deletions(-) delete mode 100644 Session/Meta/Translations/.tx/config delete mode 100644 Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/an_translation delete mode 100644 Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/ar_translation delete mode 100644 Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/bg_BG_translation delete mode 100644 Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/ca_translation delete mode 100644 Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/cs_translation delete mode 100644 Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/da_translation delete mode 100644 Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/de_translation delete mode 100644 Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/es_translation delete mode 100644 Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/eu_translation delete mode 100644 Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/fa_IR_translation delete mode 100644 Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/fa_translation delete mode 100644 Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/fi_translation delete mode 100644 Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/fil_translation delete mode 100644 Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/fr_translation delete mode 100644 Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/he_translation delete mode 100644 Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/hu_translation delete mode 100644 Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/it_IT_translation delete mode 100644 Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/ja_JP_translation delete mode 100644 Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/lv_translation delete mode 100644 Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/nb_translation delete mode 100644 Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/nl_translation delete mode 100644 Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/pl_translation delete mode 100644 Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/pt_BR_translation delete mode 100644 Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/ro_translation delete mode 100644 Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/ru_translation delete mode 100644 Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/sl_translation delete mode 100644 Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/sq_translation delete mode 100644 Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/sv_SE_translation delete mode 100644 Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/ta_translation delete mode 100644 Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/tr_TR_translation delete mode 100644 Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/uk_translation delete mode 100644 Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/zh_CN_translation delete mode 100644 Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/zh_TW.Big5_translation delete mode 100644 Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/zh_TW_translation diff --git a/Scripts/LintLocalizableStrings.swift b/Scripts/LintLocalizableStrings.swift index a363d73f6..a000469a5 100755 --- a/Scripts/LintLocalizableStrings.swift +++ b/Scripts/LintLocalizableStrings.swift @@ -14,37 +14,40 @@ let currentPath = ( /// List of files in currentPath - recursive var pathFiles: [String] = { - guard let enumerator = fileManager.enumerator(atPath: currentPath), let files = enumerator.allObjects as? [String] else { - fatalError("Could not locate files in path directory: \(currentPath)") - } + guard + let enumerator = fileManager.enumerator(atPath: currentPath), + let files = enumerator.allObjects as? [String] + else { fatalError("Could not locate files in path directory: \(currentPath)") } return files + .filter { + !$0.starts(with: ".") && // Exclude hidden files (.git, .DS_STORE, etc.) + !$0.contains("Pods/") && // Exclude files under the pods folder + !$0.contains(".xcassets") && // Exclude asset bundles + !$0.contains(".app/") && // Exclude files in the app build directories + !$0.contains(".appex/") && // Exclude files in the extension build directories + !$0.localizedCaseInsensitiveContains("tests/") && // Exclude files under test directories + !$0.localizedCaseInsensitiveContains("external/") && ( // Exclude files under external directories + // Only include relevant files + $0.hasSuffix("Localizable.strings") || + NSString(string: $0).pathExtension == "swift" || + NSString(string: $0).pathExtension == "m" + ) + } }() /// List of localizable files - not including Localizable files in the Pods var localizableFiles: [String] = { - return pathFiles - .filter { - $0.hasSuffix("Localizable.strings") && - !$0.contains(".app/") && // Exclude Built Localizable.strings files - !$0.contains(".appex/") && // Exclude Built Localizable.strings extension files - !$0.contains("Pods") // Exclude Pods - } + return pathFiles.filter { $0.hasSuffix("Localizable.strings") } }() /// List of executable files var executableFiles: [String] = { return pathFiles.filter { - !$0.localizedCaseInsensitiveContains("test") && // Exclude test files - !$0.contains(".app/") && // Exclude Built Localizable.strings files - !$0.contains(".appex/") && // Exclude Built Localizable.strings extension files - !$0.contains("Pods") && // Exclude Pods - ( - NSString(string: $0).pathExtension == "swift" || - NSString(string: $0).pathExtension == "m" - ) + $0.hasSuffix(".swift") || + $0.hasSuffix(".m") } }() diff --git a/Session/Meta/Translations/.tx/config b/Session/Meta/Translations/.tx/config deleted file mode 100644 index 16f90c7c6..000000000 --- a/Session/Meta/Translations/.tx/config +++ /dev/null @@ -1,8 +0,0 @@ -[main] -host = https://www.transifex.com - -[signal-ios.localizablestrings-30] -file_filter = .lproj/Localizable.strings -source_file = en.lproj/Localizable.strings -source_lang = en - diff --git a/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/an_translation b/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/an_translation deleted file mode 100644 index 896593f04274a24d3103a2bb2348f26a921d652c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15408 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 diff --git a/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/ar_translation b/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/ar_translation deleted file mode 100644 index 0a4f595fedd6295fe45f71e8d0b1ad1a52d95e8f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15732 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 diff --git a/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/ca_translation b/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/ca_translation deleted file mode 100644 index fe5184d7e1a2902d75a08a6288d8b809c67a45c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16282 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 diff --git a/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/cs_translation b/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/cs_translation deleted file mode 100644 index 3510a37a0d8094554943c9a35c148b90f30e52ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15016 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 Dk6$Zq diff --git a/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/da_translation b/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/da_translation deleted file mode 100644 index ee9d886ec4d9b1a300a93146c8ed499a54e770b3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15808 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 diff --git a/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/de_translation b/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/de_translation deleted file mode 100644 index 7c2aded04fe8ec44709e4e9d818559cfc3a6c8d2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16728 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 diff --git a/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/es_translation b/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/es_translation deleted file mode 100644 index de26ecf0a7df2730826d025980feaac767035252..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16732 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 diff --git a/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/eu_translation b/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/eu_translation deleted file mode 100644 index 957b6f4af59ed0407b92c926c4a1a4b7f30c8dbf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15834 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 z zet!Gso?ec4+G$Yl;=N#lM2BDY_&hNE(Z!e<6nd}7fT#IPa6(OB6zX?S3mVFQdzmtw zPkF`;F36(Xo6Tzz@}PaaL^Ub1^rgu;wR+E`q`N20KrPlqeQ1NlMq2A~XLTY?gVL#x zldjP~db*D5h|Sw;=h7aD zMzYK{G#|ObCaHOR4mzD?<%Hfw&^|uUtjCvw=eRP7l44Kmdh_c@pUfyxIw-8H-^kpq z>CGjW-2K-$C%Ji7v&EQUU2RIwB{Y#yXc#WfhhPT a&%xAxKiChOA}<`m+0&VouyYWr!v6zw`)PRq diff --git a/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/fa_IR_translation b/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/fa_IR_translation deleted file mode 100644 index 896593f04274a24d3103a2bb2348f26a921d652c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15408 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 diff --git a/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/fa_translation b/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/fa_translation deleted file mode 100644 index d8f9c028af7f27a17be23bc8fb48642707adbc6f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15590 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 diff --git a/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/fil_translation b/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/fil_translation deleted file mode 100644 index 5f547d809d132217e8b86f589fba1f99a1ddb475..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17564 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 diff --git a/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/fr_translation b/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/fr_translation deleted file mode 100644 index 7c01668dfc4b21c29b00a545b0de967c6dd0ffb1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16810 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 a4kuUqJwx>AcR;h?EsG7{?(};^qxOIPzy<~Y diff --git a/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/hu_translation b/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/hu_translation deleted file mode 100644 index 896593f04274a24d3103a2bb2348f26a921d652c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15408 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 diff --git a/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/it_IT_translation b/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/it_IT_translation deleted file mode 100644 index d7cc22c29c8917217dd92956576ec55f47b506d2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16542 zcmb`O+io1k5r*eFPqAizIDri(&iQ~Oph!xh#gn-dB^v<U#_oo|5Dda^!G-ebN%*QcQ?8}bwAR-o0@&u zJ<=@4nsZCv_w+gI?ugcjKBhiI>sa%=>0W8B%kHVJbH*dhnLKTEU+LHL?xp_T>TjWM zm--h@;N{zfn}=E(Yd-6a3Qzm`w$vx&u&=v(-|X%+D?ci$UUXk}-)Q#lG%tKX<3hBa z=o{27!~s0K)RiUmp?+QJ>1XBX7y9;8zahPa{*V~b7HxELcdwI zJo&t1p0~Qk?=SWJiAG(CH+Z`0KGNNz^6PV5G2g55?;DLlHo^6a?)?F(u{KjelDndT z9vFZAZtcwd7k!{*IiHJtOMO)ZHLk}SL?0q zRL>o2rFZoQFZH~KnrHcL#)q2!M9+R!QAlfC7EgF44Iv+E3h7@+JJ|CZ$SKSCQJ z@pFy3s3_wbC*lP^WevyTF2 zA0ohK#Y@ig`~LOs8{*!vV5fK-D@K74Tw8A`+@8iFT$>gFjzLWd?8Ol zj&F5s54k<{1FiH#RF@6iE6qKwh4r$Uvz|HYKsFD>9eN;MJ?idDL(A*9H5{K$B&XNK z!-*ShvWO~fU)tGrBep!v!l!tB2r1Yz592RkR=D1&1qpc!!}aWhLXcgy(IaUesuy(p_;G)U)Cf990x04!v@%2l5;Bc+qFPB8J$ z_57BoUFptK(GJam?5Ip?thib3Aa&=CwyJp~eVi!Ho=N9;y-{9jU@6{Q%e*dpgTO4! ztM0u?y6dW!yF~_xBUX6_ud<${_}i4Fkm-l*m8-$*Ox(kL=cMD$!z!fLRAX|7H2 z-nC8cDQ;SlDGw;24J`4zWPhxRxsA1^bkWWZn%frzw9?LhyV>FPUh%FmDeYV(JD%J!ud*T2S0_9*OWq5ElNf1I!?@-x&PN-x+fkqWC# z`OQbgu1S$aU2QESqlIR?kY63inn2YTla|g}x3tzH{b9pYBHa6=+()~0M5}Qg5y`qH z9#Gq2L+6wJJVcG$POakz>L^>=)OL}4RJKsSdL0Len6_g$xGeVJh)GNvk1PEZo++|V zdrQ4WXulBMejHuD_FAfz^QsU15HcnY5-Hv3xl?M{1ATie{id}=H26|$p@L*xNRh6= z3w=knH(w(a40R&7nL2`+#ufUqSA9HD@hR@m=xx7)tAG8bP7FuZOFrZ z7A`O6)ApL9 zdj;kNmBB@eBk8*0U5-xtW^Z1U{wjFZ2=wogB;MDKrb|ob)Z^%G(wk$Q&RTS|>C<|S zRw5uU?1A{i@1c>~tRA@>RbFsVxL6dd2X97T;C>{4ubVS>i_y%qwSepy$@7jz%vz&@ zjC}Bq)`%F1KZpq}t_HL2=tCqK>OAg>t8c|Ax{3PbQXE7_ua?&^y*55&jG(KGoK;0?xchBOv`y5{Tgv?UzGU@EA7o7Q z;@L!f!xAQH(W|y^3`sP+Av1A!BrA9!X*)|!OSYxccE!m6WI22?<&8CdsI@#R_D83> zwf0r*P0^@VpEPP~xe5PRX^U7~qt=#Y9Gv?XXgwtNFI}UZen)v2JAb67Pi4bb@9Zip z{I%rIyhcPs6+8|7@9XccO?0TMSNGw;UEYbK6Pr@8#>2b4-vnx{Q4sR1XN-O>_v^W~ z3%WSC9=(vI`N`?ooWYDt+WA&-6miMBb@mfW%h-fmPS$K=ruVp}IWVGopor9`f2CML ze)P<-gVM%jFx^6a<;eN!o$f%C=*;_IZSW6&n}|oCE3yE& zom`%CQ-68a)4N{tXQN+6RiAU+DW3F9cihVxT64?M_?p{@ndo@^ob@h4^7j2S)p9+A z`jgT)vR}`;E9j`y6V>PJ$I=&>6XbLgsW3B@8&!epwOVtVn#+h}zV3)G?D^QcTYJrj zo%^FTx7Z*UrOjbOnRCC#mv{Ygr#G#8HJ@y2Rn8G1F(M547UYNbyKl<>+235nrJxR? zU*gMt*75J(=&2f2WDnB4p&`2gf%WrXoK|RiAAx1zydMR~U+(?IZna}KHbVaQU2P{Rn-+kp(dKq^Q)%{HSyi;s5QO1$t4S3U4eQhs}gyW|*!0i7P z2em}|CpcIS`WipI;f^?%0a-YgeI*|0$b*YqSDXuhfd{N<&T`P9N2;u!GbqfkdgjAB z^Nb`{h2H2EFa~rI(!VMc=-k0Gxo6$U49;e3X$J2mKpiyKUj=74c(RsK>c(e+*vX(~ z!&4vV$zjyLSGf9GbCJow=<=xJUru^Y)kA*v=`dJZqa4G z#;fVjd!}>ZPRjbI!)jy9aR++XZTODgJl~e|#3_qoeaN!jV<*mL4Lw#7k=xy2_U;kZ zVGj>a=KlI~CsfU@lBtx&*FJ~Wp*S2_Z7w&&AMB{cSG80_a_l>Bri`5Db3YLejW2Aw zU@|gSy@z`$t%F;`r*kKcJ++AF)C*a;;-&q4J5HWaLz>r+zR`cgy|_!P!H&r%KBIx| z9EIQnkLP|?jNgCF^)7To82BK^V)-CJ({(I&*CXUo$M9MbL^k+#7N8U97)QLqPy04$ znYQbvGs7HMr#pWcdFHK~-YF-xsG)2JL-}x7q#AW!Rsz{giQH(du?FH~9TUZrA?=0nq9c7$wRPM*{af=NOjq0>*Ex7|Ba zj(g;Knyc}bES0BG>J?B|0yQyoeFhS1&}H zJ&A}RSP$_wdUBz~p>=^cy*ul3x!&L6RCvFuwG=J-#?0G~;#h3E%B*}>sm9q|@9Z9H zeN+~?!n>m47-(NGCY_QSy{}{di^({o^}7KuI(R4J$-1bx;@q^6Y~st3tIOcX>_CNig6zGA@qGHgo~_O zoP~btx@kK4&FM*uytFmUUU0E}`$3#Y- zcyuSv?|iV=TxSEi1iZ5q?-KPfrfqzDEwKYeG-hWleP>x>F#Bmxn2HOmgY!xKIKC^- zrc+1EBTqQSxJDob5O=(n1GV!v<4YF_#EV&Mv(W`=Pi#oVNG+N~# zu#n?3HTK`c<7SE7ASOorWHBzwmdmy;wTfPL#fMM!a&{OD-kz!ODFvi&o#NMY^T_nB zwL`WSC8iolvTmNCPw`Awu~!d;Y56nGZ_fD?4gH9um{F1%hQ9kfas5^u<8QQzMfX#U z#tM1Y5B{#YpQ%H8J6X|ta_=zpy88CiIxA`AKlDZx_;LLT@0S(MUrC&A^I5^6NVy}O zP9f#@C0Tn;ZN<~tyDfjib~#K7eS=G|C485TdhXG9AKR80{M%BeI`v~ndsVx1c6q1I zJIiM!UUBBL9|8Jt%XkZ3@s}?o`8q?5%Waq*Z@#*BTBE$vftisFQ3HRl&mghYXv%xF zKF>}sdwnGVEj$f8PFFNm02-rf0zN}}_1ns6g+3))cb(c@`mLf@va}H^oD)x=h;DC6 zjkA&c&g43ZX15)=y=yy**jpz z(n)p}oh#T$`j2Kqi{1swNPJRG01+EGLD-kUhDN!^_kpf9uepiJIXo$>Bhh-yTAzs; z-j~|cT0ukJ`%G)%T$1gU8qw$Z^VBtGM)!(#@R8NN2D^5zi_T+4?Cyd7cmvpHe%gu! zq}f)x2jVKu+u4GUHaVGy*IE+j<5=@X?6J%?i_h5S~s6x3h`udVNV!rA;4- z8aGjbqc*6ODkueUQA8zF(F=%&O3=bX3nC4I0x~EMBmy2%iH8uv_s#Fwnb~vBp5vri z?fUH5GynYaU%&sK@s0mA1sA zIc6+a3}%A4U>4sC!9;L>u!QdmxEjarcL(p&PvwGQP{POp=(J)~7O!&90{R1Z$&69- z-zpD0V5#(hGduTJSJw0Pw?Xho-pfIso~IYzig=mz^x`hRHwPWw3X0%%3fzwb2eAV3 zZC6mmoe`{X40o$|Ww6#cd>+B)5}wTjhcJ2!S98I9Fs-F9QG03y-xq=kzRzzwF@e7a zG5V9ZH>_!7FnUDGjx$#^eO_^2r2 zd5kUAs0@PU0PJemp=xd9`p|);{}k8M;WJN%v?>tMH+&srQtYQFEiw@m&^|xE#H=5TtPPw{4%ugxO z-%CNK_I2_5=s4xt^Whq&ZIHaIgcd})9UD~n%doS)U*|4O7>O~?FoJD9nq7OsdWBDg z)EEg#vj`rPGdHg#jy4{1hchPKh&!DT&9ME}lrw$W8Aof}lXBc=oN*mmYw7s>tTV1V z=z;9hjr*K4t{2+PryTcrXIvk$Aa#;%#xFP{2VkW`DaU=$8CMGS!uIY-IkFF{Vhn6^ zC`9v0ujz%B#ctDBm_Nd`FJ4F4q(8#7FJ32)a4n11$s=5w;&t)}*P3{pJi@gjUMG)m zEr{32BV5bjb@B+;W_X=Eq7R-k=*8{ZQ;rMU*lpfO;;I%HTQQ3Il)2paAi>vBqryo=&LE+!U>&JKAmsKrl6*PxynSjWI z7}v!(&H4^kByrit$0e+F05Pl#dD5eX^rv0j^T^?E;Qf8|4}2Q!M9q_gfm@D!Jr_To z*owRt!AmnF&b$~c@9&O1dF+niQZ0{#c@bJ28(PaEvWPtFs+hsn)Vm9m9yOB0m!U-z z(hGW&WSH6s=q8CCx#`gM6}P^Bs^`?R>Z+Sx=DrJ(zp1 z!@JRMk2;}d)jDkVW$5{Z1`=WZA?-m(m3BieKG?t9tlSAIe{vv@yzZD%7{H-Yc;Dewy?EOEv$1j$mFjB}Ms8~h zd!T<>0d4=IH4EU_A<*Mk#$8q)Yy5sA!ZuP-~6*bYnTGM?e=+O7+@vJ3DjWVQ% z6Y0@I>SKe+3!If}EMiQ`5Jy|; zDzTHBUDMAI)m}xcpVSeD`8sJu>1VB96R97-T<^>bjWiX%D(3qZXm>9(Jp`GzqrPNK z$t*4QOfO-)VQg#H*X%X5;pO+nT6G6!Om~26$7l>z!KlZ+(xUFwNKdH>o1xXLU=({w z)3}}sCh=*ORNrOy6o6 z)M@9l!A?ClyH@OzvCl-`W#`QDkikmWB|Efn-_3Lxa7@nGPdkFYi%#Sx$J7FS)MiY2 zp^wIhe%kS(??!cLy1?f$cv}Sh3}`NalTkgVv9%7mbrr-Wo4WL^$avhYnz3XeOpdii9ds@VLQX0 zMjt5;tH)|F$9>9LoXC$C|!Y{iuuRFkV4yH-F>imS`?{*PoZY zdFF)^`=BNGx^QUz%9p4qp~w& zSq-tzDwSNCZti(VXCq8-yiE;^c#-mvJO)zVA3HLY>i z-lFI;;!@AlIwKwP5%Z7Lc{8FHt6hsPlIgHMXANbkys20-8EGX`NLp!nN^U39^3#Us z!YIe+lAcx+HncRu_%C^#QZt!q$znfC)x|;S=n=0IF$zj|weY<9v&LBIS4^K|w^)4I zwYc4@PwumHXfw>4ejJR7?|)U7JRYRS-=#6J;CW+>;n-J%B_4v#!gwW&bqs2doi^eH z<_^h;(fMW8$<^F}TY-%*)A@PORti#ON7)3CL4Dk|)=^Q{Pk*aG&#b69wMB{V{t;4n z0rmZh>KC|L!zX)GjC%DuNqO{MtbNEg>B#lLcZpA=u5$GzG391AKwZ@!9BII-1x=% zs0y1qjKAafOKeS#VbmPg^U(t>cE9zty8AKgNjc-gts+`T}X8I9e=(~u51D?w&PYb(}! zEj6h=r(iA2d`r-`&45+(56w9n_7-S2rhb*@&1sqyjH1ue)9q=WNlnkK$Qct@y-PJN zJs0*aqAGb~t!^A~yEE>2A=5FaI-QTOrovIo%Hg5>uBFEw|IM($FA^)Km67)5yqMj4 zW==UDVUVAzTUrWx&z1vghw++?rb{$Sn~S2grnaE2G+ps{$~;?^MT{|>T_YUDypw}x!Gja0#;AYmg-A#BeT7w#he2Wy|`Ewe6bfE{s3Xn65bm zB>Mpca7q1JJ}*uDA$LLj0NL;_K>x4SY$nVht6H-Y&UH6q&E%1GNrWZ+8%z1}!xsww z%8ylks4i@RYMQmLDblQVSY)8UQC_S+D#9G9S%x?XZ76bQfi{RmtODNe{TBG zyN<-`-|oh7|Ht*GJgh-{O6IoG$O7$1B%BG3I&nM$-q=%Kz-vY$qjzFncU|dcuEH*) zstZd~Y>=|Fb{dDB4YC*etdDQ$Xh)%sJbJ&Bl+A(}@Wc9i9=VaV7rVCcYe-X-xIKn2 zr*WMFSKiytTv}M(eF?Vt6{Uc;dT0Sh?+`RE7v9!s^y4L@q+p`)_4WcQL-z~6A+=^3tU`M z^ElCvSKaE7@<;eblh%MxU6~%b) zF$aNdZl$azr7pL!_RsNdj$YKc;JB5fwJ&BkdlD7e>h)+Ml+t6U{W`)FTPPw@M%V9HxoN6&hmwDLVKOwSx&#Zr@ zJuPKcj`dZGPhC!**Hb6fXosf!9gYFWiCtxXmxC*@PuJrX#y#R0a_pa$FgTm1wPgNT zoC`cx_APrWP`4ZXSW9z6xSqBsd_eC6GV1XJKs`rIuq`>$e><+&VYd3>4kDko`<~Gr zd0zb;2>E5_KLX60)HCt0oF}vAjCdX-t0h5e3fDp(o@qK}_@LH$@aBI#$j+gU1c?ZI o+`j)D^S6rvm}?3cZM;lYTtZx3fSvn3RexGP4dS?1H2>-Ie|*gcwg3PC diff --git a/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/lv_translation b/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/lv_translation deleted file mode 100644 index 896593f04274a24d3103a2bb2348f26a921d652c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15408 zcmb`OX>%LL5r+43{(-;2$_FRqlyhHBB`Ff5C|bNsfTZk76^oZ-Su)A+u&n&}B=0kg zR`+0cfhFxifm|$hx@Y=)yJzsf|2}C}nsGC2Hk;jMr#Wu+oAu_)=0KmhTGJiAn>Jhe z^rRUzf7hEaXU$#ny18mD^?AR!X#U;Y>+_AS&h`D@nxB=YcA80Zq>=lgGuEgry^fnz z(VyyN9J}dXHn+_?QM*ah_B6wo!=`wjX$Iek?`88!^B6ZL&0D>K|AAJ(%8r}8GK+0} zn&{=L-PT<`uQVIg`X-|JN>rgYDyxe1-RjPXIG<^Dw|b2vi7WkmufO+tc2s6`rmL&w zy7@+bFZHB9^-iB}nm77~TYyG_`e3*gj z0Xf7o6FqaMmr2KFeJ!q|<{x^EB#|3^_kCH*`ww@wb+0{l8E*iR$6p`<^$p2DO zyOBoh^SQnW>e%d5v&Ltb#~UxX)t_%k$J|;>2B(Iq&JqA5(9$zfo6^!z7hwl5*x(d zM;dW2&Zp(h<5#q!xlYA#l6dY&UU2#`lImW~zNkKIB}nE#^Fs^7lq1Q=UdA!#MsWjhxDg4+oC>YZ`adypR=sJ#gfnRz>VxOBA~2idSt* z%VxVnEUX@p?ThO!o2*A<`{H`}h-_J0FCUR@itFVgvNdtNd_=Y*u9uI<7R2@P5!rIM zUOpn*4A;v??8$Qus<{8@z;SIG`*}4IY_}@v9!*#Ec`Q#oP}DwFe6S1)(tYwFx*nA{ zeW8dAzPvB_I&z&MGcw@qETX>?h9Kt>-Oo#IJW(!ut1H8$j@nL3)_ps=gvrTzwL z1oMGjM|xi=Gl2&^Hv2tL9C-JYINp^fFEk1?Y1l8Qy;jU-=48%a>)UamdS9}2p2w~D zxQ0MsApVT$R>~cuXE~s*7LFv>sp93abcgpEA`3}^L2raH`8$%d4V!=&K3>RmS$(js zl|`M=o;SFXwM+`ae6G3m>H3-G1g%?16iK4Pq4P8@8#7zlQoJ+C#rhr3KP$SRMmdn~ z7fHOP3I5s^U1T3rr<&_U!KcINGP{l91$~m(DpqH9^m3r4>iP-F;9+oTwg;*&R`OK* zU!-+xYZZ9WiR>AiX!fBbF9wI}s4{^=<9jm2M+|j2O(puP&`|`FVL`^u-7_%n45&Q5NEL#l~163Ia zmCgt?gw?x~3gxdVYTU}tKP#_(ADC#KhW*X&qQeohr>@7>tX}hsG-SqD6mbok-O#+L zOunAU%Jb)ZrAW33`|6ITj6D=R#*#&-`;KSUi!}OcXy|<}Kc;_C*M`*o#3UjST>z*e zVImJ$884iVS)T3S&RQoLw_iqP4>j$7G$NOc6t9RPu8|+hbw{(Ka`5VIMbZNYoQn$? zd>J3BZ_T0}yFReOZIP+P9qU0ckK3P+|NKg+?tgkti}cvPPM0!RXH_=vLiTVd&8(}= zr?v-^nDygpL_DJFdby^rxTaU#C8f7Sv<069G6PfQ4v=>Q@J(#X(BxUkLh~4r3~7D^ zVj1ZTmb{+njnD9-3kjt&`AMz8$E=N2?I0#;GNKJ-LLBBR7Z^P(@m|M z?iaDnkfU{iUGA>+S}*i^MEMp2DRA}i>XX#YfBf_6ZL@8;rImWm-0 z@&66N80a%c*m}QUk~E+F z#CAq<7F)!D{6S0sp%Aypu4L1Y@QV5+Sax8lu9?Kib$RW(^h z6KOdR%WzHM_v}MhjL3G0cjkT0poxuv8tOdLmEE?B?FG`dohWt|H)pkvTW>q+Ph@l7 zX%1Z-y?LbPn92GNE?q+e-Ptwg5;8}3hax%=)(`2j8*ouOV}ZZv_T+ks{cX@=p8c>9-`w{>?SlI^Pd8dkQeobDRiQ&a%QSrwbLJzv$t_!4jyi+A?-3IaG0 zRYT^6XRPl<73&>bm+ib&Gq1dR4=!Q_?bVFM0iQKI-QKwmP1rdPy5Uqac-C{tzZ*Y) zDLEoHYMCuv)mW@AnxILG1m?HhwDBOMZ#jCT+=NHdWsA&Ja~P8Uytl!8b5?*7olv>~ z)*VWS{tpd<-RDs4}xo4gQ9l{CSH~6WQ2A_ad4i;af@f2mNOchdPH*E?M*PsH1-T zTG@B>j?YwbG+y7y%RveFug{qXn@Fr2B#5P(Ub+ScyoQd@j+rUp5wGLY) zT|Dw66$2Jecf_?EYur?<@)e;4(;K$svD70e-$Wly!ix%4~_B$bTLanKJLZOCOdK{QA70U+%Uugl*Cl54()$6MTqm)U!8|*PY@F zdm)YzZkvy_x|j*N_g;uS?`YzkMj;6zRh$xhS?J~3k(h=C9Yqmsz$%^vYh+^>Bga8( z&tqYhi+e-f*=_r=`)8LNEE<7Lm{ZKpIv6$wpC$1%<`KNx(FdQoEO>>JiyjHEBEHEv zhR>E`+?I3Xvc3o7c!sV9vBIn`%OTrlV4k)l*ee75*y$_B`PnM*2=;2iQVyj%%f(?K z?bCuoc~RToZ$aNqAk613)*L7xb9RaK`vIqLw-ld!f5i1)^L$%$VbzRTT(-h4>id!& zQPOIA!#yCrpt~>abbHET1W{9NM@5s7*$y4C+=r$Y3Fi0cm{);ClQAuh;*oWHZO6^p zdVDW3cXNgy$k^9)InLL#nIhQS?2;3oAJROf}ad zhw{I-qHcC@Bo9N5Bh}G#Xs`=9ZHqXv4`Tawc0$s2PB?)(-?L$-5afn!KNBaS4Y`%b zSmXA#wEVoJ?e^_!P1s_rYE5(vM?EbxiCocP>Y_(g;CtqvciZ#9Yryl?5zX|OZ=jcz zBA=_`LxB$ZdPteyL*)g1;uv;@zs5P{%_2KaVCT*X^eqmLi_ZPj@i>^pN52yUlDI6Jc*bzOV5oL$j$G+6kg;7tb{Wr^AVR0?5Q+w_3fJGbgDa?O^bLtD!<=p z%c7j__xV+&sy_pQ6IQC7j(yi?uT z6mMz)c>Y2&I4>BE8WOI*OnW>JTPZyg>tTh|+rR&K7|pKVl=Q8pKPuH$LUxW*w%r}T zd_u^(0LU!tl!)(kY7&1e|F(rpg>KMJn}^pXw&Tj49u<+;!nqRjo>9sB4jIame#m?^SMxoP zOyK5~&Jf<9u61d--V2>d#vpHM;*c^Dh^OZf9FZ7g$9n3sWPMfAOL4eU3vl}>oA zS+_~2l+ToOM;o$U@!AV) diff --git a/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/nb_translation b/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/nb_translation deleted file mode 100644 index 0160ddddb9698cd4ba81e9cd9fea03fc1c810601..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15632 zcmb`OTXP$^5ryYDzk=&LB&p4Hb5ANs>1J8BZV@TT-b$59vSeE;t)%E;mw&MT!#*WB z9}Xcj$Qg2Ir&ywf!vWA}obEo|0RQ`+C(Tl`(u|soW~bS1j+(t@t@&rOug_eq>JHzH znoWIr(hQov>5n$AoBQUxxo)oXd9OKZ{@pz2^R2E<_5EL)AEl?Zn_+XPmV4@FMXfgV zI%<~H|41*7*w+8BdEXqXx6;$Dq$}fZXxyvjLgRn0*O{(Q^*&IWE48|AZbHj_&4Sq- zHM>cYEqxm5m1n%AyL?`1)~h)VHJ`WY@3^^C&&;gM@h*+^T3R@3?(`Z+8`paOsP_jw zJCIc8`qr|%)7`5ipP%}m&$p6(AUXZSnf_m@^)GerRqA7)*38Bu-=+SMfsx*&|2KNS zP2(UzUW@b8COt z&^uZ`m99V+s49Je*O8>fUXX21BOH#$*w+1nbayGCy$#hb5)SS)!iCm^)$8h;*)!5c zvKRERr5CpNDxvsD-*1h-ewrHwY@Q`wV!zVvDW7T% z=;=DY&+;pWWs+p-sOQ--H%RvbB|-BmFkC!-}5vZ1=m+ZauAQKRJIN+C7&i z+Uz;rA40n=t@BRLc7F`*b`_~vlYWx^DYP63#|J&z{W-KdY+ee5-}Y>|t6AZ9S3?i& zvSL?TT4kfR55npe5no)lVX|%!@x}H0772T+eT@E6drhy#Lod+f8Ha$5l(1-LiVm-gH@?S7fRCilj&K500{M-6xNc zbTxVFfqXY?$?WS?-{QkFYdet_KU3s`>%l-_f$!zp2hE%0^Y5A;#&Leoe9~RGgd;zU zi|7fPgSlPk#bZx$2Y58HQ%xNeY zmxLggKs$b(X~kiY_mcEu(sh4%J<9qRpPTZb)?m@Ysw6d21&g0%>b&j^Mz^IN(SGa? zB*J{>_lG>|$(Fk2?%XV+Gvl}KYSq~oW5BZEz7Z4Ta?ItaBzYNTvYhaHk#K^&zLEZ! z)qOw?_VSk*cp(-u+P?G-7V%1;vFK|%G2Ro61rD!dhj@j^j`@a6c+h+hM#;to>Luor zutI0fr<&<-7>(fiESwWGl82x|+*DoAjm~PX+<(S9# z%yy`Dd#Po#Q6u1;#=d=AfiE3xTT+pOl27(DlWkc;o-;OS ze=jy`%P2jYyJjAHtQJTEBfAYg<#T3_UUmbDb4?U^EcH(`mSZk8STkzkEzzFJ>7`zd z|DFl=>j=!sIaYnt{}?fln03O^OZ>-{YrYB(T+<5=&{ppQlSC-{llDqk_gJ-0DHi=) z(o(6UXIG}pu~=cE2;ad9$yUe=VunGk@+ryVtObolt&l(1E9G2)zFB>16n!yHY-25> z-=!2;_?9|1PZpllK2N+1wO-@DO^01o#~S_F&ijl~*Oc+pL?f)O=y+SQlgZh-E*`z} z;{C@KrT>DAV`&!#aJA4FAhTu@=A>kz=pEZQO<96-K(GMc%(>VgQ4N}$K)(ck4%2Vqs8lqP23>&%4C2Ouii`ZYT zmrnMS@4Gde+fU5y&9Tr-qLwW0O<_SWe=x#3nnH6IX$A0rydpBAv}RQ=rsiFGaF4Y$ zUM0JJCtu7e-_7hFmO>PPzk`>^JoY8;o77A6dt|iiFE~r@v!gN6-0dCig|@3>J-*1rcTM`XOE z{j$8SD=+7Fe(k5PP)*~OZBkUSkrej*diPvRiY|xQ1X47Ewo? zwn(?BO3|r$`9V6!(s@^GBjp;-m@=}bBfwBI!~avYe53cW7ybGXW9-`F;WLja3-v`^=raMr1MYGq^aFS@xuvGn!%@%XZHD|N%lmJ@yo=BJ zTEK46A+?8c zb|hyluE62vbA6&6dwI_EZU4yV#jEt2IrYMgl>cQ$(iWdKByWzWctG^-QM3Qe-j{Dm zMS^FM&uht?KBSgJPgf{Pt-|rz{&z7Hw_9|Z-E2r2*uZU?0r8Y-$+T~q^dHlRX&)1| zy;@b9>v&TT0@KLxJ4;|~Ig+t&#^<;C14Z2$F{8}>#Jj=WC%snH8}Y2J#t_%KPkv1! zDUKP>6%;;D#_2|U_nf@@L{)-%dqHIJQ)Q%ettBi1Ys3Qh`%3fK(^Y+1D_SGo8f5yM zrELvFdbbi={H4CFvrkwDzTP!Mdx6Z? z-z=ZS&KsP_C&M!PkN6|EWY4GQ=0G#VXA%*z=1ZjPMqT?@`9yWMOO#Rh+u`K_u6VAJ71G7VQ?k8i-@gz zp7w)>S_}JH?DSNaC>3l@-;x<*e;8+hy|9Y*oY-XTQGLQ7ddeD2c{erIp60X5C5=ZN zuVkW=S#azR{%(|VKDFp_SXtUd@-?6Xk66xGwDp`%Es!}Ap*b4_LoIzjm%oFJq8q%| zwX_PCC^ml~9XnUynT&?E(O`t$8}xZj@5*pec(V30)F{q}Z0lW&etMLAveUbWROUcp zna`?ZCCf0k{62l^mYK>=KG%efmQsz2opR6&E~%-;-Y3rp=}Q1kjO#l*>-vJ=e^3!IILa_)e= z>F70fgh+c1OH{PHnY3vS82K+5yzLjnetgt%_xko9JWRdfD+o^Tb*+oyg+M zbGp%-cUhPg7hcuVmDpg$#VLnNoLg1*~X&?NRo=xTv+)_A8mL+4LVFpHcGpt~O7_ zPKj|*>aysp;FS22acYf!?)cLP8Hr7@I@4oB2AV6;xtbj1gIArV46sJ@EVenl+Ef?$ zm6Z1%kCOFsW^vNCsECnA;$e{LQ~HneSad%|(mocy@^if}vY~oBJR7S-?mM~v!kIc^ zA3LMePUkb{XWGB{D7o3Aa6X>b6~;pT4u?Tk@mH4xLq5|P>o4`rZJnQ>G1h95<8Xh` zaXF_JGa6=GkKKM2W8UcSvo)=Ky$d_inp4?iZ=*zQ^iZCjUwVWjEiPst|>W3DOuSlo!r1ralUJEj!(3f z{x#p%y4sZdobfLiE_rX+FD`4$X%PA>}CAHR+C35}Ys!^7%aZa;B#;%kwKIJ~F!*;nM>9Xr~rx!a5rzA=pJFWkv12W4ukUu)4sJrMfWZtGt-=w65D0+~C;&;}eMLXYzJu-a z)tOXP&2;xP+Sr&F=;`Uo%*x8k$*h|H{r6F`+^jbH&1N%hcADd6uUT*Y*&OIIS8KY% zcl*tjK0Rti&ENE9%&X?UxoFPxX{WhqPIY~*`+seIlD^$(Ce4vX>}mX}p5D^yxLFb1 zeZ9=7jqZvlzHF{V`;(~NG`FIBrq7x3uJ~m+Huc?GJ@+m>#j_)QdarRex_)1bJJ4L1 z<8iZ_-}*ul2r>oFlzMQ%Lzvch2?yOml=9vVYRwTg`|$FX{7v-fu+_jReQL^z6ID;aU`+ za@KrIV@Bc%Cu;)j_~|Q2&o?~D_b;WJ_xk^Ly++c=t-k%JyRVvmf4;ljG6O$f>Lga? zp{tBU<;t!$Pp5XWt`%ktu$-}0jjP3$bExO`w3<)!{!g-9?8Np6?Xg~y&r}b@V_#2w zTX4x%^-MEAljcT}5omdb3tvPQ878 z8%gGe&kBtlNLJQ|XmcdnBhonPwA;d(Y=KxuEWzH;8&|iwYV)-te*Rym56P zr{5Oi@P;P?$NjDtw~l?x=Y#YkV%}8V z%i?BnWUt)FY~{=<}*P^*|B)SnfJa^GtrZM0&qe~-5^4gt{!5c+x(4eu>C}A{c8Q-1g+gJMjv9c^^4g|-yMxZ4Q zzLG@e&58b=>G>r+Q%3UoTT5%yc|DaKghF0NA|%Y1I}oRNUMTW|R>{b}((G0x`Hk+J zO2%5+J4q2M4dSEHC^6)6y2C0sm(*3tk=AElaram@Kol_g%PTTT@`8!Up&;-NlKZE< z9Ea2<-)l~+GSyCr+2&^enq^iKNx3Zh0THxY&(pMOr=of*Dc?$7YKJ>%j!*qko6E-3 zI=8f{)?wBJ6~vn)HI)K3(Wm#C9*A#SG+BX6@r9^e7F2C7izp-8M)ImIJy5OK5~E5P zu7|pMg9CURlw8^ewI6e$MtEM#h^mtcD6BjD=&rD$zx3D+`4~bXRKtl(>n+tc_{&E}0h<86JG&5Tzy<6XV6Q|cA&eUt99wq-0U zHDmwjT0H2YRGZZv9+|OvE;v>MlNfk)X#}{I}TpR7l(Z;;@{80Up`G|$?&8(yfc1NF{YhAOw;q_!8?Amjq_do=Q zY=o}w(tMo}x??f(pnjoh10z$>Qqj07pH%h6n)ng5meQ-6=0wuE0Ga#61Y z;(DstfJ^8yxI6Y%pY5TGc)I4cMSbZkiF~$V^owq@wU?eeMCZfhnbpEZ;tT5fad`Hv z^H2@MYJjFzWHHZWJ%?J&b#*7`NPv9oDflE2lQ_MeuIZDm=~efCqkBcP#0HGBi~;GD z)txPLW4sGa{%-1PbUX2iS={<@>McgoXqGGhmZO(NCYq1dfk6U+yX$Erdi^6N#`$G>h#R%XuzQH!UmgQ7=vOJh9`f$>^?qX2d9Z z(e{sa-8sVd_d{#n=wH8@XWaoO50K~Z(X8Vg&E%~4rDldc>H1-b_Y3T#k58$M%;(_2 zmQnM-PHVhEE(ttLZTX!(vwCI!m^H=|z8pPcLr0==m3U!;;j^>0?!6MM$NqOOiw%o< zpYDw$%R9aQQE9iVVco|En|XhYE^XG~VWI-YcRlHy9CB&w{(zxM3n^(w*O&DvspfvL z5!gt69?#a8jFBZpXFwyIn&yfm#gfWBIW)=rGAgrT{XC5Mo+(nzb0{fMWsFpx5F4|< zV+&{a8{sM6U}aqnTsF3!Ge70abkyq4DyO-?`4 z^R5hfCMwcV1UZ+Hv)=2WHyh|GGHl)-B5HO=RSvv3K5l?O(AYmKg&oui`{bCKeN{&iABl=8#Ptv|h z7L{Z7FuGJ})B~=~%2gVtI1|zAOtwTECF%^17?p02ahG?2&ox6b ztGiz{`T+eAmFD+Ab7#g~qBhF{jm>7ny(B-F$!OQwrJG5e5qIC~DR{D97+5D~wHj@N zMxX#1hc14R?^`#oMQgTZjk5=FPQkqeW?jb5vHG0ZipPr4v3|RXJD^Wj`=t#zHa_g+ z#jdRPF0Bsen=bn6v|caMHGK_q0DeG1`luIaw%l2K2BYqL|M({|W=~^~S93R#c|&Bx_Fe;IVS<` zQ`Zvf3+t#k49UCb=t^{a;s6ey?~)$9W_l>@sVt5<$Pi<==DpKHaaE{}lNWn>kz2i+ zPUOv%byqEPsCA?Y3iN3!b(NLRoXle191TlZJ-;E+w8h#j`8xQs=JiDOz_erXI z@bVJd8>h3Y;u<5gPVBqWzSJw(&ZQ!Vy$-%3-Sc@LM-XD~2fgAwnQGL)IBj`uS#l7sSuu3)xIGbKp6E_S|MUG1>fG-%F82-SC$TdHEi5E| zb2Ec;hpkw$A_W}LwuW}}`@y|RNyG{pyqpMBb5-x3t%6L(Q_%z6a7Vp)ra zqK&mUbAe037rHI7oe%XE`?Z-i-7D|C*Vzu8u>$RUBP)? zyrfDkVEg5)aMm+1EqmjgX7_1oJ9rZ@E;QUmJ6D+My@y`#&_$9Zd9BlLsQGr5>n$=)C}$lXAaBK7rPO}irZv$`4<-M`^}#R8_xZq zTI=Gt)MFX&AQEDM_;@}YznSz23NCv_tD0%vCwwdozn=Qim11YELnC_G8_6VnE16Gw zu3*RFFeMQYjLtV;Y_fI#{Ac;LVdgmZW^CpC5Ar*^Gn|JfgLZQn z(k0lG`QCuMn3x&8E+k!*>Jb7eU%Av88+$?WVB|9r~O zeKr3|gMHvK+V5&*%U}1fp7yEOPbY8nYdiLkh+yn!W#3sz+VG0?`6V;q{LpN?KGb+u zxUR6>mpoB4JXdY*odrDKH8AJ}ZI9CL%;vRIa{=2QNkeGFJ*q7CQ_&=+w{ggso+peC z;2%AJrRHDK;d=T8UE&v~`j)swH;;;zr#b735Al1krS$5ylv?t-qco3ZSX1&GNY>{H z(PErj@(R~|%-FR&lU6|{ti2_N2HijM4!!~<=24;@szuHnp6Wl(W&GW)KVoF=Z*7R% zy}G|>rs;&x6SbtjC=mQ2i>UQVcgj7&Y!_|6 zn|c`4wqIjCL<7TZB+j$>bX$9j&9%O5GPD`>T3}YYihXUb{FFw_Oh%-Lb zcg|;#Ea2gF*+;z>zc0#EOm*)a-Q=fdu{9YE%+37j)0yj{gmvW(A9_Nv$Y;j55eM*p z_q}TklEE&MuG=bPd=)F=cPe+_AK2qLdwz}IDZ7Se2%Jp|7uyIz-IYOS+*(Dz?!K|efB?}&*r@CR$3kW zXTFQ_LMt8TSb`?@_e^hoOYHMGbtMJrt!r&M&*;`YHLs#0i6suo;GjLxjA&%79i?>x ee}VVuz17vt?5|gcpzdAG3*W@D*uRFOrT9MrX7Dut diff --git a/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/pl_translation b/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/pl_translation deleted file mode 100644 index ac77e5f469c268770e007f65b290741e53d1a74c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16004 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 diff --git a/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/pt_BR_translation b/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/pt_BR_translation deleted file mode 100644 index eaae649f13f75d488d137a22c4191fd18ee05733..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16586 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 diff --git a/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/ro_translation b/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/ro_translation deleted file mode 100644 index 4879df64eb260672bad2870b0cf56a03d1052f15..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15746 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 diff --git a/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/ru_translation b/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/ru_translation deleted file mode 100644 index 1cd42de68f7fd33ed3af5a8d696505e9858ae082..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16678 zcmb`PX>T0I5r!wge99-Aks?Qts3m1PhaH)sNO48++Fep4!_y=s*+BpzL4w49;RJ#G z_mjL&*EXwrj-8<-5G&8lPInz$$6M7s{@;HWi%~Htu8PCrtT-*Mi;H5f__}zl?_BNb z4!>O$$NIKd42n&C7K_Dlr(7+cmJ|IuE=T2AIVe_i{X*B{a=9Gpda-y}{9MnDH1B2c zR^u-;+eF`w^>eWZDwmrLbK$!iiWiN=oZtOY$B1pZco z4z_{@Cgrxit?RR)-`4fiR=FEEnuMob291q%=b1i(VqfxYNuNi$Ue>$^`tK;*S=D^2 zl5I20!`+v9>L74q&yLC`T9dgjc0*&Hhc$tlk;bj+`K6$}L2(kOFltb2>+WXwb*OI- z^^KK-&sFiu?`V`c`2I@NS=~YLNY4zVUFJl;j5^l+E&UCO2Nh}K{mbIJ;vdBi#b1i= z3%HH8aT{x372>+)_An_9rdG8lyI}3GvoozWSKW5T%qx;(Su*bEsy_RfX1lzZ_qC{8 z$(p}S=%m(oq?KeQ=3yN+^zU=cJCVkgf{t0wqo7Zoc&U$BN4&d!qkYzdl{&hyVj1fTECx6e_zxZTco&KZfMRIlJVW=J(Rp?f1-#%RPxy$N{V5y3Z%gzO{-WYTHkpO zs;83hO4KeBdiNv?^Nyvd;d{*8EDf`>F3&W#dE-#uw}Q8A_K*!dy%xu;2~qB?{DKH) z(XKg?LlbM;EcZp#d>Jc^(cp!Co!m&etMxUVVE@qDpHkzOZxc-|1Uc}lO~tyn_QYS} z|Lt=@yZ7gFGB;TT3YX$-DdCB0%Sp4xJWa;*8?l^>_^AB2oGX9PB6F3$XUWJr<=ygU zRo*Xu-E-t>GV($Bu$(U+^&I&;8968y%Ej_=&yj1%$WP19%FoN+ z^c*=%M*g<^UHSX+i=HE2BqP5p|4=?Dzv?;iTpmQ6-%Vy{wgr6dL{Zpmx{tl(BT{SQ zy2(QG5vjFt-8~|;GOoKvq_)L%_lVS{xb7a2+7j2@BT^gUx_d-wJ6v~qkPW+y^N~>^T&kb_`8+05OthLA|T`FPU{YWF(&4RlXp@^Bm}Iq4%7i z^tHg_bIk@4rltV3j&;4FJLDnI^s0V0AsU*zc2n({8aocL#QB1s-P69j=~+*#PUpDLA}+QTq*Nj%OZ#gZ^FO6KJ!BF zLm@=4mQYgomu!hfjkARt8)%~5RW;Q!dbHvCY~W_(y9@~WNSwA=6}W#(y2DPkq&F;K z60WHfsE(-8z>;QptRfW?wc#vzjD!8DMIS5bnxMC ze^LBho_SvUQ$G5GuG?)IsV9>9Tp#S=N<4D!kKsNZn?}ofvZ~lAAo?5y+s4v~l>G9w zK2L5*59B}{X7Sl#cv`ox3e<>sZI9<#XGgwpQf6N zsGwRa=XuAdJrhkT+6~#gM`>nwqgk1OTt*FiJ+)}`(%xD4MA`gln3H;yUQ+ba1Iv9&5Jjv+3<}auw%)Mo|hDj$y)Tu zCPI#BpNu|k+O?fXH^V7%r5{QzTi007ry*zS>l(JUSOtzovN{R|kH=NEu8j5A!6I~J zL6Ey4UMvR;_9BVj@1hg5b)INrE<}Yvv8q^v<+CPkcQJqT<%X5P-yJL9(K%`cf$r&# zIMCFyOfdzqYZ$5;aGT*6?=c~NGsPS^`PzRa+1Np_UaX-2>wxELK$o3p>D}@AI(#XM zqMkxGFI8`>$#Ou&DXwYj*H1KVEDA(UtjW4@=}36P9=A1j?7&-9G%EhBKj{1@g!CUF zB+nns8X+ptBR3Ce);YD_EaVpZtu8H`!x>#|-?8b#LEfh)yOHDYSzjrON$40I5c8>| zJR0AgPCCNr5DlV*K_lILN=LXwSG8HiEf2-5XU1xmI(nGvG2L0K=IX5wHB!64qCUH^ zV6b%DOJYYU^_M6OR!Z%@ua&Zj8rwP1XIuKVo(Gb87PdW=Q{4k!V=r`nU1wlg@ar&O zBqCpqk*c%B{&97lJF+?EIG&Cl6xBOz{8_Fu)ROa-BP~3gNRRAbd)xn_|4lV+F+2C{ zNrx1DeVg&H}v96RQ~MZSZAZp9@6~~frwIg`$T;D&XMf`VLL=zdY548G%DwNSm_Q+ z-SezMt=CH_8fl_PM#Dlb87KuOY6p-x$ly9&(~BP_`G=qa3l-vuM4Z(W6d5rZn<^P5oEvAcEoQx zJ9QovgjILx%01G$t>_Pr%lARMHA38B&&eWL$}d_@chY>%@FdtGm?+PCO?lXTViJ0X zX*RN5%EM4cSw$Ev9{ z=R*u5Or!}SFZ!YOU`6P<;Tvd!@vIpux2)=(T#t0f7khtn%fV9JCt2R-#K`A?Bi3n9 zJZ|8W3dl~6r1s-yMC>#YyB^eW_}Gy&+FT`kB`Rdfv@&jsDy)b_1M}PF86Svy*1^ug z5HIO-+pc^&l|C7CLHXTxUSg#|cipROc&Cd@f5duI-O++P-_~ozUhvmwo%Q)A!gieG zxX>S2lPcLFrq|bPbrdnQeokt6y5{E57Db3D#ExUxs$pUJ#cgL~(8#c`fNzzD;Kj zom<{yM}{cK4AilP_?gM}$Sj7~^L`?Vb@8ZTk(an)J$~OSXtJi;NuTa)cSk|zFrL|R z-8g?lxVjBeofAWmE83vh8Tkfz$vwA~jjrios9Ga>Y07bTHFsPGR>!(4`PsdCl()4) z8e7x2hCCUg$1tp_*L1V|yuLEc-rnRn)Zx}E%XRl5=z-IBv%Wj{hp1@P5V#;(D;UPC z70)HNaYECch`lSzZta{I-n8fn8mH6Te!7o*Fgs2~8{`}p85nNT6MiwLyM=n?RQI5i zyYv)@qlG#7GUr9BbgDgub`98G9=#U2?{2XkpPU*o3IDJ-2~MG2UXWE9AE2eCXapXy zH9C+snrq)h&E+*Os0CY3PqgBfex8-yR9dIgIl~^YkLVGG7TqIql9T6=6Vp8Qw}*swd_(v4`h8TK|oV|F4i)*Pv|;!EtP zxz?yZz)|q-u{+H!x1~qrg||8lJN*I$Vr(_v#R&f{9B_jDSg}3sV@3OL+IK{i3c+fn zxSB++h}6N+Davh<_d=Q_=33vvkQg}C>Et_kP2T2pW@^{@%=I{%oXLGpyG<> zaiZ0B#opY@;*NFNS?or&^P(GfC`b4Y7$0@o)O0W}_wLK96exb3;xd0yMw z;`Dj7|FjTt9~NPiA>GBU8lz4zp7OQ5+6&2tMY@Eait`VZCLi71upIvj#$^CbNB(n2Ai|-Vth&E2rJ1%_iC|EvsmK0jvm( z47EqtzK&3{HxiJQsAW4~^hVJz?=7UgBkIE)#cFnhY&EeJt3NkoH)y-5Uy;_2bshK# z5l#%Gk>*VHY|+N}^fg$R+A~?3{w92YU(p3`hRm|w{`7L$&4WMy??RDN=@WTJ^qaPd z^^*_*Z0$fOjvCM+`W>snLg%xhf_Y(1u?aQbkzb~7NSW^#uC;wk-^r@4T%Vpod%v-1 zK2BlOJBR5yH1%t|d(bpzdKS9*1X{$aj(@$%s?W$Ahq6q3g@|Y86|I|Q?}MlPS(nQ>hUQ3UnR&?<*2kSi z_s=?c_egx!QL5o7ez8iN-e`J0s*BIO(^N+$cIqpy1=6l(?BAj0Oa#Uq@ z64okKvN7jKQY|x2zP^r^Zl^bTNY*LzRlXZ!Pv5_~-JDJDbUl&|K)O-0Q5(5F+Pw%4 z+zUV%evZAZ3+sK7{7gHVyPfCoPHMLfa-QlIX?2EmZr+C@s;qXOsv~PGa7})e?mpC| zp;ya$fwl{L9ikPdV5>1;PWDK}5G^OLu4<;nd$nZXygqBA2sVDE=z%vF zb>3QFzl#{|w#2(^BkcfKzoxrxxFb36ReKLN&I9(My8!}4d+8g{p1JMZaN2*b@2Kb1 zm+R3x5v-V<1d5d)Jq*W8eR-iTl~!|TAm@j!9GL7w@B}8^lilPy=6Qu^){h5*Vb~WT zS5Z$b2Oc((od&e(GlG>;3{0L%-!EO1SJ?Zbv;6F7$mr}OG*vro*x0(=oV@n)?jOPn zuflFCnT*qgRS8|9FC?3F3T&q#@=iphB8_|7plr8Sc5<21PenCH%6aL+t4}|H6}iLO zsU_{~zMr|~{OEC51NMFDy$xa%^_HU>?D0q+-aWQ+Lg|XydEvY=#y+VUuojSY>|Qmk sRFoX_a-sQSj#5runR9jpH^i(l+mdwsu$?clZ&i@2Bpq$M1|4uaJJLZbko2O_*b-S+wF zkgBSlo|y)15I|4QbX8{7$;^{kHUInXQnTEwHv7#+v(s!h$IV`|-u$aM&}**Nbcgr$ zo9BA9)C`-y>vyTyZ6?k8=DIm+PMWv69yS-c9yYhlWxD&W8S6gxmYTmcKhfW1jXr8# zXp}vTxvJOC^>f^;Xs&(zc=oQjM$Nl4=0$VWoEPKmYQ!wjnxyz%zo(k_T#{TiZ}k6F z@%aO3gO^fPSG_C~M0u1Fg1 zY^8CslySe*@5_(&e#i=MCK>Zutv1i@@v$yhSq%`gBa3jg7%{v0?7mi*^$z?o=8k?w zA4faT*nH}nV)Trs4O#j`cDR<-wkLLG^;os*=7#~+%lJk<#pU+o?$!L=}ag;Pq@Nn*P06?3xk^XOt!*vz~SR1*p|lont4>ry`r(- z==X=grT#vC>~78WH0I;9!deHCmDM5Uyb%6~H;z5s2*58-^^3<7J%+N{P|xJ}5&ErZ zm2C+S%v?|JXX2f23j3^SytmTg=A&)!3Zn1BBXQeL>ya3dNY1z;N%QMM3a*_8=0EeB z;+cL={I+;vSzfo=bEe-F&w>4~dOr92;<=}3P5a6DhvK=fn@!oj-*bN~p4-wo@AQ1` zPsMY)%IK^~zZw5rJh?9nANG9iFU4~&nwLW1QO_rd*F@vBVuWs4@u@AXva#C-Vf7Ow zzPRqfWc@^mFRm9qQ6h`$#ZQ!&;(GBDC7QTi{6vW(t`|R1B8cn7Pn5{vdhrt_X1HGb z#I8K&AYnQ3+ZR2bo5t8rt0!T8D;hnb>55*j%2N*%vyT-YoOj{GOCNLVZsG^y0X#4` zusH%b&m6(r5#9y2I}_WRD5p%CCj)cjTfK859_Ne+XAVY5_Jq%J2Ubh=%*>X%@WlOe z_eAg4jPO>Om)z(q!kFU$QgU^z@ytWb-r+XyG{S}c5A}av`Nvt~S9-OoQ6{=`s`+A# zU}jVYCC^(9|ocM=X2*?R7Gq` zgSW}%7jwpeccRxwaxQD_$=}^84?nz;eDHFZAS>g_<5sKrPVd0Z=gI83p5Ei@xniJg znJogk&JrVJWOzJC0a^3bp&l2ewxzLIkv#H9)&PUl7hnNw1%6#^!9zXkyt$D2Y4Vs^ zcCv>&)<$(r#vHI}IB$s|DmwK1N|L;+w4@%RVtF#aeqehVSAU&@2l6d*9cXPpE|CZ9 zW=noMsM&3xfR*cJjyLyGAQ`m<%-DWcGCgWHuQ(j^R2ov<;*+yI5F4z@2D|#jKlddu z_r6Z|K|mS1g3Y<6{E%=!^m#iMU0rjKyQyy+Ast0aw7K4NtsGe6KeobeqRO2toVnEG zSh0HAXQ*GTOYJc^ZqCDV+gi5YwEsNbcH}SOqI)YVsrua3tCw1>jH(?)5b}YmrtJ4r zQ`G9LQh$x8I;i&Gxm2IjA+DoGRjm-UKb;No!k6i1Hr_^(*_DL-F?Un4CP(rDd;z&j znP-m*nuvtp^{M2hzB$r0*(PG~bOl=G4(fD}e3g9YmHy^9%m{}Xl@Z8CRFlW|_{Ao( z>vKmk*Jda3P5Vl&*6A-0uaJ^X17l-tq7v*IZ*0f2$ezf6-XlG?m!2#=bt7p)oC&La z>Rj8Bi3%h*RL+j9`=VJ~D;rUd)d|lB;djYf$Ac#W@Bz{j@?b4@?(PA%R? zrz&@{%ms-fWJ7ZQS@R3MqW3^FKG&7IUfhL`fjypw8=Ci=>Kg8IuDrxJjUwEqQ%S4i8*`AL0 zNbj)*XHDj1-Bu;?&=a;NbXjp*qksVT6}dEGcEwgCJOrsI+ zyGd3fDjA)Ddsop^LuT>z)L{1b^GJ9YHUF$Uq^wWfO^0iFzl)w}+pcHAy!UOcTI`3_ zC`XH~EBDKB;=Z}lFtH3iQFE#GXwInSB=vZkvIqNESmQ3mCw!?yGj{xTaIU>0kWTbk zB)eJLrs7mRS3Qau;7}2^tQG4XgQG3EyN-dSrOboKPE-lJD!EN&y5XH=6+i{f-iA91 zeRfgyWrKGKCGMHP{=!eZH^Mp+BkA+Oh0BOt@3_!04TerzYD#oF`O zibjw=E-6eZlHobCqias!o$vRQ_o z!rg@uqo5CFQfHd3+P-PL!J#G}HA;L>-AA-_twh)l!| zA5BQqX%WZhVoT}2fb zrK|7~7wzgSKToaiS)_yi5R2h?bI>84hwo9jQ5hVFHngBxG0O7`4Y@rf08Y;h#$o&o0X zIvdp4M!DLp$L^D3*JPi(Cy{HqvW8|Yom+y_}4bIND-8Y-p@^)8n`;=Fv{W3K8m zoO;;P4;j|G+CTCzh^1A%DIy6{IR~Gv$tsq_-k^1Z&^|?brx70Da9qppF0t;krRu27$`Ci|;MvN%fPlIl+ zo#!WkJ0m3XrcapeqD_D&@ zMJ+pNKG$64_<0|Zo*w5-^Ij|3mwl)^Ne@y2>8yzNy>rG2#P{uV&9>%rw9Ohtgw3pv z&QP4(w4B(Nct2Z8yf0XmGZu02Lh%)x5_8O#u7wqH8lz>rGOnXqnNfm!5dp*MU8{|s z*Q?DP=4WFe!;7p9Imy{(vDbIcM$N>YXgx!j znYuDpTY@q(%|-*~oS!Qy$Bs~-BhS4i?M|ef8OC@Jt1mmtro1Y%LfvugnisxA%yh58 zj0k3!XWULB$N37@4TRJ+Rpg;u@0V*?&oLb_M`^wCnT;-8@Y<3ygCg@BvxwJ;gPqF{ z@MkbU-tCUBHR57#xa>VG=A6_)v0of^ZCBUjh%kF&nNzpfx|_*!SvP!>`a4INk{5T4 z9Va%Z%*p4RaDuhw(MR`Q?h_~23pW2OI-<9dHuTPgC%;O(`Qn>r^Gqv zb}z}Npg{`NI{9ZY^PiqEPOExnl#};hHEZ|T08OYljRp7?wIR`Y_L|UViZfeSZaMYV zI1vEu!7Y8>JPW!+Es8ET8lQMd#e0$L=m;7S&9y+(!0Z&ka*0M|U%yM+TCP4eCZ=)z z>aiAP4x%SWB?9Dg=o`t}R0L(0Xe3K`Uu2lrE%h?{^kcnZw*)VB4o0$ED^6>h&tR}i zU&f#vVcBuWvgMU{t}%&jcnjIj+!v2=E@P+0C!uGre*bw(y??BkIc>cvZwWt|Rbz7c z!4aNa)yQAy{Yo~b(j#Y+2k3J6#Qc1n+cS6ZWP0jU7o}xcN7o-nF?)_a^*@;;Rc+FM zcvp5RrV%o$Ygn$!j%>gv#@u}WD!J;clV1;IuDQCLmY=g_8^SXE2x6p92)fti??SNO zTjm00&nGN{V}bPgn>DjnA$F07%C`pQW4wY zI~4F6?!gS`8gVX_y=Zy>a7fS0oWu0Ju4#MKv%>aY$Y$8cJ*un=*`2)NNK5Ta6$J)3 zyF=a&X=(}oEh%=BTzsAw4mZY=o(#-yF47qFp4%cx>;BO+yFuFAdqgsRvwhsn2D`!R zkqBnTDw(m9qtA-(<1j*;^D`rJg%dp~?hzkY9qi)>@-%tfGv)ddjYaR_XXzbBPjd96 z`FXJ;_b|=r5xHxZSE~CvL=PLc>$^#;V(iXG{LlJwr(5H`n9nE1e15DuT81|;U)vwZ zgp2(lSZrC3K32wQ zzv(IWsUV2RbQ-AHV%1?_bT_aL_A9?*n^EZVrDdPI-W}hQ-%p>hFndM%a%Z;hjGQwp zt2#JZJ?pk+t?R=0KHsdFVVUbfLA|?eUZ2mEQ_r(2NVds#DxYohSF#7Z7TxL-i$r5; z270UBZ8D!C!_t9++k^R%#q!E`&@O+oAUvh?-r4>EQzxE-CRbq859LnXemZGGKH)rG zTc;&G>HgXmx}pydxj1Caw&BDM&*!s7^LKc#Y-Eu3D4>w4&nMaQIb_a~K2KH#@6(-% zjg44z;s!Dc6Vp6pwn efop%KvJZC!H{-#3Ap|Ug6VO9+;(OiMgZ~c?`KLhu diff --git a/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/sq_translation b/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/sq_translation deleted file mode 100644 index 896593f04274a24d3103a2bb2348f26a921d652c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15408 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 diff --git a/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/sv_SE_translation b/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/sv_SE_translation deleted file mode 100644 index 5a7cfb3cb41cbe616302297661800352e555f121..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15660 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 diff --git a/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/ta_translation b/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/ta_translation deleted file mode 100644 index 896593f04274a24d3103a2bb2348f26a921d652c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15408 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 diff --git a/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/tr_TR_translation b/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/tr_TR_translation deleted file mode 100644 index 896593f04274a24d3103a2bb2348f26a921d652c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15408 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 diff --git a/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/uk_translation b/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/uk_translation deleted file mode 100644 index 896593f04274a24d3103a2bb2348f26a921d652c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15408 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 diff --git a/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/zh_CN_translation b/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/zh_CN_translation deleted file mode 100644 index 896593f04274a24d3103a2bb2348f26a921d652c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15408 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 diff --git a/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/zh_TW.Big5_translation b/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/zh_TW.Big5_translation deleted file mode 100644 index 896593f04274a24d3103a2bb2348f26a921d652c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15408 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 diff --git a/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/zh_TW_translation b/Session/Meta/Translations/.tx/signal-ios.localizablestrings-30/zh_TW_translation deleted file mode 100644 index 896593f04274a24d3103a2bb2348f26a921d652c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15408 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 From 613dbb4afa7b104ba8e1cb52b4d66a4e69cafce1 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 31 May 2023 17:26:43 +1000 Subject: [PATCH 085/135] Another tweak for the string linter and a couple of UI fixes Fixed a minor UI bug with the ProfilePictureView when in a SessionCell Fixed a height calculation issue on the EditClosedGroupVC --- Scripts/LintLocalizableStrings.swift | 31 +++++++++++-------- Session/Closed Groups/EditClosedGroupVC.swift | 3 +- .../Views/SessionCell+AccessoryView.swift | 10 ++---- .../MessageReceiver+ClosedGroups.swift | 3 ++ 4 files changed, 24 insertions(+), 23 deletions(-) diff --git a/Scripts/LintLocalizableStrings.swift b/Scripts/LintLocalizableStrings.swift index a000469a5..12bd626aa 100755 --- a/Scripts/LintLocalizableStrings.swift +++ b/Scripts/LintLocalizableStrings.swift @@ -15,25 +15,30 @@ let currentPath = ( /// List of files in currentPath - recursive var pathFiles: [String] = { guard - let enumerator = fileManager.enumerator(atPath: currentPath), - let files = enumerator.allObjects as? [String] + let enumerator: FileManager.DirectoryEnumerator = fileManager.enumerator( + at: URL(fileURLWithPath: currentPath), + includingPropertiesForKeys: [.isDirectoryKey], + options: [.skipsHiddenFiles] + ), + let fileUrls: [URL] = enumerator.allObjects as? [URL] else { fatalError("Could not locate files in path directory: \(currentPath)") } - return files + return fileUrls .filter { - !$0.starts(with: ".") && // Exclude hidden files (.git, .DS_STORE, etc.) - !$0.contains("Pods/") && // Exclude files under the pods folder - !$0.contains(".xcassets") && // Exclude asset bundles - !$0.contains(".app/") && // Exclude files in the app build directories - !$0.contains(".appex/") && // Exclude files in the extension build directories - !$0.localizedCaseInsensitiveContains("tests/") && // Exclude files under test directories - !$0.localizedCaseInsensitiveContains("external/") && ( // Exclude files under external directories + ((try? $0.resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory == false) && // No directories + !$0.path.contains("Pods/") && // Exclude files under the pods folder + !$0.path.contains(".xcassets") && // Exclude asset bundles + !$0.path.contains(".app/") && // Exclude files in the app build directories + !$0.path.contains(".appex/") && // Exclude files in the extension build directories + !$0.path.localizedCaseInsensitiveContains("tests/") && // Exclude files under test directories + !$0.path.localizedCaseInsensitiveContains("external/") && ( // Exclude files under external directories // Only include relevant files - $0.hasSuffix("Localizable.strings") || - NSString(string: $0).pathExtension == "swift" || - NSString(string: $0).pathExtension == "m" + $0.path.hasSuffix("Localizable.strings") || + NSString(string: $0.path).pathExtension == "swift" || + NSString(string: $0.path).pathExtension == "m" ) } + .map { $0.path } }() diff --git a/Session/Closed Groups/EditClosedGroupVC.swift b/Session/Closed Groups/EditClosedGroupVC.swift index 987e19abe..88f87fbc9 100644 --- a/Session/Closed Groups/EditClosedGroupVC.swift +++ b/Session/Closed Groups/EditClosedGroupVC.swift @@ -301,7 +301,7 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat } private func handleMembersChanged() { - tableViewHeightConstraint.constant = CGFloat(membersAndZombies.count) * 72 + tableViewHeightConstraint.constant = CGFloat(membersAndZombies.count) * 78 tableView.reloadData() } @@ -440,7 +440,6 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat } let threadId: String = self.threadId - let threadVariant: SessionThread.Variant = self.threadVariant let updatedName: String = self.name let userPublicKey: String = self.userPublicKey let updatedMemberIds: Set = self.membersAndZombies diff --git a/Session/Shared/Views/SessionCell+AccessoryView.swift b/Session/Shared/Views/SessionCell+AccessoryView.swift index bfcb56206..44c81b9eb 100644 --- a/Session/Shared/Views/SessionCell+AccessoryView.swift +++ b/Session/Shared/Views/SessionCell+AccessoryView.swift @@ -55,10 +55,10 @@ extension SessionCell { highlightingBackgroundLabel.pin(.trailing, to: .trailing, of: self, withInset: -Values.smallSpacing), highlightingBackgroundLabel.pin(.bottom, to: .bottom, of: self) ] - private lazy var profilePictureViewLeadingConstraint: NSLayoutConstraint = profilePictureView.pin(.leading, to: .leading, of: self) - private lazy var profilePictureViewTrailingConstraint: NSLayoutConstraint = profilePictureView.pin(.trailing, to: .trailing, of: self) private lazy var profilePictureViewConstraints: [NSLayoutConstraint] = [ profilePictureView.pin(.top, to: .top, of: self), + profilePictureView.pin(.leading, to: .leading, of: self), + profilePictureView.pin(.trailing, to: .trailing, of: self), profilePictureView.pin(.bottom, to: .bottom, of: self) ] private lazy var searchBarConstraints: [NSLayoutConstraint] = [ @@ -269,8 +269,6 @@ extension SessionCell { radioBorderViewHeightConstraint.isActive = false radioBorderViewConstraints.forEach { $0.isActive = false } highlightingBackgroundLabelConstraints.forEach { $0.isActive = false } - profilePictureViewLeadingConstraint.isActive = false - profilePictureViewTrailingConstraint.isActive = false profilePictureViewConstraints.forEach { $0.isActive = false } searchBarConstraints.forEach { $0.isActive = false } buttonConstraints.forEach { $0.isActive = false } @@ -458,10 +456,6 @@ extension SessionCell { fixedWidthConstraint.constant = profileSize.viewSize fixedWidthConstraint.isActive = true - profilePictureViewLeadingConstraint.constant = (profileSize.viewSize > AccessoryView.minWidth ? 0 : Values.smallSpacing) - profilePictureViewTrailingConstraint.constant = (profileSize.viewSize > AccessoryView.minWidth ? 0 : -Values.smallSpacing) - profilePictureViewLeadingConstraint.isActive = true - profilePictureViewTrailingConstraint.isActive = true profilePictureViewConstraints.forEach { $0.isActive = true } case .search(let placeholder, let accessibility, let searchTermChanged): diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift index e413db82c..ac0a974d4 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift @@ -640,6 +640,9 @@ extension MessageReceiver { } } + // Ensure the group still exists before inserting the info message + guard ClosedGroup.filter(id: threadId).isNotEmpty(db) else { return } + // Insert the info message for this group control message _ = try Interaction( serverHash: message.serverHash, From 44469d9078b8c56f5b28738bdf955113c7e94532 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 8 Jun 2023 09:22:48 +1000 Subject: [PATCH 086/135] Few minor tweaks & bug fixes Added an 'all' namespace to make the "delete from all" behaviour more explicit Defaulted the ConfigDump 'timestampMs' database column to 0 Updated the 'conversationInConfig' logic to support the note to self conversation --- Session/Settings/NukeDataModal.swift | 2 +- .../Migrations/_013_SessionUtilChanges.swift | 1 + .../MessageReceiver+ClosedGroups.swift | 7 +++++-- .../Config Handling/SessionUtil+Shared.swift | 16 +++++++++++++--- .../Models/DeleteAllMessagesRequest.swift | 18 ++++++------------ SessionSnodeKit/Networking/SnodeAPI.swift | 4 ++-- SessionSnodeKit/Types/SnodeAPINamespace.swift | 7 +++++-- 7 files changed, 33 insertions(+), 22 deletions(-) diff --git a/Session/Settings/NukeDataModal.swift b/Session/Settings/NukeDataModal.swift index 1564f2441..13a47c145 100644 --- a/Session/Settings/NukeDataModal.swift +++ b/Session/Settings/NukeDataModal.swift @@ -163,7 +163,7 @@ final class NukeDataModal: Modal { private func clearEntireAccount(presentedViewController: UIViewController) { ModalActivityIndicatorViewController .present(fromViewController: presentedViewController, canCancel: false) { [weak self] _ in - SnodeAPI.deleteAllMessages() + SnodeAPI.deleteAllMessages(namespace: .all) .subscribe(on: DispatchQueue.global(qos: .default)) .receive(on: DispatchQueue.main) .sinkUntilComplete( diff --git a/SessionMessagingKit/Database/Migrations/_013_SessionUtilChanges.swift b/SessionMessagingKit/Database/Migrations/_013_SessionUtilChanges.swift index 3923227e2..ea8c28885 100644 --- a/SessionMessagingKit/Database/Migrations/_013_SessionUtilChanges.swift +++ b/SessionMessagingKit/Database/Migrations/_013_SessionUtilChanges.swift @@ -166,6 +166,7 @@ enum _013_SessionUtilChanges: Migration { .notNull() t.column(.timestampMs, .integer) .notNull() + .defaults(to: 0) t.primaryKey([.variant, .publicKey]) } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift index ac0a974d4..1a13665af 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ClosedGroups.swift @@ -78,8 +78,11 @@ extension MessageReceiver { changeTimestampMs: Int64(sentTimestamp) ) else { - // If the closed group already exists then store the encryption keys (just in case - there can be - // some weird edge-cases where we don't have keys we need if we don't store them) + // If the closed group already exists then store the encryption keys (since the config only stores + // the latest key we won't be able to decrypt older messages if we were added to the group within + // the last two weeks and the key has been rotated - unfortunately if the user was added more than + // two weeks ago and the keys were rotated within the last two weeks then we won't be able to decrypt + // messages received before the key rotation) let groupPublicKey: String = publicKeyAsData.toHexString() let receivedTimestamp: TimeInterval = (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) let newKeyPair: ClosedGroupKeyPair = ClosedGroupKeyPair( diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift index f6d6e74a5..8fb7d949e 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift @@ -3,6 +3,7 @@ import UIKit import GRDB import SessionUIKit +import SessionSnodeKit import SessionUtil import SessionUtilitiesKit @@ -72,7 +73,7 @@ internal extension SessionUtil { conf: conf, for: variant, publicKey: publicKey, - timestampMs: Int64(Date().timeIntervalSince1970 * 1000) + timestampMs: SnodeAPI.currentOffsetTimestampMs() )?.save(db) return config_needs_push(conf) @@ -342,21 +343,30 @@ public extension SessionUtil { // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent guard SessionUtil.userConfigsEnabled(db) else { return true } + let userPublicKey: String = getUserHexEncodedPublicKey() let configVariant: ConfigDump.Variant = { switch threadVariant { - case .contact: return .contacts + case .contact: return (threadId == userPublicKey ? .userProfile : .contacts) case .legacyGroup, .group, .community: return .userGroups } }() return SessionUtil - .config(for: configVariant, publicKey: getUserHexEncodedPublicKey()) + .config(for: configVariant, publicKey: userPublicKey) .wrappedValue .map { conf in var cThreadId: [CChar] = threadId.cArray.nullTerminated() switch threadVariant { case .contact: + // The 'Note to Self' conversation is stored in the 'userProfile' config + guard threadId != userPublicKey else { + return ( + !visibleOnly || + SessionUtil.shouldBeVisible(priority: user_profile_get_nts_priority(conf)) + ) + } + var contact: contacts_contact = contacts_contact() guard contacts_get(conf, &contact, &cThreadId) else { return false } diff --git a/SessionSnodeKit/Models/DeleteAllMessagesRequest.swift b/SessionSnodeKit/Models/DeleteAllMessagesRequest.swift index bd3526273..5104dbcca 100644 --- a/SessionSnodeKit/Models/DeleteAllMessagesRequest.swift +++ b/SessionSnodeKit/Models/DeleteAllMessagesRequest.swift @@ -13,12 +13,12 @@ extension SnodeAPI { /// /// **Note:** If omitted when sending the request, messages are deleted from the default namespace /// only (namespace 0) - let namespace: SnodeAPI.Namespace? + let namespace: SnodeAPI.Namespace // MARK: - Init public init( - namespace: SnodeAPI.Namespace?, + namespace: SnodeAPI.Namespace, pubkey: String, timestampMs: UInt64, ed25519PublicKey: [UInt8], @@ -39,11 +39,10 @@ extension SnodeAPI { override public func encode(to encoder: Encoder) throws { var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) - // If no namespace is specified it defaults to the default namespace only (namespace - // 0), so instead in this case we want to explicitly delete from `all` namespaces + // The 'all' namespace should be sent through as `all` instead of a numerical value switch namespace { - case .some(let namespace): try container.encode(namespace, forKey: .namespace) - case .none: try container.encode("all", forKey: .namespace) + case .all: try container.encode(namespace.verificationString, forKey: .namespace) + default: try container.encode(namespace, forKey: .namespace) } try super.encode(to: encoder) @@ -58,12 +57,7 @@ extension SnodeAPI { /// The signature must be signed by the ed25519 pubkey in `pubkey` (omitting the leading prefix). /// Must be base64 encoded for json requests; binary for OMQ requests. let verificationBytes: [UInt8] = SnodeAPI.Endpoint.deleteAll.rawValue.bytes - .appending( - contentsOf: (namespace == nil ? - "all" : - namespace?.verificationString - )?.bytes - ) + .appending(contentsOf: namespace.verificationString.bytes) .appending(contentsOf: timestampMs.map { "\($0)" }?.data(using: .ascii)?.bytes) guard diff --git a/SessionSnodeKit/Networking/SnodeAPI.swift b/SessionSnodeKit/Networking/SnodeAPI.swift index b0d7112bd..6e2abafbb 100644 --- a/SessionSnodeKit/Networking/SnodeAPI.swift +++ b/SessionSnodeKit/Networking/SnodeAPI.swift @@ -891,7 +891,7 @@ public final class SnodeAPI { /// Clears all the user's data from their swarm. Returns a dictionary of snode public key to deletion confirmation. public static func deleteAllMessages( - namespace: SnodeAPI.Namespace? = nil, + namespace: SnodeAPI.Namespace, using dependencies: SSKDependencies = SSKDependencies() ) -> AnyPublisher<[String: Bool], Error> { guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else { @@ -939,7 +939,7 @@ public final class SnodeAPI { /// Clears all the user's data from their swarm. Returns a dictionary of snode public key to deletion confirmation. public static func deleteAllMessages( beforeMs: UInt64, - namespace: SnodeAPI.Namespace? = nil, + namespace: SnodeAPI.Namespace, using dependencies: SSKDependencies = SSKDependencies() ) -> AnyPublisher<[String: Bool], Error> { guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else { diff --git a/SessionSnodeKit/Types/SnodeAPINamespace.swift b/SessionSnodeKit/Types/SnodeAPINamespace.swift index 726377bc2..73dd9182d 100644 --- a/SessionSnodeKit/Types/SnodeAPINamespace.swift +++ b/SessionSnodeKit/Types/SnodeAPINamespace.swift @@ -14,6 +14,8 @@ public extension SnodeAPI { case legacyClosedGroup = -10 + case all = -9999990 + // MARK: Variables var requiresReadAuthentication: Bool { @@ -50,7 +52,7 @@ public extension SnodeAPI { case .configUserProfile, .configContacts, .configConvoInfoVolatile, .configUserGroups, - .configClosedGroupInfo: + .configClosedGroupInfo, .all: return false } } @@ -58,6 +60,7 @@ public extension SnodeAPI { var verificationString: String { switch self { case .`default`: return "" + case .all: return "all" default: return "\(self.rawValue)" } } @@ -85,7 +88,7 @@ public extension SnodeAPI { case .configUserProfile, .configContacts, .configConvoInfoVolatile, .configUserGroups, - .configClosedGroupInfo: + .configClosedGroupInfo, .all: return 1 } } From 1ba060b7f0ce87d1930ee1a205c704062b8f0c7f Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 13 Jun 2023 14:40:04 +1000 Subject: [PATCH 087/135] Cleaned up the outdated message logic and fixed a few bugs Fixed a bug where showing hidden conversations would appear at the bottom of the list Fixed a bug where you would be kicked from a one-to-one conversation when opening it if you had hidden it Fixed a bug where joining a community via URL wasn't automatically opening the community Fixed a bug where the community poller could trigger again before the previous poll completed Fixed an edge-case where community messages could be missed if the app crashed at the right time when processing a poll response --- Session/Conversations/ConversationVC.swift | 4 ++ .../GlobalSearchViewController.swift | 13 +++++ Session/Open Groups/JoinOpenGroupVC.swift | 4 +- SessionMessagingKit/Messages/Message.swift | 12 ++--- .../Open Groups/OpenGroupManager.swift | 32 ++++++++---- .../Errors/MessageReceiverError.swift | 13 +++++ .../MessageReceiver+Calls.swift | 25 +-------- ...eReceiver+DataExtractionNotification.swift | 40 ++------------ .../MessageReceiver+MessageRequests.swift | 29 ++--------- .../MessageReceiver+VisibleMessages.swift | 27 +--------- .../Sending & Receiving/MessageReceiver.swift | 52 ++++++++++++++++++- .../Pollers/OpenGroupPoller.swift | 32 +++++++++--- .../Config Handling/SessionUtil+Shared.swift | 21 ++++---- .../NotificationServiceExtension.swift | 10 +++- 14 files changed, 164 insertions(+), 150 deletions(-) diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index 5b22e7ae5..37a218c4f 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -1587,6 +1587,10 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers self.scrollButton.alpha = self.getScrollButtonOpacity() self.unreadCountView.alpha = self.scrollButton.alpha + // The initial scroll can trigger this logic but we already mark the initially focused message + // as read so don't run the below until the user actually scrolls after the initial layout + guard self.didFinishInitialLayout else { return } + // We want to mark messages as read while we scroll, so grab the newest message and mark // everything older as read // diff --git a/Session/Home/GlobalSearch/GlobalSearchViewController.swift b/Session/Home/GlobalSearch/GlobalSearchViewController.swift index f36992c24..7d4103a85 100644 --- a/Session/Home/GlobalSearch/GlobalSearchViewController.swift +++ b/Session/Home/GlobalSearch/GlobalSearchViewController.swift @@ -311,6 +311,19 @@ extension GlobalSearchViewController { return } + // If it's a one-to-one thread then make sure the thread exists before pushing to it (in case the + // contact has been hidden) + if threadVariant == .contact { + Storage.shared.write { db in + try SessionThread.fetchOrCreate( + db, + id: threadId, + variant: threadVariant, + shouldBeVisible: nil // Don't change current state + ) + } + } + let viewController: ConversationVC = ConversationVC( threadId: threadId, threadVariant: threadVariant, diff --git a/Session/Open Groups/JoinOpenGroupVC.swift b/Session/Open Groups/JoinOpenGroupVC.swift index 0d36e4063..3f25f1175 100644 --- a/Session/Open Groups/JoinOpenGroupVC.swift +++ b/Session/Open Groups/JoinOpenGroupVC.swift @@ -159,10 +159,10 @@ final class JoinOpenGroupVC: BaseVC, UIPageViewControllerDataSource, UIPageViewC return } - joinOpenGroup(roomToken: room, server: server, publicKey: publicKey) + joinOpenGroup(roomToken: room, server: server, publicKey: publicKey, shouldOpenCommunity: true) } - fileprivate func joinOpenGroup(roomToken: String, server: String, publicKey: String, shouldOpenCommunity: Bool = false) { + fileprivate func joinOpenGroup(roomToken: String, server: String, publicKey: String, shouldOpenCommunity: Bool) { guard !isJoining, let navigationController: UINavigationController = navigationController else { return } isJoining = true diff --git a/SessionMessagingKit/Messages/Message.swift b/SessionMessagingKit/Messages/Message.swift index 97d6198e9..9e382e7a2 100644 --- a/SessionMessagingKit/Messages/Message.swift +++ b/SessionMessagingKit/Messages/Message.swift @@ -277,15 +277,9 @@ public extension Message { return processedMessage } catch { - // If we get 'selfSend' or 'duplicateControlMessage' errors then we still want to insert - // the SnodeReceivedMessageInfo to prevent retrieving and attempting to process the same - // message again (as well as ensure the next poll doesn't retrieve the same message) - switch error { - case MessageReceiverError.selfSend, MessageReceiverError.duplicateControlMessage: - _ = try? rawMessage.info.inserted(db) - break - - default: break + // For some error cases we want to update the last hash so do so + if (error as? MessageReceiverError)?.shouldUpdateLastHash == true { + _ = try? rawMessage.info.inserted(db) } throw error diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index 3716460df..0cb6f4a6b 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -605,18 +605,19 @@ public final class OpenGroupManager { return } - let seqNo: Int64? = messages.map { $0.seqNo }.max() let sortedMessages: [OpenGroupAPI.Message] = messages .filter { $0.deleted != true } .sorted { lhs, rhs in lhs.id < rhs.id } - var messageServerIdsToRemove: [Int64] = messages + var messageServerInfoToRemove: [(id: Int64, seqNo: Int64)] = messages .filter { $0.deleted == true } - .map { $0.id } - - if let seqNo: Int64 = seqNo { + .map { ($0.id, $0.seqNo) } + let updateSeqNo: (Database, String, inout Int64, Int64, OGMDependencies) -> () = { db, openGroupId, lastValidSeqNo, seqNo, dependencies in + // Only update the data if the 'seqNo' is larger than the lastValidSeqNo (only want it to increase) + guard seqNo > lastValidSeqNo else { return } + // Update the 'openGroupSequenceNumber' value (Note: SOGS V4 uses the 'seqNo' instead of the 'serverId') _ = try? OpenGroup - .filter(id: openGroup.id) + .filter(id: openGroupId) .updateAll(db, OpenGroup.Columns.sequenceNumber.set(to: seqNo)) // Update pendingChange cache @@ -624,13 +625,18 @@ public final class OpenGroupManager { $0.pendingChanges = $0.pendingChanges .filter { $0.seqNo == nil || $0.seqNo! > seqNo } } + + // Update the inout value + lastValidSeqNo = seqNo } // Process the messages + var lastValidSeqNo: Int64 = -1 sortedMessages.forEach { message in if message.base64EncodedData == nil && message.reactions == nil { - messageServerIdsToRemove.append(Int64(message.id)) - return + messageServerInfoToRemove.append((message.id, message.seqNo)) + + return updateSeqNo(db, openGroup.id, &lastValidSeqNo, message.seqNo, dependencies) } // Handle messages @@ -657,6 +663,7 @@ public final class OpenGroupManager { associatedWithProto: try SNProtoContent.parseData(messageInfo.serializedProtoData), dependencies: dependencies ) + updateSeqNo(db, openGroup.id, &lastValidSeqNo, message.seqNo, dependencies) } } catch { @@ -701,6 +708,7 @@ public final class OpenGroupManager { openGroupMessageServerId: message.id, openGroupReactions: reactions ) + updateSeqNo(db, openGroup.id, &lastValidSeqNo, message.seqNo, dependencies) } catch { SNLog("Couldn't handle open group reactions due to error: \(error).") @@ -709,12 +717,18 @@ public final class OpenGroupManager { } // Handle any deletions that are needed - guard !messageServerIdsToRemove.isEmpty else { return } + guard !messageServerInfoToRemove.isEmpty else { return } + let messageServerIdsToRemove: [Int64] = messageServerInfoToRemove.map { $0.id } _ = try? Interaction .filter(Interaction.Columns.threadId == openGroup.threadId) .filter(messageServerIdsToRemove.contains(Interaction.Columns.openGroupServerMessageId)) .deleteAll(db) + + // Update the seqNo for deletions + if let lastDeletionSeqNo: Int64 = messageServerInfoToRemove.map({ $0.seqNo }).max() { + updateSeqNo(db, openGroup.id, &lastValidSeqNo, lastDeletionSeqNo, dependencies) + } } internal static func handleDirectMessages( diff --git a/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift b/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift index bcc097efe..8cc59e6e4 100644 --- a/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift +++ b/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift @@ -34,6 +34,19 @@ public enum MessageReceiverError: LocalizedError { default: return true } } + + public var shouldUpdateLastHash: Bool { + switch self { + // If we get one of these errors then we still want to update the last hash to prevent + // retrieving and attempting to process the same messages again (as well as ensure the + // next poll doesn't retrieve the same message - these errors are essentially considered + // "already successfully processed") + case .selfSend, .duplicateControlMessage, .outdatedMessage: + return true + + default: return false + } + } public var errorDescription: String? { switch self { diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift index dafc634d2..8737f2643 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift @@ -13,31 +13,8 @@ extension MessageReceiver { threadVariant: SessionThread.Variant, message: CallMessage ) throws { - let timestampMs: Int64 = (message.sentTimestamp.map { Int64($0) } ?? SnodeAPI.currentOffsetTimestampMs()) - // Only support calls from contact threads - guard - threadVariant == .contact, - /// Only process the message if the thread `shouldBeVisible` or it was sent after the libSession buffer period - ( - SessionThread - .filter(id: threadId) - .filter(SessionThread.Columns.shouldBeVisible == true) - .isNotEmpty(db) || - SessionUtil.conversationInConfig( - db, - threadId: threadId, - threadVariant: threadVariant, - visibleOnly: true - ) || - SessionUtil.canPerformChange( - db, - threadId: threadId, - targetConfig: .contacts, - changeTimestampMs: timestampMs - ) - ) - else { return } + guard threadVariant == .contact else { return } switch message.kind { case .preOffer: try MessageReceiver.handleNewCallMessage(db, message: message) diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+DataExtractionNotification.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+DataExtractionNotification.swift index e9397be26..48b91fba4 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+DataExtractionNotification.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+DataExtractionNotification.swift @@ -3,7 +3,6 @@ import Foundation import GRDB import SessionSnodeKit -import SessionUtilitiesKit extension MessageReceiver { internal static func handleDataExtractionNotification( @@ -12,46 +11,12 @@ extension MessageReceiver { threadVariant: SessionThread.Variant, message: DataExtractionNotification ) throws { - let timestampMs: Int64 = ( - message.sentTimestamp.map { Int64($0) } ?? - SnodeAPI.currentOffsetTimestampMs() - ) - guard threadVariant == .contact, let sender: String = message.sender, let messageKind: DataExtractionNotification.Kind = message.kind else { throw MessageReceiverError.invalidMessage } - /// Only process the message if the thread `shouldBeVisible` or it was sent after the libSession buffer period - guard - SessionThread - .filter(id: threadId) - .filter(SessionThread.Columns.shouldBeVisible == true) - .isNotEmpty(db) || - SessionUtil.conversationInConfig( - db, - threadId: threadId, - threadVariant: threadVariant, - visibleOnly: true - ) || - SessionUtil.canPerformChange( - db, - threadId: threadId, - targetConfig: { - switch threadVariant { - case .contact: - let currentUserPublicKey: String = getUserHexEncodedPublicKey(db) - - return (threadId == currentUserPublicKey ? .userProfile : .contacts) - - default: return .userGroups - } - }(), - changeTimestampMs: timestampMs - ) - else { throw MessageReceiverError.outdatedMessage } - _ = try Interaction( serverHash: message.serverHash, threadId: threadId, @@ -62,7 +27,10 @@ extension MessageReceiver { case .mediaSaved: return .infoMediaSavedNotification } }(), - timestampMs: timestampMs + timestampMs: ( + message.sentTimestamp.map { Int64($0) } ?? + SnodeAPI.currentOffsetTimestampMs() + ) ).inserted(db) } } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift index fbaad1124..4921a2d48 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift @@ -13,10 +13,6 @@ extension MessageReceiver { dependencies: SMKDependencies ) throws { let userPublicKey = getUserHexEncodedPublicKey(db, dependencies: dependencies) - let timestampMs: Int64 = ( - message.sentTimestamp.map { Int64($0) } ?? - SnodeAPI.currentOffsetTimestampMs() - ) var blindedContactIds: [String] = [] // Ignore messages which were sent from the current user @@ -25,26 +21,6 @@ extension MessageReceiver { let senderId: String = message.sender else { throw MessageReceiverError.invalidMessage } - /// Only process the message if the thread `shouldBeVisible` or it was sent after the libSession buffer period - guard - SessionThread - .filter(id: senderId) - .filter(SessionThread.Columns.shouldBeVisible == true) - .isNotEmpty(db) || - SessionUtil.conversationInConfig( - db, - threadId: senderId, - threadVariant: .contact, - visibleOnly: true - ) || - SessionUtil.canPerformChange( - db, - threadId: senderId, - targetConfig: .contacts, - changeTimestampMs: timestampMs - ) - else { throw MessageReceiverError.outdatedMessage } - // Update profile if needed (want to do this regardless of whether the message exists or // not to ensure the profile info gets sync between a users devices at every chance) if let profile = message.profile { @@ -160,7 +136,10 @@ extension MessageReceiver { threadId: unblindedThread.id, authorId: senderId, variant: .infoMessageRequestAccepted, - timestampMs: timestampMs + timestampMs: ( + message.sentTimestamp.map { Int64($0) } ?? + SnodeAPI.currentOffsetTimestampMs() + ) ).inserted(db) } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift index d41cf4f40..1fc33de52 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift @@ -23,32 +23,6 @@ extension MessageReceiver { // seconds to maintain the accuracy) let messageSentTimestamp: TimeInterval = (TimeInterval(message.sentTimestamp ?? 0) / 1000) let isMainAppActive: Bool = (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) - let currentUserPublicKey: String = getUserHexEncodedPublicKey(db, dependencies: dependencies) - - /// Only process the message if the thread `shouldBeVisible` or it was sent after the libSession buffer period - guard - SessionThread - .filter(id: threadId) - .filter(SessionThread.Columns.shouldBeVisible == true) - .isNotEmpty(db) || - SessionUtil.conversationInConfig( - db, - threadId: threadId, - threadVariant: threadVariant, - visibleOnly: true - ) || - SessionUtil.canPerformChange( - db, - threadId: threadId, - targetConfig: { - switch threadVariant { - case .contact: return (threadId == currentUserPublicKey ? .userProfile : .contacts) - default: return .userGroups - } - }(), - changeTimestampMs: (message.sentTimestamp.map { Int64($0) } ?? SnodeAPI.currentOffsetTimestampMs()) - ) - else { throw MessageReceiverError.outdatedMessage } // Update profile if needed (want to do this regardless of whether the message exists or // not to ensure the profile info gets sync between a users devices at every chance) @@ -90,6 +64,7 @@ extension MessageReceiver { } // Store the message variant so we can run variant-specific behaviours + let currentUserPublicKey: String = getUserHexEncodedPublicKey(db, dependencies: dependencies) let thread: SessionThread = try SessionThread .fetchOrCreate(db, id: threadId, variant: threadVariant, shouldBeVisible: nil) let maybeOpenGroup: OpenGroup? = { diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index ba5f4263b..847f17805 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -192,6 +192,15 @@ public enum MessageReceiver { SessionUtil.conversationInConfig(db, threadId: threadId, threadVariant: threadVariant, visibleOnly: false) else { throw MessageReceiverError.requiredThreadNotInConfig } + // Throw if the message is outdated and shouldn't be processed + try throwIfMessageOutdated( + db, + message: message, + threadId: threadId, + threadVariant: threadVariant, + dependencies: dependencies + ) + switch message { case let message as ReadReceipt: try MessageReceiver.handleReadReceipt( @@ -315,7 +324,8 @@ public enum MessageReceiver { .filter(id: threadId) .updateAllAndConfig( db, - SessionThread.Columns.shouldBeVisible.set(to: true) + SessionThread.Columns.shouldBeVisible.set(to: true), + SessionThread.Columns.pinnedPriority.set(to: SessionUtil.visiblePriority) ) } } @@ -344,4 +354,44 @@ public enum MessageReceiver { try reaction.with(interactionId: interactionId).insert(db) } } + + public static func throwIfMessageOutdated( + _ db: Database, + message: Message, + threadId: String, + threadVariant: SessionThread.Variant, + dependencies: SMKDependencies = SMKDependencies() + ) throws { + switch message { + case is ReadReceipt: return // No visible artifact created so better to keep for more reliable read states + case is UnsendRequest: return // We should always process the removal of messages just in case + default: break + } + + // Determine the state of the conversation and the validity of the message + let currentUserPublicKey: String = getUserHexEncodedPublicKey(db, dependencies: dependencies) + let conversationVisibleInConfig: Bool = SessionUtil.conversationInConfig( + db, + threadId: threadId, + threadVariant: threadVariant, + visibleOnly: true + ) + let canPerformChange: Bool = SessionUtil.canPerformChange( + db, + threadId: threadId, + targetConfig: { + switch threadVariant { + case .contact: return (threadId == currentUserPublicKey ? .userProfile : .contacts) + default: return .userGroups + } + }(), + changeTimestampMs: (message.sentTimestamp.map { Int64($0) } ?? SnodeAPI.currentOffsetTimestampMs()) + ) + + // If the thread is visible or the message was sent more recently than the last config message (minus + // buffer period) then we should process the message, if not then throw as the message is outdated + guard !conversationVisibleInConfig && !canPerformChange else { return } + + throw MessageReceiverError.outdatedMessage + } } diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift index d4ff78b6c..7458ca7a8 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift @@ -53,16 +53,32 @@ extension OpenGroupAPI { .fetchOne(db) } .defaulting(to: 0) + let lastPollStart: TimeInterval = Date().timeIntervalSince1970 let nextPollInterval: TimeInterval = getInterval(for: minPollFailureCount, minInterval: Poller.minPollInterval, maxInterval: Poller.maxPollInterval) - poll(using: dependencies).sinkUntilComplete() - timer = Timer.scheduledTimerOnMainThread(withTimeInterval: nextPollInterval, repeats: false) { [weak self] timer in - timer.invalidate() - - Threading.pollerQueue.async { - self?.pollRecursively(using: dependencies) - } - } + // Wait until the last poll completes before polling again ensuring we don't poll any faster than + // the 'nextPollInterval' value + poll(using: dependencies) + .sinkUntilComplete( + receiveCompletion: { [weak self] _ in + let currentTime: TimeInterval = Date().timeIntervalSince1970 + let remainingInterval: TimeInterval = max(0, nextPollInterval - (currentTime - lastPollStart)) + + guard remainingInterval > 0 else { + return Threading.pollerQueue.async { + self?.pollRecursively(using: dependencies) + } + } + + self?.timer = Timer.scheduledTimerOnMainThread(withTimeInterval: remainingInterval, repeats: false) { timer in + timer.invalidate() + + Threading.pollerQueue.async { + self?.pollRecursively(using: dependencies) + } + } + } + ) } public func poll( diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift index 8fb7d949e..a925cbcb0 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift @@ -34,11 +34,14 @@ internal extension SessionUtil { return !allColumnsThatTriggerConfigUpdate.isDisjoint(with: targetColumns) } + /// A `0` `priority` value indicates visible, but not pinned + static let visiblePriority: Int32 = 0 + /// A negative `priority` value indicates hidden static let hiddenPriority: Int32 = -1 static func shouldBeVisible(priority: Int32) -> Bool { - return (priority >= 0) + return (priority >= SessionUtil.visiblePriority) } static func performAndPushChange( @@ -127,8 +130,8 @@ internal extension SessionUtil { guard noteToSelf.shouldBeVisible else { return SessionUtil.hiddenPriority } return noteToSelf.pinnedPriority - .map { Int32($0 == 0 ? 0 : max($0, 1)) } - .defaulting(to: 0) + .map { Int32($0 == 0 ? SessionUtil.visiblePriority : max($0, 1)) } + .defaulting(to: SessionUtil.visiblePriority) }(), in: conf ) @@ -154,8 +157,8 @@ internal extension SessionUtil { guard thread.shouldBeVisible else { return SessionUtil.hiddenPriority } return thread.pinnedPriority - .map { Int32($0 == 0 ? 0 : max($0, 1)) } - .defaulting(to: 0) + .map { Int32($0 == 0 ? SessionUtil.visiblePriority : max($0, 1)) } + .defaulting(to: SessionUtil.visiblePriority) }() ) }, @@ -176,8 +179,8 @@ internal extension SessionUtil { CommunityInfo( urlInfo: urlInfo, priority: thread.pinnedPriority - .map { Int32($0 == 0 ? 0 : max($0, 1)) } - .defaulting(to: 0) + .map { Int32($0 == 0 ? SessionUtil.visiblePriority : max($0, 1)) } + .defaulting(to: SessionUtil.visiblePriority) ) } }, @@ -197,8 +200,8 @@ internal extension SessionUtil { LegacyGroupInfo( id: thread.id, priority: thread.pinnedPriority - .map { Int32($0 == 0 ? 0 : max($0, 1)) } - .defaulting(to: 0) + .map { Int32($0 == 0 ? SessionUtil.visiblePriority : max($0, 1)) } + .defaulting(to: SessionUtil.visiblePriority) ) }, in: conf diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index 3a61aa8b3..e423e2784 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -75,6 +75,14 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension return } + // Throw if the message is outdated and shouldn't be processed + try MessageReceiver.throwIfMessageOutdated( + db, + message: processedMessage.messageInfo.message, + threadId: processedMessage.threadId, + threadVariant: processedMessage.threadVariant + ) + switch processedMessage.messageInfo.message { case let visibleMessage as VisibleMessage: let interactionId: Int64 = try MessageReceiver.handleVisibleMessage( @@ -174,7 +182,7 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension catch { if let error = error as? MessageReceiverError, error.isRetryable { switch error { - case .invalidGroupPublicKey, .noGroupKeyPair: self.completeSilenty() + case .invalidGroupPublicKey, .noGroupKeyPair, .outdatedMessage: self.completeSilenty() default: self.handleFailure(for: notificationContent) } } From 44f8b7f59d48d4875bd3971a2dd37afbcadd3b49 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 13 Jun 2023 16:09:06 +1000 Subject: [PATCH 088/135] Updated the GroupLeavingJob to succeed for certain error cases --- .../Jobs/Types/GroupLeavingJob.swift | 126 ++++++++++-------- .../Errors/MessageSenderError.swift | 24 +++- 2 files changed, 90 insertions(+), 60 deletions(-) diff --git a/SessionMessagingKit/Jobs/Types/GroupLeavingJob.swift b/SessionMessagingKit/Jobs/Types/GroupLeavingJob.swift index a66935fac..b078fe781 100644 --- a/SessionMessagingKit/Jobs/Types/GroupLeavingJob.swift +++ b/SessionMessagingKit/Jobs/Types/GroupLeavingJob.swift @@ -58,67 +58,75 @@ public enum GroupLeavingJob: JobExecutor { .receive(on: queue) .sinkUntilComplete( receiveCompletion: { result in - switch result { - case .failure: - Storage.shared.writeAsync { db in - try Interaction - .filter(id: job.interactionId) - .updateAll( - db, - [ - Interaction.Columns.variant - .set(to: Interaction.Variant.infoClosedGroupCurrentUserErrorLeaving), - Interaction.Columns.body.set(to: "group_unable_to_leave".localized()) - ] - ) + let failureChanges: [ConfigColumnAssignment] = [ + Interaction.Columns.variant + .set(to: Interaction.Variant.infoClosedGroupCurrentUserErrorLeaving), + Interaction.Columns.body.set(to: "group_unable_to_leave".localized()) + ] + let successfulChanges: [ConfigColumnAssignment] = [ + Interaction.Columns.variant + .set(to: Interaction.Variant.infoClosedGroupCurrentUserLeft), + Interaction.Columns.body.set(to: "GROUP_YOU_LEFT".localized()) + ] + + // Handle the appropriate response + Storage.shared.writeAsync { db in + // If it failed due to one of these errors then clear out any associated data (as somehow + // the 'SessionThread' exists but not the data required to send the 'MEMBER_LEFT' message + // which would leave the user in a state where they can't leave the group) + let errorsToSucceed: [MessageSenderError] = [ + .invalidClosedGroupUpdate, + .noKeyPair + ] + let shouldSucceed: Bool = { + switch result { + case .failure(let error as MessageSenderError): return errorsToSucceed.contains(error) + case .failure: return false + default: return true } - success(job, false) - - case .finished: - Storage.shared.writeAsync { db in - // Update the group (if the admin leaves the group is disbanded) - let currentUserPublicKey: String = getUserHexEncodedPublicKey(db) - let wasAdminUser: Bool = GroupMember - .filter(GroupMember.Columns.groupId == threadId) - .filter(GroupMember.Columns.profileId == currentUserPublicKey) - .filter(GroupMember.Columns.role == GroupMember.Role.admin) - .isNotEmpty(db) - - if wasAdminUser { - try GroupMember - .filter(GroupMember.Columns.groupId == threadId) - .deleteAll(db) - } - else { - try GroupMember - .filter(GroupMember.Columns.groupId == threadId) - .filter(GroupMember.Columns.profileId == currentUserPublicKey) - .deleteAll(db) - } - - // Update the transaction - try Interaction - .filter(id: interactionId) - .updateAll( - db, - [ - Interaction.Columns.variant - .set(to: Interaction.Variant.infoClosedGroupCurrentUserLeft), - Interaction.Columns.body.set(to: "GROUP_YOU_LEFT".localized()) - ] - ) - - // Clear out the group info as needed - try ClosedGroup.removeKeysAndUnsubscribe( - db, - threadId: threadId, - removeGroupData: details.deleteThread, - calledFromConfigHandling: false - ) - } - - success(job, false) + }() + + // Update the transaction + try Interaction + .filter(id: interactionId) + .updateAll( + db, + (shouldSucceed ? successfulChanges : failureChanges) + ) + + // If we succeed in leaving then we should try to clear the group data + guard shouldSucceed else { return } + + // Update the group (if the admin leaves the group is disbanded) + let currentUserPublicKey: String = getUserHexEncodedPublicKey(db) + let wasAdminUser: Bool = GroupMember + .filter(GroupMember.Columns.groupId == threadId) + .filter(GroupMember.Columns.profileId == currentUserPublicKey) + .filter(GroupMember.Columns.role == GroupMember.Role.admin) + .isNotEmpty(db) + + if wasAdminUser { + try GroupMember + .filter(GroupMember.Columns.groupId == threadId) + .deleteAll(db) + } + else { + try GroupMember + .filter(GroupMember.Columns.groupId == threadId) + .filter(GroupMember.Columns.profileId == currentUserPublicKey) + .deleteAll(db) + } + + // Clear out the group info as needed + try ClosedGroup.removeKeysAndUnsubscribe( + db, + threadId: threadId, + removeGroupData: details.deleteThread, + calledFromConfigHandling: false + ) } + + success(job, false) } ) } diff --git a/SessionMessagingKit/Sending & Receiving/Errors/MessageSenderError.swift b/SessionMessagingKit/Sending & Receiving/Errors/MessageSenderError.swift index 0ee8cd684..12cb06900 100644 --- a/SessionMessagingKit/Sending & Receiving/Errors/MessageSenderError.swift +++ b/SessionMessagingKit/Sending & Receiving/Errors/MessageSenderError.swift @@ -2,7 +2,7 @@ import Foundation -public enum MessageSenderError: LocalizedError { +public enum MessageSenderError: LocalizedError, Equatable { case invalidMessage case protoConversionFailed case noUserX25519KeyPair @@ -44,4 +44,26 @@ public enum MessageSenderError: LocalizedError { case .other(let error): return error.localizedDescription } } + + public static func == (lhs: MessageSenderError, rhs: MessageSenderError) -> Bool { + switch (lhs, rhs) { + case (.invalidMessage, .invalidMessage): return true + case (.protoConversionFailed, .protoConversionFailed): return true + case (.noUserX25519KeyPair, .noUserX25519KeyPair): return true + case (.noUserED25519KeyPair, .noUserED25519KeyPair): return true + case (.signingFailed, .signingFailed): return true + case (.encryptionFailed, .encryptionFailed): return true + case (.noUsername, .noUsername): return true + case (.attachmentsNotUploaded, .attachmentsNotUploaded): return true + case (.noThread, .noThread): return true + case (.noKeyPair, .noKeyPair): return true + case (.invalidClosedGroupUpdate, .invalidClosedGroupUpdate): return true + + case (.other(let lhsError), .other(let rhsError)): + // Not ideal but the best we can do + return (lhsError.localizedDescription == rhsError.localizedDescription) + + default: return false + } + } } From 65bf7d7d823cc1634d9577c60d533eeb42f71207 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 14 Jun 2023 16:39:07 +1000 Subject: [PATCH 089/135] Updated build and version numbers for internal testing --- Session.xcodeproj/project.pbxproj | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index cfa0cdded..f6e415d2b 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -6385,7 +6385,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 406; + CURRENT_PROJECT_VERSION = 407; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6409,7 +6409,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.2.14; + MARKETING_VERSION = 2.3.0; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -6457,7 +6457,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 406; + CURRENT_PROJECT_VERSION = 407; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6486,7 +6486,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.2.14; + MARKETING_VERSION = 2.3.0; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -6522,7 +6522,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 406; + CURRENT_PROJECT_VERSION = 407; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6545,7 +6545,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.2.14; + MARKETING_VERSION = 2.3.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension"; @@ -6596,7 +6596,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 406; + CURRENT_PROJECT_VERSION = 407; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6624,7 +6624,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.2.14; + MARKETING_VERSION = 2.3.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension"; @@ -7504,7 +7504,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 406; + CURRENT_PROJECT_VERSION = 407; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -7542,7 +7542,7 @@ "$(SRCROOT)", ); LLVM_LTO = NO; - MARKETING_VERSION = 2.2.14; + MARKETING_VERSION = 2.3.0; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; @@ -7575,7 +7575,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 406; + CURRENT_PROJECT_VERSION = 407; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -7613,7 +7613,7 @@ "$(SRCROOT)", ); LLVM_LTO = NO; - MARKETING_VERSION = 2.2.14; + MARKETING_VERSION = 2.3.0; OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; PRODUCT_NAME = Session; From d2c82cb9156fbc05c860d6168fdbfe8eabd65386 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 16 Jun 2023 19:38:14 +1000 Subject: [PATCH 090/135] Started working on some config contact pruning logic Added support for a ConfirmationModal with an input field Added a mechanism on Debug builds to export the database and it's key Added logic to catch exceptions thrown within libSession (need to actually plug it in) Added a debug-only mechanism to export the users database and (encrypted) database key Added a few unit tests to check the CONTACTS config message size constraints --- Scripts/DecryptExportedKey.swift | 20 + Session.xcodeproj/project.pbxproj | 8 + Session/Settings/HelpViewModel.swift | 158 +++ .../Migrations/_013_SessionUtilChanges.swift | 17 +- .../Database/Models/Contact.swift | 5 +- .../Database/Models/SessionThread.swift | 2 +- .../SessionUtil+Contacts.swift | 238 ++++- .../SessionUtil+ConvoInfoVolatile.swift | 138 +-- .../Configs/ConfigContactsSpec.swift | 871 +++++++++++----- .../Configs/ConfigConvoInfoVolatileSpec.swift | 480 ++++----- .../Configs/ConfigUserGroupsSpec.swift | 972 +++++++++--------- .../Configs/ConfigUserProfileSpec.swift | 748 +++++++------- .../LibSessionUtil/LibSessionSpec.swift | 10 +- .../ThreadSettingsViewModelSpec.swift | 8 +- .../Components/ConfirmationModal.swift | 141 ++- SessionUtilitiesKit/Database/Storage.swift | 36 + .../Meta/SessionUtilitiesKit.h | 1 + .../Utilities/CExceptionHelper.h | 16 + .../Utilities/CExceptionHelper.mm | 36 + 19 files changed, 2375 insertions(+), 1530 deletions(-) create mode 100644 Scripts/DecryptExportedKey.swift create mode 100644 SessionUtilitiesKit/Utilities/CExceptionHelper.h create mode 100644 SessionUtilitiesKit/Utilities/CExceptionHelper.mm diff --git a/Scripts/DecryptExportedKey.swift b/Scripts/DecryptExportedKey.swift new file mode 100644 index 000000000..d17e6ef2e --- /dev/null +++ b/Scripts/DecryptExportedKey.swift @@ -0,0 +1,20 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import CryptoKit + +let arguments = CommandLine.arguments + +// First argument is the file name +if arguments.count == 3 { + let encryptedData = Data(base64Encoded: arguments[1].data(using: .utf8)!)! + let hash: SHA256.Digest = SHA256.hash(data: arguments[2].data(using: .utf8)!) + let key: SymmetricKey = SymmetricKey(data: Data(hash.makeIterator())) + let sealedBox = try! ChaChaPoly.SealedBox(combined: encryptedData) + let decryptedData = try! ChaChaPoly.open(sealedBox, using: key) + + print(Array(decryptedData).map { String(format: "%02x", $0) }.joined()) +} +else { + print("Please provide the base64 encoded 'encrypted key' and plain text 'password' as arguments") +} diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index f6e415d2b..44dae29d6 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -588,6 +588,8 @@ FD2B4AFF2946C93200AB4848 /* ConfigurationSyncJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2B4AFE2946C93200AB4848 /* ConfigurationSyncJob.swift */; }; FD2B4B042949887A00AB4848 /* QueryInterfaceRequest+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2B4B032949887A00AB4848 /* QueryInterfaceRequest+Utilities.swift */; }; FD3003662A25D5B300B5A5FB /* ConfigMessageReceiveJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3003652A25D5B300B5A5FB /* ConfigMessageReceiveJob.swift */; }; + FD30036A2A3ADEC100B5A5FB /* CExceptionHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = FD3003692A3ADD6000B5A5FB /* CExceptionHelper.h */; settings = {ATTRIBUTES = (Public, ); }; }; + FD30036E2A3AE26000B5A5FB /* CExceptionHelper.mm in Sources */ = {isa = PBXBuildFile; fileRef = FD30036D2A3AE26000B5A5FB /* CExceptionHelper.mm */; }; FD368A6829DE8F9C000DBF1E /* _012_AddFTSIfNeeded.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD368A6729DE8F9B000DBF1E /* _012_AddFTSIfNeeded.swift */; }; FD368A6A29DE9E30000DBF1E /* UIContextualAction+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD368A6929DE9E30000DBF1E /* UIContextualAction+Utilities.swift */; }; FD37E9C328A1C6F3003AE748 /* ThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9C228A1C6F3003AE748 /* ThemeManager.swift */; }; @@ -1727,6 +1729,8 @@ FD2B4AFE2946C93200AB4848 /* ConfigurationSyncJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationSyncJob.swift; sourceTree = ""; }; FD2B4B032949887A00AB4848 /* QueryInterfaceRequest+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QueryInterfaceRequest+Utilities.swift"; sourceTree = ""; }; FD3003652A25D5B300B5A5FB /* ConfigMessageReceiveJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigMessageReceiveJob.swift; sourceTree = ""; }; + FD3003692A3ADD6000B5A5FB /* CExceptionHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CExceptionHelper.h; sourceTree = ""; }; + FD30036D2A3AE26000B5A5FB /* CExceptionHelper.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CExceptionHelper.mm; sourceTree = ""; }; FD368A6729DE8F9B000DBF1E /* _012_AddFTSIfNeeded.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _012_AddFTSIfNeeded.swift; sourceTree = ""; }; FD368A6929DE9E30000DBF1E /* UIContextualAction+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIContextualAction+Utilities.swift"; sourceTree = ""; }; FD37E9C228A1C6F3003AE748 /* ThemeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = ""; }; @@ -3589,6 +3593,8 @@ isa = PBXGroup; children = ( FDA8EB0F280F8238002B68E5 /* Codable+Utilities.swift */, + FD3003692A3ADD6000B5A5FB /* CExceptionHelper.h */, + FD30036D2A3AE26000B5A5FB /* CExceptionHelper.mm */, FD09796A27F6C67500936362 /* Failable.swift */, FD09797127FAA2F500936362 /* Optional+Utilities.swift */, FD09797C27FBDB2000936362 /* Notification+Utilities.swift */, @@ -4457,6 +4463,7 @@ B8856E1A256F1700001CE70E /* OWSMath.h in Headers */, C352A3772557864000338F3E /* NSTimer+Proxying.h in Headers */, C32C5B51256DC219003C73A2 /* NSNotificationCenter+OWS.h in Headers */, + FD30036A2A3ADEC100B5A5FB /* CExceptionHelper.h in Headers */, C3C2A67D255388CC00C340D1 /* SessionUtilitiesKit.h in Headers */, C32C6018256E07F9003C73A2 /* NSUserDefaults+OWS.h in Headers */, B8856D8D256F1502001CE70E /* UIView+OWS.h in Headers */, @@ -5639,6 +5646,7 @@ C3D9E4D12567777D0040E4F3 /* OWSMediaUtils.swift in Sources */, C3BBE0AA2554D4DE0050F1E3 /* Dictionary+Utilities.swift in Sources */, FD37EA1128AB34B3003AE748 /* TypedTableAlteration.swift in Sources */, + FD30036E2A3AE26000B5A5FB /* CExceptionHelper.mm in Sources */, C3D9E4DA256778410040E4F3 /* UIImage+OWS.m in Sources */, C32C600F256E07F5003C73A2 /* NSUserDefaults+OWS.m in Sources */, FDE658A329418E2F00A33BC1 /* KeyPair.swift in Sources */, diff --git a/Session/Settings/HelpViewModel.swift b/Session/Settings/HelpViewModel.swift index 1f8d6f89c..e97b2e13e 100644 --- a/Session/Settings/HelpViewModel.swift +++ b/Session/Settings/HelpViewModel.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import CryptoKit import GRDB import DifferenceKit import SessionUIKit @@ -9,6 +10,10 @@ import SessionUtilitiesKit import SignalCoreKit class HelpViewModel: SessionTableViewModel { +#if DEBUG + private var databaseKeyEncryptionPassword: String = "" +#endif + // MARK: - Section public enum Section: SessionTableSection { @@ -17,6 +22,9 @@ class HelpViewModel: SessionTableViewModel= 6 else { + self?.transitionToScreen( + ConfirmationModal( + info: ConfirmationModal.Info( + title: "Error", + body: .text("Password must be at least 6 characters") + ) + ), + transitionType: .present + ) + return + } + + do { + let exportInfo = try Storage.shared.exportInfo(password: password) + let shareVC = UIActivityViewController( + activityItems: [ + URL(fileURLWithPath: exportInfo.dbPath), + URL(fileURLWithPath: exportInfo.keyPath) + ], + applicationActivities: nil + ) + shareVC.completionWithItemsHandler = { [weak self] _, completed, _, _ in + guard + completed && + generatedPassword == self?.databaseKeyEncryptionPassword + else { return } + + self?.transitionToScreen( + ConfirmationModal( + info: ConfirmationModal.Info( + title: "Password", + body: .text(""" + The generated password was: + \(generatedPassword) + + Avoid sending this via the same means as the database + """), + confirmTitle: "Share", + dismissOnConfirm: false, + onConfirm: { [weak self] modal in + modal.dismiss(animated: true) { + let passwordShareVC = UIActivityViewController( + activityItems: [generatedPassword], + applicationActivities: nil + ) + if UIDevice.current.isIPad { + passwordShareVC.excludedActivityTypes = [] + passwordShareVC.popoverPresentationController?.permittedArrowDirections = (targetView != nil ? [.up] : []) + passwordShareVC.popoverPresentationController?.sourceView = targetView + passwordShareVC.popoverPresentationController?.sourceRect = (targetView?.bounds ?? .zero) + } + + self?.transitionToScreen(passwordShareVC, transitionType: .present) + } + } + ) + ), + transitionType: .present + ) + } + + if UIDevice.current.isIPad { + shareVC.excludedActivityTypes = [] + shareVC.popoverPresentationController?.permittedArrowDirections = (targetView != nil ? [.up] : []) + shareVC.popoverPresentationController?.sourceView = targetView + shareVC.popoverPresentationController?.sourceRect = (targetView?.bounds ?? .zero) + } + + self?.transitionToScreen(shareVC, transitionType: .present) + } + catch { + let message: String = { + switch error { + case CryptoKitError.incorrectKeySize: + return "The password must be between 6 and 32 characters (padded to 32 bytes)" + + default: return "Failed to export database" + } + }() + + self?.transitionToScreen( + ConfirmationModal( + info: ConfirmationModal.Info( + title: "Error", + body: .text(message) + ) + ), + transitionType: .present + ) + } + } + } + ) + ), + transitionType: .present + ) + } +#endif } diff --git a/SessionMessagingKit/Database/Migrations/_013_SessionUtilChanges.swift b/SessionMessagingKit/Database/Migrations/_013_SessionUtilChanges.swift index ea8c28885..f70169fca 100644 --- a/SessionMessagingKit/Database/Migrations/_013_SessionUtilChanges.swift +++ b/SessionMessagingKit/Database/Migrations/_013_SessionUtilChanges.swift @@ -55,11 +55,9 @@ enum _013_SessionUtilChanges: Migration { // shares the same 'id' as the 'groupId') so we can cascade delete automatically t.column(.groupId, .text) .notNull() - .indexed() // Quicker querying .references(SessionThread.self, onDelete: .cascade) // Delete if Thread deleted t.column(.profileId, .text) .notNull() - .indexed() // Quicker querying t.column(.role, .integer).notNull() t.column(.isHidden, .boolean) .notNull() @@ -80,6 +78,11 @@ enum _013_SessionUtilChanges: Migration { try db.drop(table: GroupMember.self) try db.rename(table: TmpGroupMember.databaseTableName, to: GroupMember.databaseTableName) + // Need to create the indexes separately from creating 'TmpGroupMember' to ensure they + // have the correct names + try db.createIndex(on: GroupMember.self, columns: [.groupId]) + try db.createIndex(on: GroupMember.self, columns: [.profileId]) + // SQLite doesn't support removing unique constraints so we need to create a new table with // the setup we want, copy data from the old table over, drop the old table and rename the new table struct TmpClosedGroupKeyPair: Codable, TableRecord, FetchableRecord, PersistableRecord, ColumnExpressible { @@ -107,18 +110,16 @@ enum _013_SessionUtilChanges: Migration { try db.create(table: TmpClosedGroupKeyPair.self) { t in t.column(.threadId, .text) .notNull() - .indexed() // Quicker querying .references(ClosedGroup.self, onDelete: .cascade) // Delete if ClosedGroup deleted t.column(.publicKey, .blob).notNull() t.column(.secretKey, .blob).notNull() t.column(.receivedTimestamp, .double) .notNull() - .indexed() // Quicker querying t.column(.threadKeyPairHash, .integer) .notNull() .unique() - .indexed() } + // Insert into the new table, drop the old table and rename the new table to be the old one try ClosedGroupKeyPair .fetchAll(db) @@ -144,6 +145,12 @@ enum _013_SessionUtilChanges: Migration { try db.rename(table: TmpClosedGroupKeyPair.databaseTableName, to: ClosedGroupKeyPair.databaseTableName) // Add an index for the 'ClosedGroupKeyPair' so we can lookup existing keys more easily + // + // Note: Need to create the indexes separately from creating 'TmpClosedGroupKeyPair' to ensure they + // have the correct names + try db.createIndex(on: ClosedGroupKeyPair.self, columns: [.threadId]) + try db.createIndex(on: ClosedGroupKeyPair.self, columns: [.receivedTimestamp]) + try db.createIndex(on: ClosedGroupKeyPair.self, columns: [.threadKeyPairHash]) try db.createIndex( on: ClosedGroupKeyPair.self, columns: [.threadId, .threadKeyPairHash] diff --git a/SessionMessagingKit/Database/Models/Contact.swift b/SessionMessagingKit/Database/Models/Contact.swift index 6cc3b4fc5..6f3b05c47 100644 --- a/SessionMessagingKit/Database/Models/Contact.swift +++ b/SessionMessagingKit/Database/Models/Contact.swift @@ -54,12 +54,13 @@ public struct Contact: Codable, Identifiable, Equatable, FetchableRecord, Persis isApproved: Bool = false, isBlocked: Bool = false, didApproveMe: Bool = false, - hasBeenBlocked: Bool = false + hasBeenBlocked: Bool = false, + dependencies: Dependencies = Dependencies() ) { self.id = id self.isTrusted = ( isTrusted || - id == getUserHexEncodedPublicKey() // Always trust ourselves + id == getUserHexEncodedPublicKey(dependencies: dependencies) // Always trust ourselves ) self.isApproved = isApproved self.isBlocked = isBlocked diff --git a/SessionMessagingKit/Database/Models/SessionThread.swift b/SessionMessagingKit/Database/Models/SessionThread.swift index a8ef201fe..0e5df42e5 100644 --- a/SessionMessagingKit/Database/Models/SessionThread.swift +++ b/SessionMessagingKit/Database/Models/SessionThread.swift @@ -36,7 +36,7 @@ public struct SessionThread: Codable, Identifiable, Equatable, FetchableRecord, case pinnedPriority } - public enum Variant: Int, Codable, Hashable, DatabaseValueConvertible { + public enum Variant: Int, Codable, Hashable, DatabaseValueConvertible, CaseIterable { case contact case legacyGroup case community diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift index 66fe34ec3..96b185793 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift @@ -34,63 +34,16 @@ internal extension SessionUtil { mergeNeedsDump: Bool, latestConfigSentTimestampMs: Int64 ) throws { - typealias ContactData = [ - String: ( - contact: Contact, - profile: Profile, - priority: Int32, - created: TimeInterval - ) - ] - guard mergeNeedsDump else { return } guard conf != nil else { throw SessionUtilError.nilConfigObject } - var contactData: ContactData = [:] - var contact: contacts_contact = contacts_contact() - let contactIterator: UnsafeMutablePointer = contacts_iterator_new(conf) - - while !contacts_iterator_done(contactIterator, &contact) { - let contactId: String = String(cString: withUnsafeBytes(of: contact.session_id) { [UInt8]($0) } - .map { CChar($0) } - .nullTerminated() - ) - let contactResult: Contact = Contact( - id: contactId, - isApproved: contact.approved, - isBlocked: contact.blocked, - didApproveMe: contact.approved_me - ) - let profilePictureUrl: String? = String(libSessionVal: contact.profile_pic.url, nullIfEmpty: true) - let profileResult: Profile = Profile( - id: contactId, - name: String(libSessionVal: contact.name), - lastNameUpdate: (TimeInterval(latestConfigSentTimestampMs) / 1000), - nickname: String(libSessionVal: contact.nickname, nullIfEmpty: true), - profilePictureUrl: profilePictureUrl, - profileEncryptionKey: (profilePictureUrl == nil ? nil : - Data( - libSessionVal: contact.profile_pic.key, - count: ProfileManager.avatarAES256KeyByteLength - ) - ), - lastProfilePictureUpdate: (TimeInterval(latestConfigSentTimestampMs) / 1000) - ) - - contactData[contactId] = ( - contactResult, - profileResult, - contact.priority, - TimeInterval(contact.created) - ) - contacts_iterator_advance(contactIterator) - } - contacts_iterator_free(contactIterator) // Need to free the iterator - // The current users contact data is handled separately so exclude it if it's present (as that's // actually a bug) let userPublicKey: String = getUserHexEncodedPublicKey(db) - let targetContactData: ContactData = contactData.filter { $0.key != userPublicKey } + let targetContactData: [String: ContactData] = extractContacts( + from: conf, + latestConfigSentTimestampMs: latestConfigSentTimestampMs + ).filter { $0.key != userPublicKey } // Since we don't sync 100% of the data stored against the contact and profile objects we // need to only update the data we do have to ensure we don't overwrite anything that doesn't @@ -490,6 +443,121 @@ internal extension SessionUtil { return updated } + + // MARK: - Pruning + + static func pruningIfNeeded( + _ db: Database, + conf: UnsafeMutablePointer? + ) throws { + // First make sure we are actually thowing the correct size constraint error (don't want to prune contacts + // as a result of some other random error + do { + try CExceptionHelper.performSafely { config_push(conf).deallocate() } + return // If we didn't error then no need to prune + } + catch { + guard (error as NSError).userInfo["NSLocalizedDescription"] as? String == "Config data is too large" else { + throw error + } + } + + // Extract the contact data from the config + var allContactData: [String: ContactData] = extractContacts( + from: conf, + latestConfigSentTimestampMs: 0 + ) + + // Remove the current user profile info (shouldn't be in there but just in case) + let userPublicKey: String = getUserHexEncodedPublicKey(db) + var cUserPublicKey: [CChar] = userPublicKey.cArray.nullTerminated() + contacts_erase(conf, &cUserPublicKey) + + /// Do the following in stages (we want to prune as few contacts as possible because we are essentially deleting data and removing these + /// contacts will result in not just contact data but also associated conversation data for the contact being removed from linked devices + /// + /// + /// **Step 1** First of all we want to try to detect spam-attacks (ie. if someone creates a bunch of accounts and messages you, and you + /// systematically block every one of those accounts - this can quickly add up) + /// + /// We will do this by filtering the contact data to only include blocked contacts, grouping those contacts into contacts created within the + /// same hour and then only including groups that have more than 10 contacts (ie. if you blocked 20 users within an hour we expect those + /// contacts were spammers) + let blockSpamBatchingResolution: TimeInterval = (60 * 60) + // TODO: Do we want to only do this case for contacts that were created over X time ago? (to avoid unintentionally unblocking accounts that were recently blocked + let likelySpammerContacts: [ContactData] = allContactData + .values + .filter { $0.contact.isBlocked } + .grouped(by: { $0.created / blockSpamBatchingResolution }) + .filter { _, values in values.count > 20 } + .values + .flatMap { $0 } + + if !likelySpammerContacts.isEmpty { + likelySpammerContacts.forEach { contact in + var cSessionId: [CChar] = contact.contact.id.cArray.nullTerminated() + contacts_erase(conf, &cSessionId) + + allContactData.removeValue(forKey: contact.contact.id) + } + + // If we are no longer erroring then we can stop here + do { return try CExceptionHelper.performSafely { config_push(conf).deallocate() } } + catch {} + } + + /// We retrieve the `CONVO_INFO_VOLATILE` records and one-to-one conversation message counts as they will be relevant for subsequent checks + let volatileThreadInfo: [String: VolatileThreadInfo] = SessionUtil + .config(for: .convoInfoVolatile, publicKey: userPublicKey) + .wrappedValue + .map { SessionUtil.extractConvoVolatileInfo(from: $0) } + .defaulting(to: []) + .reduce(into: [:]) { result, next in result[next.threadId] = next } + let conversationMessageCounts: [String: Int] = try SessionThread + .filter(SessionThread.Columns.variant == SessionThread.Variant.contact) + .select(.id) + .annotated(with: SessionThread.interactions.count) + .asRequest(of: ThreadCount.self) + .fetchAll(db) + .reduce(into: [:]) { result, next in result[next.id] = next.interactionCount } + + /// **Step 2** Next up we want to remove contact records which are likely to be invalid, this means contacts which: + /// - Aren't blocked + /// - Have no `name` value + /// - Have no `CONVO_INFO_VOLATILE` record + /// - Have no messages in their one-to-one conversations + /// + /// Any contacts that meet the above criteria are likely either invalid contacts or are contacts which the user hasn't seen or interacted + /// with for 30+ days + let likelyInvalidContacts: [ContactData] = allContactData + .values + .filter { !$0.contact.isBlocked } + .filter { $0.profile.name.isEmpty } + .filter { volatileThreadInfo[$0.contact.id] == nil } + .filter { (conversationMessageCounts[$0.contact.id] ?? 0) == 0 } + + if !likelyInvalidContacts.isEmpty { + likelyInvalidContacts.forEach { contact in + var cSessionId: [CChar] = contact.contact.id.cArray.nullTerminated() + contacts_erase(conf, &cSessionId) + + allContactData.removeValue(forKey: contact.contact.id) + } + + // If we are no longer erroring then we can stop here + do { return try CExceptionHelper.performSafely { config_push(conf).deallocate() } } + catch {} + } + + + // TODO: Exclude contacts that have no profile info(?) + // TODO: Exclude contacts that have a CONVO_INFO_VOLATILE record + // TODO: Exclude contacts that have a conversation with messages in the database (ie. only delete "empty" contacts) + + // TODO: Start pruning valid contacts which have really old conversations... + + print("RAWR") + } } // MARK: - External Outgoing Changes @@ -555,3 +623,71 @@ extension SessionUtil { } } } + +// MARK: - ContactData + +private struct ContactData { + let contact: Contact + let profile: Profile + let priority: Int32 + let created: TimeInterval +} + +// MARK: - ThreadCount + +private struct ThreadCount: Codable, FetchableRecord { + let id: String + let interactionCount: Int +} + +// MARK: - Convenience + +private extension SessionUtil { + static func extractContacts( + from conf: UnsafeMutablePointer?, + latestConfigSentTimestampMs: Int64 + ) -> [String: ContactData] { + var result: [String: ContactData] = [:] + var contact: contacts_contact = contacts_contact() + let contactIterator: UnsafeMutablePointer = contacts_iterator_new(conf) + + while !contacts_iterator_done(contactIterator, &contact) { + let contactId: String = String(cString: withUnsafeBytes(of: contact.session_id) { [UInt8]($0) } + .map { CChar($0) } + .nullTerminated() + ) + let contactResult: Contact = Contact( + id: contactId, + isApproved: contact.approved, + isBlocked: contact.blocked, + didApproveMe: contact.approved_me + ) + let profilePictureUrl: String? = String(libSessionVal: contact.profile_pic.url, nullIfEmpty: true) + let profileResult: Profile = Profile( + id: contactId, + name: String(libSessionVal: contact.name), + lastNameUpdate: (TimeInterval(latestConfigSentTimestampMs) / 1000), + nickname: String(libSessionVal: contact.nickname, nullIfEmpty: true), + profilePictureUrl: profilePictureUrl, + profileEncryptionKey: (profilePictureUrl == nil ? nil : + Data( + libSessionVal: contact.profile_pic.key, + count: ProfileManager.avatarAES256KeyByteLength + ) + ), + lastProfilePictureUpdate: (TimeInterval(latestConfigSentTimestampMs) / 1000) + ) + + result[contactId] = ContactData( + contact: contactResult, + profile: profileResult, + priority: contact.priority, + created: TimeInterval(contact.created) + ) + contacts_iterator_advance(contactIterator) + } + contacts_iterator_free(contactIterator) // Need to free the iterator + + return result + } +} diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift index c114816f2..47964c504 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift @@ -22,71 +22,8 @@ internal extension SessionUtil { guard mergeNeedsDump else { return } guard conf != nil else { throw SessionUtilError.nilConfigObject } - var volatileThreadInfo: [VolatileThreadInfo] = [] - var oneToOne: convo_info_volatile_1to1 = convo_info_volatile_1to1() - var community: convo_info_volatile_community = convo_info_volatile_community() - var legacyGroup: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() - let convoIterator: OpaquePointer = convo_info_volatile_iterator_new(conf) - - while !convo_info_volatile_iterator_done(convoIterator) { - if convo_info_volatile_it_is_1to1(convoIterator, &oneToOne) { - volatileThreadInfo.append( - VolatileThreadInfo( - threadId: String(libSessionVal: oneToOne.session_id), - variant: .contact, - changes: [ - .markedAsUnread(oneToOne.unread), - .lastReadTimestampMs(oneToOne.last_read) - ] - ) - ) - } - else if convo_info_volatile_it_is_community(convoIterator, &community) { - let server: String = String(libSessionVal: community.base_url) - let roomToken: String = String(libSessionVal: community.room) - let publicKey: String = Data( - libSessionVal: community.pubkey, - count: OpenGroup.pubkeyByteLength - ).toHexString() - - volatileThreadInfo.append( - VolatileThreadInfo( - threadId: OpenGroup.idFor(roomToken: roomToken, server: server), - variant: .community, - openGroupUrlInfo: OpenGroupUrlInfo( - threadId: OpenGroup.idFor(roomToken: roomToken, server: server), - server: server, - roomToken: roomToken, - publicKey: publicKey - ), - changes: [ - .markedAsUnread(community.unread), - .lastReadTimestampMs(community.last_read) - ] - ) - ) - } - else if convo_info_volatile_it_is_legacy_group(convoIterator, &legacyGroup) { - volatileThreadInfo.append( - VolatileThreadInfo( - threadId: String(libSessionVal: legacyGroup.group_id), - variant: .legacyGroup, - changes: [ - .markedAsUnread(legacyGroup.unread), - .lastReadTimestampMs(legacyGroup.last_read) - ] - ) - ) - } - else { - SNLog("Ignoring unknown conversation type when iterating through volatile conversation info update") - } - - convo_info_volatile_iterator_advance(convoIterator) - } - convo_info_volatile_iterator_free(convoIterator) // Need to free the iterator - - // Get the local volatile thread info from all conversations + // Get the volatile thread info from the conf and local conversations + let volatileThreadInfo: [VolatileThreadInfo] = extractConvoVolatileInfo(from: conf) let localVolatileThreadInfo: [String: VolatileThreadInfo] = VolatileThreadInfo.fetchAll(db) .reduce(into: [:]) { result, next in result[next.threadId] = next } @@ -572,6 +509,76 @@ public extension SessionUtil { } } } + + internal static func extractConvoVolatileInfo( + from conf: UnsafeMutablePointer? + ) -> [VolatileThreadInfo] { + var result: [VolatileThreadInfo] = [] + var oneToOne: convo_info_volatile_1to1 = convo_info_volatile_1to1() + var community: convo_info_volatile_community = convo_info_volatile_community() + var legacyGroup: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() + let convoIterator: OpaquePointer = convo_info_volatile_iterator_new(conf) + + while !convo_info_volatile_iterator_done(convoIterator) { + if convo_info_volatile_it_is_1to1(convoIterator, &oneToOne) { + result.append( + VolatileThreadInfo( + threadId: String(libSessionVal: oneToOne.session_id), + variant: .contact, + changes: [ + .markedAsUnread(oneToOne.unread), + .lastReadTimestampMs(oneToOne.last_read) + ] + ) + ) + } + else if convo_info_volatile_it_is_community(convoIterator, &community) { + let server: String = String(libSessionVal: community.base_url) + let roomToken: String = String(libSessionVal: community.room) + let publicKey: String = Data( + libSessionVal: community.pubkey, + count: OpenGroup.pubkeyByteLength + ).toHexString() + + result.append( + VolatileThreadInfo( + threadId: OpenGroup.idFor(roomToken: roomToken, server: server), + variant: .community, + openGroupUrlInfo: OpenGroupUrlInfo( + threadId: OpenGroup.idFor(roomToken: roomToken, server: server), + server: server, + roomToken: roomToken, + publicKey: publicKey + ), + changes: [ + .markedAsUnread(community.unread), + .lastReadTimestampMs(community.last_read) + ] + ) + ) + } + else if convo_info_volatile_it_is_legacy_group(convoIterator, &legacyGroup) { + result.append( + VolatileThreadInfo( + threadId: String(libSessionVal: legacyGroup.group_id), + variant: .legacyGroup, + changes: [ + .markedAsUnread(legacyGroup.unread), + .lastReadTimestampMs(legacyGroup.last_read) + ] + ) + ) + } + else { + SNLog("Ignoring unknown conversation type when iterating through volatile conversation info update") + } + + convo_info_volatile_iterator_advance(convoIterator) + } + convo_info_volatile_iterator_free(convoIterator) // Need to free the iterator + + return result + } } fileprivate extension [SessionUtil.VolatileThreadInfo.Change] { @@ -597,3 +604,4 @@ fileprivate extension [SessionUtil.VolatileThreadInfo.Change] { return nil } } + diff --git a/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigContactsSpec.swift b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigContactsSpec.swift index ad31995a5..a363422b1 100644 --- a/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigContactsSpec.swift +++ b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigContactsSpec.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import GRDB import Sodium import SessionUtil import SessionUtilitiesKit @@ -8,299 +9,601 @@ import SessionUtilitiesKit import Quick import Nimble +@testable import SessionMessagingKit + /// This spec is designed to replicate the initial test cases for the libSession-util to ensure the behaviour matches class ConfigContactsSpec { + enum ContactProperty: CaseIterable { + case name + case nickname + case approved + case approved_me + case blocked + case profile_pic + case created + case notifications + case mute_until + } + // MARK: - Spec static func spec() { - it("generates Contact configs correctly") { - let createdTs: Int64 = 1680064059 - let nowTs: Int64 = Int64(Date().timeIntervalSince1970) - let seed: Data = Data(hex: "0123456789abcdef0123456789abcdef") - - // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately - let identity = try! Identity.generate(from: seed) - var edSK: [UInt8] = identity.ed25519KeyPair.secretKey - expect(edSK.toHexString().suffix(64)) - .to(equal("4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7")) - expect(identity.x25519KeyPair.publicKey.toHexString()) - .to(equal("d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72")) - expect(String(edSK.toHexString().prefix(32))).to(equal(seed.toHexString())) - - // Initialize a brand new, empty config because we have no dump data to deal with. - let error: UnsafeMutablePointer? = nil - var conf: UnsafeMutablePointer? = nil - expect(contacts_init(&conf, &edSK, nil, 0, error)).to(equal(0)) - error?.deallocate() - - // Empty contacts shouldn't have an existing contact - let definitelyRealId: String = "050000000000000000000000000000000000000000000000000000000000000000" - var cDefinitelyRealId: [CChar] = definitelyRealId.cArray.nullTerminated() - let contactPtr: UnsafeMutablePointer? = nil - expect(contacts_get(conf, contactPtr, &cDefinitelyRealId)).to(beFalse()) - - expect(contacts_size(conf)).to(equal(0)) - - var contact2: contacts_contact = contacts_contact() - expect(contacts_get_or_construct(conf, &contact2, &cDefinitelyRealId)).to(beTrue()) - expect(String(libSessionVal: contact2.name)).to(beEmpty()) - expect(String(libSessionVal: contact2.nickname)).to(beEmpty()) - expect(contact2.approved).to(beFalse()) - expect(contact2.approved_me).to(beFalse()) - expect(contact2.blocked).to(beFalse()) - expect(contact2.profile_pic).toNot(beNil()) // Creates an empty instance apparently - expect(String(libSessionVal: contact2.profile_pic.url)).to(beEmpty()) - expect(contact2.created).to(equal(0)) - expect(contact2.notifications).to(equal(CONVO_NOTIFY_DEFAULT)) - expect(contact2.mute_until).to(equal(0)) - - expect(config_needs_push(conf)).to(beFalse()) - expect(config_needs_dump(conf)).to(beFalse()) - - let pushData1: UnsafeMutablePointer = config_push(conf) - expect(pushData1.pointee.seqno).to(equal(0)) - pushData1.deallocate() - - // Update the contact data - contact2.name = "Joe".toLibSession() - contact2.nickname = "Joey".toLibSession() - contact2.approved = true - contact2.approved_me = true - contact2.created = createdTs - contact2.notifications = CONVO_NOTIFY_ALL - contact2.mute_until = nowTs + 1800 - - // Update the contact - contacts_set(conf, &contact2) - - // Ensure the contact details were updated - var contact3: contacts_contact = contacts_contact() - expect(contacts_get(conf, &contact3, &cDefinitelyRealId)).to(beTrue()) - expect(String(libSessionVal: contact3.name)).to(equal("Joe")) - expect(String(libSessionVal: contact3.nickname)).to(equal("Joey")) - expect(contact3.approved).to(beTrue()) - expect(contact3.approved_me).to(beTrue()) - expect(contact3.profile_pic).toNot(beNil()) // Creates an empty instance apparently - expect(String(libSessionVal: contact3.profile_pic.url)).to(beEmpty()) - expect(contact3.blocked).to(beFalse()) - expect(String(libSessionVal: contact3.session_id)).to(equal(definitelyRealId)) - expect(contact3.created).to(equal(createdTs)) - expect(contact2.notifications).to(equal(CONVO_NOTIFY_ALL)) - expect(contact2.mute_until).to(equal(nowTs + 1800)) - - - // Since we've made changes, we should need to push new config to the swarm, *and* should need - // to dump the updated state: - expect(config_needs_push(conf)).to(beTrue()) - expect(config_needs_dump(conf)).to(beTrue()) - - // incremented since we made changes (this only increments once between - // dumps; even though we changed multiple fields here). - let pushData2: UnsafeMutablePointer = config_push(conf) - - // incremented since we made changes (this only increments once between - // dumps; even though we changed multiple fields here). - expect(pushData2.pointee.seqno).to(equal(1)) - - // Pretend we uploaded it - let fakeHash1: String = "fakehash1" - var cFakeHash1: [CChar] = fakeHash1.cArray.nullTerminated() - config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash1) - expect(config_needs_push(conf)).to(beFalse()) - expect(config_needs_dump(conf)).to(beTrue()) - pushData2.deallocate() - - // NB: Not going to check encrypted data and decryption here because that's general (not - // specific to contacts) and is covered already in the user profile tests. - var dump1: UnsafeMutablePointer? = nil - var dump1Len: Int = 0 - config_dump(conf, &dump1, &dump1Len) - - let error2: UnsafeMutablePointer? = nil - var conf2: UnsafeMutablePointer? = nil - expect(contacts_init(&conf2, &edSK, dump1, dump1Len, error2)).to(equal(0)) - error2?.deallocate() - dump1?.deallocate() - - expect(config_needs_push(conf2)).to(beFalse()) - expect(config_needs_dump(conf2)).to(beFalse()) - - let pushData3: UnsafeMutablePointer = config_push(conf2) - expect(pushData3.pointee.seqno).to(equal(1)) - pushData3.deallocate() - - // Because we just called dump() above, to load up contacts2 - expect(config_needs_dump(conf)).to(beFalse()) - - // Ensure the contact details were updated - var contact4: contacts_contact = contacts_contact() - expect(contacts_get(conf2, &contact4, &cDefinitelyRealId)).to(beTrue()) - expect(String(libSessionVal: contact4.name)).to(equal("Joe")) - expect(String(libSessionVal: contact4.nickname)).to(equal("Joey")) - expect(contact4.approved).to(beTrue()) - expect(contact4.approved_me).to(beTrue()) - expect(contact4.profile_pic).toNot(beNil()) // Creates an empty instance apparently - expect(String(libSessionVal: contact4.profile_pic.url)).to(beEmpty()) - expect(contact4.blocked).to(beFalse()) - expect(contact4.created).to(equal(createdTs)) - - let anotherId: String = "051111111111111111111111111111111111111111111111111111111111111111" - var cAnotherId: [CChar] = anotherId.cArray.nullTerminated() - var contact5: contacts_contact = contacts_contact() - expect(contacts_get_or_construct(conf2, &contact5, &cAnotherId)).to(beTrue()) - expect(String(libSessionVal: contact5.name)).to(beEmpty()) - expect(String(libSessionVal: contact5.nickname)).to(beEmpty()) - expect(contact5.approved).to(beFalse()) - expect(contact5.approved_me).to(beFalse()) - expect(contact5.profile_pic).toNot(beNil()) // Creates an empty instance apparently - expect(String(libSessionVal: contact5.profile_pic.url)).to(beEmpty()) - expect(contact5.blocked).to(beFalse()) - - // We're not setting any fields, but we should still keep a record of the session id - contacts_set(conf2, &contact5) - expect(config_needs_push(conf2)).to(beTrue()) - - let pushData4: UnsafeMutablePointer = config_push(conf2) - expect(pushData4.pointee.seqno).to(equal(2)) - - // Check the merging - let fakeHash2: String = "fakehash2" - var cFakeHash2: [CChar] = fakeHash2.cArray.nullTerminated() - var mergeHashes: [UnsafePointer?] = [cFakeHash2].unsafeCopy() - var mergeData: [UnsafePointer?] = [UnsafePointer(pushData4.pointee.config)] - var mergeSize: [Int] = [pushData4.pointee.config_len] - expect(config_merge(conf, &mergeHashes, &mergeData, &mergeSize, 1)).to(equal(1)) - config_confirm_pushed(conf2, pushData4.pointee.seqno, &cFakeHash2) - mergeHashes.forEach { $0?.deallocate() } - pushData4.deallocate() - - expect(config_needs_push(conf)).to(beFalse()) - - let pushData5: UnsafeMutablePointer = config_push(conf) - expect(pushData5.pointee.seqno).to(equal(2)) - pushData5.deallocate() - - // Iterate through and make sure we got everything we expected - var sessionIds: [String] = [] - var nicknames: [String] = [] - expect(contacts_size(conf)).to(equal(2)) - - var contact6: contacts_contact = contacts_contact() - let contactIterator: UnsafeMutablePointer = contacts_iterator_new(conf) - while !contacts_iterator_done(contactIterator, &contact6) { - sessionIds.append(String(libSessionVal: contact6.session_id)) - nicknames.append(String(libSessionVal: contact6.nickname, nullIfEmpty: true) ?? "(N/A)") - contacts_iterator_advance(contactIterator) + context("CONTACTS") { + // MARK: - when checking error catching + context("when checking error catching") { + var seed: Data! + var identity: (ed25519KeyPair: KeyPair, x25519KeyPair: KeyPair)! + var edSK: [UInt8]! + var error: UnsafeMutablePointer? + var conf: UnsafeMutablePointer? + + beforeEach { + seed = Data(hex: "0123456789abcdef0123456789abcdef") + + // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately + identity = try! Identity.generate(from: seed) + edSK = identity.ed25519KeyPair.secretKey + + // Initialize a brand new, empty config because we have no dump data to deal with. + error = nil + conf = nil + _ = contacts_init(&conf, &edSK, nil, 0, error) + error?.deallocate() + } + + // MARK: -- it can catch size limit errors thrown when pushing + it("can catch size limit errors thrown when pushing") { + try (0..<10000).forEach { index in + var contact: contacts_contact = try createContact(for: index, in: conf, maxing: .allProperties) + contacts_set(conf, &contact) + } + + expect(contacts_size(conf)).to(equal(10000)) + expect(config_needs_push(conf)).to(beTrue()) + expect(config_needs_dump(conf)).to(beTrue()) + + expect { + try CExceptionHelper.performSafely { config_push(conf).deallocate() } + } + .to(throwError(NSError(domain: "cpp_exception", code: -2, userInfo: ["NSLocalizedDescription": "Config data is too large"]))) + } + + // MARK: -- can catch size limit errors thrown when dumping + it("can catch size limit errors thrown when dumping") { + try (0..<10000).forEach { index in + var contact: contacts_contact = try createContact(for: index, in: conf, maxing: .allProperties) + contacts_set(conf, &contact) + } + + expect(contacts_size(conf)).to(equal(10000)) + expect(config_needs_push(conf)).to(beTrue()) + expect(config_needs_dump(conf)).to(beTrue()) + + expect { + try CExceptionHelper.performSafely { + var dump: UnsafeMutablePointer? = nil + var dumpLen: Int = 0 + config_dump(conf, &dump, &dumpLen) + dump?.deallocate() + } + } + .to(throwError(NSError(domain: "cpp_exception", code: -2, userInfo: ["NSLocalizedDescription": "Config data is too large"]))) + } } - contacts_iterator_free(contactIterator) // Need to free the iterator - expect(sessionIds.count).to(equal(2)) - expect(sessionIds.count).to(equal(contacts_size(conf))) - expect(sessionIds.first).to(equal(definitelyRealId)) - expect(sessionIds.last).to(equal(anotherId)) - expect(nicknames.first).to(equal("Joey")) - expect(nicknames.last).to(equal("(N/A)")) - - // Conflict! Oh no! - - // On client 1 delete a contact: - contacts_erase(conf, definitelyRealId) - - // Client 2 adds a new friend: - let thirdId: String = "052222222222222222222222222222222222222222222222222222222222222222" - var cThirdId: [CChar] = thirdId.cArray.nullTerminated() - var contact7: contacts_contact = contacts_contact() - expect(contacts_get_or_construct(conf2, &contact7, &cThirdId)).to(beTrue()) - contact7.nickname = "Nickname 3".toLibSession() - contact7.approved = true - contact7.approved_me = true - contact7.profile_pic.url = "http://example.com/huge.bmp".toLibSession() - contact7.profile_pic.key = "qwerty78901234567890123456789012".data(using: .utf8)!.toLibSession() - contacts_set(conf2, &contact7) - - expect(config_needs_push(conf)).to(beTrue()) - expect(config_needs_push(conf2)).to(beTrue()) - - let pushData6: UnsafeMutablePointer = config_push(conf) - expect(pushData6.pointee.seqno).to(equal(3)) - - let pushData7: UnsafeMutablePointer = config_push(conf2) - expect(pushData7.pointee.seqno).to(equal(3)) - - let pushData6Str: String = String(pointer: pushData6.pointee.config, length: pushData6.pointee.config_len, encoding: .ascii)! - let pushData7Str: String = String(pointer: pushData7.pointee.config, length: pushData7.pointee.config_len, encoding: .ascii)! - expect(pushData6Str).toNot(equal(pushData7Str)) - expect([String](pointer: pushData6.pointee.obsolete, count: pushData6.pointee.obsolete_len)) - .to(equal([fakeHash2])) - expect([String](pointer: pushData7.pointee.obsolete, count: pushData7.pointee.obsolete_len)) - .to(equal([fakeHash2])) - - let fakeHash3a: String = "fakehash3a" - var cFakeHash3a: [CChar] = fakeHash3a.cArray.nullTerminated() - let fakeHash3b: String = "fakehash3b" - var cFakeHash3b: [CChar] = fakeHash3b.cArray.nullTerminated() - config_confirm_pushed(conf, pushData6.pointee.seqno, &cFakeHash3a) - config_confirm_pushed(conf2, pushData7.pointee.seqno, &cFakeHash3b) - - var mergeHashes2: [UnsafePointer?] = [cFakeHash3b].unsafeCopy() - var mergeData2: [UnsafePointer?] = [UnsafePointer(pushData7.pointee.config)] - var mergeSize2: [Int] = [pushData7.pointee.config_len] - expect(config_merge(conf, &mergeHashes2, &mergeData2, &mergeSize2, 1)).to(equal(1)) - expect(config_needs_push(conf)).to(beTrue()) - - var mergeHashes3: [UnsafePointer?] = [cFakeHash3a].unsafeCopy() - var mergeData3: [UnsafePointer?] = [UnsafePointer(pushData6.pointee.config)] - var mergeSize3: [Int] = [pushData6.pointee.config_len] - expect(config_merge(conf2, &mergeHashes3, &mergeData3, &mergeSize3, 1)).to(equal(1)) - expect(config_needs_push(conf2)).to(beTrue()) - mergeHashes2.forEach { $0?.deallocate() } - mergeHashes3.forEach { $0?.deallocate() } - pushData6.deallocate() - pushData7.deallocate() - - let pushData8: UnsafeMutablePointer = config_push(conf) - expect(pushData8.pointee.seqno).to(equal(4)) - - let pushData9: UnsafeMutablePointer = config_push(conf2) - expect(pushData9.pointee.seqno).to(equal(pushData8.pointee.seqno)) - - let pushData8Str: String = String(pointer: pushData8.pointee.config, length: pushData8.pointee.config_len, encoding: .ascii)! - let pushData9Str: String = String(pointer: pushData9.pointee.config, length: pushData9.pointee.config_len, encoding: .ascii)! - expect(pushData8Str).to(equal(pushData9Str)) - expect([String](pointer: pushData8.pointee.obsolete, count: pushData8.pointee.obsolete_len)) - .to(equal([fakeHash3b, fakeHash3a])) - expect([String](pointer: pushData9.pointee.obsolete, count: pushData9.pointee.obsolete_len)) - .to(equal([fakeHash3a, fakeHash3b])) - - let fakeHash4: String = "fakeHash4" - var cFakeHash4: [CChar] = fakeHash4.cArray.nullTerminated() - config_confirm_pushed(conf, pushData8.pointee.seqno, &cFakeHash4) - config_confirm_pushed(conf2, pushData9.pointee.seqno, &cFakeHash4) - pushData8.deallocate() - pushData9.deallocate() - - expect(config_needs_push(conf)).to(beFalse()) - expect(config_needs_push(conf2)).to(beFalse()) - - // Validate the changes - var sessionIds2: [String] = [] - var nicknames2: [String] = [] - expect(contacts_size(conf)).to(equal(2)) - - var contact8: contacts_contact = contacts_contact() - let contactIterator2: UnsafeMutablePointer = contacts_iterator_new(conf) - while !contacts_iterator_done(contactIterator2, &contact8) { - sessionIds2.append(String(libSessionVal: contact8.session_id)) - nicknames2.append(String(libSessionVal: contact8.nickname, nullIfEmpty: true) ?? "(N/A)") - contacts_iterator_advance(contactIterator2) + // MARK: - when checking size limits + context("when checking size limits") { + var numRecords: Int! + var seed: Data! + var identity: (ed25519KeyPair: KeyPair, x25519KeyPair: KeyPair)! + var edSK: [UInt8]! + var error: UnsafeMutablePointer? + var conf: UnsafeMutablePointer? + + beforeEach { + numRecords = 0 + seed = Data(hex: "0123456789abcdef0123456789abcdef") + + // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately + identity = try! Identity.generate(from: seed) + edSK = identity.ed25519KeyPair.secretKey + + // Initialize a brand new, empty config because we have no dump data to deal with. + error = nil + conf = nil + _ = contacts_init(&conf, &edSK, nil, 0, error) + error?.deallocate() + } + + // MARK: -- has not changed the max empty records + it("has not changed the max empty records") { + for index in (0..<10000) { + var contact: contacts_contact = try createContact(for: index, in: conf) + contacts_set(conf, &contact) + + do { try CExceptionHelper.performSafely { config_push(conf).deallocate() } } + catch { break } + + // We successfully inserted a contact and didn't hit the limit so increment the counter + numRecords += 1 + } + + // Check that the record count matches the maximum when we last checked + expect(numRecords).to(equal(1775)) + } + + // MARK: -- has not changed the max name only records + it("has not changed the max name only records") { + for index in (0..<10000) { + var contact: contacts_contact = try createContact(for: index, in: conf, maxing: [.name]) + contacts_set(conf, &contact) + + do { try CExceptionHelper.performSafely { config_push(conf).deallocate() } } + catch { break } + + // We successfully inserted a contact and didn't hit the limit so increment the counter + numRecords += 1 + } + + // Check that the record count matches the maximum when we last checked + expect(numRecords).to(equal(526)) + } + + // MARK: -- has not changed the max name and profile pic only records + it("has not changed the max name and profile pic only records") { + for index in (0..<10000) { + var contact: contacts_contact = try createContact(for: index, in: conf, maxing: [.name, .profile_pic]) + contacts_set(conf, &contact) + + do { try CExceptionHelper.performSafely { config_push(conf).deallocate() } } + catch { break } + + // We successfully inserted a contact and didn't hit the limit so increment the counter + numRecords += 1 + } + + // Check that the record count matches the maximum when we last checked + expect(numRecords).to(equal(184)) + } + + // MARK: -- has not changed the max filled records + it("has not changed the max filled records") { + for index in (0..<10000) { + var contact: contacts_contact = try createContact(for: index, in: conf, maxing: .allProperties) + contacts_set(conf, &contact) + + do { try CExceptionHelper.performSafely { config_push(conf).deallocate() } } + catch { break } + + // We successfully inserted a contact and didn't hit the limit so increment the counter + numRecords += 1 + } + + // Check that the record count matches the maximum when we last checked + expect(numRecords).to(equal(134)) + } } - contacts_iterator_free(contactIterator2) // Need to free the iterator - expect(sessionIds2.count).to(equal(2)) - expect(sessionIds2.first).to(equal(anotherId)) - expect(sessionIds2.last).to(equal(thirdId)) - expect(nicknames2.first).to(equal("(N/A)")) - expect(nicknames2.last).to(equal("Nickname 3")) + // MARK: - when pruning + context("when pruning") { + var mockStorage: Storage! + var seed: Data! + var identity: (ed25519KeyPair: KeyPair, x25519KeyPair: KeyPair)! + var edSK: [UInt8]! + var error: UnsafeMutablePointer? + var conf: UnsafeMutablePointer? + + beforeEach { + mockStorage = Storage( + customWriter: try! DatabaseQueue(), + customMigrations: [ + SNUtilitiesKit.migrations(), + SNMessagingKit.migrations() + ] + ) + seed = Data(hex: "0123456789abcdef0123456789abcdef") + + // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately + identity = try! Identity.generate(from: seed) + edSK = identity.ed25519KeyPair.secretKey + + // Initialize a brand new, empty config because we have no dump data to deal with. + error = nil + conf = nil + _ = contacts_init(&conf, &edSK, nil, 0, error) + error?.deallocate() + } + + it("does something") { + mockStorage.write { db in + try SessionThread.fetchOrCreate(db, id: "1", variant: .contact, shouldBeVisible: true) + try SessionThread.fetchOrCreate(db, id: "2", variant: .contact, shouldBeVisible: true) + try SessionThread.fetchOrCreate(db, id: "3", variant: .contact, shouldBeVisible: true) + _ = try Interaction( + threadId: "1", + authorId: "1", + variant: .standardIncoming, + body: "Test1" + ).inserted(db) + _ = try Interaction( + threadId: "1", + authorId: "2", + variant: .standardIncoming, + body: "Test2" + ).inserted(db) + _ = try Interaction( + threadId: "3", + authorId: "3", + variant: .standardIncoming, + body: "Test3" + ).inserted(db) + + try SessionUtil.pruningIfNeeded( + db, + conf: conf + ) + + expect(contacts_size(conf)).to(equal(0)) + } + } + } + + // MARK: - generates config correctly + + it("generates config correctly") { + let createdTs: Int64 = 1680064059 + let nowTs: Int64 = Int64(Date().timeIntervalSince1970) + let seed: Data = Data(hex: "0123456789abcdef0123456789abcdef") + + // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately + let identity = try! Identity.generate(from: seed) + var edSK: [UInt8] = identity.ed25519KeyPair.secretKey + expect(edSK.toHexString().suffix(64)) + .to(equal("4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7")) + expect(identity.x25519KeyPair.publicKey.toHexString()) + .to(equal("d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72")) + expect(String(edSK.toHexString().prefix(32))).to(equal(seed.toHexString())) + + // Initialize a brand new, empty config because we have no dump data to deal with. + let error: UnsafeMutablePointer? = nil + var conf: UnsafeMutablePointer? = nil + expect(contacts_init(&conf, &edSK, nil, 0, error)).to(equal(0)) + error?.deallocate() + + // Empty contacts shouldn't have an existing contact + let definitelyRealId: String = "050000000000000000000000000000000000000000000000000000000000000000" + var cDefinitelyRealId: [CChar] = definitelyRealId.cArray.nullTerminated() + let contactPtr: UnsafeMutablePointer? = nil + expect(contacts_get(conf, contactPtr, &cDefinitelyRealId)).to(beFalse()) + + expect(contacts_size(conf)).to(equal(0)) + + var contact2: contacts_contact = contacts_contact() + expect(contacts_get_or_construct(conf, &contact2, &cDefinitelyRealId)).to(beTrue()) + expect(String(libSessionVal: contact2.name)).to(beEmpty()) + expect(String(libSessionVal: contact2.nickname)).to(beEmpty()) + expect(contact2.approved).to(beFalse()) + expect(contact2.approved_me).to(beFalse()) + expect(contact2.blocked).to(beFalse()) + expect(contact2.profile_pic).toNot(beNil()) // Creates an empty instance apparently + expect(String(libSessionVal: contact2.profile_pic.url)).to(beEmpty()) + expect(contact2.created).to(equal(0)) + expect(contact2.notifications).to(equal(CONVO_NOTIFY_DEFAULT)) + expect(contact2.mute_until).to(equal(0)) + + expect(config_needs_push(conf)).to(beFalse()) + expect(config_needs_dump(conf)).to(beFalse()) + + let pushData1: UnsafeMutablePointer = config_push(conf) + expect(pushData1.pointee.seqno).to(equal(0)) + pushData1.deallocate() + + // Update the contact data + contact2.name = "Joe".toLibSession() + contact2.nickname = "Joey".toLibSession() + contact2.approved = true + contact2.approved_me = true + contact2.created = createdTs + contact2.notifications = CONVO_NOTIFY_ALL + contact2.mute_until = nowTs + 1800 + + // Update the contact + contacts_set(conf, &contact2) + + // Ensure the contact details were updated + var contact3: contacts_contact = contacts_contact() + expect(contacts_get(conf, &contact3, &cDefinitelyRealId)).to(beTrue()) + expect(String(libSessionVal: contact3.name)).to(equal("Joe")) + expect(String(libSessionVal: contact3.nickname)).to(equal("Joey")) + expect(contact3.approved).to(beTrue()) + expect(contact3.approved_me).to(beTrue()) + expect(contact3.profile_pic).toNot(beNil()) // Creates an empty instance apparently + expect(String(libSessionVal: contact3.profile_pic.url)).to(beEmpty()) + expect(contact3.blocked).to(beFalse()) + expect(String(libSessionVal: contact3.session_id)).to(equal(definitelyRealId)) + expect(contact3.created).to(equal(createdTs)) + expect(contact2.notifications).to(equal(CONVO_NOTIFY_ALL)) + expect(contact2.mute_until).to(equal(nowTs + 1800)) + + + // Since we've made changes, we should need to push new config to the swarm, *and* should need + // to dump the updated state: + expect(config_needs_push(conf)).to(beTrue()) + expect(config_needs_dump(conf)).to(beTrue()) + + // incremented since we made changes (this only increments once between + // dumps; even though we changed multiple fields here). + let pushData2: UnsafeMutablePointer = config_push(conf) + + // incremented since we made changes (this only increments once between + // dumps; even though we changed multiple fields here). + expect(pushData2.pointee.seqno).to(equal(1)) + + // Pretend we uploaded it + let fakeHash1: String = "fakehash1" + var cFakeHash1: [CChar] = fakeHash1.cArray.nullTerminated() + config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash1) + expect(config_needs_push(conf)).to(beFalse()) + expect(config_needs_dump(conf)).to(beTrue()) + pushData2.deallocate() + + // NB: Not going to check encrypted data and decryption here because that's general (not + // specific to contacts) and is covered already in the user profile tests. + var dump1: UnsafeMutablePointer? = nil + var dump1Len: Int = 0 + config_dump(conf, &dump1, &dump1Len) + + let error2: UnsafeMutablePointer? = nil + var conf2: UnsafeMutablePointer? = nil + expect(contacts_init(&conf2, &edSK, dump1, dump1Len, error2)).to(equal(0)) + error2?.deallocate() + dump1?.deallocate() + + expect(config_needs_push(conf2)).to(beFalse()) + expect(config_needs_dump(conf2)).to(beFalse()) + + let pushData3: UnsafeMutablePointer = config_push(conf2) + expect(pushData3.pointee.seqno).to(equal(1)) + pushData3.deallocate() + + // Because we just called dump() above, to load up contacts2 + expect(config_needs_dump(conf)).to(beFalse()) + + // Ensure the contact details were updated + var contact4: contacts_contact = contacts_contact() + expect(contacts_get(conf2, &contact4, &cDefinitelyRealId)).to(beTrue()) + expect(String(libSessionVal: contact4.name)).to(equal("Joe")) + expect(String(libSessionVal: contact4.nickname)).to(equal("Joey")) + expect(contact4.approved).to(beTrue()) + expect(contact4.approved_me).to(beTrue()) + expect(contact4.profile_pic).toNot(beNil()) // Creates an empty instance apparently + expect(String(libSessionVal: contact4.profile_pic.url)).to(beEmpty()) + expect(contact4.blocked).to(beFalse()) + expect(contact4.created).to(equal(createdTs)) + + let anotherId: String = "051111111111111111111111111111111111111111111111111111111111111111" + var cAnotherId: [CChar] = anotherId.cArray.nullTerminated() + var contact5: contacts_contact = contacts_contact() + expect(contacts_get_or_construct(conf2, &contact5, &cAnotherId)).to(beTrue()) + expect(String(libSessionVal: contact5.name)).to(beEmpty()) + expect(String(libSessionVal: contact5.nickname)).to(beEmpty()) + expect(contact5.approved).to(beFalse()) + expect(contact5.approved_me).to(beFalse()) + expect(contact5.profile_pic).toNot(beNil()) // Creates an empty instance apparently + expect(String(libSessionVal: contact5.profile_pic.url)).to(beEmpty()) + expect(contact5.blocked).to(beFalse()) + + // We're not setting any fields, but we should still keep a record of the session id + contacts_set(conf2, &contact5) + expect(config_needs_push(conf2)).to(beTrue()) + + let pushData4: UnsafeMutablePointer = config_push(conf2) + expect(pushData4.pointee.seqno).to(equal(2)) + + // Check the merging + let fakeHash2: String = "fakehash2" + var cFakeHash2: [CChar] = fakeHash2.cArray.nullTerminated() + var mergeHashes: [UnsafePointer?] = [cFakeHash2].unsafeCopy() + var mergeData: [UnsafePointer?] = [UnsafePointer(pushData4.pointee.config)] + var mergeSize: [Int] = [pushData4.pointee.config_len] + expect(config_merge(conf, &mergeHashes, &mergeData, &mergeSize, 1)).to(equal(1)) + config_confirm_pushed(conf2, pushData4.pointee.seqno, &cFakeHash2) + mergeHashes.forEach { $0?.deallocate() } + pushData4.deallocate() + + expect(config_needs_push(conf)).to(beFalse()) + + let pushData5: UnsafeMutablePointer = config_push(conf) + expect(pushData5.pointee.seqno).to(equal(2)) + pushData5.deallocate() + + // Iterate through and make sure we got everything we expected + var sessionIds: [String] = [] + var nicknames: [String] = [] + expect(contacts_size(conf)).to(equal(2)) + + var contact6: contacts_contact = contacts_contact() + let contactIterator: UnsafeMutablePointer = contacts_iterator_new(conf) + while !contacts_iterator_done(contactIterator, &contact6) { + sessionIds.append(String(libSessionVal: contact6.session_id)) + nicknames.append(String(libSessionVal: contact6.nickname, nullIfEmpty: true) ?? "(N/A)") + contacts_iterator_advance(contactIterator) + } + contacts_iterator_free(contactIterator) // Need to free the iterator + + expect(sessionIds.count).to(equal(2)) + expect(sessionIds.count).to(equal(contacts_size(conf))) + expect(sessionIds.first).to(equal(definitelyRealId)) + expect(sessionIds.last).to(equal(anotherId)) + expect(nicknames.first).to(equal("Joey")) + expect(nicknames.last).to(equal("(N/A)")) + + // Conflict! Oh no! + + // On client 1 delete a contact: + contacts_erase(conf, definitelyRealId) + + // Client 2 adds a new friend: + let thirdId: String = "052222222222222222222222222222222222222222222222222222222222222222" + var cThirdId: [CChar] = thirdId.cArray.nullTerminated() + var contact7: contacts_contact = contacts_contact() + expect(contacts_get_or_construct(conf2, &contact7, &cThirdId)).to(beTrue()) + contact7.nickname = "Nickname 3".toLibSession() + contact7.approved = true + contact7.approved_me = true + contact7.profile_pic.url = "http://example.com/huge.bmp".toLibSession() + contact7.profile_pic.key = "qwerty78901234567890123456789012".data(using: .utf8)!.toLibSession() + contacts_set(conf2, &contact7) + + expect(config_needs_push(conf)).to(beTrue()) + expect(config_needs_push(conf2)).to(beTrue()) + + let pushData6: UnsafeMutablePointer = config_push(conf) + expect(pushData6.pointee.seqno).to(equal(3)) + + let pushData7: UnsafeMutablePointer = config_push(conf2) + expect(pushData7.pointee.seqno).to(equal(3)) + + let pushData6Str: String = String(pointer: pushData6.pointee.config, length: pushData6.pointee.config_len, encoding: .ascii)! + let pushData7Str: String = String(pointer: pushData7.pointee.config, length: pushData7.pointee.config_len, encoding: .ascii)! + expect(pushData6Str).toNot(equal(pushData7Str)) + expect([String](pointer: pushData6.pointee.obsolete, count: pushData6.pointee.obsolete_len)) + .to(equal([fakeHash2])) + expect([String](pointer: pushData7.pointee.obsolete, count: pushData7.pointee.obsolete_len)) + .to(equal([fakeHash2])) + + let fakeHash3a: String = "fakehash3a" + var cFakeHash3a: [CChar] = fakeHash3a.cArray.nullTerminated() + let fakeHash3b: String = "fakehash3b" + var cFakeHash3b: [CChar] = fakeHash3b.cArray.nullTerminated() + config_confirm_pushed(conf, pushData6.pointee.seqno, &cFakeHash3a) + config_confirm_pushed(conf2, pushData7.pointee.seqno, &cFakeHash3b) + + var mergeHashes2: [UnsafePointer?] = [cFakeHash3b].unsafeCopy() + var mergeData2: [UnsafePointer?] = [UnsafePointer(pushData7.pointee.config)] + var mergeSize2: [Int] = [pushData7.pointee.config_len] + expect(config_merge(conf, &mergeHashes2, &mergeData2, &mergeSize2, 1)).to(equal(1)) + expect(config_needs_push(conf)).to(beTrue()) + + var mergeHashes3: [UnsafePointer?] = [cFakeHash3a].unsafeCopy() + var mergeData3: [UnsafePointer?] = [UnsafePointer(pushData6.pointee.config)] + var mergeSize3: [Int] = [pushData6.pointee.config_len] + expect(config_merge(conf2, &mergeHashes3, &mergeData3, &mergeSize3, 1)).to(equal(1)) + expect(config_needs_push(conf2)).to(beTrue()) + mergeHashes2.forEach { $0?.deallocate() } + mergeHashes3.forEach { $0?.deallocate() } + pushData6.deallocate() + pushData7.deallocate() + + let pushData8: UnsafeMutablePointer = config_push(conf) + expect(pushData8.pointee.seqno).to(equal(4)) + + let pushData9: UnsafeMutablePointer = config_push(conf2) + expect(pushData9.pointee.seqno).to(equal(pushData8.pointee.seqno)) + + let pushData8Str: String = String(pointer: pushData8.pointee.config, length: pushData8.pointee.config_len, encoding: .ascii)! + let pushData9Str: String = String(pointer: pushData9.pointee.config, length: pushData9.pointee.config_len, encoding: .ascii)! + expect(pushData8Str).to(equal(pushData9Str)) + expect([String](pointer: pushData8.pointee.obsolete, count: pushData8.pointee.obsolete_len)) + .to(equal([fakeHash3b, fakeHash3a])) + expect([String](pointer: pushData9.pointee.obsolete, count: pushData9.pointee.obsolete_len)) + .to(equal([fakeHash3a, fakeHash3b])) + + let fakeHash4: String = "fakeHash4" + var cFakeHash4: [CChar] = fakeHash4.cArray.nullTerminated() + config_confirm_pushed(conf, pushData8.pointee.seqno, &cFakeHash4) + config_confirm_pushed(conf2, pushData9.pointee.seqno, &cFakeHash4) + pushData8.deallocate() + pushData9.deallocate() + + expect(config_needs_push(conf)).to(beFalse()) + expect(config_needs_push(conf2)).to(beFalse()) + + // Validate the changes + var sessionIds2: [String] = [] + var nicknames2: [String] = [] + expect(contacts_size(conf)).to(equal(2)) + + var contact8: contacts_contact = contacts_contact() + let contactIterator2: UnsafeMutablePointer = contacts_iterator_new(conf) + while !contacts_iterator_done(contactIterator2, &contact8) { + sessionIds2.append(String(libSessionVal: contact8.session_id)) + nicknames2.append(String(libSessionVal: contact8.nickname, nullIfEmpty: true) ?? "(N/A)") + contacts_iterator_advance(contactIterator2) + } + contacts_iterator_free(contactIterator2) // Need to free the iterator + + expect(sessionIds2.count).to(equal(2)) + expect(sessionIds2.first).to(equal(anotherId)) + expect(sessionIds2.last).to(equal(thirdId)) + expect(nicknames2.first).to(equal("(N/A)")) + expect(nicknames2.last).to(equal("Nickname 3")) + } } } + + // MARK: - Convenience + + private static func createContact( + for index: Int, + in conf: UnsafeMutablePointer?, + maxing properties: [ContactProperty] = [] + ) throws -> contacts_contact { + let postPrefixId: String = "050000000000000000000000000000000000000000000000000000000000000000" + let sessionId: String = ("05\(index)a" + postPrefixId.suffix(postPrefixId.count - "05\(index)a".count)) + var cSessionId: [CChar] = sessionId.cArray.nullTerminated() + var contact: contacts_contact = contacts_contact() + + guard contacts_get_or_construct(conf, &contact, &cSessionId) else { + throw SessionUtilError.getOrConstructFailedUnexpectedly + } + + // Set the values to the maximum data that can fit + properties.forEach { property in + switch property { + case .approved: contact.approved = true + case .approved_me: contact.approved_me = true + case .blocked: contact.blocked = true + case .created: contact.created = Int64.max + case .notifications: contact.notifications = CONVO_NOTIFY_MENTIONS_ONLY + case .mute_until: contact.mute_until = Int64.max + + case .name: + contact.name = String( + data: Data( + repeating: "A".data(using: .utf8)![0], + count: SessionUtil.libSessionMaxNameByteLength + ), + encoding: .utf8 + ).toLibSession() + + case .nickname: + contact.nickname = String( + data: Data( + repeating: "A".data(using: .utf8)![0], + count: SessionUtil.libSessionMaxNameByteLength + ), + encoding: .utf8 + ).toLibSession() + + case .profile_pic: + contact.profile_pic = user_profile_pic( + url: String( + data: Data( + repeating: "A".data(using: .utf8)![0], + count: SessionUtil.libSessionMaxProfileUrlByteLength + ), + encoding: .utf8 + ).toLibSession(), + key: "qwerty78901234567890123456789012".data(using: .utf8)!.toLibSession() + ) + } + } + + return contact + } +} + +fileprivate extension Array where Element == ConfigContactsSpec.ContactProperty { + static var allProperties: [ConfigContactsSpec.ContactProperty] = ConfigContactsSpec.ContactProperty.allCases } diff --git a/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigConvoInfoVolatileSpec.swift b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigConvoInfoVolatileSpec.swift index 668d54d1e..86325c4ad 100644 --- a/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigConvoInfoVolatileSpec.swift +++ b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigConvoInfoVolatileSpec.swift @@ -11,255 +11,257 @@ import Nimble /// This spec is designed to replicate the initial test cases for the libSession-util to ensure the behaviour matches class ConfigConvoInfoVolatileSpec { // MARK: - Spec - + static func spec() { - it("generates ConvoInfoVolatile configs correctly") { - let seed: Data = Data(hex: "0123456789abcdef0123456789abcdef") - - // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately - let identity = try! Identity.generate(from: seed) - var edSK: [UInt8] = identity.ed25519KeyPair.secretKey - expect(edSK.toHexString().suffix(64)) - .to(equal("4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7")) - expect(identity.x25519KeyPair.publicKey.toHexString()) - .to(equal("d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72")) - expect(String(edSK.toHexString().prefix(32))).to(equal(seed.toHexString())) - - // Initialize a brand new, empty config because we have no dump data to deal with. - let error: UnsafeMutablePointer? = nil - var conf: UnsafeMutablePointer? = nil - expect(convo_info_volatile_init(&conf, &edSK, nil, 0, error)).to(equal(0)) - error?.deallocate() - - // Empty contacts shouldn't have an existing contact - let definitelyRealId: String = "055000000000000000000000000000000000000000000000000000000000000000" - var cDefinitelyRealId: [CChar] = definitelyRealId.cArray.nullTerminated() - var oneToOne1: convo_info_volatile_1to1 = convo_info_volatile_1to1() - expect(convo_info_volatile_get_1to1(conf, &oneToOne1, &cDefinitelyRealId)).to(beFalse()) - expect(convo_info_volatile_size(conf)).to(equal(0)) - - var oneToOne2: convo_info_volatile_1to1 = convo_info_volatile_1to1() - expect(convo_info_volatile_get_or_construct_1to1(conf, &oneToOne2, &cDefinitelyRealId)) - .to(beTrue()) - expect(String(libSessionVal: oneToOne2.session_id)).to(equal(definitelyRealId)) - expect(oneToOne2.last_read).to(equal(0)) - expect(oneToOne2.unread).to(beFalse()) - - // No need to sync a conversation with a default state - expect(config_needs_push(conf)).to(beFalse()) - expect(config_needs_dump(conf)).to(beFalse()) - - // Update the last read - let nowTimestampMs: Int64 = Int64(floor(Date().timeIntervalSince1970 * 1000)) - oneToOne2.last_read = nowTimestampMs - - // The new data doesn't get stored until we call this: - convo_info_volatile_set_1to1(conf, &oneToOne2) - - var legacyGroup1: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() - var oneToOne3: convo_info_volatile_1to1 = convo_info_volatile_1to1() - expect(convo_info_volatile_get_legacy_group(conf, &legacyGroup1, &cDefinitelyRealId)) - .to(beFalse()) - expect(convo_info_volatile_get_1to1(conf, &oneToOne3, &cDefinitelyRealId)).to(beTrue()) - expect(oneToOne3.last_read).to(equal(nowTimestampMs)) - - expect(config_needs_push(conf)).to(beTrue()) - expect(config_needs_dump(conf)).to(beTrue()) - - let openGroupBaseUrl: String = "http://Example.ORG:5678" - var cOpenGroupBaseUrl: [CChar] = openGroupBaseUrl.cArray.nullTerminated() - let openGroupBaseUrlResult: String = openGroupBaseUrl.lowercased() - // ("http://Example.ORG:5678" - // .lowercased() - // .cArray + - // [CChar](repeating: 0, count: (268 - openGroupBaseUrl.count)) - // ) - let openGroupRoom: String = "SudokuRoom" - var cOpenGroupRoom: [CChar] = openGroupRoom.cArray.nullTerminated() - let openGroupRoomResult: String = openGroupRoom.lowercased() - // ("SudokuRoom" - // .lowercased() - // .cArray + - // [CChar](repeating: 0, count: (65 - openGroupRoom.count)) - // ) - var cOpenGroupPubkey: [UInt8] = Data(hex: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") - .bytes - var community1: convo_info_volatile_community = convo_info_volatile_community() - expect(convo_info_volatile_get_or_construct_community(conf, &community1, &cOpenGroupBaseUrl, &cOpenGroupRoom, &cOpenGroupPubkey)).to(beTrue()) - expect(String(libSessionVal: community1.base_url)).to(equal(openGroupBaseUrlResult)) - expect(String(libSessionVal: community1.room)).to(equal(openGroupRoomResult)) - expect(Data(libSessionVal: community1.pubkey, count: 32).toHexString()) - .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) - community1.unread = true - - // The new data doesn't get stored until we call this: - convo_info_volatile_set_community(conf, &community1); - - // We don't need to push since we haven't changed anything, so this call is mainly just for - // testing: - let pushData1: UnsafeMutablePointer = config_push(conf) - expect(pushData1.pointee.seqno).to(equal(1)) - - // Pretend we uploaded it - let fakeHash1: String = "fakehash1" - var cFakeHash1: [CChar] = fakeHash1.cArray.nullTerminated() - config_confirm_pushed(conf, pushData1.pointee.seqno, &cFakeHash1) - expect(config_needs_dump(conf)).to(beTrue()) - expect(config_needs_push(conf)).to(beFalse()) - pushData1.deallocate() - - var dump1: UnsafeMutablePointer? = nil - var dump1Len: Int = 0 - config_dump(conf, &dump1, &dump1Len) - - let error2: UnsafeMutablePointer? = nil - var conf2: UnsafeMutablePointer? = nil - expect(convo_info_volatile_init(&conf2, &edSK, dump1, dump1Len, error2)).to(equal(0)) - error2?.deallocate() - dump1?.deallocate() - - expect(config_needs_dump(conf2)).to(beFalse()) - expect(config_needs_push(conf2)).to(beFalse()) - - var oneToOne4: convo_info_volatile_1to1 = convo_info_volatile_1to1() - expect(convo_info_volatile_get_1to1(conf2, &oneToOne4, &cDefinitelyRealId)).to(equal(true)) - expect(oneToOne4.last_read).to(equal(nowTimestampMs)) - expect(String(libSessionVal: oneToOne4.session_id)).to(equal(definitelyRealId)) - expect(oneToOne4.unread).to(beFalse()) - - var community2: convo_info_volatile_community = convo_info_volatile_community() - expect(convo_info_volatile_get_community(conf2, &community2, &cOpenGroupBaseUrl, &cOpenGroupRoom)).to(beTrue()) - expect(String(libSessionVal: community2.base_url)).to(equal(openGroupBaseUrlResult)) - expect(String(libSessionVal: community2.room)).to(equal(openGroupRoomResult)) - expect(Data(libSessionVal: community2.pubkey, count: 32).toHexString()) - .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) - community2.unread = true - - let anotherId: String = "051111111111111111111111111111111111111111111111111111111111111111" - var cAnotherId: [CChar] = anotherId.cArray.nullTerminated() - var oneToOne5: convo_info_volatile_1to1 = convo_info_volatile_1to1() - expect(convo_info_volatile_get_or_construct_1to1(conf2, &oneToOne5, &cAnotherId)).to(beTrue()) - oneToOne5.unread = true - convo_info_volatile_set_1to1(conf2, &oneToOne5) - - let thirdId: String = "05cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" - var cThirdId: [CChar] = thirdId.cArray.nullTerminated() - var legacyGroup2: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() - expect(convo_info_volatile_get_or_construct_legacy_group(conf2, &legacyGroup2, &cThirdId)).to(beTrue()) - legacyGroup2.last_read = (nowTimestampMs - 50) - convo_info_volatile_set_legacy_group(conf2, &legacyGroup2) - expect(config_needs_push(conf2)).to(beTrue()) - - let pushData2: UnsafeMutablePointer = config_push(conf2) - expect(pushData2.pointee.seqno).to(equal(2)) - - // Check the merging - let fakeHash2: String = "fakehash2" - var cFakeHash2: [CChar] = fakeHash2.cArray.nullTerminated() - var mergeHashes: [UnsafePointer?] = [cFakeHash2].unsafeCopy() - var mergeData: [UnsafePointer?] = [UnsafePointer(pushData2.pointee.config)] - var mergeSize: [Int] = [pushData2.pointee.config_len] - expect(config_merge(conf, &mergeHashes, &mergeData, &mergeSize, 1)).to(equal(1)) - config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash2) - pushData2.deallocate() - - expect(config_needs_push(conf)).to(beFalse()) - - for targetConf in [conf, conf2] { - // Iterate through and make sure we got everything we expected - var seen: [String] = [] - expect(convo_info_volatile_size(conf)).to(equal(4)) - expect(convo_info_volatile_size_1to1(conf)).to(equal(2)) - expect(convo_info_volatile_size_communities(conf)).to(equal(1)) - expect(convo_info_volatile_size_legacy_groups(conf)).to(equal(1)) + context("CONVO_INFO_VOLATILE") { + it("generates config correctly") { + let seed: Data = Data(hex: "0123456789abcdef0123456789abcdef") - var c1: convo_info_volatile_1to1 = convo_info_volatile_1to1() - var c2: convo_info_volatile_community = convo_info_volatile_community() - var c3: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() - let it: OpaquePointer = convo_info_volatile_iterator_new(targetConf) + // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately + let identity = try! Identity.generate(from: seed) + var edSK: [UInt8] = identity.ed25519KeyPair.secretKey + expect(edSK.toHexString().suffix(64)) + .to(equal("4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7")) + expect(identity.x25519KeyPair.publicKey.toHexString()) + .to(equal("d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72")) + expect(String(edSK.toHexString().prefix(32))).to(equal(seed.toHexString())) - while !convo_info_volatile_iterator_done(it) { - if convo_info_volatile_it_is_1to1(it, &c1) { - seen.append("1-to-1: \(String(libSessionVal: c1.session_id))") - } - else if convo_info_volatile_it_is_community(it, &c2) { - seen.append("og: \(String(libSessionVal: c2.base_url))/r/\(String(libSessionVal: c2.room))") - } - else if convo_info_volatile_it_is_legacy_group(it, &c3) { - seen.append("cl: \(String(libSessionVal: c3.group_id))") + // Initialize a brand new, empty config because we have no dump data to deal with. + let error: UnsafeMutablePointer? = nil + var conf: UnsafeMutablePointer? = nil + expect(convo_info_volatile_init(&conf, &edSK, nil, 0, error)).to(equal(0)) + error?.deallocate() + + // Empty contacts shouldn't have an existing contact + let definitelyRealId: String = "055000000000000000000000000000000000000000000000000000000000000000" + var cDefinitelyRealId: [CChar] = definitelyRealId.cArray.nullTerminated() + var oneToOne1: convo_info_volatile_1to1 = convo_info_volatile_1to1() + expect(convo_info_volatile_get_1to1(conf, &oneToOne1, &cDefinitelyRealId)).to(beFalse()) + expect(convo_info_volatile_size(conf)).to(equal(0)) + + var oneToOne2: convo_info_volatile_1to1 = convo_info_volatile_1to1() + expect(convo_info_volatile_get_or_construct_1to1(conf, &oneToOne2, &cDefinitelyRealId)) + .to(beTrue()) + expect(String(libSessionVal: oneToOne2.session_id)).to(equal(definitelyRealId)) + expect(oneToOne2.last_read).to(equal(0)) + expect(oneToOne2.unread).to(beFalse()) + + // No need to sync a conversation with a default state + expect(config_needs_push(conf)).to(beFalse()) + expect(config_needs_dump(conf)).to(beFalse()) + + // Update the last read + let nowTimestampMs: Int64 = Int64(floor(Date().timeIntervalSince1970 * 1000)) + oneToOne2.last_read = nowTimestampMs + + // The new data doesn't get stored until we call this: + convo_info_volatile_set_1to1(conf, &oneToOne2) + + var legacyGroup1: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() + var oneToOne3: convo_info_volatile_1to1 = convo_info_volatile_1to1() + expect(convo_info_volatile_get_legacy_group(conf, &legacyGroup1, &cDefinitelyRealId)) + .to(beFalse()) + expect(convo_info_volatile_get_1to1(conf, &oneToOne3, &cDefinitelyRealId)).to(beTrue()) + expect(oneToOne3.last_read).to(equal(nowTimestampMs)) + + expect(config_needs_push(conf)).to(beTrue()) + expect(config_needs_dump(conf)).to(beTrue()) + + let openGroupBaseUrl: String = "http://Example.ORG:5678" + var cOpenGroupBaseUrl: [CChar] = openGroupBaseUrl.cArray.nullTerminated() + let openGroupBaseUrlResult: String = openGroupBaseUrl.lowercased() + // ("http://Example.ORG:5678" + // .lowercased() + // .cArray + + // [CChar](repeating: 0, count: (268 - openGroupBaseUrl.count)) + // ) + let openGroupRoom: String = "SudokuRoom" + var cOpenGroupRoom: [CChar] = openGroupRoom.cArray.nullTerminated() + let openGroupRoomResult: String = openGroupRoom.lowercased() + // ("SudokuRoom" + // .lowercased() + // .cArray + + // [CChar](repeating: 0, count: (65 - openGroupRoom.count)) + // ) + var cOpenGroupPubkey: [UInt8] = Data(hex: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") + .bytes + var community1: convo_info_volatile_community = convo_info_volatile_community() + expect(convo_info_volatile_get_or_construct_community(conf, &community1, &cOpenGroupBaseUrl, &cOpenGroupRoom, &cOpenGroupPubkey)).to(beTrue()) + expect(String(libSessionVal: community1.base_url)).to(equal(openGroupBaseUrlResult)) + expect(String(libSessionVal: community1.room)).to(equal(openGroupRoomResult)) + expect(Data(libSessionVal: community1.pubkey, count: 32).toHexString()) + .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) + community1.unread = true + + // The new data doesn't get stored until we call this: + convo_info_volatile_set_community(conf, &community1); + + // We don't need to push since we haven't changed anything, so this call is mainly just for + // testing: + let pushData1: UnsafeMutablePointer = config_push(conf) + expect(pushData1.pointee.seqno).to(equal(1)) + + // Pretend we uploaded it + let fakeHash1: String = "fakehash1" + var cFakeHash1: [CChar] = fakeHash1.cArray.nullTerminated() + config_confirm_pushed(conf, pushData1.pointee.seqno, &cFakeHash1) + expect(config_needs_dump(conf)).to(beTrue()) + expect(config_needs_push(conf)).to(beFalse()) + pushData1.deallocate() + + var dump1: UnsafeMutablePointer? = nil + var dump1Len: Int = 0 + config_dump(conf, &dump1, &dump1Len) + + let error2: UnsafeMutablePointer? = nil + var conf2: UnsafeMutablePointer? = nil + expect(convo_info_volatile_init(&conf2, &edSK, dump1, dump1Len, error2)).to(equal(0)) + error2?.deallocate() + dump1?.deallocate() + + expect(config_needs_dump(conf2)).to(beFalse()) + expect(config_needs_push(conf2)).to(beFalse()) + + var oneToOne4: convo_info_volatile_1to1 = convo_info_volatile_1to1() + expect(convo_info_volatile_get_1to1(conf2, &oneToOne4, &cDefinitelyRealId)).to(equal(true)) + expect(oneToOne4.last_read).to(equal(nowTimestampMs)) + expect(String(libSessionVal: oneToOne4.session_id)).to(equal(definitelyRealId)) + expect(oneToOne4.unread).to(beFalse()) + + var community2: convo_info_volatile_community = convo_info_volatile_community() + expect(convo_info_volatile_get_community(conf2, &community2, &cOpenGroupBaseUrl, &cOpenGroupRoom)).to(beTrue()) + expect(String(libSessionVal: community2.base_url)).to(equal(openGroupBaseUrlResult)) + expect(String(libSessionVal: community2.room)).to(equal(openGroupRoomResult)) + expect(Data(libSessionVal: community2.pubkey, count: 32).toHexString()) + .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) + community2.unread = true + + let anotherId: String = "051111111111111111111111111111111111111111111111111111111111111111" + var cAnotherId: [CChar] = anotherId.cArray.nullTerminated() + var oneToOne5: convo_info_volatile_1to1 = convo_info_volatile_1to1() + expect(convo_info_volatile_get_or_construct_1to1(conf2, &oneToOne5, &cAnotherId)).to(beTrue()) + oneToOne5.unread = true + convo_info_volatile_set_1to1(conf2, &oneToOne5) + + let thirdId: String = "05cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + var cThirdId: [CChar] = thirdId.cArray.nullTerminated() + var legacyGroup2: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() + expect(convo_info_volatile_get_or_construct_legacy_group(conf2, &legacyGroup2, &cThirdId)).to(beTrue()) + legacyGroup2.last_read = (nowTimestampMs - 50) + convo_info_volatile_set_legacy_group(conf2, &legacyGroup2) + expect(config_needs_push(conf2)).to(beTrue()) + + let pushData2: UnsafeMutablePointer = config_push(conf2) + expect(pushData2.pointee.seqno).to(equal(2)) + + // Check the merging + let fakeHash2: String = "fakehash2" + var cFakeHash2: [CChar] = fakeHash2.cArray.nullTerminated() + var mergeHashes: [UnsafePointer?] = [cFakeHash2].unsafeCopy() + var mergeData: [UnsafePointer?] = [UnsafePointer(pushData2.pointee.config)] + var mergeSize: [Int] = [pushData2.pointee.config_len] + expect(config_merge(conf, &mergeHashes, &mergeData, &mergeSize, 1)).to(equal(1)) + config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash2) + pushData2.deallocate() + + expect(config_needs_push(conf)).to(beFalse()) + + for targetConf in [conf, conf2] { + // Iterate through and make sure we got everything we expected + var seen: [String] = [] + expect(convo_info_volatile_size(conf)).to(equal(4)) + expect(convo_info_volatile_size_1to1(conf)).to(equal(2)) + expect(convo_info_volatile_size_communities(conf)).to(equal(1)) + expect(convo_info_volatile_size_legacy_groups(conf)).to(equal(1)) + + var c1: convo_info_volatile_1to1 = convo_info_volatile_1to1() + var c2: convo_info_volatile_community = convo_info_volatile_community() + var c3: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() + let it: OpaquePointer = convo_info_volatile_iterator_new(targetConf) + + while !convo_info_volatile_iterator_done(it) { + if convo_info_volatile_it_is_1to1(it, &c1) { + seen.append("1-to-1: \(String(libSessionVal: c1.session_id))") + } + else if convo_info_volatile_it_is_community(it, &c2) { + seen.append("og: \(String(libSessionVal: c2.base_url))/r/\(String(libSessionVal: c2.room))") + } + else if convo_info_volatile_it_is_legacy_group(it, &c3) { + seen.append("cl: \(String(libSessionVal: c3.group_id))") + } + + convo_info_volatile_iterator_advance(it) } - convo_info_volatile_iterator_advance(it) + convo_info_volatile_iterator_free(it) + + expect(seen).to(equal([ + "1-to-1: 051111111111111111111111111111111111111111111111111111111111111111", + "1-to-1: 055000000000000000000000000000000000000000000000000000000000000000", + "og: http://example.org:5678/r/sudokuroom", + "cl: 05cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + ])) } - convo_info_volatile_iterator_free(it) + let fourthId: String = "052000000000000000000000000000000000000000000000000000000000000000" + var cFourthId: [CChar] = fourthId.cArray.nullTerminated() + expect(config_needs_push(conf)).to(beFalse()) + convo_info_volatile_erase_1to1(conf, &cFourthId) + expect(config_needs_push(conf)).to(beFalse()) + convo_info_volatile_erase_1to1(conf, &cDefinitelyRealId) + expect(config_needs_push(conf)).to(beTrue()) + expect(convo_info_volatile_size(conf)).to(equal(3)) + expect(convo_info_volatile_size_1to1(conf)).to(equal(1)) - expect(seen).to(equal([ - "1-to-1: 051111111111111111111111111111111111111111111111111111111111111111", - "1-to-1: 055000000000000000000000000000000000000000000000000000000000000000", - "og: http://example.org:5678/r/sudokuroom", - "cl: 05cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + // Check the single-type iterators: + var seen1: [String?] = [] + var c1: convo_info_volatile_1to1 = convo_info_volatile_1to1() + let it1: OpaquePointer = convo_info_volatile_iterator_new_1to1(conf) + + while !convo_info_volatile_iterator_done(it1) { + expect(convo_info_volatile_it_is_1to1(it1, &c1)).to(beTrue()) + + seen1.append(String(libSessionVal: c1.session_id)) + convo_info_volatile_iterator_advance(it1) + } + + convo_info_volatile_iterator_free(it1) + expect(seen1).to(equal([ + "051111111111111111111111111111111111111111111111111111111111111111" + ])) + + var seen2: [String?] = [] + var c2: convo_info_volatile_community = convo_info_volatile_community() + let it2: OpaquePointer = convo_info_volatile_iterator_new_communities(conf) + + while !convo_info_volatile_iterator_done(it2) { + expect(convo_info_volatile_it_is_community(it2, &c2)).to(beTrue()) + + seen2.append(String(libSessionVal: c2.base_url)) + convo_info_volatile_iterator_advance(it2) + } + + convo_info_volatile_iterator_free(it2) + expect(seen2).to(equal([ + "http://example.org:5678" + ])) + + var seen3: [String?] = [] + var c3: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() + let it3: OpaquePointer = convo_info_volatile_iterator_new_legacy_groups(conf) + + while !convo_info_volatile_iterator_done(it3) { + expect(convo_info_volatile_it_is_legacy_group(it3, &c3)).to(beTrue()) + + seen3.append(String(libSessionVal: c3.group_id)) + convo_info_volatile_iterator_advance(it3) + } + + convo_info_volatile_iterator_free(it3) + expect(seen3).to(equal([ + "05cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" ])) } - - let fourthId: String = "052000000000000000000000000000000000000000000000000000000000000000" - var cFourthId: [CChar] = fourthId.cArray.nullTerminated() - expect(config_needs_push(conf)).to(beFalse()) - convo_info_volatile_erase_1to1(conf, &cFourthId) - expect(config_needs_push(conf)).to(beFalse()) - convo_info_volatile_erase_1to1(conf, &cDefinitelyRealId) - expect(config_needs_push(conf)).to(beTrue()) - expect(convo_info_volatile_size(conf)).to(equal(3)) - expect(convo_info_volatile_size_1to1(conf)).to(equal(1)) - - // Check the single-type iterators: - var seen1: [String?] = [] - var c1: convo_info_volatile_1to1 = convo_info_volatile_1to1() - let it1: OpaquePointer = convo_info_volatile_iterator_new_1to1(conf) - - while !convo_info_volatile_iterator_done(it1) { - expect(convo_info_volatile_it_is_1to1(it1, &c1)).to(beTrue()) - - seen1.append(String(libSessionVal: c1.session_id)) - convo_info_volatile_iterator_advance(it1) - } - - convo_info_volatile_iterator_free(it1) - expect(seen1).to(equal([ - "051111111111111111111111111111111111111111111111111111111111111111" - ])) - - var seen2: [String?] = [] - var c2: convo_info_volatile_community = convo_info_volatile_community() - let it2: OpaquePointer = convo_info_volatile_iterator_new_communities(conf) - - while !convo_info_volatile_iterator_done(it2) { - expect(convo_info_volatile_it_is_community(it2, &c2)).to(beTrue()) - - seen2.append(String(libSessionVal: c2.base_url)) - convo_info_volatile_iterator_advance(it2) - } - - convo_info_volatile_iterator_free(it2) - expect(seen2).to(equal([ - "http://example.org:5678" - ])) - - var seen3: [String?] = [] - var c3: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() - let it3: OpaquePointer = convo_info_volatile_iterator_new_legacy_groups(conf) - - while !convo_info_volatile_iterator_done(it3) { - expect(convo_info_volatile_it_is_legacy_group(it3, &c3)).to(beTrue()) - - seen3.append(String(libSessionVal: c3.group_id)) - convo_info_volatile_iterator_advance(it3) - } - - convo_info_volatile_iterator_free(it3) - expect(seen3).to(equal([ - "05cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" - ])) } } } diff --git a/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserGroupsSpec.swift b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserGroupsSpec.swift index 6ace1db75..926cf74f6 100644 --- a/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserGroupsSpec.swift +++ b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserGroupsSpec.swift @@ -82,505 +82,507 @@ class ConfigUserGroupsSpec { expect(result8?.publicKey) .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) } - - it("generates UserGroup configs correctly") { - let createdTs: Int64 = 1680064059 - let nowTs: Int64 = Int64(Date().timeIntervalSince1970) - let seed: Data = Data(hex: "0123456789abcdef0123456789abcdef") - - // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately - let identity = try! Identity.generate(from: seed) - var edSK: [UInt8] = identity.ed25519KeyPair.secretKey - expect(edSK.toHexString().suffix(64)) - .to(equal("4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7")) - expect(identity.x25519KeyPair.publicKey.toHexString()) - .to(equal("d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72")) - expect(String(edSK.toHexString().prefix(32))).to(equal(seed.toHexString())) - - // Initialize a brand new, empty config because we have no dump data to deal with. - let error: UnsafeMutablePointer? = nil - var conf: UnsafeMutablePointer? = nil - expect(user_groups_init(&conf, &edSK, nil, 0, error)).to(equal(0)) - error?.deallocate() - - // Empty contacts shouldn't have an existing contact - let definitelyRealId: String = "055000000000000000000000000000000000000000000000000000000000000000" - var cDefinitelyRealId: [CChar] = definitelyRealId.cArray.nullTerminated() - let legacyGroup1: UnsafeMutablePointer? = user_groups_get_legacy_group(conf, &cDefinitelyRealId) - expect(legacyGroup1?.pointee).to(beNil()) - expect(user_groups_size(conf)).to(equal(0)) - - let legacyGroup2: UnsafeMutablePointer = user_groups_get_or_construct_legacy_group(conf, &cDefinitelyRealId) - expect(legacyGroup2.pointee).toNot(beNil()) - expect(String(libSessionVal: legacyGroup2.pointee.session_id)) - .to(equal(definitelyRealId)) - expect(legacyGroup2.pointee.disappearing_timer).to(equal(0)) - expect(String(libSessionVal: legacyGroup2.pointee.enc_pubkey, fixedLength: 32)).to(equal("")) - expect(String(libSessionVal: legacyGroup2.pointee.enc_seckey, fixedLength: 32)).to(equal("")) - expect(legacyGroup2.pointee.priority).to(equal(0)) - expect(String(libSessionVal: legacyGroup2.pointee.name)).to(equal("")) - expect(legacyGroup2.pointee.joined_at).to(equal(0)) - expect(legacyGroup2.pointee.notifications).to(equal(CONVO_NOTIFY_DEFAULT)) - expect(legacyGroup2.pointee.mute_until).to(equal(0)) - - // Iterate through and make sure we got everything we expected - var membersSeen1: [String: Bool] = [:] - var memberSessionId1: UnsafePointer? = nil - var memberAdmin1: Bool = false - let membersIt1: OpaquePointer = ugroups_legacy_members_begin(legacyGroup2) - - while ugroups_legacy_members_next(membersIt1, &memberSessionId1, &memberAdmin1) { - membersSeen1[String(cString: memberSessionId1!)] = memberAdmin1 - } - - ugroups_legacy_members_free(membersIt1) - - expect(membersSeen1).to(beEmpty()) - - // No need to sync a conversation with a default state - expect(config_needs_push(conf)).to(beFalse()) - expect(config_needs_dump(conf)).to(beFalse()) - - // We don't need to push since we haven't changed anything, so this call is mainly just for - // testing: - let pushData1: UnsafeMutablePointer = config_push(conf) - expect(pushData1.pointee.seqno).to(equal(0)) - expect([String](pointer: pushData1.pointee.obsolete, count: pushData1.pointee.obsolete_len)) - .to(beEmpty()) - expect(pushData1.pointee.config_len).to(equal(256)) - pushData1.deallocate() - - let users: [String] = [ - "050000000000000000000000000000000000000000000000000000000000000000", - "051111111111111111111111111111111111111111111111111111111111111111", - "052222222222222222222222222222222222222222222222222222222222222222", - "053333333333333333333333333333333333333333333333333333333333333333", - "054444444444444444444444444444444444444444444444444444444444444444", - "055555555555555555555555555555555555555555555555555555555555555555", - "056666666666666666666666666666666666666666666666666666666666666666" - ] - var cUsers: [[CChar]] = users.map { $0.cArray.nullTerminated() } - legacyGroup2.pointee.name = "Englishmen".toLibSession() - legacyGroup2.pointee.disappearing_timer = 60 - legacyGroup2.pointee.joined_at = createdTs - legacyGroup2.pointee.notifications = CONVO_NOTIFY_ALL - legacyGroup2.pointee.mute_until = (nowTs + 3600) - expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[0], false)).to(beTrue()) - expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[1], true)).to(beTrue()) - expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[2], false)).to(beTrue()) - expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[4], true)).to(beTrue()) - expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[5], false)).to(beTrue()) - expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[2], false)).to(beFalse()) - - // Flip to and from admin - expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[2], true)).to(beTrue()) - expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[1], false)).to(beTrue()) - - expect(ugroups_legacy_member_remove(legacyGroup2, &cUsers[5])).to(beTrue()) - expect(ugroups_legacy_member_remove(legacyGroup2, &cUsers[4])).to(beTrue()) - - var membersSeen2: [String: Bool] = [:] - var memberSessionId2: UnsafePointer? = nil - var memberAdmin2: Bool = false - let membersIt2: OpaquePointer = ugroups_legacy_members_begin(legacyGroup2) - - while ugroups_legacy_members_next(membersIt2, &memberSessionId2, &memberAdmin2) { - membersSeen2[String(cString: memberSessionId2!)] = memberAdmin2 - } - - ugroups_legacy_members_free(membersIt2) - - expect(membersSeen2).to(equal([ - "050000000000000000000000000000000000000000000000000000000000000000": false, - "051111111111111111111111111111111111111111111111111111111111111111": false, - "052222222222222222222222222222222222222222222222222222222222222222": true - ])) - - // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately - let groupSeed: Data = Data(hex: "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff") - let groupEd25519KeyPair = Sodium().sign.keyPair(seed: groupSeed.bytes)! - let groupX25519PublicKey = Sodium().sign.toX25519(ed25519PublicKey: groupEd25519KeyPair.publicKey)! - - // Note: this isn't exactly what Session actually does here for legacy closed - // groups (rather it uses X25519 keys) but for this test the distinction doesn't matter. - legacyGroup2.pointee.enc_pubkey = Data(groupX25519PublicKey).toLibSession() - legacyGroup2.pointee.enc_seckey = Data(groupEd25519KeyPair.secretKey).toLibSession() - legacyGroup2.pointee.priority = 3 - - expect(Data(libSessionVal: legacyGroup2.pointee.enc_pubkey, count: 32).toHexString()) - .to(equal("c5ba413c336f2fe1fb9a2c525f8a86a412a1db128a7841b4e0e217fa9eb7fd5e")) - expect(Data(libSessionVal: legacyGroup2.pointee.enc_seckey, count: 32).toHexString()) - .to(equal("00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff")) - - // The new data doesn't get stored until we call this: - user_groups_set_free_legacy_group(conf, legacyGroup2) - - let legacyGroup3: UnsafeMutablePointer? = user_groups_get_legacy_group(conf, &cDefinitelyRealId) - expect(legacyGroup3?.pointee).toNot(beNil()) - expect(config_needs_push(conf)).to(beTrue()) - expect(config_needs_dump(conf)).to(beTrue()) - ugroups_legacy_group_free(legacyGroup3) - - let communityPubkey: String = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" - var cCommunityPubkey: [UInt8] = Data(hex: communityPubkey).cArray - var cCommunityBaseUrl: [CChar] = "http://Example.ORG:5678".cArray.nullTerminated() - var cCommunityRoom: [CChar] = "SudokuRoom".cArray.nullTerminated() - var community1: ugroups_community_info = ugroups_community_info() - expect(user_groups_get_or_construct_community(conf, &community1, &cCommunityBaseUrl, &cCommunityRoom, &cCommunityPubkey)) - .to(beTrue()) - - expect(String(libSessionVal: community1.base_url)).to(equal("http://example.org:5678")) // Note: lower-case - expect(String(libSessionVal: community1.room)).to(equal("SudokuRoom")) // Note: case-preserving - expect(Data(libSessionVal: community1.pubkey, count: 32).toHexString()) - .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) - community1.priority = 14 - - // The new data doesn't get stored until we call this: - user_groups_set_community(conf, &community1) - - // incremented since we made changes (this only increments once between - // dumps; even though we changed two fields here). - let pushData2: UnsafeMutablePointer = config_push(conf) - expect(pushData2.pointee.seqno).to(equal(1)) - expect([String](pointer: pushData2.pointee.obsolete, count: pushData2.pointee.obsolete_len)) - .to(beEmpty()) - - // Pretend we uploaded it - let fakeHash1: String = "fakehash1" - var cFakeHash1: [CChar] = fakeHash1.cArray.nullTerminated() - config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash1) - expect(config_needs_dump(conf)).to(beTrue()) - expect(config_needs_push(conf)).to(beFalse()) - - var dump1: UnsafeMutablePointer? = nil - var dump1Len: Int = 0 - config_dump(conf, &dump1, &dump1Len) - - let error2: UnsafeMutablePointer? = nil - var conf2: UnsafeMutablePointer? = nil - expect(user_groups_init(&conf2, &edSK, dump1, dump1Len, error2)).to(equal(0)) - error2?.deallocate() - dump1?.deallocate() - - expect(config_needs_dump(conf)).to(beFalse()) // Because we just called dump() above, to load up conf2 - expect(config_needs_push(conf)).to(beFalse()) - - let pushData3: UnsafeMutablePointer = config_push(conf) - expect(pushData3.pointee.seqno).to(equal(1)) - expect([String](pointer: pushData3.pointee.obsolete, count: pushData3.pointee.obsolete_len)) - .to(beEmpty()) - pushData3.deallocate() - - let currentHashes1: UnsafeMutablePointer? = config_current_hashes(conf) - expect([String](pointer: currentHashes1?.pointee.value, count: currentHashes1?.pointee.len)) - .to(equal(["fakehash1"])) - currentHashes1?.deallocate() - - expect(config_needs_push(conf2)).to(beFalse()) - expect(config_needs_dump(conf2)).to(beFalse()) - - let pushData4: UnsafeMutablePointer = config_push(conf2) - expect(pushData4.pointee.seqno).to(equal(1)) - expect(config_needs_dump(conf2)).to(beFalse()) - expect([String](pointer: pushData4.pointee.obsolete, count: pushData4.pointee.obsolete_len)) - .to(beEmpty()) - pushData4.deallocate() - - let currentHashes2: UnsafeMutablePointer? = config_current_hashes(conf2) - expect([String](pointer: currentHashes2?.pointee.value, count: currentHashes2?.pointee.len)) - .to(equal(["fakehash1"])) - currentHashes2?.deallocate() - - expect(user_groups_size(conf2)).to(equal(2)) - expect(user_groups_size_communities(conf2)).to(equal(1)) - expect(user_groups_size_legacy_groups(conf2)).to(equal(1)) - - let legacyGroup4: UnsafeMutablePointer? = user_groups_get_legacy_group(conf2, &cDefinitelyRealId) - expect(legacyGroup4?.pointee).toNot(beNil()) - expect(String(libSessionVal: legacyGroup4?.pointee.enc_pubkey, fixedLength: 32)).to(equal("")) - expect(String(libSessionVal: legacyGroup4?.pointee.enc_seckey, fixedLength: 32)).to(equal("")) - expect(legacyGroup4?.pointee.disappearing_timer).to(equal(60)) - expect(String(libSessionVal: legacyGroup4?.pointee.session_id)).to(equal(definitelyRealId)) - expect(legacyGroup4?.pointee.priority).to(equal(3)) - expect(String(libSessionVal: legacyGroup4?.pointee.name)).to(equal("Englishmen")) - expect(legacyGroup4?.pointee.joined_at).to(equal(createdTs)) - expect(legacyGroup2.pointee.notifications).to(equal(CONVO_NOTIFY_ALL)) - expect(legacyGroup2.pointee.mute_until).to(equal(nowTs + 3600)) - - var membersSeen3: [String: Bool] = [:] - var memberSessionId3: UnsafePointer? = nil - var memberAdmin3: Bool = false - let membersIt3: OpaquePointer = ugroups_legacy_members_begin(legacyGroup4) - - while ugroups_legacy_members_next(membersIt3, &memberSessionId3, &memberAdmin3) { - membersSeen3[String(cString: memberSessionId3!)] = memberAdmin3 - } - - ugroups_legacy_members_free(membersIt3) - ugroups_legacy_group_free(legacyGroup4) - - expect(membersSeen3).to(equal([ - "050000000000000000000000000000000000000000000000000000000000000000": false, - "051111111111111111111111111111111111111111111111111111111111111111": false, - "052222222222222222222222222222222222222222222222222222222222222222": true - ])) - - expect(config_needs_push(conf2)).to(beFalse()) - expect(config_needs_dump(conf2)).to(beFalse()) - - let pushData5: UnsafeMutablePointer = config_push(conf2) - expect(pushData5.pointee.seqno).to(equal(1)) - expect(config_needs_dump(conf2)).to(beFalse()) - pushData5.deallocate() - - for targetConf in [conf, conf2] { + + context("USER_GROUPS") { + it("generates config correctly") { + let createdTs: Int64 = 1680064059 + let nowTs: Int64 = Int64(Date().timeIntervalSince1970) + let seed: Data = Data(hex: "0123456789abcdef0123456789abcdef") + + // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately + let identity = try! Identity.generate(from: seed) + var edSK: [UInt8] = identity.ed25519KeyPair.secretKey + expect(edSK.toHexString().suffix(64)) + .to(equal("4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7")) + expect(identity.x25519KeyPair.publicKey.toHexString()) + .to(equal("d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72")) + expect(String(edSK.toHexString().prefix(32))).to(equal(seed.toHexString())) + + // Initialize a brand new, empty config because we have no dump data to deal with. + let error: UnsafeMutablePointer? = nil + var conf: UnsafeMutablePointer? = nil + expect(user_groups_init(&conf, &edSK, nil, 0, error)).to(equal(0)) + error?.deallocate() + + // Empty contacts shouldn't have an existing contact + let definitelyRealId: String = "055000000000000000000000000000000000000000000000000000000000000000" + var cDefinitelyRealId: [CChar] = definitelyRealId.cArray.nullTerminated() + let legacyGroup1: UnsafeMutablePointer? = user_groups_get_legacy_group(conf, &cDefinitelyRealId) + expect(legacyGroup1?.pointee).to(beNil()) + expect(user_groups_size(conf)).to(equal(0)) + + let legacyGroup2: UnsafeMutablePointer = user_groups_get_or_construct_legacy_group(conf, &cDefinitelyRealId) + expect(legacyGroup2.pointee).toNot(beNil()) + expect(String(libSessionVal: legacyGroup2.pointee.session_id)) + .to(equal(definitelyRealId)) + expect(legacyGroup2.pointee.disappearing_timer).to(equal(0)) + expect(String(libSessionVal: legacyGroup2.pointee.enc_pubkey, fixedLength: 32)).to(equal("")) + expect(String(libSessionVal: legacyGroup2.pointee.enc_seckey, fixedLength: 32)).to(equal("")) + expect(legacyGroup2.pointee.priority).to(equal(0)) + expect(String(libSessionVal: legacyGroup2.pointee.name)).to(equal("")) + expect(legacyGroup2.pointee.joined_at).to(equal(0)) + expect(legacyGroup2.pointee.notifications).to(equal(CONVO_NOTIFY_DEFAULT)) + expect(legacyGroup2.pointee.mute_until).to(equal(0)) + // Iterate through and make sure we got everything we expected - var seen: [String] = [] + var membersSeen1: [String: Bool] = [:] + var memberSessionId1: UnsafePointer? = nil + var memberAdmin1: Bool = false + let membersIt1: OpaquePointer = ugroups_legacy_members_begin(legacyGroup2) - var c1: ugroups_legacy_group_info = ugroups_legacy_group_info() - var c2: ugroups_community_info = ugroups_community_info() - let it: OpaquePointer = user_groups_iterator_new(targetConf) - - while !user_groups_iterator_done(it) { - if user_groups_it_is_legacy_group(it, &c1) { - var memberCount: Int = 0 - var adminCount: Int = 0 - ugroups_legacy_members_count(&c1, &memberCount, &adminCount) - seen.append("legacy: \(String(libSessionVal: c1.name)), \(adminCount) admins, \(memberCount) members") - } - else if user_groups_it_is_community(it, &c2) { - seen.append("community: \(String(libSessionVal: c2.base_url))/r/\(String(libSessionVal: c2.room))") - } - else { - seen.append("unknown") - } - - user_groups_iterator_advance(it) + while ugroups_legacy_members_next(membersIt1, &memberSessionId1, &memberAdmin1) { + membersSeen1[String(cString: memberSessionId1!)] = memberAdmin1 } - user_groups_iterator_free(it) + ugroups_legacy_members_free(membersIt1) - expect(seen).to(equal([ - "community: http://example.org:5678/r/SudokuRoom", - "legacy: Englishmen, 1 admins, 2 members" + expect(membersSeen1).to(beEmpty()) + + // No need to sync a conversation with a default state + expect(config_needs_push(conf)).to(beFalse()) + expect(config_needs_dump(conf)).to(beFalse()) + + // We don't need to push since we haven't changed anything, so this call is mainly just for + // testing: + let pushData1: UnsafeMutablePointer = config_push(conf) + expect(pushData1.pointee.seqno).to(equal(0)) + expect([String](pointer: pushData1.pointee.obsolete, count: pushData1.pointee.obsolete_len)) + .to(beEmpty()) + expect(pushData1.pointee.config_len).to(equal(256)) + pushData1.deallocate() + + let users: [String] = [ + "050000000000000000000000000000000000000000000000000000000000000000", + "051111111111111111111111111111111111111111111111111111111111111111", + "052222222222222222222222222222222222222222222222222222222222222222", + "053333333333333333333333333333333333333333333333333333333333333333", + "054444444444444444444444444444444444444444444444444444444444444444", + "055555555555555555555555555555555555555555555555555555555555555555", + "056666666666666666666666666666666666666666666666666666666666666666" + ] + var cUsers: [[CChar]] = users.map { $0.cArray.nullTerminated() } + legacyGroup2.pointee.name = "Englishmen".toLibSession() + legacyGroup2.pointee.disappearing_timer = 60 + legacyGroup2.pointee.joined_at = createdTs + legacyGroup2.pointee.notifications = CONVO_NOTIFY_ALL + legacyGroup2.pointee.mute_until = (nowTs + 3600) + expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[0], false)).to(beTrue()) + expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[1], true)).to(beTrue()) + expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[2], false)).to(beTrue()) + expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[4], true)).to(beTrue()) + expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[5], false)).to(beTrue()) + expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[2], false)).to(beFalse()) + + // Flip to and from admin + expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[2], true)).to(beTrue()) + expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[1], false)).to(beTrue()) + + expect(ugroups_legacy_member_remove(legacyGroup2, &cUsers[5])).to(beTrue()) + expect(ugroups_legacy_member_remove(legacyGroup2, &cUsers[4])).to(beTrue()) + + var membersSeen2: [String: Bool] = [:] + var memberSessionId2: UnsafePointer? = nil + var memberAdmin2: Bool = false + let membersIt2: OpaquePointer = ugroups_legacy_members_begin(legacyGroup2) + + while ugroups_legacy_members_next(membersIt2, &memberSessionId2, &memberAdmin2) { + membersSeen2[String(cString: memberSessionId2!)] = memberAdmin2 + } + + ugroups_legacy_members_free(membersIt2) + + expect(membersSeen2).to(equal([ + "050000000000000000000000000000000000000000000000000000000000000000": false, + "051111111111111111111111111111111111111111111111111111111111111111": false, + "052222222222222222222222222222222222222222222222222222222222222222": true ])) - } - - var cCommunity2BaseUrl: [CChar] = "http://example.org:5678".cArray.nullTerminated() - var cCommunity2Room: [CChar] = "sudokuRoom".cArray.nullTerminated() - var community2: ugroups_community_info = ugroups_community_info() - expect(user_groups_get_community(conf2, &community2, &cCommunity2BaseUrl, &cCommunity2Room)) - .to(beTrue()) - expect(String(libSessionVal: community2.base_url)).to(equal("http://example.org:5678")) - expect(String(libSessionVal: community2.room)).to(equal("SudokuRoom")) // Case preserved from the stored value, not the input value - expect(Data(libSessionVal: community2.pubkey, count: 32).toHexString()) - .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) - expect(community2.priority).to(equal(14)) - - expect(config_needs_push(conf2)).to(beFalse()) - expect(config_needs_dump(conf2)).to(beFalse()) - - let pushData6: UnsafeMutablePointer = config_push(conf2) - expect(pushData6.pointee.seqno).to(equal(1)) - expect(config_needs_dump(conf2)).to(beFalse()) - pushData6.deallocate() - - community2.room = "sudokuRoom".toLibSession() // Change capitalization - user_groups_set_community(conf2, &community2) - - expect(config_needs_push(conf2)).to(beTrue()) - expect(config_needs_dump(conf2)).to(beTrue()) - - let fakeHash2: String = "fakehash2" - var cFakeHash2: [CChar] = fakeHash2.cArray.nullTerminated() - let pushData7: UnsafeMutablePointer = config_push(conf2) - expect(pushData7.pointee.seqno).to(equal(2)) - config_confirm_pushed(conf2, pushData7.pointee.seqno, &cFakeHash2) - expect([String](pointer: pushData7.pointee.obsolete, count: pushData7.pointee.obsolete_len)) - .to(equal([fakeHash1])) - - let currentHashes3: UnsafeMutablePointer? = config_current_hashes(conf2) - expect([String](pointer: currentHashes3?.pointee.value, count: currentHashes3?.pointee.len)) - .to(equal([fakeHash2])) - currentHashes3?.deallocate() - - var dump2: UnsafeMutablePointer? = nil - var dump2Len: Int = 0 - config_dump(conf2, &dump2, &dump2Len) - - expect(config_needs_push(conf2)).to(beFalse()) - expect(config_needs_dump(conf2)).to(beFalse()) - - let pushData8: UnsafeMutablePointer = config_push(conf2) - expect(pushData8.pointee.seqno).to(equal(2)) - config_confirm_pushed(conf2, pushData8.pointee.seqno, &cFakeHash2) - expect(config_needs_dump(conf2)).to(beFalse()) - - var mergeHashes1: [UnsafePointer?] = [cFakeHash2].unsafeCopy() - var mergeData1: [UnsafePointer?] = [UnsafePointer(pushData8.pointee.config)] - var mergeSize1: [Int] = [pushData8.pointee.config_len] - expect(config_merge(conf, &mergeHashes1, &mergeData1, &mergeSize1, 1)).to(equal(1)) - pushData8.deallocate() - - var cCommunity3BaseUrl: [CChar] = "http://example.org:5678".cArray.nullTerminated() - var cCommunity3Room: [CChar] = "SudokuRoom".cArray.nullTerminated() - var community3: ugroups_community_info = ugroups_community_info() - expect(user_groups_get_community(conf, &community3, &cCommunity3BaseUrl, &cCommunity3Room)) - .to(beTrue()) - expect(String(libSessionVal: community3.room)).to(equal("sudokuRoom")) // We picked up the capitalization change - - expect(user_groups_size(conf)).to(equal(2)) - expect(user_groups_size_communities(conf)).to(equal(1)) - expect(user_groups_size_legacy_groups(conf)).to(equal(1)) - - let legacyGroup5: UnsafeMutablePointer? = user_groups_get_legacy_group(conf2, &cDefinitelyRealId) - expect(ugroups_legacy_member_add(legacyGroup5, &cUsers[4], false)).to(beTrue()) - expect(ugroups_legacy_member_add(legacyGroup5, &cUsers[5], true)).to(beTrue()) - expect(ugroups_legacy_member_add(legacyGroup5, &cUsers[6], true)).to(beTrue()) - expect(ugroups_legacy_member_remove(legacyGroup5, &cUsers[1])).to(beTrue()) - - expect(config_needs_push(conf2)).to(beFalse()) - expect(config_needs_dump(conf2)).to(beFalse()) - - let pushData9: UnsafeMutablePointer = config_push(conf2) - expect(pushData9.pointee.seqno).to(equal(2)) - expect(config_needs_dump(conf2)).to(beFalse()) - pushData9.deallocate() - - user_groups_set_free_legacy_group(conf2, legacyGroup5) - expect(config_needs_push(conf2)).to(beTrue()) - expect(config_needs_dump(conf2)).to(beTrue()) - - var cCommunity4BaseUrl: [CChar] = "http://exAMple.ORG:5678".cArray.nullTerminated() - var cCommunity4Room: [CChar] = "sudokuROOM".cArray.nullTerminated() - user_groups_erase_community(conf2, &cCommunity4BaseUrl, &cCommunity4Room) - - let fakeHash3: String = "fakehash3" - var cFakeHash3: [CChar] = fakeHash3.cArray.nullTerminated() - let pushData10: UnsafeMutablePointer = config_push(conf2) - config_confirm_pushed(conf2, pushData10.pointee.seqno, &cFakeHash3) - - expect(pushData10.pointee.seqno).to(equal(3)) - expect([String](pointer: pushData10.pointee.obsolete, count: pushData10.pointee.obsolete_len)) - .to(equal([fakeHash2])) - - let currentHashes4: UnsafeMutablePointer? = config_current_hashes(conf2) - expect([String](pointer: currentHashes4?.pointee.value, count: currentHashes4?.pointee.len)) - .to(equal([fakeHash3])) - currentHashes4?.deallocate() - - var mergeHashes2: [UnsafePointer?] = [cFakeHash3].unsafeCopy() - var mergeData2: [UnsafePointer?] = [UnsafePointer(pushData10.pointee.config)] - var mergeSize2: [Int] = [pushData10.pointee.config_len] - expect(config_merge(conf, &mergeHashes2, &mergeData2, &mergeSize2, 1)).to(equal(1)) - - expect(user_groups_size(conf)).to(equal(1)) - expect(user_groups_size_communities(conf)).to(equal(0)) - expect(user_groups_size_legacy_groups(conf)).to(equal(1)) - - var prio: Int32 = 0 - var cBeanstalkBaseUrl: [CChar] = "http://jacksbeanstalk.org".cArray.nullTerminated() - var cBeanstalkPubkey: [UInt8] = Data( - hex: "0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff" - ).cArray - - ["fee", "fi", "fo", "fum"].forEach { room in - var cRoom: [CChar] = room.cArray.nullTerminated() - prio += 1 - var community4: ugroups_community_info = ugroups_community_info() - expect(user_groups_get_or_construct_community(conf, &community4, &cBeanstalkBaseUrl, &cRoom, &cBeanstalkPubkey)) + // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately + let groupSeed: Data = Data(hex: "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff") + let groupEd25519KeyPair = Sodium().sign.keyPair(seed: groupSeed.bytes)! + let groupX25519PublicKey = Sodium().sign.toX25519(ed25519PublicKey: groupEd25519KeyPair.publicKey)! + + // Note: this isn't exactly what Session actually does here for legacy closed + // groups (rather it uses X25519 keys) but for this test the distinction doesn't matter. + legacyGroup2.pointee.enc_pubkey = Data(groupX25519PublicKey).toLibSession() + legacyGroup2.pointee.enc_seckey = Data(groupEd25519KeyPair.secretKey).toLibSession() + legacyGroup2.pointee.priority = 3 + + expect(Data(libSessionVal: legacyGroup2.pointee.enc_pubkey, count: 32).toHexString()) + .to(equal("c5ba413c336f2fe1fb9a2c525f8a86a412a1db128a7841b4e0e217fa9eb7fd5e")) + expect(Data(libSessionVal: legacyGroup2.pointee.enc_seckey, count: 32).toHexString()) + .to(equal("00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff")) + + // The new data doesn't get stored until we call this: + user_groups_set_free_legacy_group(conf, legacyGroup2) + + let legacyGroup3: UnsafeMutablePointer? = user_groups_get_legacy_group(conf, &cDefinitelyRealId) + expect(legacyGroup3?.pointee).toNot(beNil()) + expect(config_needs_push(conf)).to(beTrue()) + expect(config_needs_dump(conf)).to(beTrue()) + ugroups_legacy_group_free(legacyGroup3) + + let communityPubkey: String = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + var cCommunityPubkey: [UInt8] = Data(hex: communityPubkey).cArray + var cCommunityBaseUrl: [CChar] = "http://Example.ORG:5678".cArray.nullTerminated() + var cCommunityRoom: [CChar] = "SudokuRoom".cArray.nullTerminated() + var community1: ugroups_community_info = ugroups_community_info() + expect(user_groups_get_or_construct_community(conf, &community1, &cCommunityBaseUrl, &cCommunityRoom, &cCommunityPubkey)) .to(beTrue()) - community4.priority = prio - user_groups_set_community(conf, &community4) - } - - expect(user_groups_size(conf)).to(equal(5)) - expect(user_groups_size_communities(conf)).to(equal(4)) - expect(user_groups_size_legacy_groups(conf)).to(equal(1)) - - let fakeHash4: String = "fakehash4" - var cFakeHash4: [CChar] = fakeHash4.cArray.nullTerminated() - let pushData11: UnsafeMutablePointer = config_push(conf) - config_confirm_pushed(conf, pushData11.pointee.seqno, &cFakeHash4) - expect(pushData11.pointee.seqno).to(equal(4)) - expect([String](pointer: pushData11.pointee.obsolete, count: pushData11.pointee.obsolete_len)) - .to(equal([fakeHash3, fakeHash2, fakeHash1])) - - // Load some obsolete ones in just to check that they get immediately obsoleted - let fakeHash10: String = "fakehash10" - let cFakeHash10: [CChar] = fakeHash10.cArray.nullTerminated() - let fakeHash11: String = "fakehash11" - let cFakeHash11: [CChar] = fakeHash11.cArray.nullTerminated() - let fakeHash12: String = "fakehash12" - let cFakeHash12: [CChar] = fakeHash12.cArray.nullTerminated() - var mergeHashes3: [UnsafePointer?] = [cFakeHash10, cFakeHash11, cFakeHash12, cFakeHash4].unsafeCopy() - var mergeData3: [UnsafePointer?] = [ - UnsafePointer(pushData10.pointee.config), - UnsafePointer(pushData2.pointee.config), - UnsafePointer(pushData7.pointee.config), - UnsafePointer(pushData11.pointee.config) - ] - var mergeSize3: [Int] = [ - pushData10.pointee.config_len, - pushData2.pointee.config_len, - pushData7.pointee.config_len, - pushData11.pointee.config_len - ] - expect(config_merge(conf2, &mergeHashes3, &mergeData3, &mergeSize3, 4)).to(equal(4)) - expect(config_needs_dump(conf2)).to(beTrue()) - expect(config_needs_push(conf2)).to(beFalse()) - pushData2.deallocate() - pushData7.deallocate() - pushData10.deallocate() - pushData11.deallocate() - - let currentHashes5: UnsafeMutablePointer? = config_current_hashes(conf2) - expect([String](pointer: currentHashes5?.pointee.value, count: currentHashes5?.pointee.len)) - .to(equal([fakeHash4])) - currentHashes5?.deallocate() - - let pushData12: UnsafeMutablePointer = config_push(conf2) - expect(pushData12.pointee.seqno).to(equal(4)) - expect([String](pointer: pushData12.pointee.obsolete, count: pushData12.pointee.obsolete_len)) - .to(equal([fakeHash11, fakeHash12, fakeHash10, fakeHash3])) - pushData12.deallocate() - - for targetConf in [conf, conf2] { - // Iterate through and make sure we got everything we expected - var seen: [String] = [] - var c1: ugroups_legacy_group_info = ugroups_legacy_group_info() - var c2: ugroups_community_info = ugroups_community_info() - let it: OpaquePointer = user_groups_iterator_new(targetConf) + expect(String(libSessionVal: community1.base_url)).to(equal("http://example.org:5678")) // Note: lower-case + expect(String(libSessionVal: community1.room)).to(equal("SudokuRoom")) // Note: case-preserving + expect(Data(libSessionVal: community1.pubkey, count: 32).toHexString()) + .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) + community1.priority = 14 - while !user_groups_iterator_done(it) { - if user_groups_it_is_legacy_group(it, &c1) { - var memberCount: Int = 0 - var adminCount: Int = 0 - ugroups_legacy_members_count(&c1, &memberCount, &adminCount) - - seen.append("legacy: \(String(libSessionVal: c1.name)), \(adminCount) admins, \(memberCount) members") - } - else if user_groups_it_is_community(it, &c2) { - seen.append("community: \(String(libSessionVal: c2.base_url))/r/\(String(libSessionVal: c2.room))") - } - else { - seen.append("unknown") - } - - user_groups_iterator_advance(it) + // The new data doesn't get stored until we call this: + user_groups_set_community(conf, &community1) + + // incremented since we made changes (this only increments once between + // dumps; even though we changed two fields here). + let pushData2: UnsafeMutablePointer = config_push(conf) + expect(pushData2.pointee.seqno).to(equal(1)) + expect([String](pointer: pushData2.pointee.obsolete, count: pushData2.pointee.obsolete_len)) + .to(beEmpty()) + + // Pretend we uploaded it + let fakeHash1: String = "fakehash1" + var cFakeHash1: [CChar] = fakeHash1.cArray.nullTerminated() + config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash1) + expect(config_needs_dump(conf)).to(beTrue()) + expect(config_needs_push(conf)).to(beFalse()) + + var dump1: UnsafeMutablePointer? = nil + var dump1Len: Int = 0 + config_dump(conf, &dump1, &dump1Len) + + let error2: UnsafeMutablePointer? = nil + var conf2: UnsafeMutablePointer? = nil + expect(user_groups_init(&conf2, &edSK, dump1, dump1Len, error2)).to(equal(0)) + error2?.deallocate() + dump1?.deallocate() + + expect(config_needs_dump(conf)).to(beFalse()) // Because we just called dump() above, to load up conf2 + expect(config_needs_push(conf)).to(beFalse()) + + let pushData3: UnsafeMutablePointer = config_push(conf) + expect(pushData3.pointee.seqno).to(equal(1)) + expect([String](pointer: pushData3.pointee.obsolete, count: pushData3.pointee.obsolete_len)) + .to(beEmpty()) + pushData3.deallocate() + + let currentHashes1: UnsafeMutablePointer? = config_current_hashes(conf) + expect([String](pointer: currentHashes1?.pointee.value, count: currentHashes1?.pointee.len)) + .to(equal(["fakehash1"])) + currentHashes1?.deallocate() + + expect(config_needs_push(conf2)).to(beFalse()) + expect(config_needs_dump(conf2)).to(beFalse()) + + let pushData4: UnsafeMutablePointer = config_push(conf2) + expect(pushData4.pointee.seqno).to(equal(1)) + expect(config_needs_dump(conf2)).to(beFalse()) + expect([String](pointer: pushData4.pointee.obsolete, count: pushData4.pointee.obsolete_len)) + .to(beEmpty()) + pushData4.deallocate() + + let currentHashes2: UnsafeMutablePointer? = config_current_hashes(conf2) + expect([String](pointer: currentHashes2?.pointee.value, count: currentHashes2?.pointee.len)) + .to(equal(["fakehash1"])) + currentHashes2?.deallocate() + + expect(user_groups_size(conf2)).to(equal(2)) + expect(user_groups_size_communities(conf2)).to(equal(1)) + expect(user_groups_size_legacy_groups(conf2)).to(equal(1)) + + let legacyGroup4: UnsafeMutablePointer? = user_groups_get_legacy_group(conf2, &cDefinitelyRealId) + expect(legacyGroup4?.pointee).toNot(beNil()) + expect(String(libSessionVal: legacyGroup4?.pointee.enc_pubkey, fixedLength: 32)).to(equal("")) + expect(String(libSessionVal: legacyGroup4?.pointee.enc_seckey, fixedLength: 32)).to(equal("")) + expect(legacyGroup4?.pointee.disappearing_timer).to(equal(60)) + expect(String(libSessionVal: legacyGroup4?.pointee.session_id)).to(equal(definitelyRealId)) + expect(legacyGroup4?.pointee.priority).to(equal(3)) + expect(String(libSessionVal: legacyGroup4?.pointee.name)).to(equal("Englishmen")) + expect(legacyGroup4?.pointee.joined_at).to(equal(createdTs)) + expect(legacyGroup2.pointee.notifications).to(equal(CONVO_NOTIFY_ALL)) + expect(legacyGroup2.pointee.mute_until).to(equal(nowTs + 3600)) + + var membersSeen3: [String: Bool] = [:] + var memberSessionId3: UnsafePointer? = nil + var memberAdmin3: Bool = false + let membersIt3: OpaquePointer = ugroups_legacy_members_begin(legacyGroup4) + + while ugroups_legacy_members_next(membersIt3, &memberSessionId3, &memberAdmin3) { + membersSeen3[String(cString: memberSessionId3!)] = memberAdmin3 } - user_groups_iterator_free(it) + ugroups_legacy_members_free(membersIt3) + ugroups_legacy_group_free(legacyGroup4) - expect(seen).to(equal([ - "community: http://jacksbeanstalk.org/r/fee", - "community: http://jacksbeanstalk.org/r/fi", - "community: http://jacksbeanstalk.org/r/fo", - "community: http://jacksbeanstalk.org/r/fum", - "legacy: Englishmen, 3 admins, 2 members" + expect(membersSeen3).to(equal([ + "050000000000000000000000000000000000000000000000000000000000000000": false, + "051111111111111111111111111111111111111111111111111111111111111111": false, + "052222222222222222222222222222222222222222222222222222222222222222": true ])) + + expect(config_needs_push(conf2)).to(beFalse()) + expect(config_needs_dump(conf2)).to(beFalse()) + + let pushData5: UnsafeMutablePointer = config_push(conf2) + expect(pushData5.pointee.seqno).to(equal(1)) + expect(config_needs_dump(conf2)).to(beFalse()) + pushData5.deallocate() + + for targetConf in [conf, conf2] { + // Iterate through and make sure we got everything we expected + var seen: [String] = [] + + var c1: ugroups_legacy_group_info = ugroups_legacy_group_info() + var c2: ugroups_community_info = ugroups_community_info() + let it: OpaquePointer = user_groups_iterator_new(targetConf) + + while !user_groups_iterator_done(it) { + if user_groups_it_is_legacy_group(it, &c1) { + var memberCount: Int = 0 + var adminCount: Int = 0 + ugroups_legacy_members_count(&c1, &memberCount, &adminCount) + seen.append("legacy: \(String(libSessionVal: c1.name)), \(adminCount) admins, \(memberCount) members") + } + else if user_groups_it_is_community(it, &c2) { + seen.append("community: \(String(libSessionVal: c2.base_url))/r/\(String(libSessionVal: c2.room))") + } + else { + seen.append("unknown") + } + + user_groups_iterator_advance(it) + } + + user_groups_iterator_free(it) + + expect(seen).to(equal([ + "community: http://example.org:5678/r/SudokuRoom", + "legacy: Englishmen, 1 admins, 2 members" + ])) + } + + var cCommunity2BaseUrl: [CChar] = "http://example.org:5678".cArray.nullTerminated() + var cCommunity2Room: [CChar] = "sudokuRoom".cArray.nullTerminated() + var community2: ugroups_community_info = ugroups_community_info() + expect(user_groups_get_community(conf2, &community2, &cCommunity2BaseUrl, &cCommunity2Room)) + .to(beTrue()) + expect(String(libSessionVal: community2.base_url)).to(equal("http://example.org:5678")) + expect(String(libSessionVal: community2.room)).to(equal("SudokuRoom")) // Case preserved from the stored value, not the input value + expect(Data(libSessionVal: community2.pubkey, count: 32).toHexString()) + .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) + expect(community2.priority).to(equal(14)) + + expect(config_needs_push(conf2)).to(beFalse()) + expect(config_needs_dump(conf2)).to(beFalse()) + + let pushData6: UnsafeMutablePointer = config_push(conf2) + expect(pushData6.pointee.seqno).to(equal(1)) + expect(config_needs_dump(conf2)).to(beFalse()) + pushData6.deallocate() + + community2.room = "sudokuRoom".toLibSession() // Change capitalization + user_groups_set_community(conf2, &community2) + + expect(config_needs_push(conf2)).to(beTrue()) + expect(config_needs_dump(conf2)).to(beTrue()) + + let fakeHash2: String = "fakehash2" + var cFakeHash2: [CChar] = fakeHash2.cArray.nullTerminated() + let pushData7: UnsafeMutablePointer = config_push(conf2) + expect(pushData7.pointee.seqno).to(equal(2)) + config_confirm_pushed(conf2, pushData7.pointee.seqno, &cFakeHash2) + expect([String](pointer: pushData7.pointee.obsolete, count: pushData7.pointee.obsolete_len)) + .to(equal([fakeHash1])) + + let currentHashes3: UnsafeMutablePointer? = config_current_hashes(conf2) + expect([String](pointer: currentHashes3?.pointee.value, count: currentHashes3?.pointee.len)) + .to(equal([fakeHash2])) + currentHashes3?.deallocate() + + var dump2: UnsafeMutablePointer? = nil + var dump2Len: Int = 0 + config_dump(conf2, &dump2, &dump2Len) + + expect(config_needs_push(conf2)).to(beFalse()) + expect(config_needs_dump(conf2)).to(beFalse()) + + let pushData8: UnsafeMutablePointer = config_push(conf2) + expect(pushData8.pointee.seqno).to(equal(2)) + config_confirm_pushed(conf2, pushData8.pointee.seqno, &cFakeHash2) + expect(config_needs_dump(conf2)).to(beFalse()) + + var mergeHashes1: [UnsafePointer?] = [cFakeHash2].unsafeCopy() + var mergeData1: [UnsafePointer?] = [UnsafePointer(pushData8.pointee.config)] + var mergeSize1: [Int] = [pushData8.pointee.config_len] + expect(config_merge(conf, &mergeHashes1, &mergeData1, &mergeSize1, 1)).to(equal(1)) + pushData8.deallocate() + + var cCommunity3BaseUrl: [CChar] = "http://example.org:5678".cArray.nullTerminated() + var cCommunity3Room: [CChar] = "SudokuRoom".cArray.nullTerminated() + var community3: ugroups_community_info = ugroups_community_info() + expect(user_groups_get_community(conf, &community3, &cCommunity3BaseUrl, &cCommunity3Room)) + .to(beTrue()) + expect(String(libSessionVal: community3.room)).to(equal("sudokuRoom")) // We picked up the capitalization change + + expect(user_groups_size(conf)).to(equal(2)) + expect(user_groups_size_communities(conf)).to(equal(1)) + expect(user_groups_size_legacy_groups(conf)).to(equal(1)) + + let legacyGroup5: UnsafeMutablePointer? = user_groups_get_legacy_group(conf2, &cDefinitelyRealId) + expect(ugroups_legacy_member_add(legacyGroup5, &cUsers[4], false)).to(beTrue()) + expect(ugroups_legacy_member_add(legacyGroup5, &cUsers[5], true)).to(beTrue()) + expect(ugroups_legacy_member_add(legacyGroup5, &cUsers[6], true)).to(beTrue()) + expect(ugroups_legacy_member_remove(legacyGroup5, &cUsers[1])).to(beTrue()) + + expect(config_needs_push(conf2)).to(beFalse()) + expect(config_needs_dump(conf2)).to(beFalse()) + + let pushData9: UnsafeMutablePointer = config_push(conf2) + expect(pushData9.pointee.seqno).to(equal(2)) + expect(config_needs_dump(conf2)).to(beFalse()) + pushData9.deallocate() + + user_groups_set_free_legacy_group(conf2, legacyGroup5) + expect(config_needs_push(conf2)).to(beTrue()) + expect(config_needs_dump(conf2)).to(beTrue()) + + var cCommunity4BaseUrl: [CChar] = "http://exAMple.ORG:5678".cArray.nullTerminated() + var cCommunity4Room: [CChar] = "sudokuROOM".cArray.nullTerminated() + user_groups_erase_community(conf2, &cCommunity4BaseUrl, &cCommunity4Room) + + let fakeHash3: String = "fakehash3" + var cFakeHash3: [CChar] = fakeHash3.cArray.nullTerminated() + let pushData10: UnsafeMutablePointer = config_push(conf2) + config_confirm_pushed(conf2, pushData10.pointee.seqno, &cFakeHash3) + + expect(pushData10.pointee.seqno).to(equal(3)) + expect([String](pointer: pushData10.pointee.obsolete, count: pushData10.pointee.obsolete_len)) + .to(equal([fakeHash2])) + + let currentHashes4: UnsafeMutablePointer? = config_current_hashes(conf2) + expect([String](pointer: currentHashes4?.pointee.value, count: currentHashes4?.pointee.len)) + .to(equal([fakeHash3])) + currentHashes4?.deallocate() + + var mergeHashes2: [UnsafePointer?] = [cFakeHash3].unsafeCopy() + var mergeData2: [UnsafePointer?] = [UnsafePointer(pushData10.pointee.config)] + var mergeSize2: [Int] = [pushData10.pointee.config_len] + expect(config_merge(conf, &mergeHashes2, &mergeData2, &mergeSize2, 1)).to(equal(1)) + + expect(user_groups_size(conf)).to(equal(1)) + expect(user_groups_size_communities(conf)).to(equal(0)) + expect(user_groups_size_legacy_groups(conf)).to(equal(1)) + + var prio: Int32 = 0 + var cBeanstalkBaseUrl: [CChar] = "http://jacksbeanstalk.org".cArray.nullTerminated() + var cBeanstalkPubkey: [UInt8] = Data( + hex: "0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff" + ).cArray + + ["fee", "fi", "fo", "fum"].forEach { room in + var cRoom: [CChar] = room.cArray.nullTerminated() + prio += 1 + + var community4: ugroups_community_info = ugroups_community_info() + expect(user_groups_get_or_construct_community(conf, &community4, &cBeanstalkBaseUrl, &cRoom, &cBeanstalkPubkey)) + .to(beTrue()) + community4.priority = prio + user_groups_set_community(conf, &community4) + } + + expect(user_groups_size(conf)).to(equal(5)) + expect(user_groups_size_communities(conf)).to(equal(4)) + expect(user_groups_size_legacy_groups(conf)).to(equal(1)) + + let fakeHash4: String = "fakehash4" + var cFakeHash4: [CChar] = fakeHash4.cArray.nullTerminated() + let pushData11: UnsafeMutablePointer = config_push(conf) + config_confirm_pushed(conf, pushData11.pointee.seqno, &cFakeHash4) + expect(pushData11.pointee.seqno).to(equal(4)) + expect([String](pointer: pushData11.pointee.obsolete, count: pushData11.pointee.obsolete_len)) + .to(equal([fakeHash3, fakeHash2, fakeHash1])) + + // Load some obsolete ones in just to check that they get immediately obsoleted + let fakeHash10: String = "fakehash10" + let cFakeHash10: [CChar] = fakeHash10.cArray.nullTerminated() + let fakeHash11: String = "fakehash11" + let cFakeHash11: [CChar] = fakeHash11.cArray.nullTerminated() + let fakeHash12: String = "fakehash12" + let cFakeHash12: [CChar] = fakeHash12.cArray.nullTerminated() + var mergeHashes3: [UnsafePointer?] = [cFakeHash10, cFakeHash11, cFakeHash12, cFakeHash4].unsafeCopy() + var mergeData3: [UnsafePointer?] = [ + UnsafePointer(pushData10.pointee.config), + UnsafePointer(pushData2.pointee.config), + UnsafePointer(pushData7.pointee.config), + UnsafePointer(pushData11.pointee.config) + ] + var mergeSize3: [Int] = [ + pushData10.pointee.config_len, + pushData2.pointee.config_len, + pushData7.pointee.config_len, + pushData11.pointee.config_len + ] + expect(config_merge(conf2, &mergeHashes3, &mergeData3, &mergeSize3, 4)).to(equal(4)) + expect(config_needs_dump(conf2)).to(beTrue()) + expect(config_needs_push(conf2)).to(beFalse()) + pushData2.deallocate() + pushData7.deallocate() + pushData10.deallocate() + pushData11.deallocate() + + let currentHashes5: UnsafeMutablePointer? = config_current_hashes(conf2) + expect([String](pointer: currentHashes5?.pointee.value, count: currentHashes5?.pointee.len)) + .to(equal([fakeHash4])) + currentHashes5?.deallocate() + + let pushData12: UnsafeMutablePointer = config_push(conf2) + expect(pushData12.pointee.seqno).to(equal(4)) + expect([String](pointer: pushData12.pointee.obsolete, count: pushData12.pointee.obsolete_len)) + .to(equal([fakeHash11, fakeHash12, fakeHash10, fakeHash3])) + pushData12.deallocate() + + for targetConf in [conf, conf2] { + // Iterate through and make sure we got everything we expected + var seen: [String] = [] + + var c1: ugroups_legacy_group_info = ugroups_legacy_group_info() + var c2: ugroups_community_info = ugroups_community_info() + let it: OpaquePointer = user_groups_iterator_new(targetConf) + + while !user_groups_iterator_done(it) { + if user_groups_it_is_legacy_group(it, &c1) { + var memberCount: Int = 0 + var adminCount: Int = 0 + ugroups_legacy_members_count(&c1, &memberCount, &adminCount) + + seen.append("legacy: \(String(libSessionVal: c1.name)), \(adminCount) admins, \(memberCount) members") + } + else if user_groups_it_is_community(it, &c2) { + seen.append("community: \(String(libSessionVal: c2.base_url))/r/\(String(libSessionVal: c2.room))") + } + else { + seen.append("unknown") + } + + user_groups_iterator_advance(it) + } + + user_groups_iterator_free(it) + + expect(seen).to(equal([ + "community: http://jacksbeanstalk.org/r/fee", + "community: http://jacksbeanstalk.org/r/fi", + "community: http://jacksbeanstalk.org/r/fo", + "community: http://jacksbeanstalk.org/r/fum", + "legacy: Englishmen, 3 admins, 2 members" + ])) + } } } } diff --git a/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserProfileSpec.swift b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserProfileSpec.swift index 15cafd306..ce0ba7a6e 100644 --- a/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserProfileSpec.swift +++ b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserProfileSpec.swift @@ -12,386 +12,388 @@ import Nimble /// This spec is designed to replicate the initial test cases for the libSession-util to ensure the behaviour matches class ConfigUserProfileSpec { // MARK: - Spec - + static func spec() { - it("generates UserProfile configs correctly") { - let seed: Data = Data(hex: "0123456789abcdef0123456789abcdef") - - // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately - let identity = try! Identity.generate(from: seed) - var edSK: [UInt8] = identity.ed25519KeyPair.secretKey - expect(edSK.toHexString().suffix(64)) - .to(equal("4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7")) - expect(identity.x25519KeyPair.publicKey.toHexString()) - .to(equal("d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72")) - expect(String(edSK.toHexString().prefix(32))).to(equal(seed.toHexString())) - - // Initialize a brand new, empty config because we have no dump data to deal with. - let error: UnsafeMutablePointer? = nil - var conf: UnsafeMutablePointer? = nil - expect(user_profile_init(&conf, &edSK, nil, 0, error)).to(equal(0)) - error?.deallocate() - - // We don't need to push anything, since this is an empty config - expect(config_needs_push(conf)).to(beFalse()) - // And we haven't changed anything so don't need to dump to db - expect(config_needs_dump(conf)).to(beFalse()) - - // Since it's empty there shouldn't be a name. - let namePtr: UnsafePointer? = user_profile_get_name(conf) - expect(namePtr).to(beNil()) - - // We don't need to push since we haven't changed anything, so this call is mainly just for - // testing: - let pushData1: UnsafeMutablePointer = config_push(conf) - expect(pushData1.pointee).toNot(beNil()) - expect(pushData1.pointee.seqno).to(equal(0)) - expect(pushData1.pointee.config_len).to(equal(256)) - - let encDomain: [CChar] = "UserProfile" - .bytes - .map { CChar(bitPattern: $0) } - expect(String(cString: config_encryption_domain(conf))).to(equal("UserProfile")) - - var toPushDecSize: Int = 0 - let toPushDecrypted: UnsafeMutablePointer? = config_decrypt(pushData1.pointee.config, pushData1.pointee.config_len, edSK, encDomain, &toPushDecSize) - let prefixPadding: String = (0..<193) - .map { _ in "\0" } - .joined() - expect(toPushDecrypted).toNot(beNil()) - expect(toPushDecSize).to(equal(216)) // 256 - 40 overhead - expect(String(pointer: toPushDecrypted, length: toPushDecSize)) - .to(equal("\(prefixPadding)d1:#i0e1:&de1:? = user_profile_get_name(conf) - expect(namePtr2).toNot(beNil()) - expect(String(cString: namePtr2!)).to(equal("Kallie")) - - let pic2: user_profile_pic = user_profile_get_pic(conf); - expect(String(libSessionVal: pic2.url)).to(equal("http://example.org/omg-pic-123.bmp")) - expect(Data(libSessionVal: pic2.key, count: ProfileManager.avatarAES256KeyByteLength)) - .to(equal("secret78901234567890123456789012".data(using: .utf8))) - expect(user_profile_get_nts_priority(conf)).to(equal(9)) - - // Since we've made changes, we should need to push new config to the swarm, *and* should need - // to dump the updated state: - expect(config_needs_push(conf)).to(beTrue()) - expect(config_needs_dump(conf)).to(beTrue()) - - // incremented since we made changes (this only increments once between - // dumps; even though we changed two fields here). - let pushData2: UnsafeMutablePointer = config_push(conf) - expect(pushData2.pointee.seqno).to(equal(1)) - - // Note: This hex value differs from the value in the library tests because - // it looks like the library has an "end of cell mark" character added at the - // end (0x07 or '0007') so we need to manually add it to work - let expHash0: [UInt8] = Data(hex: "ea173b57beca8af18c3519a7bbf69c3e7a05d1c049fa9558341d8ebb48b0c965") - .bytes - // The data to be actually pushed, expanded like this to make it somewhat human-readable: - let expPush1Decrypted: [UInt8] = [""" - d - 1:#i1e - 1:& d - 1:+ i9e - 1:n 6:Kallie - 1:p 34:http://example.org/omg-pic-123.bmp - 1:q 32:secret78901234567890123456789012 - e - 1:< l - l i0e 32: - """.removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) // For readability - .bytes, - expHash0, - """ - de e - e - 1:= d - 1:+ 0: - 1:n 0: - 1:p 0: - 1:q 0: - e - e - """.removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) // For readability - .bytes - ].flatMap { $0 } - let expPush1Encrypted: [UInt8] = Data(hex: [ - "9693a69686da3055f1ecdfb239c3bf8e746951a36d888c2fb7c02e856a5c2091b24e39a7e1af828f", - "1fa09fe8bf7d274afde0a0847ba143c43ffb8722301b5ae32e2f078b9a5e19097403336e50b18c84", - "aade446cd2823b011f97d6ad2116a53feb814efecc086bc172d31f4214b4d7c630b63bbe575b0868", - "2d146da44915063a07a78556ab5eff4f67f6aa26211e8d330b53d28567a931028c393709a325425d", - "e7486ccde24416a7fd4a8ba5fa73899c65f4276dfaddd5b2100adcf0f793104fb235b31ce32ec656", - "056009a9ebf58d45d7d696b74e0c7ff0499c4d23204976f19561dc0dba6dc53a2497d28ce03498ea", - "49bf122762d7bc1d6d9c02f6d54f8384" - ].joined()).bytes - - let pushData2Str: String = String(pointer: pushData2.pointee.config, length: pushData2.pointee.config_len, encoding: .ascii)! - let expPush1EncryptedStr: String = String(pointer: expPush1Encrypted, length: expPush1Encrypted.count, encoding: .ascii)! - expect(pushData2Str).to(equal(expPush1EncryptedStr)) - - // Raw decryption doesn't unpad (i.e. the padding is part of the encrypted data) - var pushData2DecSize: Int = 0 - let pushData2Decrypted: UnsafeMutablePointer? = config_decrypt( - pushData2.pointee.config, - pushData2.pointee.config_len, - edSK, - encDomain, - &pushData2DecSize - ) - let prefixPadding2: String = (0..<(256 - 40 - expPush1Decrypted.count)) - .map { _ in "\0" } - .joined() - expect(pushData2DecSize).to(equal(216)) // 256 - 40 overhead - - let pushData2DecryptedStr: String = String(pointer: pushData2Decrypted, length: pushData2DecSize, encoding: .ascii)! - let expPush1DecryptedStr: String = String(pointer: expPush1Decrypted, length: expPush1Decrypted.count, encoding: .ascii) - .map { "\(prefixPadding2)\($0)" }! - expect(pushData2DecryptedStr).to(equal(expPush1DecryptedStr)) - pushData2Decrypted?.deallocate() - - // We haven't dumped, so still need to dump: - expect(config_needs_dump(conf)).to(beTrue()) - // We did call push, but we haven't confirmed it as stored yet, so this will still return true: - expect(config_needs_push(conf)).to(beTrue()) - - var dump1: UnsafeMutablePointer? = nil - var dump1Len: Int = 0 - - config_dump(conf, &dump1, &dump1Len) - // (in a real client we'd now store this to disk) - - expect(config_needs_dump(conf)).to(beFalse()) - - let expDump1: [CChar] = [ - """ - d - 1:! i2e - 1:$ \(expPush1Decrypted.count): - """ - .removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) - .bytes - .map { CChar(bitPattern: $0) }, - expPush1Decrypted - .map { CChar(bitPattern: $0) }, - """ - 1:(0: - 1:)le - e - """.removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) + context("USER_PROFILE") { + it("generates config correctly") { + let seed: Data = Data(hex: "0123456789abcdef0123456789abcdef") + + // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately + let identity = try! Identity.generate(from: seed) + var edSK: [UInt8] = identity.ed25519KeyPair.secretKey + expect(edSK.toHexString().suffix(64)) + .to(equal("4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7")) + expect(identity.x25519KeyPair.publicKey.toHexString()) + .to(equal("d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72")) + expect(String(edSK.toHexString().prefix(32))).to(equal(seed.toHexString())) + + // Initialize a brand new, empty config because we have no dump data to deal with. + let error: UnsafeMutablePointer? = nil + var conf: UnsafeMutablePointer? = nil + expect(user_profile_init(&conf, &edSK, nil, 0, error)).to(equal(0)) + error?.deallocate() + + // We don't need to push anything, since this is an empty config + expect(config_needs_push(conf)).to(beFalse()) + // And we haven't changed anything so don't need to dump to db + expect(config_needs_dump(conf)).to(beFalse()) + + // Since it's empty there shouldn't be a name. + let namePtr: UnsafePointer? = user_profile_get_name(conf) + expect(namePtr).to(beNil()) + + // We don't need to push since we haven't changed anything, so this call is mainly just for + // testing: + let pushData1: UnsafeMutablePointer = config_push(conf) + expect(pushData1.pointee).toNot(beNil()) + expect(pushData1.pointee.seqno).to(equal(0)) + expect(pushData1.pointee.config_len).to(equal(256)) + + let encDomain: [CChar] = "UserProfile" .bytes .map { CChar(bitPattern: $0) } - ].flatMap { $0 } - expect(String(pointer: dump1, length: dump1Len, encoding: .ascii)) - .to(equal(String(pointer: expDump1, length: expDump1.count, encoding: .ascii))) - dump1?.deallocate() - - // So now imagine we got back confirmation from the swarm that the push has been stored: - let fakeHash1: String = "fakehash1" - var cFakeHash1: [CChar] = fakeHash1.cArray.nullTerminated() - config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash1) - pushData2.deallocate() - - expect(config_needs_push(conf)).to(beFalse()) - expect(config_needs_dump(conf)).to(beTrue()) // The confirmation changes state, so this makes us need a dump - - var dump2: UnsafeMutablePointer? = nil - var dump2Len: Int = 0 - config_dump(conf, &dump2, &dump2Len) - - let expDump2: [CChar] = [ - """ + expect(String(cString: config_encryption_domain(conf))).to(equal("UserProfile")) + + var toPushDecSize: Int = 0 + let toPushDecrypted: UnsafeMutablePointer? = config_decrypt(pushData1.pointee.config, pushData1.pointee.config_len, edSK, encDomain, &toPushDecSize) + let prefixPadding: String = (0..<193) + .map { _ in "\0" } + .joined() + expect(toPushDecrypted).toNot(beNil()) + expect(toPushDecSize).to(equal(216)) // 256 - 40 overhead + expect(String(pointer: toPushDecrypted, length: toPushDecSize)) + .to(equal("\(prefixPadding)d1:#i0e1:&de1:? = user_profile_get_name(conf) + expect(namePtr2).toNot(beNil()) + expect(String(cString: namePtr2!)).to(equal("Kallie")) + + let pic2: user_profile_pic = user_profile_get_pic(conf); + expect(String(libSessionVal: pic2.url)).to(equal("http://example.org/omg-pic-123.bmp")) + expect(Data(libSessionVal: pic2.key, count: ProfileManager.avatarAES256KeyByteLength)) + .to(equal("secret78901234567890123456789012".data(using: .utf8))) + expect(user_profile_get_nts_priority(conf)).to(equal(9)) + + // Since we've made changes, we should need to push new config to the swarm, *and* should need + // to dump the updated state: + expect(config_needs_push(conf)).to(beTrue()) + expect(config_needs_dump(conf)).to(beTrue()) + + // incremented since we made changes (this only increments once between + // dumps; even though we changed two fields here). + let pushData2: UnsafeMutablePointer = config_push(conf) + expect(pushData2.pointee.seqno).to(equal(1)) + + // Note: This hex value differs from the value in the library tests because + // it looks like the library has an "end of cell mark" character added at the + // end (0x07 or '0007') so we need to manually add it to work + let expHash0: [UInt8] = Data(hex: "ea173b57beca8af18c3519a7bbf69c3e7a05d1c049fa9558341d8ebb48b0c965") + .bytes + // The data to be actually pushed, expanded like this to make it somewhat human-readable: + let expPush1Decrypted: [UInt8] = [""" d - 1:! i0e - 1:$ \(expPush1Decrypted.count): + 1:#i1e + 1:& d + 1:+ i9e + 1:n 6:Kallie + 1:p 34:http://example.org/omg-pic-123.bmp + 1:q 32:secret78901234567890123456789012 + e + 1:< l + l i0e 32: + """.removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) // For readability + .bytes, + expHash0, """ - .removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) - .bytes - .map { CChar(bitPattern: $0) }, - expPush1Decrypted - .map { CChar(bitPattern: $0) }, - """ - 1:(9:fakehash1 - 1:)le + de e + e + 1:= d + 1:+ 0: + 1:n 0: + 1:p 0: + 1:q 0: + e e - """.removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) + """.removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) // For readability .bytes - .map { CChar(bitPattern: $0) } - ].flatMap { $0 } - expect(String(pointer: dump2, length: dump2Len, encoding: .ascii)) - .to(equal(String(pointer: expDump2, length: expDump2.count, encoding: .ascii))) - dump2?.deallocate() - expect(config_needs_dump(conf)).to(beFalse()) - - // Now we're going to set up a second, competing config object (in the real world this would be - // another Session client somewhere). - - // Start with an empty config, as above: - let error2: UnsafeMutablePointer? = nil - var conf2: UnsafeMutablePointer? = nil - expect(user_profile_init(&conf2, &edSK, nil, 0, error2)).to(equal(0)) - expect(config_needs_dump(conf2)).to(beFalse()) - error2?.deallocate() - - // Now imagine we just pulled down the `exp_push1` string from the swarm; we merge it into - // conf2: - var mergeHashes: [UnsafePointer?] = [cFakeHash1].unsafeCopy() - var mergeData: [UnsafePointer?] = [expPush1Encrypted].unsafeCopy() - var mergeSize: [Int] = [expPush1Encrypted.count] - expect(config_merge(conf2, &mergeHashes, &mergeData, &mergeSize, 1)).to(equal(1)) - mergeHashes.forEach { $0?.deallocate() } - mergeData.forEach { $0?.deallocate() } - - // Our state has changed, so we need to dump: - expect(config_needs_dump(conf2)).to(beTrue()) - var dump3: UnsafeMutablePointer? = nil - var dump3Len: Int = 0 - config_dump(conf2, &dump3, &dump3Len) - // (store in db) - dump3?.deallocate() - expect(config_needs_dump(conf2)).to(beFalse()) - - // We *don't* need to push: even though we updated, all we did is update to the merged data (and - // didn't have any sort of merge conflict needed): - expect(config_needs_push(conf2)).to(beFalse()) - - // Now let's create a conflicting update: - - // Change the name on both clients: - user_profile_set_name(conf, "Nibbler") - user_profile_set_name(conf2, "Raz") - - // And, on conf2, we're also going to change the profile pic: - let p2: user_profile_pic = user_profile_pic( - url: "http://new.example.com/pic".toLibSession(), - key: "qwert\0yuio1234567890123456789012".data(using: .utf8)!.toLibSession() - ) - user_profile_set_pic(conf2, p2) - - // Both have changes, so push need a push - expect(config_needs_push(conf)).to(beTrue()) - expect(config_needs_push(conf2)).to(beTrue()) - - let fakeHash2: String = "fakehash2" - var cFakeHash2: [CChar] = fakeHash2.cArray.nullTerminated() - let pushData3: UnsafeMutablePointer = config_push(conf) - expect(pushData3.pointee.seqno).to(equal(2)) // incremented, since we made a field change - config_confirm_pushed(conf, pushData3.pointee.seqno, &cFakeHash2) - - let fakeHash3: String = "fakehash3" - var cFakeHash3: [CChar] = fakeHash3.cArray.nullTerminated() - let pushData4: UnsafeMutablePointer = config_push(conf2) - expect(pushData4.pointee.seqno).to(equal(2)) // incremented, since we made a field change - config_confirm_pushed(conf, pushData4.pointee.seqno, &cFakeHash3) - - var dump4: UnsafeMutablePointer? = nil - var dump4Len: Int = 0 - config_dump(conf, &dump4, &dump4Len); - var dump5: UnsafeMutablePointer? = nil - var dump5Len: Int = 0 - config_dump(conf2, &dump5, &dump5Len); - // (store in db) - dump4?.deallocate() - dump5?.deallocate() - - // Since we set different things, we're going to get back different serialized data to be - // pushed: - let pushData3Str: String? = String(pointer: pushData3.pointee.config, length: pushData3.pointee.config_len, encoding: .ascii) - let pushData4Str: String? = String(pointer: pushData4.pointee.config, length: pushData4.pointee.config_len, encoding: .ascii) - expect(pushData3Str).toNot(equal(pushData4Str)) - - // Now imagine that each client pushed its `seqno=2` config to the swarm, but then each client - // also fetches new messages and pulls down the other client's `seqno=2` value. - - // Feed the new config into each other. (This array could hold multiple configs if we pulled - // down more than one). - var mergeHashes2: [UnsafePointer?] = [cFakeHash2].unsafeCopy() - var mergeData2: [UnsafePointer?] = [UnsafePointer(pushData3.pointee.config)] - var mergeSize2: [Int] = [pushData3.pointee.config_len] - expect(config_merge(conf2, &mergeHashes2, &mergeData2, &mergeSize2, 1)).to(equal(1)) - pushData3.deallocate() - var mergeHashes3: [UnsafePointer?] = [cFakeHash3].unsafeCopy() - var mergeData3: [UnsafePointer?] = [UnsafePointer(pushData4.pointee.config)] - var mergeSize3: [Int] = [pushData4.pointee.config_len] - expect(config_merge(conf, &mergeHashes3, &mergeData3, &mergeSize3, 1)).to(equal(1)) - pushData4.deallocate() - - // Now after the merge we *will* want to push from both client, since both will have generated a - // merge conflict update (with seqno = 3). - expect(config_needs_push(conf)).to(beTrue()) - expect(config_needs_push(conf2)).to(beTrue()) - let pushData5: UnsafeMutablePointer = config_push(conf) - let pushData6: UnsafeMutablePointer = config_push(conf2) - expect(pushData5.pointee.seqno).to(equal(3)) - expect(pushData6.pointee.seqno).to(equal(3)) - - // They should have resolved the conflict to the same thing: - expect(String(cString: user_profile_get_name(conf)!)).to(equal("Nibbler")) - expect(String(cString: user_profile_get_name(conf2)!)).to(equal("Nibbler")) - // (Note that they could have also both resolved to "Raz" here, but the hash of the serialized - // message just happens to have a higher hash -- and thus gets priority -- for this particular - // test). - - // Since only one of them set a profile pic there should be no conflict there: - let pic3: user_profile_pic = user_profile_get_pic(conf) - expect(pic3.url).toNot(beNil()) - expect(String(libSessionVal: pic3.url)).to(equal("http://new.example.com/pic")) - expect(pic3.key).toNot(beNil()) - expect(Data(libSessionVal: pic3.key, count: 32).toHexString()) - .to(equal("7177657274007975696f31323334353637383930313233343536373839303132")) - let pic4: user_profile_pic = user_profile_get_pic(conf2) - expect(pic4.url).toNot(beNil()) - expect(String(libSessionVal: pic4.url)).to(equal("http://new.example.com/pic")) - expect(pic4.key).toNot(beNil()) - expect(Data(libSessionVal: pic4.key, count: 32).toHexString()) - .to(equal("7177657274007975696f31323334353637383930313233343536373839303132")) - expect(user_profile_get_nts_priority(conf)).to(equal(9)) - expect(user_profile_get_nts_priority(conf2)).to(equal(9)) - - let fakeHash4: String = "fakehash4" - var cFakeHash4: [CChar] = fakeHash4.cArray.nullTerminated() - let fakeHash5: String = "fakehash5" - var cFakeHash5: [CChar] = fakeHash5.cArray.nullTerminated() - config_confirm_pushed(conf, pushData5.pointee.seqno, &cFakeHash4) - config_confirm_pushed(conf2, pushData6.pointee.seqno, &cFakeHash5) - pushData5.deallocate() - pushData6.deallocate() - - var dump6: UnsafeMutablePointer? = nil - var dump6Len: Int = 0 - config_dump(conf, &dump6, &dump6Len); - var dump7: UnsafeMutablePointer? = nil - var dump7Len: Int = 0 - config_dump(conf2, &dump7, &dump7Len); - // (store in db) - dump6?.deallocate() - dump7?.deallocate() - - expect(config_needs_dump(conf)).to(beFalse()) - expect(config_needs_dump(conf2)).to(beFalse()) - expect(config_needs_push(conf)).to(beFalse()) - expect(config_needs_push(conf2)).to(beFalse()) - - // Wouldn't do this in a normal session but doing it here to properly clean up - // after the test - conf?.deallocate() - conf2?.deallocate() + ].flatMap { $0 } + let expPush1Encrypted: [UInt8] = Data(hex: [ + "9693a69686da3055f1ecdfb239c3bf8e746951a36d888c2fb7c02e856a5c2091b24e39a7e1af828f", + "1fa09fe8bf7d274afde0a0847ba143c43ffb8722301b5ae32e2f078b9a5e19097403336e50b18c84", + "aade446cd2823b011f97d6ad2116a53feb814efecc086bc172d31f4214b4d7c630b63bbe575b0868", + "2d146da44915063a07a78556ab5eff4f67f6aa26211e8d330b53d28567a931028c393709a325425d", + "e7486ccde24416a7fd4a8ba5fa73899c65f4276dfaddd5b2100adcf0f793104fb235b31ce32ec656", + "056009a9ebf58d45d7d696b74e0c7ff0499c4d23204976f19561dc0dba6dc53a2497d28ce03498ea", + "49bf122762d7bc1d6d9c02f6d54f8384" + ].joined()).bytes + + let pushData2Str: String = String(pointer: pushData2.pointee.config, length: pushData2.pointee.config_len, encoding: .ascii)! + let expPush1EncryptedStr: String = String(pointer: expPush1Encrypted, length: expPush1Encrypted.count, encoding: .ascii)! + expect(pushData2Str).to(equal(expPush1EncryptedStr)) + + // Raw decryption doesn't unpad (i.e. the padding is part of the encrypted data) + var pushData2DecSize: Int = 0 + let pushData2Decrypted: UnsafeMutablePointer? = config_decrypt( + pushData2.pointee.config, + pushData2.pointee.config_len, + edSK, + encDomain, + &pushData2DecSize + ) + let prefixPadding2: String = (0..<(256 - 40 - expPush1Decrypted.count)) + .map { _ in "\0" } + .joined() + expect(pushData2DecSize).to(equal(216)) // 256 - 40 overhead + + let pushData2DecryptedStr: String = String(pointer: pushData2Decrypted, length: pushData2DecSize, encoding: .ascii)! + let expPush1DecryptedStr: String = String(pointer: expPush1Decrypted, length: expPush1Decrypted.count, encoding: .ascii) + .map { "\(prefixPadding2)\($0)" }! + expect(pushData2DecryptedStr).to(equal(expPush1DecryptedStr)) + pushData2Decrypted?.deallocate() + + // We haven't dumped, so still need to dump: + expect(config_needs_dump(conf)).to(beTrue()) + // We did call push, but we haven't confirmed it as stored yet, so this will still return true: + expect(config_needs_push(conf)).to(beTrue()) + + var dump1: UnsafeMutablePointer? = nil + var dump1Len: Int = 0 + + config_dump(conf, &dump1, &dump1Len) + // (in a real client we'd now store this to disk) + + expect(config_needs_dump(conf)).to(beFalse()) + + let expDump1: [CChar] = [ + """ + d + 1:! i2e + 1:$ \(expPush1Decrypted.count): + """ + .removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) + .bytes + .map { CChar(bitPattern: $0) }, + expPush1Decrypted + .map { CChar(bitPattern: $0) }, + """ + 1:(0: + 1:)le + e + """.removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) + .bytes + .map { CChar(bitPattern: $0) } + ].flatMap { $0 } + expect(String(pointer: dump1, length: dump1Len, encoding: .ascii)) + .to(equal(String(pointer: expDump1, length: expDump1.count, encoding: .ascii))) + dump1?.deallocate() + + // So now imagine we got back confirmation from the swarm that the push has been stored: + let fakeHash1: String = "fakehash1" + var cFakeHash1: [CChar] = fakeHash1.cArray.nullTerminated() + config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash1) + pushData2.deallocate() + + expect(config_needs_push(conf)).to(beFalse()) + expect(config_needs_dump(conf)).to(beTrue()) // The confirmation changes state, so this makes us need a dump + + var dump2: UnsafeMutablePointer? = nil + var dump2Len: Int = 0 + config_dump(conf, &dump2, &dump2Len) + + let expDump2: [CChar] = [ + """ + d + 1:! i0e + 1:$ \(expPush1Decrypted.count): + """ + .removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) + .bytes + .map { CChar(bitPattern: $0) }, + expPush1Decrypted + .map { CChar(bitPattern: $0) }, + """ + 1:(9:fakehash1 + 1:)le + e + """.removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) + .bytes + .map { CChar(bitPattern: $0) } + ].flatMap { $0 } + expect(String(pointer: dump2, length: dump2Len, encoding: .ascii)) + .to(equal(String(pointer: expDump2, length: expDump2.count, encoding: .ascii))) + dump2?.deallocate() + expect(config_needs_dump(conf)).to(beFalse()) + + // Now we're going to set up a second, competing config object (in the real world this would be + // another Session client somewhere). + + // Start with an empty config, as above: + let error2: UnsafeMutablePointer? = nil + var conf2: UnsafeMutablePointer? = nil + expect(user_profile_init(&conf2, &edSK, nil, 0, error2)).to(equal(0)) + expect(config_needs_dump(conf2)).to(beFalse()) + error2?.deallocate() + + // Now imagine we just pulled down the `exp_push1` string from the swarm; we merge it into + // conf2: + var mergeHashes: [UnsafePointer?] = [cFakeHash1].unsafeCopy() + var mergeData: [UnsafePointer?] = [expPush1Encrypted].unsafeCopy() + var mergeSize: [Int] = [expPush1Encrypted.count] + expect(config_merge(conf2, &mergeHashes, &mergeData, &mergeSize, 1)).to(equal(1)) + mergeHashes.forEach { $0?.deallocate() } + mergeData.forEach { $0?.deallocate() } + + // Our state has changed, so we need to dump: + expect(config_needs_dump(conf2)).to(beTrue()) + var dump3: UnsafeMutablePointer? = nil + var dump3Len: Int = 0 + config_dump(conf2, &dump3, &dump3Len) + // (store in db) + dump3?.deallocate() + expect(config_needs_dump(conf2)).to(beFalse()) + + // We *don't* need to push: even though we updated, all we did is update to the merged data (and + // didn't have any sort of merge conflict needed): + expect(config_needs_push(conf2)).to(beFalse()) + + // Now let's create a conflicting update: + + // Change the name on both clients: + user_profile_set_name(conf, "Nibbler") + user_profile_set_name(conf2, "Raz") + + // And, on conf2, we're also going to change the profile pic: + let p2: user_profile_pic = user_profile_pic( + url: "http://new.example.com/pic".toLibSession(), + key: "qwert\0yuio1234567890123456789012".data(using: .utf8)!.toLibSession() + ) + user_profile_set_pic(conf2, p2) + + // Both have changes, so push need a push + expect(config_needs_push(conf)).to(beTrue()) + expect(config_needs_push(conf2)).to(beTrue()) + + let fakeHash2: String = "fakehash2" + var cFakeHash2: [CChar] = fakeHash2.cArray.nullTerminated() + let pushData3: UnsafeMutablePointer = config_push(conf) + expect(pushData3.pointee.seqno).to(equal(2)) // incremented, since we made a field change + config_confirm_pushed(conf, pushData3.pointee.seqno, &cFakeHash2) + + let fakeHash3: String = "fakehash3" + var cFakeHash3: [CChar] = fakeHash3.cArray.nullTerminated() + let pushData4: UnsafeMutablePointer = config_push(conf2) + expect(pushData4.pointee.seqno).to(equal(2)) // incremented, since we made a field change + config_confirm_pushed(conf, pushData4.pointee.seqno, &cFakeHash3) + + var dump4: UnsafeMutablePointer? = nil + var dump4Len: Int = 0 + config_dump(conf, &dump4, &dump4Len); + var dump5: UnsafeMutablePointer? = nil + var dump5Len: Int = 0 + config_dump(conf2, &dump5, &dump5Len); + // (store in db) + dump4?.deallocate() + dump5?.deallocate() + + // Since we set different things, we're going to get back different serialized data to be + // pushed: + let pushData3Str: String? = String(pointer: pushData3.pointee.config, length: pushData3.pointee.config_len, encoding: .ascii) + let pushData4Str: String? = String(pointer: pushData4.pointee.config, length: pushData4.pointee.config_len, encoding: .ascii) + expect(pushData3Str).toNot(equal(pushData4Str)) + + // Now imagine that each client pushed its `seqno=2` config to the swarm, but then each client + // also fetches new messages and pulls down the other client's `seqno=2` value. + + // Feed the new config into each other. (This array could hold multiple configs if we pulled + // down more than one). + var mergeHashes2: [UnsafePointer?] = [cFakeHash2].unsafeCopy() + var mergeData2: [UnsafePointer?] = [UnsafePointer(pushData3.pointee.config)] + var mergeSize2: [Int] = [pushData3.pointee.config_len] + expect(config_merge(conf2, &mergeHashes2, &mergeData2, &mergeSize2, 1)).to(equal(1)) + pushData3.deallocate() + var mergeHashes3: [UnsafePointer?] = [cFakeHash3].unsafeCopy() + var mergeData3: [UnsafePointer?] = [UnsafePointer(pushData4.pointee.config)] + var mergeSize3: [Int] = [pushData4.pointee.config_len] + expect(config_merge(conf, &mergeHashes3, &mergeData3, &mergeSize3, 1)).to(equal(1)) + pushData4.deallocate() + + // Now after the merge we *will* want to push from both client, since both will have generated a + // merge conflict update (with seqno = 3). + expect(config_needs_push(conf)).to(beTrue()) + expect(config_needs_push(conf2)).to(beTrue()) + let pushData5: UnsafeMutablePointer = config_push(conf) + let pushData6: UnsafeMutablePointer = config_push(conf2) + expect(pushData5.pointee.seqno).to(equal(3)) + expect(pushData6.pointee.seqno).to(equal(3)) + + // They should have resolved the conflict to the same thing: + expect(String(cString: user_profile_get_name(conf)!)).to(equal("Nibbler")) + expect(String(cString: user_profile_get_name(conf2)!)).to(equal("Nibbler")) + // (Note that they could have also both resolved to "Raz" here, but the hash of the serialized + // message just happens to have a higher hash -- and thus gets priority -- for this particular + // test). + + // Since only one of them set a profile pic there should be no conflict there: + let pic3: user_profile_pic = user_profile_get_pic(conf) + expect(pic3.url).toNot(beNil()) + expect(String(libSessionVal: pic3.url)).to(equal("http://new.example.com/pic")) + expect(pic3.key).toNot(beNil()) + expect(Data(libSessionVal: pic3.key, count: 32).toHexString()) + .to(equal("7177657274007975696f31323334353637383930313233343536373839303132")) + let pic4: user_profile_pic = user_profile_get_pic(conf2) + expect(pic4.url).toNot(beNil()) + expect(String(libSessionVal: pic4.url)).to(equal("http://new.example.com/pic")) + expect(pic4.key).toNot(beNil()) + expect(Data(libSessionVal: pic4.key, count: 32).toHexString()) + .to(equal("7177657274007975696f31323334353637383930313233343536373839303132")) + expect(user_profile_get_nts_priority(conf)).to(equal(9)) + expect(user_profile_get_nts_priority(conf2)).to(equal(9)) + + let fakeHash4: String = "fakehash4" + var cFakeHash4: [CChar] = fakeHash4.cArray.nullTerminated() + let fakeHash5: String = "fakehash5" + var cFakeHash5: [CChar] = fakeHash5.cArray.nullTerminated() + config_confirm_pushed(conf, pushData5.pointee.seqno, &cFakeHash4) + config_confirm_pushed(conf2, pushData6.pointee.seqno, &cFakeHash5) + pushData5.deallocate() + pushData6.deallocate() + + var dump6: UnsafeMutablePointer? = nil + var dump6Len: Int = 0 + config_dump(conf, &dump6, &dump6Len); + var dump7: UnsafeMutablePointer? = nil + var dump7Len: Int = 0 + config_dump(conf2, &dump7, &dump7Len); + // (store in db) + dump6?.deallocate() + dump7?.deallocate() + + expect(config_needs_dump(conf)).to(beFalse()) + expect(config_needs_dump(conf2)).to(beFalse()) + expect(config_needs_push(conf)).to(beFalse()) + expect(config_needs_push(conf2)).to(beFalse()) + + // Wouldn't do this in a normal session but doing it here to properly clean up + // after the test + conf?.deallocate() + conf2?.deallocate() + } } } } diff --git a/SessionMessagingKitTests/LibSessionUtil/LibSessionSpec.swift b/SessionMessagingKitTests/LibSessionUtil/LibSessionSpec.swift index be670ad5d..42d0746d7 100644 --- a/SessionMessagingKitTests/LibSessionUtil/LibSessionSpec.swift +++ b/SessionMessagingKitTests/LibSessionUtil/LibSessionSpec.swift @@ -12,9 +12,11 @@ class LibSessionSpec: QuickSpec { // MARK: - Spec override func spec() { - ConfigContactsSpec.spec() - ConfigUserProfileSpec.spec() - ConfigConvoInfoVolatileSpec.spec() - ConfigUserGroupsSpec.spec() + describe("libSession") { + ConfigContactsSpec.spec() + ConfigUserProfileSpec.spec() + ConfigConvoInfoVolatileSpec.spec() + ConfigUserGroupsSpec.spec() + } } } diff --git a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift index da4ca0e8f..5d2b08362 100644 --- a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift +++ b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift @@ -53,12 +53,16 @@ class ThreadSettingsViewModelSpec: QuickSpec { try Profile( id: "05\(TestConstants.publicKey)", - name: "TestMe" + name: "TestMe", + lastNameUpdate: 0, + lastProfilePictureUpdate: 0 ).insert(db) try Profile( id: "TestId", - name: "TestUser" + name: "TestUser", + lastNameUpdate: 0, + lastProfilePictureUpdate: 0 ).insert(db) } viewModel = ThreadSettingsViewModel( diff --git a/SessionUIKit/Components/ConfirmationModal.swift b/SessionUIKit/Components/ConfirmationModal.swift index 27cf08520..a7f4088d7 100644 --- a/SessionUIKit/Components/ConfirmationModal.swift +++ b/SessionUIKit/Components/ConfirmationModal.swift @@ -4,15 +4,38 @@ import UIKit import SessionUtilitiesKit // FIXME: Refactor as part of the Groups Rebuild -public class ConfirmationModal: Modal { +public class ConfirmationModal: Modal, UITextFieldDelegate { private static let closeSize: CGFloat = 24 private var internalOnConfirm: ((ConfirmationModal) -> ())? = nil private var internalOnCancel: ((ConfirmationModal) -> ())? = nil private var internalOnBodyTap: (() -> ())? = nil + private var internalOnTextChanged: ((String) -> ())? = nil // MARK: - Components + private lazy var contentTapGestureRecognizer: UITapGestureRecognizer = { + let result: UITapGestureRecognizer = UITapGestureRecognizer( + target: self, + action: #selector(contentViewTapped) + ) + contentView.addGestureRecognizer(result) + result.isEnabled = false + + return result + }() + + private lazy var imageViewTapGestureRecognizer: UITapGestureRecognizer = { + let result: UITapGestureRecognizer = UITapGestureRecognizer( + target: self, + action: #selector(imageViewTapped) + ) + imageViewContainer.addGestureRecognizer(result) + result.isEnabled = false + + return result + }() + private lazy var titleLabel: UILabel = { let result: UILabel = UILabel() result.font = .boldSystemFont(ofSize: Values.mediumFontSize) @@ -36,16 +59,30 @@ public class ConfirmationModal: Modal { return result }() + private lazy var textFieldContainer: UIView = { + let result: UIView = UIView() + result.themeBorderColor = .borderSeparator + result.layer.cornerRadius = 11 + result.layer.borderWidth = 1 + result.isHidden = true + result.set(.height, to: 40) + + return result + }() + + private lazy var textField: UITextField = { + let result: UITextField = UITextField() + result.font = .systemFont(ofSize: Values.smallFontSize) + result.themeTextColor = .textPrimary + result.delegate = self + + return result + }() + private lazy var imageViewContainer: UIView = { let result: UIView = UIView() result.isHidden = true - let gestureRecogniser: UITapGestureRecognizer = UITapGestureRecognizer( - target: self, - action: #selector(imageViewTapped) - ) - result.addGestureRecognizer(gestureRecogniser) - return result }() @@ -70,7 +107,7 @@ public class ConfirmationModal: Modal { }() private lazy var contentStackView: UIStackView = { - let result = UIStackView(arrangedSubviews: [ titleLabel, explanationLabel, imageViewContainer ]) + let result = UIStackView(arrangedSubviews: [ titleLabel, explanationLabel, textFieldContainer, imageViewContainer ]) result.axis = .vertical result.spacing = Values.smallSpacing result.isLayoutMarginsRelativeArrangement = true @@ -132,13 +169,22 @@ public class ConfirmationModal: Modal { } public override func populateContentView() { + let gestureRecogniser: UITapGestureRecognizer = UITapGestureRecognizer( + target: self, + action: #selector(contentViewTapped) + ) + contentView.addGestureRecognizer(gestureRecogniser) + contentView.addSubview(mainStackView) contentView.addSubview(closeButton) + textFieldContainer.addSubview(textField) + textField.pin(to: textFieldContainer, withInset: 12) + imageViewContainer.addSubview(profileView) profileView.center(.horizontal, in: imageViewContainer) - profileView.pin(.top, to: .top, of: imageViewContainer)//, withInset: 15) - profileView.pin(.bottom, to: .bottom, of: imageViewContainer)//, withInset: -15) + profileView.pin(.top, to: .top, of: imageViewContainer) + profileView.pin(.bottom, to: .bottom, of: imageViewContainer) mainStackView.pin(to: contentView) closeButton.pin(.top, to: .top, of: contentView, withInset: 8) @@ -149,6 +195,7 @@ public class ConfirmationModal: Modal { public func updateContent(with info: Info) { internalOnBodyTap = nil + internalOnTextChanged = nil internalOnConfirm = { modal in if info.dismissOnConfirm { modal.close() @@ -161,6 +208,8 @@ public class ConfirmationModal: Modal { info.onCancel?(modal) } + contentTapGestureRecognizer.isEnabled = true + imageViewTapGestureRecognizer.isEnabled = false // Set the content based on the provided info titleLabel.text = info.title @@ -179,6 +228,15 @@ public class ConfirmationModal: Modal { explanationLabel.attributedText = attributedText explanationLabel.isHidden = false + case .input(let explanation, let placeholder, let value, let clearButton, let onTextChanged): + explanationLabel.attributedText = explanation + explanationLabel.isHidden = (explanation == nil) + textField.placeholder = placeholder + textField.text = (value ?? "") + textField.clearButtonMode = (clearButton ? .always : .never) + textFieldContainer.isHidden = false + internalOnTextChanged = onTextChanged + case .image(let placeholder, let value, let icon, let style, let accessibility, let onClick): imageViewContainer.isAccessibilityElement = (accessibility != nil) imageViewContainer.accessibilityIdentifier = accessibility?.identifier @@ -193,6 +251,8 @@ public class ConfirmationModal: Modal { ) ) internalOnBodyTap = onClick + contentTapGestureRecognizer.isEnabled = false + imageViewTapGestureRecognizer.isEnabled = true } confirmButton.accessibilityLabel = info.confirmAccessibility?.label @@ -216,8 +276,38 @@ public class ConfirmationModal: Modal { contentView.accessibilityIdentifier = info.accessibility?.identifier } + // MARK: - UITextFieldDelegate + + public func textFieldShouldReturn(_ textField: UITextField) -> Bool { + textField.resignFirstResponder() + return true + } + + public func textFieldShouldClear(_ textField: UITextField) -> Bool { + internalOnTextChanged?("") + return true + } + + public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + if let text: String = textField.text, let textRange: Range = Range(range, in: text) { + let updatedText = text.replacingCharacters(in: textRange, with: string) + + internalOnTextChanged?(updatedText) + } + + return true + } + // MARK: - Interaction + @objc private func contentViewTapped() { + if textField.isFirstResponder { + textField.resignFirstResponder() + } + + internalOnBodyTap?() + } + @objc private func imageViewTapped() { internalOnBodyTap?() } @@ -400,8 +490,14 @@ public extension ConfirmationModal.Info { case none case text(String) case attributedText(NSAttributedString) - // FIXME: Implement these - // case input(placeholder: String, value: String?) + case input( + explanation: NSAttributedString?, + placeholder: String, + initialValue: String?, + clearButton: Bool, + onChange: (String) -> () + ) + // FIXME: Implement this // case radio(explanation: NSAttributedString?, options: [(title: String, selected: Bool)]) case image( placeholderData: Data?, @@ -418,14 +514,15 @@ public extension ConfirmationModal.Info { case (.text(let lhsText), .text(let rhsText)): return (lhsText == rhsText) case (.attributedText(let lhsText), .attributedText(let rhsText)): return (lhsText == rhsText) - // FIXME: Implement these - //case (.input(let lhsPlaceholder, let lhsValue), .input(let rhsPlaceholder, let rhsValue)): - // return ( - // lhsPlaceholder == rhsPlaceholder && - // lhsValue == rhsValue && - // ) + case (.input(let lhsExplanation, let lhsPlaceholder, let lhsInitialValue, let lhsClearButton, _), .input(let rhsExplanation, let rhsPlaceholder, let rhsInitialValue, let rhsClearButton, _)): + return ( + lhsExplanation == rhsExplanation && + lhsPlaceholder == rhsPlaceholder && + lhsInitialValue == rhsInitialValue && + lhsClearButton == rhsClearButton + ) - // FIXME: Implement these + // FIXME: Implement this //case (.radio(let lhsExplanation, let lhsOptions), .radio(let rhsExplanation, let rhsOptions)): // return ( // lhsExplanation == rhsExplanation && @@ -450,6 +547,12 @@ public extension ConfirmationModal.Info { case .none: break case .text(let text): text.hash(into: &hasher) case .attributedText(let text): text.hash(into: &hasher) + + case .input(let explanation, let placeholder, let initialValue, let clearButton, _): + explanation.hash(into: &hasher) + placeholder.hash(into: &hasher) + initialValue.hash(into: &hasher) + clearButton.hash(into: &hasher) case .image(let placeholder, let value, let icon, let style, let accessibility, _): placeholder.hash(into: &hasher) diff --git a/SessionUtilitiesKit/Database/Storage.swift b/SessionUtilitiesKit/Database/Storage.swift index 4993be3f7..1a28da053 100644 --- a/SessionUtilitiesKit/Database/Storage.swift +++ b/SessionUtilitiesKit/Database/Storage.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import CryptoKit import Combine import GRDB import SignalCoreKit @@ -492,3 +493,38 @@ public extension ValueObservation { .eraseToAnyPublisher() } } + +// MARK: - Debug Convenience + +#if DEBUG +public extension Storage { + func exportInfo(password: String) throws -> (dbPath: String, keyPath: String) { + var keySpec: Data = try Storage.getOrGenerateDatabaseKeySpec() + defer { keySpec.resetBytes(in: 0.. #import #import +#import diff --git a/SessionUtilitiesKit/Utilities/CExceptionHelper.h b/SessionUtilitiesKit/Utilities/CExceptionHelper.h new file mode 100644 index 000000000..6d5cd8adc --- /dev/null +++ b/SessionUtilitiesKit/Utilities/CExceptionHelper.h @@ -0,0 +1,16 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +#ifndef __CExceptionHelper_h__ +#define __CExceptionHelper_h__ + +#import + +#define noEscape __attribute__((noescape)) + +@interface CExceptionHelper: NSObject + ++ (BOOL)performSafely:(noEscape void(^)(void))tryBlock error:(__autoreleasing NSError **)error; + +@end + +#endif diff --git a/SessionUtilitiesKit/Utilities/CExceptionHelper.mm b/SessionUtilitiesKit/Utilities/CExceptionHelper.mm new file mode 100644 index 000000000..fac2e007e --- /dev/null +++ b/SessionUtilitiesKit/Utilities/CExceptionHelper.mm @@ -0,0 +1,36 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. +// +// This logic is not foolproof and may result in memory-leaks, when possible we should look to remove this +// and use the native C++ <-> Swift interoperability coming with Swift 5.9 +// +// This solution was sourced from the following link, for more information please refer to this thread: +// https://forums.swift.org/t/pitch-a-swift-representation-for-thrown-and-caught-exceptions/54583 + +#import "CExceptionHelper.h" +#include + +@implementation CExceptionHelper + ++ (BOOL)performSafely:(noEscape void(^)(void))tryBlock error:(__autoreleasing NSError **)error { + try { + tryBlock(); + return YES; + } + catch(NSException* e) { + *error = [[NSError alloc] initWithDomain:e.name code:-1 userInfo:e.userInfo]; + return NO; + } + catch (std::exception& e) { + NSString* what = [NSString stringWithUTF8String: e.what()]; + NSDictionary* userInfo = @{NSLocalizedDescriptionKey : what}; + *error = [[NSError alloc] initWithDomain:@"cpp_exception" code:-2 userInfo:userInfo]; + return NO; + } + catch(...) { + NSDictionary* userInfo = @{NSLocalizedDescriptionKey:@"Other C++ exception"}; + *error = [[NSError alloc] initWithDomain:@"cpp_exception" code:-3 userInfo:userInfo]; + return NO; + } +} + +@end From f07313c7acfc63386719d76a13c3a1f954e82246 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Mon, 19 Jun 2023 18:19:47 +1000 Subject: [PATCH 091/135] Fixed a number of bugs found during internal testing Updated to the latest libSession to increase the available size for config message content (size check now happens after compression rather than before) Added some additional logs for config size info Fixed a bug where the database could be accessed before the migrations ran which could result in unexpected behaviours Fixed a bug where you couldn't mark a non one-to-one thread as read/unread Fixed a bug where a database initialization failure wouldn't result in a migration failure (user would be stuck on the splash screen indefinitely) Fixed a bug where if a message was too large for the screen the conversation would open it centered on the screen (now it will be positioned to the top) Started looking at broken unit tests Increased the build number --- LibSession-Util | 2 +- Session.xcodeproj/project.pbxproj | 38 +-- .../ConversationVC+Interaction.swift | 19 +- Session/Conversations/ConversationVC.swift | 242 +++++++++++------- .../Conversations/ConversationViewModel.swift | 30 ++- .../Message Cells/MessageCell.swift | 1 + .../Message Cells/UnreadMarkerCell.swift | 73 ++++++ .../Message Cells/VisibleMessageCell.swift | 2 +- ...ttomButton.swift => RoundIconButton.swift} | 35 +-- Session/Home/HomeVC.swift | 7 +- Session/Meta/AppDelegate.swift | 15 +- .../Translations/de.lproj/Localizable.strings | 2 + .../Translations/en.lproj/Localizable.strings | 2 + .../Translations/es.lproj/Localizable.strings | 2 + .../Translations/fa.lproj/Localizable.strings | 2 + .../Translations/fi.lproj/Localizable.strings | 2 + .../Translations/fr.lproj/Localizable.strings | 2 + .../Translations/hi.lproj/Localizable.strings | 2 + .../Translations/hr.lproj/Localizable.strings | 2 + .../id-ID.lproj/Localizable.strings | 2 + .../Translations/it.lproj/Localizable.strings | 2 + .../Translations/ja.lproj/Localizable.strings | 2 + .../Translations/nl.lproj/Localizable.strings | 2 + .../Translations/pl.lproj/Localizable.strings | 2 + .../pt_BR.lproj/Localizable.strings | 2 + .../Translations/ru.lproj/Localizable.strings | 2 + .../Translations/si.lproj/Localizable.strings | 2 + .../Translations/sk.lproj/Localizable.strings | 2 + .../Translations/sv.lproj/Localizable.strings | 2 + .../Translations/th.lproj/Localizable.strings | 2 + .../vi-VN.lproj/Localizable.strings | 2 + .../zh-Hant.lproj/Localizable.strings | 2 + .../zh_CN.lproj/Localizable.strings | 2 + Session/Shared/ScreenLockUI.swift | 4 +- Session/Utilities/MockDataGenerator.swift | 69 +---- .../Config Handling/SessionUtil+Shared.swift | 2 +- .../SessionUtil/SessionUtil.swift | 36 ++- .../Shared Models/MessageViewModel.swift | 1 + .../Configs/ConfigContactsSpec.swift | 109 +++++--- SessionUIKit/Style Guide/ThemeManager.swift | 6 +- .../Themes/Theme+ClassicDark.swift | 5 +- .../Themes/Theme+ClassicLight.swift | 5 +- .../Style Guide/Themes/Theme+OceanDark.swift | 5 +- .../Style Guide/Themes/Theme+OceanLight.swift | 5 +- SessionUIKit/Style Guide/Themes/Theme.swift | 3 + SessionUtilitiesKit/Database/Storage.swift | 21 +- .../Database/StorageError.swift | 1 + .../Database/Types/Migration.swift | 15 +- .../DatabaseMigrator+Utilities.swift | 9 +- .../Utilities/ARC4RandomNumberGenerator.swift | 73 ++++++ .../PersistableRecordUtilitiesSpec.swift | 3 +- .../ScreenLockViewController.swift | 2 +- 52 files changed, 596 insertions(+), 286 deletions(-) create mode 100644 Session/Conversations/Message Cells/UnreadMarkerCell.swift rename Session/Conversations/Views & Modals/{ScrollToBottomButton.swift => RoundIconButton.swift} (67%) create mode 100644 SessionUtilitiesKit/Utilities/ARC4RandomNumberGenerator.swift diff --git a/LibSession-Util b/LibSession-Util index 9777b37e8..49c78682a 160000 --- a/LibSession-Util +++ b/LibSession-Util @@ -1 +1 @@ -Subproject commit 9777b37e8545febcc082578341352dba7433db21 +Subproject commit 49c78682a6f4546c8773113f3e201244f0b1e65a diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 44dae29d6..6ef13920c 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -229,7 +229,7 @@ B88FA7FB26114EA70049422F /* Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = B88FA7FA26114EA70049422F /* Hex.swift */; }; B893063F2383961A005EAA8E /* ScanQRCodeWrapperVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B893063E2383961A005EAA8E /* ScanQRCodeWrapperVC.swift */; }; B894D0752339EDCF00B4D94D /* NukeDataModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B894D0742339EDCF00B4D94D /* NukeDataModal.swift */; }; - B897621C25D201F7004F83B2 /* ScrollToBottomButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B897621B25D201F7004F83B2 /* ScrollToBottomButton.swift */; }; + B897621C25D201F7004F83B2 /* RoundIconButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B897621B25D201F7004F83B2 /* RoundIconButton.swift */; }; B8B320B7258C30D70020074B /* HTMLMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B320B6258C30D70020074B /* HTMLMetadata.swift */; }; B8B558F126C4BB0600693325 /* CameraManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558F026C4BB0600693325 /* CameraManager.swift */; }; B8B558FF26C4E05E00693325 /* WebRTCSession+MessageHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B558FE26C4E05E00693325 /* WebRTCSession+MessageHandling.swift */; }; @@ -746,6 +746,8 @@ FD9004142818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9004132818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift */; }; FD9004152818B46300ABAAF6 /* JobRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B7432804EF1B004C14C5 /* JobRunner.swift */; }; FD9004162818B46700ABAAF6 /* JobRunnerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE77F68280F9EDA002CFC5D /* JobRunnerError.swift */; }; + FD97B2402A3FEB050027DD57 /* ARC4RandomNumberGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD97B23F2A3FEB050027DD57 /* ARC4RandomNumberGenerator.swift */; }; + FD97B2422A3FEBF30027DD57 /* UnreadMarkerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD97B2412A3FEBF30027DD57 /* UnreadMarkerCell.swift */; }; FD9B30F3293EA0BF008DEE3E /* BatchResponseSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9B30F2293EA0BF008DEE3E /* BatchResponseSpec.swift */; }; FDA1E83629A5748F00C5C3BD /* ConfigUserGroupsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA1E83529A5748F00C5C3BD /* ConfigUserGroupsSpec.swift */; }; FDA1E83929A5771A00C5C3BD /* LibSessionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA1E83829A5771A00C5C3BD /* LibSessionSpec.swift */; }; @@ -1361,7 +1363,7 @@ B88FA7FA26114EA70049422F /* Hex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Hex.swift; sourceTree = ""; }; B893063E2383961A005EAA8E /* ScanQRCodeWrapperVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanQRCodeWrapperVC.swift; sourceTree = ""; }; B894D0742339EDCF00B4D94D /* NukeDataModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NukeDataModal.swift; sourceTree = ""; }; - B897621B25D201F7004F83B2 /* ScrollToBottomButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollToBottomButton.swift; sourceTree = ""; }; + B897621B25D201F7004F83B2 /* RoundIconButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundIconButton.swift; sourceTree = ""; }; B8B320B6258C30D70020074B /* HTMLMetadata.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLMetadata.swift; sourceTree = ""; }; B8B558F026C4BB0600693325 /* CameraManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraManager.swift; sourceTree = ""; }; B8B558FE26C4E05E00693325 /* WebRTCSession+MessageHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebRTCSession+MessageHandling.swift"; sourceTree = ""; }; @@ -1795,6 +1797,7 @@ FD5C7304284F0FF30029977D /* MessageReceiver+VisibleMessages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+VisibleMessages.swift"; sourceTree = ""; }; FD5C7306284F103B0029977D /* MessageReceiver+MessageRequests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+MessageRequests.swift"; sourceTree = ""; }; FD5C7308285007920029977D /* BlindedIdLookup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlindedIdLookup.swift; sourceTree = ""; }; + FD5CE3442A3C5D96001A6DE3 /* DecryptExportedKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecryptExportedKey.swift; sourceTree = ""; }; FD5D201D27B0D87C00FEA984 /* SessionId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionId.swift; sourceTree = ""; }; FD6A7A682818BE7300035AC1 /* RetrieveDefaultOpenGroupRoomsJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetrieveDefaultOpenGroupRoomsJob.swift; sourceTree = ""; }; FD6A7A6A2818C17C00035AC1 /* UpdateProfilePictureJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateProfilePictureJob.swift; sourceTree = ""; }; @@ -1881,6 +1884,8 @@ FD8ECF912938552800C0D1BB /* Threading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Threading.swift; sourceTree = ""; }; FD8ECF93293856AF00C0D1BB /* Randomness.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Randomness.swift; sourceTree = ""; }; FD9004132818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_SetupStandardJobs.swift; sourceTree = ""; }; + FD97B23F2A3FEB050027DD57 /* ARC4RandomNumberGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ARC4RandomNumberGenerator.swift; sourceTree = ""; }; + FD97B2412A3FEBF30027DD57 /* UnreadMarkerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnreadMarkerCell.swift; sourceTree = ""; }; FD9B30F2293EA0BF008DEE3E /* BatchResponseSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchResponseSpec.swift; sourceTree = ""; }; FDA1E83529A5748F00C5C3BD /* ConfigUserGroupsSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigUserGroupsSpec.swift; sourceTree = ""; }; FDA1E83829A5771A00C5C3BD /* LibSessionSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibSessionSpec.swift; sourceTree = ""; }; @@ -2483,7 +2488,7 @@ B821493625D4D6A7009C0F2A /* Views & Modals */ = { isa = PBXGroup; children = ( - B897621B25D201F7004F83B2 /* ScrollToBottomButton.swift */, + B897621B25D201F7004F83B2 /* RoundIconButton.swift */, B82149C025D605C6009C0F2A /* InfoBanner.swift */, C374EEEA25DA3CA70073A857 /* ConversationTitleView.swift */, FD4B200D283492210034334B /* InsetLockableTableView.swift */, @@ -2519,6 +2524,7 @@ 7B0EFDEF275084AA00FFAAE7 /* CallMessageCell.swift */, B8041AA625C90927003C2166 /* TypingIndicatorCell.swift */, FDB7400C28EBEC240094D718 /* DateHeaderCell.swift */, + FD97B2412A3FEBF30027DD57 /* UnreadMarkerCell.swift */, ); path = "Message Cells"; sourceTree = ""; @@ -2958,7 +2964,6 @@ children = ( C33FD9B7255A54A300E217F9 /* Meta */, C36096ED25AD20FD008B62B2 /* Media Viewing & Editing */, - FD16AB5D2A1DD8E70083D849 /* Profile Pictures */, C36096EE25AD21BC008B62B2 /* Screen Lock */, C3851CD225624B060061EEB0 /* Shared Views */, C360970125AD22D3008B62B2 /* Shared View Controllers */, @@ -3592,6 +3597,7 @@ FD09796527F6B0A800936362 /* Utilities */ = { isa = PBXGroup; children = ( + FD97B23F2A3FEB050027DD57 /* ARC4RandomNumberGenerator.swift */, FDA8EB0F280F8238002B68E5 /* Codable+Utilities.swift */, FD3003692A3ADD6000B5A5FB /* CExceptionHelper.h */, FD30036D2A3AE26000B5A5FB /* CExceptionHelper.mm */, @@ -3635,13 +3641,6 @@ path = Models; sourceTree = ""; }; - FD16AB5D2A1DD8E70083D849 /* Profile Pictures */ = { - isa = PBXGroup; - children = ( - ); - path = "Profile Pictures"; - sourceTree = ""; - }; FD17D79427F3E03300122BE0 /* Migrations */ = { isa = PBXGroup; children = ( @@ -4289,6 +4288,7 @@ children = ( FDE7214F287E50D50093DF33 /* ProtoWrappers.py */, FDE72150287E50D50093DF33 /* LintLocalizableStrings.swift */, + FD5CE3442A3C5D96001A6DE3 /* DecryptExportedKey.swift */, ); path = Scripts; sourceTree = ""; @@ -5645,6 +5645,7 @@ FD17D7CA27F546D900122BE0 /* _001_InitialSetupMigration.swift in Sources */, C3D9E4D12567777D0040E4F3 /* OWSMediaUtils.swift in Sources */, C3BBE0AA2554D4DE0050F1E3 /* Dictionary+Utilities.swift in Sources */, + FD97B2402A3FEB050027DD57 /* ARC4RandomNumberGenerator.swift in Sources */, FD37EA1128AB34B3003AE748 /* TypedTableAlteration.swift in Sources */, FD30036E2A3AE26000B5A5FB /* CExceptionHelper.mm in Sources */, C3D9E4DA256778410040E4F3 /* UIImage+OWS.m in Sources */, @@ -5920,6 +5921,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + FD97B2422A3FEBF30027DD57 /* UnreadMarkerCell.swift in Sources */, FD52090928B59411006098F6 /* ScreenLockUI.swift in Sources */, 7B2561C429874851005C086C /* SessionCarouselView+Info.swift in Sources */, FDF2220B2818F38D000A4995 /* SessionApp.swift in Sources */, @@ -6103,7 +6105,7 @@ 7B4C75CD26BB92060000AC89 /* DeletedMessageView.swift in Sources */, FDD250722837234B00198BDA /* MediaGalleryNavigationController.swift in Sources */, FDD2506E283711D600198BDA /* DifferenceKit+Utilities.swift in Sources */, - B897621C25D201F7004F83B2 /* ScrollToBottomButton.swift in Sources */, + B897621C25D201F7004F83B2 /* RoundIconButton.swift in Sources */, 346B66311F4E29B200E5122F /* CropScaleImageViewController.swift in Sources */, FD1C98E4282E3C5B00B76F9E /* UINavigationBar+Utilities.swift in Sources */, C302093E25DCBF08001F572D /* MentionSelectionView.swift in Sources */, @@ -6393,7 +6395,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 407; + CURRENT_PROJECT_VERSION = 408; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6465,7 +6467,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 407; + CURRENT_PROJECT_VERSION = 408; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6530,7 +6532,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 407; + CURRENT_PROJECT_VERSION = 408; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6604,7 +6606,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 407; + CURRENT_PROJECT_VERSION = 408; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -7512,7 +7514,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 407; + CURRENT_PROJECT_VERSION = 408; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -7583,7 +7585,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 407; + CURRENT_PROJECT_VERSION = 408; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 715a4ab34..315480a08 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -16,7 +16,6 @@ extension ConversationVC: InputViewDelegate, MessageCellDelegate, ContextMenuActionDelegate, - ScrollToBottomButtonDelegate, SendMediaNavDelegate, UIDocumentPickerDelegate, AttachmentApprovalViewControllerDelegate, @@ -51,15 +50,6 @@ extension ConversationVC: navigationController?.pushViewController(viewController, animated: true) } - // MARK: - ScrollToBottomButtonDelegate - - func handleScrollToBottomButtonTapped() { - // The table view's content size is calculated by the estimated height of cells, - // so the result may be inaccurate before all the cells are loaded. Use this - // to scroll to the last row instead. - scrollToBottom(isAnimated: true) - } - // MARK: - Call @objc func startCall(_ sender: Any?) { @@ -858,10 +848,7 @@ extension ConversationVC: UIView.animate( withDuration: 0.25, - animations: { - self?.scrollButton.alpha = (self?.getScrollButtonOpacity() ?? 0) - self?.unreadCountView.alpha = (self?.scrollButton.alpha ?? 0) - }, + animations: { self?.updateScrollToBottom() }, completion: { _ in guard let contentOffset: CGPoint = self?.tableView.contentOffset else { return } @@ -1052,7 +1039,7 @@ extension ConversationVC: return } - self.scrollToInteractionIfNeeded(with: interactionInfo, highlight: true) + self.scrollToInteractionIfNeeded(with: interactionInfo, focusBehaviour: .highlight) } else if let linkPreview: LinkPreview = cellViewModel.linkPreview { switch linkPreview.variant { @@ -1725,7 +1712,7 @@ extension ConversationVC: func copy(_ cellViewModel: MessageViewModel) { switch cellViewModel.cellType { - case .typingIndicator, .dateHeader: break + case .typingIndicator, .dateHeader, .unreadMarker: break case .textOnlyMessage: if cellViewModel.body == nil, let linkPreview: LinkPreview = cellViewModel.linkPreview { diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index 37a218c4f..c52f8d1cb 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -26,6 +26,7 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers private var hasReloadedThreadDataAfterDisappearance: Bool = true var focusedInteractionInfo: Interaction.TimestampInfo? + var focusBehaviour: ConversationViewModel.FocusBehaviour = .none var shouldHighlightNextScrollToInteraction: Bool = false // Search @@ -157,6 +158,7 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers ) result.registerHeaderFooterView(view: UITableViewHeaderFooterView.self) result.register(view: DateHeaderCell.self) + result.register(view: UnreadMarkerCell.self) result.register(view: VisibleMessageCell.self) result.register(view: InfoMessageCell.self) result.register(view: TypingIndicatorCell.self) @@ -182,6 +184,7 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers result.set(.width, greaterThanOrEqualTo: ConversationVC.unreadCountViewSize) result.set(.height, to: ConversationVC.unreadCountViewSize) result.isHidden = true + result.alpha = 0 return result }() @@ -253,7 +256,20 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers return result }() - lazy var scrollButton: ScrollToBottomButton = ScrollToBottomButton(delegate: self) + lazy var scrollButton: RoundIconButton = { + let result: RoundIconButton = RoundIconButton( + image: UIImage(named: "ic_chevron_down")? + .withRenderingMode(.alwaysTemplate) + ) { [weak self] in + // The table view's content size is calculated by the estimated height of cells, + // so the result may be inaccurate before all the cells are loaded. Use this + // to scroll to the last row instead. + self?.scrollToBottom(isAnimated: true) + } + result.alpha = 0 + + return result + }() lazy var messageRequestBackgroundView: UIView = { let result: UIView = UIView() @@ -936,7 +952,6 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers .firstIndex(where: { item -> Bool in // Since the first item is probably a `DateHeaderCell` (which would likely // be removed when inserting items above it) we check if the id matches - // either the first or second item let messages: [MessageViewModel] = self.viewModel .interactionData[oldSectionIndex] .elements @@ -992,8 +1007,8 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers self?.searchController.resultsBar.stopLoading() self?.scrollToInteractionIfNeeded( with: focusedInteractionInfo, - isAnimated: true, - highlight: (self?.shouldHighlightNextScrollToInteraction == true) + focusBehaviour: (self?.shouldHighlightNextScrollToInteraction == true ? .highlight : .none), + isAnimated: true ) if wasLoadingMore { @@ -1020,8 +1035,7 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers } else { // Need to update the scroll button alpha in case new messages were added but we didn't scroll - self.scrollButton.alpha = self.getScrollButtonOpacity() - self.unreadCountView.alpha = self.scrollButton.alpha + self.updateScrollToBottom() } return } @@ -1070,8 +1084,8 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers self?.searchController.resultsBar.stopLoading() self?.scrollToInteractionIfNeeded( with: focusedInteractionInfo, - isAnimated: true, - highlight: (self?.shouldHighlightNextScrollToInteraction == true) + focusBehaviour: (self?.shouldHighlightNextScrollToInteraction == true ? .highlight : .none), + isAnimated: true ) } } @@ -1090,8 +1104,8 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers self?.searchController.resultsBar.stopLoading() self?.scrollToInteractionIfNeeded( with: focusedInteractionInfo, - isAnimated: true, - highlight: (self?.shouldHighlightNextScrollToInteraction == true) + focusBehaviour: (self?.shouldHighlightNextScrollToInteraction == true ? .highlight : .none), + isAnimated: true ) // Complete page loading @@ -1132,14 +1146,17 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers // When the unread message count is more than the number of view items of a page, // the screen will scroll to the bottom instead of the first unread message if let focusedInteractionInfo: Interaction.TimestampInfo = self.viewModel.focusedInteractionInfo { - self.scrollToInteractionIfNeeded(with: focusedInteractionInfo, isAnimated: false, highlight: true) + self.scrollToInteractionIfNeeded( + with: focusedInteractionInfo, + focusBehaviour: self.viewModel.focusBehaviour, + isAnimated: false + ) } else { self.scrollToBottom(isAnimated: false) } - self.scrollButton.alpha = self.getScrollButtonOpacity() - self.unreadCountView.alpha = self.scrollButton.alpha + self.updateScrollToBottom() self.hasPerformedInitialScroll = true // Now that the data has loaded we need to check if either of the "load more" sections are @@ -1327,10 +1344,7 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers self?.messageRequestsViewBotomConstraint?.constant = -(keyboardTop + 12) self?.tableView.contentInset = newContentInset self?.tableView.contentOffset.y = newContentOffsetY - - let scrollButtonOpacity: CGFloat = (self?.getScrollButtonOpacity() ?? 0) - self?.scrollButton.alpha = scrollButtonOpacity - self?.unreadCountView.alpha = scrollButtonOpacity + self?.updateScrollToBottom() self?.view.setNeedsLayout() self?.view.layoutIfNeeded() @@ -1372,10 +1386,7 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers animations: { [weak self] in self?.scrollButtonBottomConstraint?.constant = -(keyboardTop + 12) self?.messageRequestsViewBotomConstraint?.constant = -(keyboardTop + 12) - - let scrollButtonOpacity: CGFloat = (self?.getScrollButtonOpacity() ?? 0) - self?.scrollButton.alpha = scrollButtonOpacity - self?.unreadCountView.alpha = scrollButtonOpacity + self?.updateScrollToBottom() self?.view.setNeedsLayout() self?.view.layoutIfNeeded() @@ -1584,48 +1595,13 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers } func scrollViewDidScroll(_ scrollView: UIScrollView) { - self.scrollButton.alpha = self.getScrollButtonOpacity() - self.unreadCountView.alpha = self.scrollButton.alpha + self.updateScrollToBottom() // The initial scroll can trigger this logic but we already mark the initially focused message // as read so don't run the below until the user actually scrolls after the initial layout guard self.didFinishInitialLayout else { return } - // We want to mark messages as read while we scroll, so grab the newest message and mark - // everything older as read - // - // Note: For the 'tableVisualBottom' we remove the 'Values.mediumSpacing' as that is the distance - // the table content appears above the input view - let tableVisualBottom: CGFloat = (tableView.frame.maxY - (tableView.contentInset.bottom - Values.mediumSpacing)) - - if - let visibleIndexPaths: [IndexPath] = self.tableView.indexPathsForVisibleRows, - let messagesSection: Int = visibleIndexPaths - .first(where: { self.viewModel.interactionData[$0.section].model == .messages })? - .section, - let newestCellViewModel: MessageViewModel = visibleIndexPaths - .sorted() - .filter({ $0.section == messagesSection }) - .compactMap({ indexPath -> (frame: CGRect, cellViewModel: MessageViewModel)? in - guard let frame: CGRect = tableView.cellForRow(at: indexPath)?.frame else { - return nil - } - - return ( - view.convert(frame, from: tableView), - self.viewModel.interactionData[indexPath.section].elements[indexPath.row] - ) - }) - // Exclude messages that are partially off the bottom of the screen - .filter({ $0.frame.maxY <= tableVisualBottom }) - .last? - .cellViewModel - { - self.viewModel.markAsRead( - target: .threadAndInteractions(interactionsBeforeInclusive: newestCellViewModel.id), - timestampMs: newestCellViewModel.timestampMs - ) - } + self.markFullyVisibleAndOlderCellsAsRead(interactionInfo: nil) } func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { @@ -1634,12 +1610,16 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers self.shouldHighlightNextScrollToInteraction else { self.focusedInteractionInfo = nil + self.focusBehaviour = .none self.shouldHighlightNextScrollToInteraction = false return } + let behaviour: ConversationViewModel.FocusBehaviour = self.focusBehaviour + DispatchQueue.main.async { [weak self] in - self?.highlightCellIfNeeded(interactionId: focusedInteractionInfo.id) + self?.markFullyVisibleAndOlderCellsAsRead(interactionInfo: focusedInteractionInfo) + self?.highlightCellIfNeeded(interactionId: focusedInteractionInfo.id, behaviour: behaviour) } } @@ -1650,12 +1630,20 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers unreadCountLabel.font = .boldSystemFont(ofSize: fontSize) unreadCountView.isHidden = (unreadCount == 0) } - - func getScrollButtonOpacity() -> CGFloat { - let contentOffsetY = tableView.contentOffset.y + + public func updateScrollToBottom() { + // The initial scroll can trigger this logic but we already mark the initially focused message + // as read so don't run the below until the user actually scrolls after the initial layout + guard self.didFinishInitialLayout else { return } + + // Calculate the target opacity for the scroll button + let contentOffsetY: CGFloat = tableView.contentOffset.y let x = (lastPageTop - ConversationVC.bottomInset - contentOffsetY).clamp(0, .greatestFiniteMagnitude) let a = 1 / (ConversationVC.scrollButtonFullVisibilityThreshold - ConversationVC.scrollButtonNoVisibilityThreshold) - return max(0, min(1, a * x)) + let targetOpacity: CGFloat = max(0, min(1, a * x)) + + self.scrollButton.alpha = targetOpacity + self.unreadCountView.alpha = targetOpacity } // MARK: - Search @@ -1769,23 +1757,19 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers } func conversationSearchController(_ conversationSearchController: ConversationSearchController, didSelectInteractionInfo interactionInfo: Interaction.TimestampInfo) { - scrollToInteractionIfNeeded(with: interactionInfo, highlight: true) + scrollToInteractionIfNeeded(with: interactionInfo, focusBehaviour: .highlight) } func scrollToInteractionIfNeeded( with interactionInfo: Interaction.TimestampInfo, + focusBehaviour: ConversationViewModel.FocusBehaviour = .none, position: UITableView.ScrollPosition = .middle, isJumpingToLastInteraction: Bool = false, - isAnimated: Bool = true, - highlight: Bool = false + isAnimated: Bool = true ) { // Store the info incase we need to load more data (call will be re-triggered) self.focusedInteractionInfo = interactionInfo - self.shouldHighlightNextScrollToInteraction = highlight - self.viewModel.markAsRead( - target: .threadAndInteractions(interactionsBeforeInclusive: interactionInfo.id), - timestampMs: interactionInfo.timestampMs - ) + self.shouldHighlightNextScrollToInteraction = (focusBehaviour == .highlight) // Ensure the target interaction has been loaded guard @@ -1819,16 +1803,47 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers return } - let targetIndexPath: IndexPath = IndexPath( - row: targetMessageIndex, - section: messageSectionIndex - ) + // If it's before the initial layout and the index before the target is an 'UnreadMarker' then + // we should scroll to that instead (will be better UX) + let targetIndexPath: IndexPath = { + guard + !self.didFinishInitialLayout && + targetMessageIndex > 0 && + self.viewModel.interactionData[messageSectionIndex] + .elements[targetMessageIndex - 1] + .cellType == .unreadMarker + else { + return IndexPath( + row: targetMessageIndex, + section: messageSectionIndex + ) + } + + return IndexPath( + row: (targetMessageIndex - 1), + section: messageSectionIndex + ) + }() + let targetPosition: UITableView.ScrollPosition = { + guard position == .middle else { return position } + + // Make sure the target cell isn't too large for the screen (if it is then we want to scroll + // it to the top rather than the middle + let cellSize: CGSize = self.tableView( + tableView, + cellForRowAt: targetIndexPath + ).systemLayoutSizeFitting(view.bounds.size) + + guard cellSize.height > tableView.frame.size.height else { return position } + + return .top + }() // If we aren't animating or aren't highlighting then everything can be run immediately - guard isAnimated && highlight else { + guard isAnimated else { self.tableView.scrollToRow( at: targetIndexPath, - at: position, + at: targetPosition, animated: (self.didFinishInitialLayout && isAnimated) ) @@ -1837,16 +1852,17 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers // of messages) self.scrollViewDidScroll(self.tableView) - // If we haven't finished the initial layout then we want to delay the highlight slightly - // so it doesn't look buggy with the push transition - if highlight { - DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(self.didFinishInitialLayout ? 0 : 150)) { [weak self] in - self?.highlightCellIfNeeded(interactionId: interactionInfo.id) - } + // If we haven't finished the initial layout then we want to delay the highlight/markRead slightly + // so it doesn't look buggy with the push transition and we know for sure the correct visible cells + // have been loaded + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(self.didFinishInitialLayout ? 0 : 150)) { [weak self] in + self?.markFullyVisibleAndOlderCellsAsRead(interactionInfo: interactionInfo) + self?.highlightCellIfNeeded(interactionId: interactionInfo.id, behaviour: focusBehaviour) } self.shouldHighlightNextScrollToInteraction = false self.focusedInteractionInfo = nil + self.focusBehaviour = .none return } @@ -1857,16 +1873,70 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers let targetRect: CGRect = self.tableView.rectForRow(at: targetIndexPath) guard !self.tableView.bounds.contains(targetRect) else { - self.highlightCellIfNeeded(interactionId: interactionInfo.id) + self.markFullyVisibleAndOlderCellsAsRead(interactionInfo: interactionInfo) + self.highlightCellIfNeeded(interactionId: interactionInfo.id, behaviour: focusBehaviour) return } - self.tableView.scrollToRow(at: targetIndexPath, at: position, animated: true) + self.tableView.scrollToRow(at: targetIndexPath, at: targetPosition, animated: true) } - func highlightCellIfNeeded(interactionId: Int64) { + func markFullyVisibleAndOlderCellsAsRead(interactionInfo: Interaction.TimestampInfo?) { + // We want to mark messages as read on load and while we scroll, so grab the newest message and mark + // everything older as read + // + // Note: For the 'tableVisualBottom' we remove the 'Values.mediumSpacing' as that is the distance + // the table content appears above the input view + let tableVisualBottom: CGFloat = (tableView.frame.maxY - (tableView.contentInset.bottom - Values.mediumSpacing)) + + guard + let visibleIndexPaths: [IndexPath] = self.tableView.indexPathsForVisibleRows, + let messagesSection: Int = visibleIndexPaths + .first(where: { self.viewModel.interactionData[$0.section].model == .messages })? + .section, + let newestCellViewModel: MessageViewModel = visibleIndexPaths + .sorted() + .filter({ $0.section == messagesSection }) + .compactMap({ indexPath -> (frame: CGRect, cellViewModel: MessageViewModel)? in + guard let cell: VisibleMessageCell = tableView.cellForRow(at: indexPath) as? VisibleMessageCell else { + return nil + } + + return ( + view.convert(cell.frame, from: tableView), + self.viewModel.interactionData[indexPath.section].elements[indexPath.row] + ) + }) + // Exclude messages that are partially off the bottom of the screen + .filter({ $0.frame.maxY <= tableVisualBottom }) + .last? + .cellViewModel + else { + // If we weren't able to get any visible cells for some reason then we should fall back to + // marking the provided interactionInfo as read just in case + if let interactionInfo: Interaction.TimestampInfo = interactionInfo { + self.viewModel.markAsRead( + target: .threadAndInteractions(interactionsBeforeInclusive: interactionInfo.id), + timestampMs: interactionInfo.timestampMs + ) + } + return + } + + // Mark all interactions before the newest entirely-visible one as read + self.viewModel.markAsRead( + target: .threadAndInteractions(interactionsBeforeInclusive: newestCellViewModel.id), + timestampMs: newestCellViewModel.timestampMs + ) + } + + func highlightCellIfNeeded(interactionId: Int64, behaviour: ConversationViewModel.FocusBehaviour) { self.shouldHighlightNextScrollToInteraction = false self.focusedInteractionInfo = nil + self.focusBehaviour = .none + + // Only trigger the highlight if that's the desired behaviour + guard behaviour == .highlight else { return } // Trigger on the next run loop incase we are still finishing some other animation DispatchQueue.main.async { diff --git a/Session/Conversations/ConversationViewModel.swift b/Session/Conversations/ConversationViewModel.swift index 9b891ce49..b0cbbdf34 100644 --- a/Session/Conversations/ConversationViewModel.swift +++ b/Session/Conversations/ConversationViewModel.swift @@ -9,6 +9,13 @@ import SessionUtilitiesKit public class ConversationViewModel: OWSAudioPlayerDelegate { public typealias SectionModel = ArraySection + // MARK: - FocusBehaviour + + public enum FocusBehaviour { + case none + case highlight + } + // MARK: - Action public enum Action { @@ -35,6 +42,8 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { public var sentMessageBeforeUpdate: Bool = false public var lastSearchedText: String? public let focusedInteractionInfo: Interaction.TimestampInfo? // Note: This is used for global search + public let focusBehaviour: FocusBehaviour + private let initialUnreadInteractionId: Int64? public lazy var blockedBannerMessage: String = { switch self.threadData.threadVariant { @@ -116,6 +125,13 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { self.threadId = threadId self.initialThreadVariant = threadVariant self.focusedInteractionInfo = initialData?.targetInteractionInfo + self.focusBehaviour = (focusedInteractionInfo == nil ? .none : .highlight) + self.initialUnreadInteractionId = (focusedInteractionInfo == nil ? + // If we didn't provide a 'focusedInteractionInfo' then 'initialData?.targetInteractionInfo?.id' will be + // the oldest unread interaction + initialData?.targetInteractionInfo?.id : + nil + ) self.threadData = SessionThreadViewModel( threadId: threadId, threadVariant: threadVariant, @@ -321,6 +337,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { } private func process(data: [MessageViewModel], for pageInfo: PagedData.PageInfo) -> [SectionModel] { + let initialUnreadInteractionId: Int64? = self.initialUnreadInteractionId let typingIndicator: MessageViewModel? = data.first(where: { $0.isTypingIndicator == true }) let sortedData: [MessageViewModel] = data .filter { $0.isTypingIndicator != true } @@ -362,11 +379,20 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { ) } .reduce([]) { result, message in + let updatedResult: [MessageViewModel] = result + .appending(initialUnreadInteractionId == nil || message.id != initialUnreadInteractionId ? + nil : + MessageViewModel( + timestampMs: message.timestampMs, + cellType: .unreadMarker + ) + ) + guard message.shouldShowDateHeader else { - return result.appending(message) + return updatedResult.appending(message) } - return result + return updatedResult .appending( MessageViewModel( timestampMs: message.timestampMs, diff --git a/Session/Conversations/Message Cells/MessageCell.swift b/Session/Conversations/Message Cells/MessageCell.swift index 44886ba9c..dde344352 100644 --- a/Session/Conversations/Message Cells/MessageCell.swift +++ b/Session/Conversations/Message Cells/MessageCell.swift @@ -65,6 +65,7 @@ public class MessageCell: UITableViewCell { static func cellType(for viewModel: MessageViewModel) -> MessageCell.Type { guard viewModel.cellType != .typingIndicator else { return TypingIndicatorCell.self } guard viewModel.cellType != .dateHeader else { return DateHeaderCell.self } + guard viewModel.cellType != .unreadMarker else { return UnreadMarkerCell.self } switch viewModel.variant { case .standardOutgoing, .standardIncoming, .standardIncomingDeleted: diff --git a/Session/Conversations/Message Cells/UnreadMarkerCell.swift b/Session/Conversations/Message Cells/UnreadMarkerCell.swift new file mode 100644 index 000000000..76410c050 --- /dev/null +++ b/Session/Conversations/Message Cells/UnreadMarkerCell.swift @@ -0,0 +1,73 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import UIKit +import SignalUtilitiesKit +import SessionUtilitiesKit +import SessionMessagingKit + +final class UnreadMarkerCell: MessageCell { + public static let height: CGFloat = 32 + + // MARK: - UI + + private let leftLine: UIView = { + let result: UIView = UIView() + result.themeBackgroundColor = .unreadMarker + result.set(.height, to: 1) // Intentionally 1 instead of 'separatorThickness' + + return result + }() + + private lazy var titleLabel: UILabel = { + let result = UILabel() + result.font = .boldSystemFont(ofSize: Values.smallFontSize) + result.text = "UNREAD_MESSAGES".localized() + result.themeTextColor = .unreadMarker + result.textAlignment = .center + + return result + }() + + private let rightLine: UIView = { + let result: UIView = UIView() + result.themeBackgroundColor = .unreadMarker + result.set(.height, to: 1) // Intentionally 1 instead of 'separatorThickness' + + return result + }() + + // MARK: - Initialization + + override func setUpViewHierarchy() { + super.setUpViewHierarchy() + + addSubview(leftLine) + addSubview(titleLabel) + addSubview(rightLine) + + leftLine.pin(.leading, to: .leading, of: self, withInset: Values.mediumSpacing) + leftLine.pin(.trailing, to: .leading, of: titleLabel, withInset: -Values.smallSpacing) + leftLine.center(.vertical, in: self) + titleLabel.center(.horizontal, in: self) + titleLabel.center(.vertical, in: self) + titleLabel.pin(.top, to: .top, of: self, withInset: Values.smallSpacing) + titleLabel.pin(.bottom, to: .bottom, of: self, withInset: -Values.smallSpacing) + rightLine.pin(.leading, to: .trailing, of: titleLabel, withInset: Values.smallSpacing) + rightLine.pin(.trailing, to: .trailing, of: self, withInset: -Values.mediumSpacing) + rightLine.center(.vertical, in: self) + } + + // MARK: - Updating + + override func update( + with cellViewModel: MessageViewModel, + mediaCache: NSCache, + playbackInfo: ConversationViewModel.PlaybackInfo?, + showExpandedReactions: Bool, + lastSearchText: String? + ) { + guard cellViewModel.cellType == .unreadMarker else { return } + } + + override func dynamicUpdate(with cellViewModel: MessageViewModel, playbackInfo: ConversationViewModel.PlaybackInfo?) {} +} diff --git a/Session/Conversations/Message Cells/VisibleMessageCell.swift b/Session/Conversations/Message Cells/VisibleMessageCell.swift index 79b137e5d..509e7592c 100644 --- a/Session/Conversations/Message Cells/VisibleMessageCell.swift +++ b/Session/Conversations/Message Cells/VisibleMessageCell.swift @@ -489,7 +489,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { } switch cellViewModel.cellType { - case .typingIndicator, .dateHeader: break + case .typingIndicator, .dateHeader, .unreadMarker: break case .textOnlyMessage: let inset: CGFloat = 12 diff --git a/Session/Conversations/Views & Modals/ScrollToBottomButton.swift b/Session/Conversations/Views & Modals/RoundIconButton.swift similarity index 67% rename from Session/Conversations/Views & Modals/ScrollToBottomButton.swift rename to Session/Conversations/Views & Modals/RoundIconButton.swift index 413dbdbb2..74e6a7978 100644 --- a/Session/Conversations/Views & Modals/ScrollToBottomButton.swift +++ b/Session/Conversations/Views & Modals/RoundIconButton.swift @@ -3,8 +3,8 @@ import UIKit import SessionUIKit -final class ScrollToBottomButton: UIView { - private weak var delegate: ScrollToBottomButtonDelegate? +final class RoundIconButton: UIView { + private let onTap: () -> () // MARK: - Settings @@ -13,12 +13,12 @@ final class ScrollToBottomButton: UIView { // MARK: - Lifecycle - init(delegate: ScrollToBottomButtonDelegate) { - self.delegate = delegate + init(image: UIImage?, onTap: @escaping () -> ()) { + self.onTap = onTap super.init(frame: CGRect.zero) - setUpViewHierarchy() + setUpViewHierarchy(image: image) } override init(frame: CGRect) { @@ -29,7 +29,7 @@ final class ScrollToBottomButton: UIView { preconditionFailure("Use init(delegate:) instead.") } - private func setUpViewHierarchy() { + private func setUpViewHierarchy(image: UIImage?) { // Background & blur let backgroundView = UIView() backgroundView.themeBackgroundColor = .backgroundSecondary @@ -49,9 +49,9 @@ final class ScrollToBottomButton: UIView { } // Size & shape - set(.width, to: ScrollToBottomButton.size) - set(.height, to: ScrollToBottomButton.size) - layer.cornerRadius = (ScrollToBottomButton.size / 2) + set(.width, to: RoundIconButton.size) + set(.height, to: RoundIconButton.size) + layer.cornerRadius = (RoundIconButton.size / 2) layer.masksToBounds = true // Border @@ -59,16 +59,13 @@ final class ScrollToBottomButton: UIView { layer.borderWidth = Values.separatorThickness // Icon - let iconImageView = UIImageView( - image: UIImage(named: "ic_chevron_down")? - .withRenderingMode(.alwaysTemplate) - ) + let iconImageView = UIImageView(image: image) iconImageView.themeTintColor = .textPrimary iconImageView.contentMode = .scaleAspectFit addSubview(iconImageView) iconImageView.center(in: self) - iconImageView.set(.width, to: ScrollToBottomButton.iconSize) - iconImageView.set(.height, to: ScrollToBottomButton.iconSize) + iconImageView.set(.width, to: RoundIconButton.iconSize) + iconImageView.set(.height, to: RoundIconButton.iconSize) // Gesture recognizer let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap)) @@ -78,12 +75,6 @@ final class ScrollToBottomButton: UIView { // MARK: - Interaction @objc private func handleTap() { - delegate?.handleScrollToBottomButtonTapped() + onTap() } } - -// MARK: - ScrollToBottomButtonDelegate - -protocol ScrollToBottomButtonDelegate: AnyObject { - func handleScrollToBottomButtonTapped() -} diff --git a/Session/Home/HomeVC.swift b/Session/Home/HomeVC.swift index cca5b4dbb..eb7d34ec5 100644 --- a/Session/Home/HomeVC.swift +++ b/Session/Home/HomeVC.swift @@ -651,9 +651,10 @@ final class HomeVC: BaseVC, SessionUtilRespondingViewController, UITableViewData switch section.model { case .threads: // Cannot properly sync outgoing blinded message requests so don't provide the option - guard SessionId(from: section.elements[indexPath.row].threadId)?.prefix == .standard else { - return nil - } + guard + threadViewModel.threadVariant != .contact || + SessionId(from: section.elements[indexPath.row].threadId)?.prefix == .standard + else { return nil } return UIContextualAction.configuration( for: UIContextualAction.generateSwipeActions( diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 3de4c535c..dcab9b1d8 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -47,9 +47,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD let mainWindow: UIWindow = TraitObservingWindow(frame: UIScreen.main.bounds) self.loadingViewController = LoadingViewController() - // Store a weak reference in the ThemeManager so it can properly apply themes as needed - ThemeManager.mainWindow = mainWindow - AppSetup.setupEnvironment( appSpecificBlock: { // Create AppEnvironment @@ -78,6 +75,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD return } + /// Store a weak reference in the ThemeManager so it can properly apply themes as needed + /// + /// **Note:** Need to do this after the db migrations because theme preferences are stored in the database and + /// we don't want to access it until after the migrations run + ThemeManager.mainWindow = mainWindow self?.completePostMigrationSetup(calledFrom: .finishLaunching, needsConfigSync: needsConfigSync) } ) @@ -333,7 +335,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD private func showFailedMigrationAlert(calledFrom lifecycleMethod: LifecycleMethod, error: Error?) { let alert = UIAlertController( title: "Session", - message: "DATABASE_MIGRATION_FAILED".localized(), + message: { + switch (error ?? StorageError.generic) { + case StorageError.startupFailed: return "DATABASE_STARTUP_FAILED".localized() + default: return "DATABASE_MIGRATION_FAILED".localized() + } + }(), preferredStyle: .alert ) alert.addAction(UIAlertAction(title: "HELP_REPORT_BUG_ACTION_TITLE".localized(), style: .default) { _ in diff --git a/Session/Meta/Translations/de.lproj/Localizable.strings b/Session/Meta/Translations/de.lproj/Localizable.strings index 692363dec..cf306a367 100644 --- a/Session/Meta/Translations/de.lproj/Localizable.strings +++ b/Session/Meta/Translations/de.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -637,6 +638,7 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; +"UNREAD_MESSAGES" = "Unread Messages"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index a17e9d9fa..0853ba3fb 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -637,6 +638,7 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; +"UNREAD_MESSAGES" = "Unread Messages"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; diff --git a/Session/Meta/Translations/es.lproj/Localizable.strings b/Session/Meta/Translations/es.lproj/Localizable.strings index 7f99b5324..2cd107fb4 100644 --- a/Session/Meta/Translations/es.lproj/Localizable.strings +++ b/Session/Meta/Translations/es.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -637,6 +638,7 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; +"UNREAD_MESSAGES" = "Unread Messages"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; diff --git a/Session/Meta/Translations/fa.lproj/Localizable.strings b/Session/Meta/Translations/fa.lproj/Localizable.strings index d264984ad..721d742fe 100644 --- a/Session/Meta/Translations/fa.lproj/Localizable.strings +++ b/Session/Meta/Translations/fa.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "متاسفانه خطایی رخ داده است"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "لطفا بعدا دوباره تلاش کنید"; "LOADING_CONVERSATIONS" = "درحال بارگزاری پیام ها..."; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "هنگام بهینه‌سازی پایگاه داده خطایی روی داد\n\nشما می‌توانید گزارش‌های برنامه خود را صادر کنید تا بتوانید برای عیب‌یابی به اشتراک بگذارید یا می‌توانید دستگاه خود را بازیابی کنید\n\nهشدار: بازیابی دستگاه شما منجر به از دست رفتن داده‌های قدیمی‌تر از دو هفته می‌شود."; "RECOVERY_PHASE_ERROR_GENERIC" = "مشکلی پیش آمد. لطفاً عبارت بازیابی خود را بررسی کنید و دوباره امتحان کنید."; "RECOVERY_PHASE_ERROR_LENGTH" = "به نظر می رسد کلمات کافی وارد نکرده اید. لطفاً عبارت بازیابی خود را بررسی کنید و دوباره امتحان کنید."; @@ -637,6 +638,7 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; +"UNREAD_MESSAGES" = "Unread Messages"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; diff --git a/Session/Meta/Translations/fi.lproj/Localizable.strings b/Session/Meta/Translations/fi.lproj/Localizable.strings index b7dd60b4c..e06981e6c 100644 --- a/Session/Meta/Translations/fi.lproj/Localizable.strings +++ b/Session/Meta/Translations/fi.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -637,6 +638,7 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; +"UNREAD_MESSAGES" = "Unread Messages"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; diff --git a/Session/Meta/Translations/fr.lproj/Localizable.strings b/Session/Meta/Translations/fr.lproj/Localizable.strings index 3cbd8abc8..0f42d5c3b 100644 --- a/Session/Meta/Translations/fr.lproj/Localizable.strings +++ b/Session/Meta/Translations/fr.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oups, une erreur est survenue"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Veuillez réessayer plus tard"; "LOADING_CONVERSATIONS" = "Chargement des conversations..."; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "Une erreur est survenue pendant l'optimisation de la base de données\n\nVous pouvez exporter votre journal d'application pour le partager et aider à régler le problème ou vous pouvez restaurer votre appareil\n\nAttention : restaurer votre appareil résultera en une perte des données des deux dernières semaines"; "RECOVERY_PHASE_ERROR_GENERIC" = "Quelque chose s'est mal passé. Vérifiez votre phrase de récupération et réessayez s'il vous plaît."; "RECOVERY_PHASE_ERROR_LENGTH" = "Il semble que vous n'avez pas saisi tous les mots. Vérifiez votre phrase de récupération et réessayez s'il vous plaît."; @@ -637,6 +638,7 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; +"UNREAD_MESSAGES" = "Unread Messages"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; diff --git a/Session/Meta/Translations/hi.lproj/Localizable.strings b/Session/Meta/Translations/hi.lproj/Localizable.strings index 3f8db15d5..bf51ebd6c 100644 --- a/Session/Meta/Translations/hi.lproj/Localizable.strings +++ b/Session/Meta/Translations/hi.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -637,6 +638,7 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; +"UNREAD_MESSAGES" = "Unread Messages"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; diff --git a/Session/Meta/Translations/hr.lproj/Localizable.strings b/Session/Meta/Translations/hr.lproj/Localizable.strings index 78b23b6c5..5bac5abfb 100644 --- a/Session/Meta/Translations/hr.lproj/Localizable.strings +++ b/Session/Meta/Translations/hr.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -637,6 +638,7 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; +"UNREAD_MESSAGES" = "Unread Messages"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; diff --git a/Session/Meta/Translations/id-ID.lproj/Localizable.strings b/Session/Meta/Translations/id-ID.lproj/Localizable.strings index ecb22004e..4e54eb039 100644 --- a/Session/Meta/Translations/id-ID.lproj/Localizable.strings +++ b/Session/Meta/Translations/id-ID.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -637,6 +638,7 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; +"UNREAD_MESSAGES" = "Unread Messages"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; diff --git a/Session/Meta/Translations/it.lproj/Localizable.strings b/Session/Meta/Translations/it.lproj/Localizable.strings index b1648c3ec..b41f34667 100644 --- a/Session/Meta/Translations/it.lproj/Localizable.strings +++ b/Session/Meta/Translations/it.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -637,6 +638,7 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; +"UNREAD_MESSAGES" = "Unread Messages"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; diff --git a/Session/Meta/Translations/ja.lproj/Localizable.strings b/Session/Meta/Translations/ja.lproj/Localizable.strings index 5048ae1a8..f2b83382f 100644 --- a/Session/Meta/Translations/ja.lproj/Localizable.strings +++ b/Session/Meta/Translations/ja.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -637,6 +638,7 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; +"UNREAD_MESSAGES" = "Unread Messages"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; diff --git a/Session/Meta/Translations/nl.lproj/Localizable.strings b/Session/Meta/Translations/nl.lproj/Localizable.strings index ec4311bd3..0aaa027f7 100644 --- a/Session/Meta/Translations/nl.lproj/Localizable.strings +++ b/Session/Meta/Translations/nl.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -637,6 +638,7 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; +"UNREAD_MESSAGES" = "Unread Messages"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; diff --git a/Session/Meta/Translations/pl.lproj/Localizable.strings b/Session/Meta/Translations/pl.lproj/Localizable.strings index 0284e903d..dbefe0039 100644 --- a/Session/Meta/Translations/pl.lproj/Localizable.strings +++ b/Session/Meta/Translations/pl.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -637,6 +638,7 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; +"UNREAD_MESSAGES" = "Unread Messages"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; diff --git a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings index 00fcb652b..888d81746 100644 --- a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings +++ b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -637,6 +638,7 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; +"UNREAD_MESSAGES" = "Unread Messages"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; diff --git a/Session/Meta/Translations/ru.lproj/Localizable.strings b/Session/Meta/Translations/ru.lproj/Localizable.strings index a6773a96d..77850b124 100644 --- a/Session/Meta/Translations/ru.lproj/Localizable.strings +++ b/Session/Meta/Translations/ru.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -637,6 +638,7 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; +"UNREAD_MESSAGES" = "Unread Messages"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; diff --git a/Session/Meta/Translations/si.lproj/Localizable.strings b/Session/Meta/Translations/si.lproj/Localizable.strings index e530f410a..f28501061 100644 --- a/Session/Meta/Translations/si.lproj/Localizable.strings +++ b/Session/Meta/Translations/si.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -637,6 +638,7 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; +"UNREAD_MESSAGES" = "Unread Messages"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; diff --git a/Session/Meta/Translations/sk.lproj/Localizable.strings b/Session/Meta/Translations/sk.lproj/Localizable.strings index 53bd20179..5a995249d 100644 --- a/Session/Meta/Translations/sk.lproj/Localizable.strings +++ b/Session/Meta/Translations/sk.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -637,6 +638,7 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; +"UNREAD_MESSAGES" = "Unread Messages"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; diff --git a/Session/Meta/Translations/sv.lproj/Localizable.strings b/Session/Meta/Translations/sv.lproj/Localizable.strings index bca898792..688680b2a 100644 --- a/Session/Meta/Translations/sv.lproj/Localizable.strings +++ b/Session/Meta/Translations/sv.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -637,6 +638,7 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; +"UNREAD_MESSAGES" = "Unread Messages"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; diff --git a/Session/Meta/Translations/th.lproj/Localizable.strings b/Session/Meta/Translations/th.lproj/Localizable.strings index 126504931..98ca3e5bb 100644 --- a/Session/Meta/Translations/th.lproj/Localizable.strings +++ b/Session/Meta/Translations/th.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -637,6 +638,7 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; +"UNREAD_MESSAGES" = "Unread Messages"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; diff --git a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings index ce9bd9a6a..aa5527991 100644 --- a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings +++ b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -637,6 +638,7 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; +"UNREAD_MESSAGES" = "Unread Messages"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; diff --git a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings index 5e28a0543..8296fa917 100644 --- a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -637,6 +638,7 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; +"UNREAD_MESSAGES" = "Unread Messages"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; diff --git a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings index 4f5422166..68076c678 100644 --- a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings @@ -414,6 +414,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; @@ -637,6 +638,7 @@ "CONTACT_NICKNAME_PLACEHOLDER" = "Enter a name"; "MARK_AS_READ" = "Mark Read"; "MARK_AS_UNREAD" = "Mark Unread"; +"UNREAD_MESSAGES" = "Unread Messages"; "CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!"; "CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@."; "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@."; diff --git a/Session/Shared/ScreenLockUI.swift b/Session/Shared/ScreenLockUI.swift index d61f842b0..dcf7d55c6 100644 --- a/Session/Shared/ScreenLockUI.swift +++ b/Session/Shared/ScreenLockUI.swift @@ -15,7 +15,7 @@ class ScreenLockUI { result.isHidden = false result.windowLevel = ._Background result.isOpaque = true - result.themeBackgroundColor = .backgroundPrimary + result.themeBackgroundColorForced = .theme(.classicDark, color: .backgroundPrimary) result.rootViewController = self.screenBlockingViewController return result @@ -291,7 +291,7 @@ class ScreenLockUI { window.isHidden = false window.windowLevel = ._Background window.isOpaque = true - window.themeBackgroundColor = .backgroundPrimary + window.themeBackgroundColorForced = .theme(.classicDark, color: .backgroundPrimary) let viewController: ScreenLockViewController = ScreenLockViewController { [weak self] in guard self?.appIsInactiveOrBackground == false else { diff --git a/Session/Utilities/MockDataGenerator.swift b/Session/Utilities/MockDataGenerator.swift index d4ab94abc..1cbf94fbe 100644 --- a/Session/Utilities/MockDataGenerator.swift +++ b/Session/Utilities/MockDataGenerator.swift @@ -6,67 +6,6 @@ import Curve25519Kit import SessionMessagingKit enum MockDataGenerator { - // Note: This was taken from TensorFlow's Random (https://github.com/apple/swift/blob/bc8f9e61d333b8f7a625f74d48ef0b554726e349/stdlib/public/TensorFlow/Random.swift) - // the complex approach is needed due to an issue with Swift's randomElement(using:) - // generation (see https://stackoverflow.com/a/64897775 for more info) - struct ARC4RandomNumberGenerator: RandomNumberGenerator { - var state: [UInt8] = Array(0...255) - var iPos: UInt8 = 0 - var jPos: UInt8 = 0 - - init(seed: T) { - self.init( - seed: (0..<(UInt64.bitWidth / UInt64.bitWidth)).map { index in - UInt8(truncatingIfNeeded: seed >> (UInt8.bitWidth * index)) - } - ) - } - - init(seed: [UInt8]) { - precondition(seed.count > 0, "Length of seed must be positive") - precondition(seed.count <= 256, "Length of seed must be at most 256") - - // Note: Have to use a for loop instead of a 'forEach' otherwise - // it doesn't work properly (not sure why...) - var j: UInt8 = 0 - for i: UInt8 in 0...255 { - j &+= S(i) &+ seed[Int(i) % seed.count] - swapAt(i, j) - } - } - - /// Produce the next random UInt64 from the stream, and advance the internal state - mutating func next() -> UInt64 { - // Note: Have to use a for loop instead of a 'forEach' otherwise - // it doesn't work properly (not sure why...) - var result: UInt64 = 0 - for _ in 0.. UInt8 { - return state[Int(index)] - } - - /// Helper to swap elements of the state - private mutating func swapAt(_ i: UInt8, _ j: UInt8) { - state.swapAt(Int(i), Int(j)) - } - - /// Generates the next byte in the keystream. - private mutating func nextByte() -> UInt8 { - iPos &+= 1 - jPos &+= S(iPos) - swapAt(iPos, jPos) - return S(S(iPos) &+ S(jPos)) - } - } - // MARK: - Generation static var printProgress: Bool = true @@ -125,7 +64,7 @@ enum MockDataGenerator { logProgress("DM Thread \(threadIndex)", "Start") - let data = Data((0..<16).map { _ in UInt8.random(in: (UInt8.min...UInt8.max), using: &dmThreadRandomGenerator) }) + let data: Data = Data(dmThreadRandomGenerator.nextBytes(count: 16)) let randomSessionId: String = try! Identity.generate(from: data).x25519KeyPair.hexEncodedPublicKey let isMessageRequest: Bool = Bool.random(using: &dmThreadRandomGenerator) let contactNameLength: Int = ((5..<20).randomElement(using: &dmThreadRandomGenerator) ?? 0) @@ -207,7 +146,7 @@ enum MockDataGenerator { logProgress("Closed Group Thread \(threadIndex)", "Start") - let data = Data((0..<16).map { _ in UInt8.random(in: (UInt8.min...UInt8.max), using: &cgThreadRandomGenerator) }) + let data: Data = Data(cgThreadRandomGenerator.nextBytes(count: 16)) let randomGroupPublicKey: String = try! Identity.generate(from: data).x25519KeyPair.hexEncodedPublicKey let groupNameLength: Int = ((5..<20).randomElement(using: &cgThreadRandomGenerator) ?? 0) let groupName: String = (0..? = nil var dumpResultLen: Int = 0 - config_dump(conf, &dumpResult, &dumpResultLen) + try CExceptionHelper.performSafely { + config_dump(conf, &dumpResult, &dumpResultLen) + } guard let dumpResult: UnsafeMutablePointer = dumpResult else { return nil } @@ -308,15 +310,40 @@ public enum SessionUtil { // Ensure we always check the required user config types for changes even if there is no dump // data yet (to deal with first launch cases) - return existingDumpVariants + return try existingDumpVariants .compactMap { variant -> OutgoingConfResult? in - SessionUtil + try SessionUtil .config(for: variant, publicKey: publicKey) .mutate { conf in // Check if the config needs to be pushed guard conf != nil && config_needs_push(conf) else { return nil } - let cPushData: UnsafeMutablePointer = config_push(conf) + var cPushData: UnsafeMutablePointer! + let configCountInfo: String = { + var result: String = "Invalid" + + try? CExceptionHelper.performSafely { + switch variant { + case .userProfile: result = "1 profile" + case .contacts: result = "\(contacts_size(conf)) contacts" + case .userGroups: result = "\(user_groups_size(conf)) group conversations" + case .convoInfoVolatile: result = "\(convo_info_volatile_size(conf)) volatile conversations" + } + } + + return result + }() + + do { + try CExceptionHelper.performSafely { + cPushData = config_push(conf) + } + } + catch { + SNLog("[libSession] Failed to generate push data for \(variant) config data, size: \(configCountInfo), error: \(error)") + throw error + } + let pushData: Data = Data( bytes: cPushData.pointee.config, count: cPushData.pointee.config_len @@ -328,6 +355,7 @@ public enum SessionUtil { ) let seqNo: Int64 = cPushData.pointee.seqno cPushData.deallocate() + SNLog("[libSession - DEBUG] Push data for \(variant) config data, size: \(configCountInfo), bytes: \(pushData.count)") return OutgoingConfResult( message: SharedConfigMessage( diff --git a/SessionMessagingKit/Shared Models/MessageViewModel.swift b/SessionMessagingKit/Shared Models/MessageViewModel.swift index 177dcbbbb..bc143b0e5 100644 --- a/SessionMessagingKit/Shared Models/MessageViewModel.swift +++ b/SessionMessagingKit/Shared Models/MessageViewModel.swift @@ -55,6 +55,7 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable, case genericAttachment case typingIndicator case dateHeader + case unreadMarker } public var differenceIdentifier: Int64 { id } diff --git a/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigContactsSpec.swift b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigContactsSpec.swift index a363422b1..666ca512d 100644 --- a/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigContactsSpec.swift +++ b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigContactsSpec.swift @@ -53,8 +53,15 @@ class ConfigContactsSpec { // MARK: -- it can catch size limit errors thrown when pushing it("can catch size limit errors thrown when pushing") { + var randomGenerator: ARC4RandomNumberGenerator = ARC4RandomNumberGenerator(seed: 1000) + try (0..<10000).forEach { index in - var contact: contacts_contact = try createContact(for: index, in: conf, maxing: .allProperties) + var contact: contacts_contact = try createContact( + for: index, + in: conf, + rand: &randomGenerator, + maxing: .allProperties + ) contacts_set(conf, &contact) } @@ -70,12 +77,19 @@ class ConfigContactsSpec { // MARK: -- can catch size limit errors thrown when dumping it("can catch size limit errors thrown when dumping") { - try (0..<10000).forEach { index in - var contact: contacts_contact = try createContact(for: index, in: conf, maxing: .allProperties) + var randomGenerator: ARC4RandomNumberGenerator = ARC4RandomNumberGenerator(seed: 1000) + + try (0..<100000).forEach { index in + var contact: contacts_contact = try createContact( + for: index, + in: conf, + rand: &randomGenerator, + maxing: .allProperties + ) contacts_set(conf, &contact) } - expect(contacts_size(conf)).to(equal(10000)) + expect(contacts_size(conf)).to(equal(100000)) expect(config_needs_push(conf)).to(beTrue()) expect(config_needs_dump(conf)).to(beTrue()) @@ -117,8 +131,14 @@ class ConfigContactsSpec { // MARK: -- has not changed the max empty records it("has not changed the max empty records") { - for index in (0..<10000) { - var contact: contacts_contact = try createContact(for: index, in: conf) + var randomGenerator: ARC4RandomNumberGenerator = ARC4RandomNumberGenerator(seed: 1000) + + for index in (0..<100000) { + var contact: contacts_contact = try createContact( + for: index, + in: conf, + rand: &randomGenerator + ) contacts_set(conf, &contact) do { try CExceptionHelper.performSafely { config_push(conf).deallocate() } } @@ -129,13 +149,20 @@ class ConfigContactsSpec { } // Check that the record count matches the maximum when we last checked - expect(numRecords).to(equal(1775)) + expect(numRecords).to(equal(2370)) } // MARK: -- has not changed the max name only records it("has not changed the max name only records") { - for index in (0..<10000) { - var contact: contacts_contact = try createContact(for: index, in: conf, maxing: [.name]) + var randomGenerator: ARC4RandomNumberGenerator = ARC4RandomNumberGenerator(seed: 1000) + + for index in (0..<100000) { + var contact: contacts_contact = try createContact( + for: index, + in: conf, + rand: &randomGenerator, + maxing: [.name] + ) contacts_set(conf, &contact) do { try CExceptionHelper.performSafely { config_push(conf).deallocate() } } @@ -146,13 +173,20 @@ class ConfigContactsSpec { } // Check that the record count matches the maximum when we last checked - expect(numRecords).to(equal(526)) + expect(numRecords).to(equal(796)) } // MARK: -- has not changed the max name and profile pic only records it("has not changed the max name and profile pic only records") { - for index in (0..<10000) { - var contact: contacts_contact = try createContact(for: index, in: conf, maxing: [.name, .profile_pic]) + var randomGenerator: ARC4RandomNumberGenerator = ARC4RandomNumberGenerator(seed: 1000) + + for index in (0..<100000) { + var contact: contacts_contact = try createContact( + for: index, + in: conf, + rand: &randomGenerator, + maxing: [.name, .profile_pic] + ) contacts_set(conf, &contact) do { try CExceptionHelper.performSafely { config_push(conf).deallocate() } } @@ -163,13 +197,20 @@ class ConfigContactsSpec { } // Check that the record count matches the maximum when we last checked - expect(numRecords).to(equal(184)) + expect(numRecords).to(equal(290)) } // MARK: -- has not changed the max filled records it("has not changed the max filled records") { - for index in (0..<10000) { - var contact: contacts_contact = try createContact(for: index, in: conf, maxing: .allProperties) + var randomGenerator: ARC4RandomNumberGenerator = ARC4RandomNumberGenerator(seed: 1000) + + for index in (0..<100000) { + var contact: contacts_contact = try createContact( + for: index, + in: conf, + rand: &randomGenerator, + maxing: .allProperties + ) contacts_set(conf, &contact) do { try CExceptionHelper.performSafely { config_push(conf).deallocate() } } @@ -180,7 +221,7 @@ class ConfigContactsSpec { } // Check that the record count matches the maximum when we last checked - expect(numRecords).to(equal(134)) + expect(numRecords).to(equal(236)) } } @@ -547,9 +588,10 @@ class ConfigContactsSpec { private static func createContact( for index: Int, in conf: UnsafeMutablePointer?, + rand: inout ARC4RandomNumberGenerator, maxing properties: [ContactProperty] = [] ) throws -> contacts_contact { - let postPrefixId: String = "050000000000000000000000000000000000000000000000000000000000000000" + let postPrefixId: String = "05\(rand.nextBytes(count: 32).toHexString())" let sessionId: String = ("05\(index)a" + postPrefixId.suffix(postPrefixId.count - "05\(index)a".count)) var cSessionId: [CChar] = sessionId.cArray.nullTerminated() var contact: contacts_contact = contacts_contact() @@ -569,33 +611,22 @@ class ConfigContactsSpec { case .mute_until: contact.mute_until = Int64.max case .name: - contact.name = String( - data: Data( - repeating: "A".data(using: .utf8)![0], - count: SessionUtil.libSessionMaxNameByteLength - ), - encoding: .utf8 - ).toLibSession() + contact.name = rand.nextBytes(count: SessionUtil.libSessionMaxNameByteLength) + .toHexString() + .toLibSession() case .nickname: - contact.nickname = String( - data: Data( - repeating: "A".data(using: .utf8)![0], - count: SessionUtil.libSessionMaxNameByteLength - ), - encoding: .utf8 - ).toLibSession() + contact.nickname = rand.nextBytes(count: SessionUtil.libSessionMaxNameByteLength) + .toHexString() + .toLibSession() case .profile_pic: contact.profile_pic = user_profile_pic( - url: String( - data: Data( - repeating: "A".data(using: .utf8)![0], - count: SessionUtil.libSessionMaxProfileUrlByteLength - ), - encoding: .utf8 - ).toLibSession(), - key: "qwerty78901234567890123456789012".data(using: .utf8)!.toLibSession() + url: rand.nextBytes(count: SessionUtil.libSessionMaxProfileUrlByteLength) + .toHexString() + .toLibSession(), + key: Data(rand.nextBytes(count: 32)) + .toLibSession() ) } } diff --git a/SessionUIKit/Style Guide/ThemeManager.swift b/SessionUIKit/Style Guide/ThemeManager.swift index fa165e996..5926a82d1 100644 --- a/SessionUIKit/Style Guide/ThemeManager.swift +++ b/SessionUIKit/Style Guide/ThemeManager.swift @@ -407,8 +407,10 @@ internal class ThemeApplier { .compactMap { $0?.clearingOtherAppliers() } .filter { $0.info != info } - // Automatically apply the theme immediately - self.apply(theme: ThemeManager.currentTheme, isInitialApplication: true) + // Automatically apply the theme immediately (if the database has been setup) + if Storage.hasCreatedValidInstance { + self.apply(theme: ThemeManager.currentTheme, isInitialApplication: true) + } } // MARK: - Functions diff --git a/SessionUIKit/Style Guide/Themes/Theme+ClassicDark.swift b/SessionUIKit/Style Guide/Themes/Theme+ClassicDark.swift index 28f8bec4d..1bd0e039a 100644 --- a/SessionUIKit/Style Guide/Themes/Theme+ClassicDark.swift +++ b/SessionUIKit/Style Guide/Themes/Theme+ClassicDark.swift @@ -115,6 +115,9 @@ internal enum Theme_ClassicDark: ThemeColors { // Profile .profileIcon: .primary, .profileIcon_greenPrimaryColor: .black, - .profileIcon_background: .white + .profileIcon_background: .white, + + // Unread Marker + .unreadMarker: .primary ] } diff --git a/SessionUIKit/Style Guide/Themes/Theme+ClassicLight.swift b/SessionUIKit/Style Guide/Themes/Theme+ClassicLight.swift index a04dda681..b659a95e0 100644 --- a/SessionUIKit/Style Guide/Themes/Theme+ClassicLight.swift +++ b/SessionUIKit/Style Guide/Themes/Theme+ClassicLight.swift @@ -115,6 +115,9 @@ internal enum Theme_ClassicLight: ThemeColors { // Profile .profileIcon: .primary, .profileIcon_greenPrimaryColor: .primary, - .profileIcon_background: .black + .profileIcon_background: .black, + + // Unread Marker + .unreadMarker: .black ] } diff --git a/SessionUIKit/Style Guide/Themes/Theme+OceanDark.swift b/SessionUIKit/Style Guide/Themes/Theme+OceanDark.swift index 4f6e1bf3f..a87cf4d4d 100644 --- a/SessionUIKit/Style Guide/Themes/Theme+OceanDark.swift +++ b/SessionUIKit/Style Guide/Themes/Theme+OceanDark.swift @@ -115,6 +115,9 @@ internal enum Theme_OceanDark: ThemeColors { // Profile .profileIcon: .primary, .profileIcon_greenPrimaryColor: .black, - .profileIcon_background: .white + .profileIcon_background: .white, + + // Unread Marker + .unreadMarker: .primary ] } diff --git a/SessionUIKit/Style Guide/Themes/Theme+OceanLight.swift b/SessionUIKit/Style Guide/Themes/Theme+OceanLight.swift index 97a3ed812..ec4df6764 100644 --- a/SessionUIKit/Style Guide/Themes/Theme+OceanLight.swift +++ b/SessionUIKit/Style Guide/Themes/Theme+OceanLight.swift @@ -115,6 +115,9 @@ internal enum Theme_OceanLight: ThemeColors { // Profile .profileIcon: .primary, .profileIcon_greenPrimaryColor: .primary, - .profileIcon_background: .oceanLight1 + .profileIcon_background: .oceanLight1, + + // Unread Marker + .unreadMarker: .black ] } diff --git a/SessionUIKit/Style Guide/Themes/Theme.swift b/SessionUIKit/Style Guide/Themes/Theme.swift index f846304ea..d34d1a702 100644 --- a/SessionUIKit/Style Guide/Themes/Theme.swift +++ b/SessionUIKit/Style Guide/Themes/Theme.swift @@ -204,6 +204,9 @@ public indirect enum ThemeValue: Hashable { case profileIcon case profileIcon_greenPrimaryColor case profileIcon_background + + // Unread Marker + case unreadMarker } // MARK: - ForcedThemeValue diff --git a/SessionUtilitiesKit/Database/Storage.swift b/SessionUtilitiesKit/Database/Storage.swift index 1a28da053..1ea913cb3 100644 --- a/SessionUtilitiesKit/Database/Storage.swift +++ b/SessionUtilitiesKit/Database/Storage.swift @@ -17,13 +17,16 @@ open class Storage { private static var databasePathShm: String { "\(Storage.sharedDatabaseDirectoryPath)/\(Storage.dbFileName)-shm" } private static var databasePathWal: String { "\(Storage.sharedDatabaseDirectoryPath)/\(Storage.dbFileName)-wal" } + public static var hasCreatedValidInstance: Bool { internalHasCreatedValidInstance.wrappedValue } public static var isDatabasePasswordAccessible: Bool { guard (try? getDatabaseCipherKeySpec()) != nil else { return false } return true } + private var startupError: Error? private let migrationsCompleted: Atomic = Atomic(false) + private static let internalHasCreatedValidInstance: Atomic = Atomic(false) internal let internalCurrentlyRunningMigration: Atomic<(identifier: TargetMigrations.Identifier, migration: Migration.Type)?> = Atomic(nil) public static let shared: Storage = Storage() @@ -52,8 +55,9 @@ open class Storage { // If a custom writer was provided then use that (for unit testing) guard customWriter == nil else { dbWriter = customWriter - isValid = true perform(migrations: (customMigrations ?? []), async: false, onProgressUpdate: nil, onComplete: { _, _ in }) + isValid = true + Storage.internalHasCreatedValidInstance.mutate { $0 = true } return } @@ -99,8 +103,9 @@ open class Storage { configuration: config ) isValid = true + Storage.internalHasCreatedValidInstance.mutate { $0 = true } } - catch {} + catch { startupError = error } } // MARK: - Migrations @@ -118,7 +123,12 @@ open class Storage { onProgressUpdate: ((CGFloat, TimeInterval) -> ())?, onComplete: @escaping (Swift.Result, Bool) -> () ) { - guard isValid, let dbWriter: DatabaseWriter = dbWriter else { return } + guard isValid, let dbWriter: DatabaseWriter = dbWriter else { + let error: Error = (startupError ?? StorageError.startupFailed) + SNLog("[Database Error] Statup failed with error: \(error)") + onComplete(.failure(StorageError.startupFailed), false) + return + } typealias MigrationInfo = (identifier: TargetMigrations.Identifier, migrations: TargetMigrations.MigrationSet) let sortedMigrationInfo: [MigrationInfo] = migrations @@ -135,11 +145,11 @@ open class Storage { .reduce(into: []) { result, next in result.append(contentsOf: next) } // Setup and run any required migrations - migrator = { + migrator = { [weak self] in var migrator: DatabaseMigrator = DatabaseMigrator() sortedMigrationInfo.forEach { migrationInfo in migrationInfo.migrations.forEach { migration in - migrator.registerMigration(migrationInfo.identifier, migration: migration) + migrator.registerMigration(self, targetIdentifier: migrationInfo.identifier, migration: migration) } } @@ -316,6 +326,7 @@ open class Storage { try? SUKLegacy.deleteLegacyDatabaseFilesAndKey() Storage.shared.isValid = false + Storage.internalHasCreatedValidInstance.mutate { $0 = false } Storage.shared.migrationsCompleted.mutate { $0 = false } Storage.shared.dbWriter = nil diff --git a/SessionUtilitiesKit/Database/StorageError.swift b/SessionUtilitiesKit/Database/StorageError.swift index 7e48cabc5..b8fbfdf73 100644 --- a/SessionUtilitiesKit/Database/StorageError.swift +++ b/SessionUtilitiesKit/Database/StorageError.swift @@ -5,6 +5,7 @@ import Foundation public enum StorageError: Error { case generic case databaseInvalid + case startupFailed case migrationFailed case invalidKeySpec case decodingFailed diff --git a/SessionUtilitiesKit/Database/Types/Migration.swift b/SessionUtilitiesKit/Database/Types/Migration.swift index b0d87d187..6e4c909e5 100644 --- a/SessionUtilitiesKit/Database/Types/Migration.swift +++ b/SessionUtilitiesKit/Database/Types/Migration.swift @@ -13,15 +13,16 @@ public protocol Migration { } public extension Migration { - static func loggedMigrate(_ targetIdentifier: TargetMigrations.Identifier) -> ((_ db: Database) throws -> ()) { + static func loggedMigrate( + _ storage: Storage?, + targetIdentifier: TargetMigrations.Identifier + ) -> ((_ db: Database) throws -> ()) { return { (db: Database) in SNLogNotTests("[Migration Info] Starting \(targetIdentifier.key(with: self))") - Storage.shared.internalCurrentlyRunningMigration.mutate { $0 = (targetIdentifier, self) } - do { try migrate(db) } - catch { - Storage.shared.internalCurrentlyRunningMigration.mutate { $0 = nil } - throw error - } + storage?.internalCurrentlyRunningMigration.mutate { $0 = (targetIdentifier, self) } + defer { storage?.internalCurrentlyRunningMigration.mutate { $0 = nil } } + + try migrate(db) SNLogNotTests("[Migration Info] Completed \(targetIdentifier.key(with: self))") } } diff --git a/SessionUtilitiesKit/Database/Utilities/DatabaseMigrator+Utilities.swift b/SessionUtilitiesKit/Database/Utilities/DatabaseMigrator+Utilities.swift index 337dd805f..179a3edd3 100644 --- a/SessionUtilitiesKit/Database/Utilities/DatabaseMigrator+Utilities.swift +++ b/SessionUtilitiesKit/Database/Utilities/DatabaseMigrator+Utilities.swift @@ -4,10 +4,15 @@ import Foundation import GRDB public extension DatabaseMigrator { - mutating func registerMigration(_ targetIdentifier: TargetMigrations.Identifier, migration: Migration.Type, foreignKeyChecks: ForeignKeyChecks = .deferred) { + mutating func registerMigration( + _ storage: Storage?, + targetIdentifier: TargetMigrations.Identifier, + migration: Migration.Type, + foreignKeyChecks: ForeignKeyChecks = .deferred + ) { self.registerMigration( targetIdentifier.key(with: migration), - migrate: migration.loggedMigrate(targetIdentifier) + migrate: migration.loggedMigrate(storage, targetIdentifier: targetIdentifier) ) } } diff --git a/SessionUtilitiesKit/Utilities/ARC4RandomNumberGenerator.swift b/SessionUtilitiesKit/Utilities/ARC4RandomNumberGenerator.swift new file mode 100644 index 000000000..355b48a5f --- /dev/null +++ b/SessionUtilitiesKit/Utilities/ARC4RandomNumberGenerator.swift @@ -0,0 +1,73 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. +// +// Note: This was taken from TensorFlow's Random: +// https://github.com/apple/swift/blob/bc8f9e61d333b8f7a625f74d48ef0b554726e349/stdlib/public/TensorFlow/Random.swift +// +// the complex approach is needed due to an issue with Swift's randomElement(using:) +// generation (see https://stackoverflow.com/a/64897775 for more info) + +import Foundation + +public struct ARC4RandomNumberGenerator: RandomNumberGenerator { + var state: [UInt8] = Array(0...255) + var iPos: UInt8 = 0 + var jPos: UInt8 = 0 + + public init(seed: T) { + self.init( + seed: (0..<(UInt64.bitWidth / UInt64.bitWidth)).map { index in + UInt8(truncatingIfNeeded: seed >> (UInt8.bitWidth * index)) + } + ) + } + + public init(seed: [UInt8]) { + precondition(seed.count > 0, "Length of seed must be positive") + precondition(seed.count <= 256, "Length of seed must be at most 256") + + // Note: Have to use a for loop instead of a 'forEach' otherwise + // it doesn't work properly (not sure why...) + var j: UInt8 = 0 + for i: UInt8 in 0...255 { + j &+= S(i) &+ seed[Int(i) % seed.count] + swapAt(i, j) + } + } + + /// Produce the next random UInt64 from the stream, and advance the internal state + public mutating func next() -> UInt64 { + // Note: Have to use a for loop instead of a 'forEach' otherwise + // it doesn't work properly (not sure why...) + var result: UInt64 = 0 + for _ in 0.. UInt8 { + return state[Int(index)] + } + + /// Helper to swap elements of the state + private mutating func swapAt(_ i: UInt8, _ j: UInt8) { + state.swapAt(Int(i), Int(j)) + } + + /// Generates the next byte in the keystream. + private mutating func nextByte() -> UInt8 { + iPos &+= 1 + jPos &+= S(iPos) + swapAt(iPos, jPos) + return S(S(iPos) &+ S(jPos)) + } +} + +public extension ARC4RandomNumberGenerator { + mutating func nextBytes(count: Int) -> [UInt8] { + (0.. Date: Tue, 20 Jun 2023 10:03:50 +1000 Subject: [PATCH 092/135] Tweaked the logic so the unreadMarker will appear via global search --- .../Conversations/ConversationViewModel.swift | 34 ++++++++----------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/Session/Conversations/ConversationViewModel.swift b/Session/Conversations/ConversationViewModel.swift index b0cbbdf34..63ae30e30 100644 --- a/Session/Conversations/ConversationViewModel.swift +++ b/Session/Conversations/ConversationViewModel.swift @@ -63,7 +63,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { init(threadId: String, threadVariant: SessionThread.Variant, focusedInteractionInfo: Interaction.TimestampInfo?) { typealias InitialData = ( - targetInteractionInfo: Interaction.TimestampInfo?, + initialUnreadInteractionInfo: Interaction.TimestampInfo?, threadIsBlocked: Bool, currentUserIsClosedGroupMember: Bool?, openGroupPermissions: OpenGroup.Permissions?, @@ -76,15 +76,13 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { // If we have a specified 'focusedInteractionInfo' then use that, otherwise retrieve the oldest // unread interaction and start focused around that one - let targetInteractionInfo: Interaction.TimestampInfo? = (focusedInteractionInfo != nil ? focusedInteractionInfo : - try Interaction - .select(.id, .timestampMs) - .filter(interaction[.wasRead] == false) - .filter(interaction[.threadId] == threadId) - .order(interaction[.timestampMs].asc) - .asRequest(of: Interaction.TimestampInfo.self) - .fetchOne(db) - ) + let initialUnreadInteractionInfo: Interaction.TimestampInfo? = try Interaction + .select(.id, .timestampMs) + .filter(interaction[.wasRead] == false) + .filter(interaction[.threadId] == threadId) + .order(interaction[.timestampMs].asc) + .asRequest(of: Interaction.TimestampInfo.self) + .fetchOne(db) let threadIsBlocked: Bool = (threadVariant != .contact ? false : try Contact .filter(id: threadId) @@ -114,7 +112,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { ) return ( - targetInteractionInfo, + initialUnreadInteractionInfo, threadIsBlocked, currentUserIsClosedGroupMember, openGroupPermissions, @@ -124,14 +122,9 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { self.threadId = threadId self.initialThreadVariant = threadVariant - self.focusedInteractionInfo = initialData?.targetInteractionInfo + self.focusedInteractionInfo = (focusedInteractionInfo ?? initialData?.initialUnreadInteractionInfo) self.focusBehaviour = (focusedInteractionInfo == nil ? .none : .highlight) - self.initialUnreadInteractionId = (focusedInteractionInfo == nil ? - // If we didn't provide a 'focusedInteractionInfo' then 'initialData?.targetInteractionInfo?.id' will be - // the oldest unread interaction - initialData?.targetInteractionInfo?.id : - nil - ) + self.initialUnreadInteractionId = initialData?.initialUnreadInteractionInfo?.id self.threadData = SessionThreadViewModel( threadId: threadId, threadVariant: threadVariant, @@ -159,7 +152,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { DispatchQueue.global(qos: .userInitiated).async { [weak self] in // If we don't have a `initialFocusedInfo` then default to `.pageBefore` (it'll query // from a `0` offset) - guard let initialFocusedInfo: Interaction.TimestampInfo = initialData?.targetInteractionInfo else { + guard let initialFocusedInfo: Interaction.TimestampInfo = (focusedInteractionInfo ?? initialData?.initialUnreadInteractionInfo) else { self?.pagedDataObserver?.load(.pageBefore) return } @@ -683,6 +676,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { // Then setup the state for the new audio currentPlayingInteraction.mutate { $0 = viewModel.id } + let currentPlaybackTime: TimeInterval? = playbackInfo.wrappedValue[viewModel.id]?.progress audioPlayer.mutate { [weak self] player in // Note: We clear the delegate and explicitly set to nil here as when the OWSAudioPlayer // gets deallocated it triggers state changes which cause UI bugs when auto-playing @@ -695,7 +689,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { delegate: self ) audioPlayer.play() - audioPlayer.setCurrentTime(playbackInfo.wrappedValue[viewModel.id]?.progress ?? 0) + audioPlayer.setCurrentTime(currentPlaybackTime ?? 0) player = audioPlayer } } From 5db254303ae9cf929103e87b7fb289bd7a36621b Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 20 Jun 2023 14:05:04 +1000 Subject: [PATCH 093/135] Removed the wip pruning logic (no longer needed) Fixed broken unit tests --- .../SessionUtil+Contacts.swift | 115 ------------------ .../Configs/ConfigContactsSpec.swift | 93 -------------- SessionUtilitiesKit/Database/Storage.swift | 2 +- 3 files changed, 1 insertion(+), 209 deletions(-) diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift index 96b185793..f3d07f91d 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift @@ -443,121 +443,6 @@ internal extension SessionUtil { return updated } - - // MARK: - Pruning - - static func pruningIfNeeded( - _ db: Database, - conf: UnsafeMutablePointer? - ) throws { - // First make sure we are actually thowing the correct size constraint error (don't want to prune contacts - // as a result of some other random error - do { - try CExceptionHelper.performSafely { config_push(conf).deallocate() } - return // If we didn't error then no need to prune - } - catch { - guard (error as NSError).userInfo["NSLocalizedDescription"] as? String == "Config data is too large" else { - throw error - } - } - - // Extract the contact data from the config - var allContactData: [String: ContactData] = extractContacts( - from: conf, - latestConfigSentTimestampMs: 0 - ) - - // Remove the current user profile info (shouldn't be in there but just in case) - let userPublicKey: String = getUserHexEncodedPublicKey(db) - var cUserPublicKey: [CChar] = userPublicKey.cArray.nullTerminated() - contacts_erase(conf, &cUserPublicKey) - - /// Do the following in stages (we want to prune as few contacts as possible because we are essentially deleting data and removing these - /// contacts will result in not just contact data but also associated conversation data for the contact being removed from linked devices - /// - /// - /// **Step 1** First of all we want to try to detect spam-attacks (ie. if someone creates a bunch of accounts and messages you, and you - /// systematically block every one of those accounts - this can quickly add up) - /// - /// We will do this by filtering the contact data to only include blocked contacts, grouping those contacts into contacts created within the - /// same hour and then only including groups that have more than 10 contacts (ie. if you blocked 20 users within an hour we expect those - /// contacts were spammers) - let blockSpamBatchingResolution: TimeInterval = (60 * 60) - // TODO: Do we want to only do this case for contacts that were created over X time ago? (to avoid unintentionally unblocking accounts that were recently blocked - let likelySpammerContacts: [ContactData] = allContactData - .values - .filter { $0.contact.isBlocked } - .grouped(by: { $0.created / blockSpamBatchingResolution }) - .filter { _, values in values.count > 20 } - .values - .flatMap { $0 } - - if !likelySpammerContacts.isEmpty { - likelySpammerContacts.forEach { contact in - var cSessionId: [CChar] = contact.contact.id.cArray.nullTerminated() - contacts_erase(conf, &cSessionId) - - allContactData.removeValue(forKey: contact.contact.id) - } - - // If we are no longer erroring then we can stop here - do { return try CExceptionHelper.performSafely { config_push(conf).deallocate() } } - catch {} - } - - /// We retrieve the `CONVO_INFO_VOLATILE` records and one-to-one conversation message counts as they will be relevant for subsequent checks - let volatileThreadInfo: [String: VolatileThreadInfo] = SessionUtil - .config(for: .convoInfoVolatile, publicKey: userPublicKey) - .wrappedValue - .map { SessionUtil.extractConvoVolatileInfo(from: $0) } - .defaulting(to: []) - .reduce(into: [:]) { result, next in result[next.threadId] = next } - let conversationMessageCounts: [String: Int] = try SessionThread - .filter(SessionThread.Columns.variant == SessionThread.Variant.contact) - .select(.id) - .annotated(with: SessionThread.interactions.count) - .asRequest(of: ThreadCount.self) - .fetchAll(db) - .reduce(into: [:]) { result, next in result[next.id] = next.interactionCount } - - /// **Step 2** Next up we want to remove contact records which are likely to be invalid, this means contacts which: - /// - Aren't blocked - /// - Have no `name` value - /// - Have no `CONVO_INFO_VOLATILE` record - /// - Have no messages in their one-to-one conversations - /// - /// Any contacts that meet the above criteria are likely either invalid contacts or are contacts which the user hasn't seen or interacted - /// with for 30+ days - let likelyInvalidContacts: [ContactData] = allContactData - .values - .filter { !$0.contact.isBlocked } - .filter { $0.profile.name.isEmpty } - .filter { volatileThreadInfo[$0.contact.id] == nil } - .filter { (conversationMessageCounts[$0.contact.id] ?? 0) == 0 } - - if !likelyInvalidContacts.isEmpty { - likelyInvalidContacts.forEach { contact in - var cSessionId: [CChar] = contact.contact.id.cArray.nullTerminated() - contacts_erase(conf, &cSessionId) - - allContactData.removeValue(forKey: contact.contact.id) - } - - // If we are no longer erroring then we can stop here - do { return try CExceptionHelper.performSafely { config_push(conf).deallocate() } } - catch {} - } - - - // TODO: Exclude contacts that have no profile info(?) - // TODO: Exclude contacts that have a CONVO_INFO_VOLATILE record - // TODO: Exclude contacts that have a conversation with messages in the database (ie. only delete "empty" contacts) - - // TODO: Start pruning valid contacts which have really old conversations... - - print("RAWR") - } } // MARK: - External Outgoing Changes diff --git a/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigContactsSpec.swift b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigContactsSpec.swift index 666ca512d..a57839b28 100644 --- a/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigContactsSpec.swift +++ b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigContactsSpec.swift @@ -74,35 +74,6 @@ class ConfigContactsSpec { } .to(throwError(NSError(domain: "cpp_exception", code: -2, userInfo: ["NSLocalizedDescription": "Config data is too large"]))) } - - // MARK: -- can catch size limit errors thrown when dumping - it("can catch size limit errors thrown when dumping") { - var randomGenerator: ARC4RandomNumberGenerator = ARC4RandomNumberGenerator(seed: 1000) - - try (0..<100000).forEach { index in - var contact: contacts_contact = try createContact( - for: index, - in: conf, - rand: &randomGenerator, - maxing: .allProperties - ) - contacts_set(conf, &contact) - } - - expect(contacts_size(conf)).to(equal(100000)) - expect(config_needs_push(conf)).to(beTrue()) - expect(config_needs_dump(conf)).to(beTrue()) - - expect { - try CExceptionHelper.performSafely { - var dump: UnsafeMutablePointer? = nil - var dumpLen: Int = 0 - config_dump(conf, &dump, &dumpLen) - dump?.deallocate() - } - } - .to(throwError(NSError(domain: "cpp_exception", code: -2, userInfo: ["NSLocalizedDescription": "Config data is too large"]))) - } } // MARK: - when checking size limits @@ -225,70 +196,6 @@ class ConfigContactsSpec { } } - // MARK: - when pruning - context("when pruning") { - var mockStorage: Storage! - var seed: Data! - var identity: (ed25519KeyPair: KeyPair, x25519KeyPair: KeyPair)! - var edSK: [UInt8]! - var error: UnsafeMutablePointer? - var conf: UnsafeMutablePointer? - - beforeEach { - mockStorage = Storage( - customWriter: try! DatabaseQueue(), - customMigrations: [ - SNUtilitiesKit.migrations(), - SNMessagingKit.migrations() - ] - ) - seed = Data(hex: "0123456789abcdef0123456789abcdef") - - // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately - identity = try! Identity.generate(from: seed) - edSK = identity.ed25519KeyPair.secretKey - - // Initialize a brand new, empty config because we have no dump data to deal with. - error = nil - conf = nil - _ = contacts_init(&conf, &edSK, nil, 0, error) - error?.deallocate() - } - - it("does something") { - mockStorage.write { db in - try SessionThread.fetchOrCreate(db, id: "1", variant: .contact, shouldBeVisible: true) - try SessionThread.fetchOrCreate(db, id: "2", variant: .contact, shouldBeVisible: true) - try SessionThread.fetchOrCreate(db, id: "3", variant: .contact, shouldBeVisible: true) - _ = try Interaction( - threadId: "1", - authorId: "1", - variant: .standardIncoming, - body: "Test1" - ).inserted(db) - _ = try Interaction( - threadId: "1", - authorId: "2", - variant: .standardIncoming, - body: "Test2" - ).inserted(db) - _ = try Interaction( - threadId: "3", - authorId: "3", - variant: .standardIncoming, - body: "Test3" - ).inserted(db) - - try SessionUtil.pruningIfNeeded( - db, - conf: conf - ) - - expect(contacts_size(conf)).to(equal(0)) - } - } - } - // MARK: - generates config correctly it("generates config correctly") { diff --git a/SessionUtilitiesKit/Database/Storage.swift b/SessionUtilitiesKit/Database/Storage.swift index 1ea913cb3..40b0380e9 100644 --- a/SessionUtilitiesKit/Database/Storage.swift +++ b/SessionUtilitiesKit/Database/Storage.swift @@ -55,9 +55,9 @@ open class Storage { // If a custom writer was provided then use that (for unit testing) guard customWriter == nil else { dbWriter = customWriter - perform(migrations: (customMigrations ?? []), async: false, onProgressUpdate: nil, onComplete: { _, _ in }) isValid = true Storage.internalHasCreatedValidInstance.mutate { $0 = true } + perform(migrations: (customMigrations ?? []), async: false, onProgressUpdate: nil, onComplete: { _, _ in }) return } From 53a5db0ea517beb771b6bb4c0f4ae807e34a13fd Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 23 Jun 2023 17:54:29 +1000 Subject: [PATCH 094/135] Fixed a number of issues found during internal testing Added copy for an unrecoverable startup case Added some additional logs to better debug ValueObservation query errors Increased the pageSize to 20 on iPad devices (to prevent it immediately loading a second page) Cleaned up a bunch of threading logic (try to avoid overriding subscribe/receive threads specified at subscription) Consolidated the 'sendMessage' and 'sendAttachments' functions Updated the various frameworks to use 'DAWRF with DSYM' to allow for better debugging during debug mode (at the cost of a longer build time) Updated the logic to optimistically insert messages when sending to avoid any database write delays Updated the logic to avoid sending notifications for messages which are already marked as read by the config Fixed an issue where multiple paths could incorrectly get built at the same time in some cases Fixed an issue where other job queues could be started before the blockingQueue finishes Fixed a potential bug with the snode version comparison (was just a string comparison which would fail when getting to double-digit values) Fixed a bug where you couldn't remove the last reaction on a message Fixed the broken media message zoom animations Fixed a bug where the last message read in a conversation wouldn't be correctly detected as already read Fixed a bug where the QuoteView had no line limits (resulting in the '@You' mention background highlight being incorrectly positioned in the quote preview) Fixed a bug where a large number of configSyncJobs could be scheduled (only one would run at a time but this could result in performance impacts) --- LibSession-Util | 2 +- Session.xcodeproj/project.pbxproj | 48 +- .../Context Menu/ContextMenuVC+Action.swift | 5 +- .../ConversationVC+Interaction.swift | 646 ++++++++---------- Session/Conversations/ConversationVC.swift | 1 + .../Conversations/ConversationViewModel.swift | 182 ++++- .../Conversations/Input View/InputView.swift | 1 + .../Content Views/QuoteView.swift | 56 +- ...isappearingMessagesSettingsViewModel.swift | 1 + .../Settings/ThreadSettingsViewModel.swift | 1 + Session/Home/HomeViewModel.swift | 3 +- .../MessageRequestsViewModel.swift | 2 +- Session/Home/New Conversation/NewDMVC.swift | 1 + .../GIFs/GifPickerViewController.swift | 3 + .../GIFs/GiphyAPI.swift | 2 - .../MediaGalleryViewModel.swift | 3 +- .../PhotoCapture.swift | 10 +- .../MediaDismissAnimationController.swift | 26 + .../MediaZoomAnimationController.swift | 25 + Session/Meta/AppDelegate.swift | 89 ++- .../Translations/de.lproj/Localizable.strings | 1 + .../Translations/en.lproj/Localizable.strings | 1 + .../Translations/es.lproj/Localizable.strings | 1 + .../Translations/fa.lproj/Localizable.strings | 1 + .../Translations/fi.lproj/Localizable.strings | 1 + .../Translations/fr.lproj/Localizable.strings | 1 + .../Translations/hi.lproj/Localizable.strings | 1 + .../Translations/hr.lproj/Localizable.strings | 1 + .../id-ID.lproj/Localizable.strings | 1 + .../Translations/it.lproj/Localizable.strings | 1 + .../Translations/ja.lproj/Localizable.strings | 1 + .../Translations/nl.lproj/Localizable.strings | 1 + .../Translations/pl.lproj/Localizable.strings | 1 + .../pt_BR.lproj/Localizable.strings | 1 + .../Translations/ru.lproj/Localizable.strings | 1 + .../Translations/si.lproj/Localizable.strings | 1 + .../Translations/sk.lproj/Localizable.strings | 1 + .../Translations/sv.lproj/Localizable.strings | 1 + .../Translations/th.lproj/Localizable.strings | 1 + .../vi-VN.lproj/Localizable.strings | 1 + .../zh-Hant.lproj/Localizable.strings | 1 + .../zh_CN.lproj/Localizable.strings | 1 + Session/Notifications/AppNotifications.swift | 2 - .../PushRegistrationManager.swift | 3 +- .../UserNotificationsAdaptee.swift | 2 + Session/Onboarding/DisplayNameVC.swift | 2 +- Session/Onboarding/Onboarding.swift | 7 +- Session/Onboarding/PNModeVC.swift | 1 + .../Open Groups/OpenGroupSuggestionGrid.swift | 5 +- .../ConversationSettingsViewModel.swift | 1 + Session/Settings/HelpViewModel.swift | 1 + .../NotificationContentViewModel.swift | 1 + .../NotificationSettingsViewModel.swift | 1 + .../Settings/NotificationSoundViewModel.swift | 1 + Session/Settings/NukeDataModal.swift | 3 +- .../Settings/PrivacySettingsViewModel.swift | 1 + Session/Settings/SettingsViewModel.swift | 3 +- .../Shared/SessionTableViewController.swift | 15 +- Session/Utilities/BackgroundPoller.swift | 54 +- SessionMessagingKit/Calls/WebRTCSession.swift | 2 + .../Database/Models/Attachment.swift | 98 +-- .../DisappearingMessageConfiguration.swift | 2 +- .../Database/Models/Interaction.swift | 14 +- .../Database/Models/LinkPreview.swift | 7 +- .../File Server/FileServerAPI.swift | 1 - .../Jobs/Types/AttachmentDownloadJob.swift | 29 +- .../Jobs/Types/ConfigurationSyncJob.swift | 30 +- .../Jobs/Types/GroupLeavingJob.swift | 1 + .../Jobs/Types/MessageSendJob.swift | 1 + .../Jobs/Types/SendReadReceiptsJob.swift | 1 + .../Open Groups/OpenGroupAPI.swift | 521 +++++++------- .../Open Groups/OpenGroupManager.swift | 128 ++-- .../Open Groups/Types/OpenGroupAPIError.swift | 2 + .../Open Groups/Types/PreparedSendData.swift | 125 ++++ SessionMessagingKit/SMKDependencies.swift | 7 +- .../MessageReceiver+Calls.swift | 55 +- ...eReceiver+DataExtractionNotification.swift | 15 +- .../MessageReceiver+ExpirationTimers.swift | 12 +- .../MessageReceiver+UnsendRequests.swift | 1 + .../MessageReceiver+VisibleMessages.swift | 23 +- .../MessageSender+Convenience.swift | 5 +- .../Sending & Receiving/MessageSender.swift | 19 +- .../Pollers/ClosedGroupPoller.swift | 8 +- .../Pollers/CurrentUserPoller.swift | 8 +- .../Pollers/OpenGroupPoller.swift | 42 +- .../Sending & Receiving/Pollers/Poller.swift | 54 +- .../SessionUtil+ConvoInfoVolatile.swift | 6 +- .../SessionUtil/SessionUtil.swift | 1 - .../Shared Models/MessageViewModel.swift | 102 ++- .../SessionThreadViewModel.swift | 19 +- .../Utilities/ProfileManager.swift | 21 +- SessionMessagingKit/Utilities/Threading.swift | 5 +- .../Open Groups/OpenGroupAPISpec.swift | 379 +++++----- .../Open Groups/OpenGroupManagerSpec.swift | 17 +- .../NotificationServiceExtension.swift | 24 +- SessionShareExtension/ThreadPickerVC.swift | 27 +- .../ThreadPickerViewModel.swift | 1 + SessionSnodeKit/Jobs/GetSnodePoolJob.swift | 2 +- SessionSnodeKit/Models/GetStatsResponse.swift | 16 + .../Networking/OnionRequestAPI.swift | 140 ++-- SessionSnodeKit/Networking/SnodeAPI.swift | 23 +- SessionSnodeKit/SSKDependencies.swift | 6 +- ...eadDisappearingMessagesViewModelSpec.swift | 10 +- .../ThreadSettingsViewModelSpec.swift | 8 +- .../NotificationContentViewModelSpec.swift | 6 +- .../HighlightMentionBackgroundView.swift | 2 +- .../Components/TopBannerController.swift | 18 +- .../Migrations/_001_ThemePreferences.swift | 10 +- SessionUIKit/Style Guide/ThemeManager.swift | 20 +- .../Combine/Publisher+Utilities.swift | 32 +- SessionUtilitiesKit/Database/Storage.swift | 19 +- .../Types/PagedDatabaseObserver.swift | 92 +-- .../General/Dependencies.swift | 20 +- SessionUtilitiesKit/JobRunner/JobRunner.swift | 33 +- SessionUtilitiesKit/Networking/Request.swift | 4 +- SessionUtilitiesKit/Utilities/Version.swift | 55 ++ .../Utilities/VersionSpec.swift | 98 +++ .../MediaMessageView.swift | 1 + _SharedTestUtilities/CombineExtensions.swift | 6 +- 119 files changed, 2290 insertions(+), 1376 deletions(-) create mode 100644 SessionMessagingKit/Open Groups/Types/PreparedSendData.swift create mode 100644 SessionSnodeKit/Models/GetStatsResponse.swift create mode 100644 SessionUtilitiesKit/Utilities/Version.swift create mode 100644 SessionUtilitiesKitTests/Utilities/VersionSpec.swift diff --git a/LibSession-Util b/LibSession-Util index 49c78682a..e0b994201 160000 --- a/LibSession-Util +++ b/LibSession-Util @@ -1 +1 @@ -Subproject commit 49c78682a6f4546c8773113f3e201244f0b1e65a +Subproject commit e0b994201a016cc5bf9065526a0ceb4291f60d5a diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 6ef13920c..0a5a4e81a 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -578,6 +578,10 @@ FD245C6B2850667400B966DD /* VisibleMessage+Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C300A5B12554AF9800555489 /* VisibleMessage+Profile.swift */; }; FD245C6C2850669200B966DD /* MessageReceiveJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = C352A31225574F5200338F3E /* MessageReceiveJob.swift */; }; FD245C6D285066A400B966DD /* NotifyPushServerJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = C352A32E2557549C00338F3E /* NotifyPushServerJob.swift */; }; + FD29598B2A43BB8100888A17 /* GetStatsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD29598A2A43BB8100888A17 /* GetStatsResponse.swift */; }; + FD29598D2A43BC0B00888A17 /* Version.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD29598C2A43BC0B00888A17 /* Version.swift */; }; + FD2959902A43BE5F00888A17 /* VersionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD29598F2A43BE5F00888A17 /* VersionSpec.swift */; }; + FD2959922A4417A900888A17 /* PreparedSendData.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2959912A4417A900888A17 /* PreparedSendData.swift */; }; FD2AAAED28ED3E1000A49611 /* MockGeneralCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */; }; FD2AAAEE28ED3E1100A49611 /* MockGeneralCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */; }; FD2AAAF028ED57B500A49611 /* SynchronousStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2AAAEF28ED57B500A49611 /* SynchronousStorage.swift */; }; @@ -1281,7 +1285,6 @@ 7BC707F127290ACB002817AD /* SessionCallManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCallManager.swift; sourceTree = ""; }; 7BCD116B27016062006330F1 /* WebRTCSession+DataChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebRTCSession+DataChannel.swift"; sourceTree = ""; }; 7BD477A727EC39F5004E2822 /* Atomic.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Atomic.swift; sourceTree = ""; }; - 7BD477A927F15F24004E2822 /* OpenGroupServerIdLookup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenGroupServerIdLookup.swift; sourceTree = ""; }; 7BDCFC0424206E7300641C39 /* SessionNotificationServiceExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SessionNotificationServiceExtension.entitlements; sourceTree = ""; }; 7BDCFC07242186E700641C39 /* NotificationServiceExtensionContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtensionContext.swift; sourceTree = ""; }; 7BFA8AE22831D0D4001876F3 /* ContextMenuVC+EmojiReactsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContextMenuVC+EmojiReactsView.swift"; sourceTree = ""; }; @@ -1725,6 +1728,10 @@ FD23EA6028ED0B260058676E /* CombineExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombineExtensions.swift; sourceTree = ""; }; FD245C612850664300B966DD /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; FD28A4F527EAD44C00FF65E7 /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = ""; }; + FD29598A2A43BB8100888A17 /* GetStatsResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetStatsResponse.swift; sourceTree = ""; }; + FD29598C2A43BC0B00888A17 /* Version.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Version.swift; sourceTree = ""; }; + FD29598F2A43BE5F00888A17 /* VersionSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionSpec.swift; sourceTree = ""; }; + FD2959912A4417A900888A17 /* PreparedSendData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreparedSendData.swift; sourceTree = ""; }; FD2AAAEF28ED57B500A49611 /* SynchronousStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SynchronousStorage.swift; sourceTree = ""; }; FD2B4AFA29429D1000AB4848 /* ConfigContactsSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigContactsSpec.swift; sourceTree = ""; }; FD2B4AFC294688D000AB4848 /* SessionUtil+Contacts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionUtil+Contacts.swift"; sourceTree = ""; }; @@ -3217,7 +3224,6 @@ FDC4381827B34EAD00C60D73 /* Models */, FDC4380727B31D3A00C60D73 /* Types */, FD3C907427E83AC200CD579F /* OpenGroupServerIdLookup.swift */, - 7BD477A927F15F24004E2822 /* OpenGroupServerIdLookup.swift */, B88FA7B726045D100049422F /* OpenGroupAPI.swift */, C3DB66AB260ACA42001EFC55 /* OpenGroupManager.swift */, ); @@ -3609,6 +3615,7 @@ FD8ECF93293856AF00C0D1BB /* Randomness.swift */, C33FDB38255A580B00E217F9 /* OWSBackgroundTask.h */, C33FDC1B255A581F00E217F9 /* OWSBackgroundTask.m */, + FD29598C2A43BC0B00888A17 /* Version.swift */, ); path = Utilities; sourceTree = ""; @@ -3784,6 +3791,14 @@ path = Utilities; sourceTree = ""; }; + FD29598E2A43BE5400888A17 /* Utilities */ = { + isa = PBXGroup; + children = ( + FD29598F2A43BE5F00888A17 /* VersionSpec.swift */, + ); + path = Utilities; + sourceTree = ""; + }; FD2B4B022949886900AB4848 /* Database */ = { isa = PBXGroup; children = ( @@ -4033,6 +4048,7 @@ FD37EA1228AB3F60003AE748 /* Database */, FD83B9B927CF20A5005E1583 /* General */, FD9B30F1293EA0AF008DEE3E /* Networking */, + FD29598E2A43BE5400888A17 /* Utilities */, ); path = SessionUtilitiesKitTests; sourceTree = ""; @@ -4167,6 +4183,7 @@ FDC4381F27B36ADC00C60D73 /* SOGSEndpoint.swift */, FDC4380827B31D4E00C60D73 /* OpenGroupAPIError.swift */, FDC4381627B32EC700C60D73 /* Personalization.swift */, + FD2959912A4417A900888A17 /* PreparedSendData.swift */, FDC4381427B329CE00C60D73 /* NonceGenerator.swift */, FDC438C227BB512200C60D73 /* SodiumProtocols.swift */, ); @@ -4395,6 +4412,7 @@ FDF848A629405C5A007DCAE5 /* ONSResolveRequest.swift */, FDF8489E29405C5A007DCAE5 /* ONSResolveResponse.swift */, FDF8489C29405C5A007DCAE5 /* GetServiceNodesRequest.swift */, + FD29598A2A43BB8100888A17 /* GetStatsResponse.swift */, ); path = Models; sourceTree = ""; @@ -5580,6 +5598,7 @@ FDF848CC29405C5B007DCAE5 /* SnodeReceivedMessage.swift in Sources */, FDF848C129405C5A007DCAE5 /* UpdateExpiryRequest.swift in Sources */, FDF848C729405C5B007DCAE5 /* SendMessageResponse.swift in Sources */, + FD29598B2A43BB8100888A17 /* GetStatsResponse.swift in Sources */, FDF848CA29405C5B007DCAE5 /* DeleteAllBeforeRequest.swift in Sources */, FDF848D229405C5B007DCAE5 /* LegacyGetMessagesRequest.swift in Sources */, FDF848CB29405C5B007DCAE5 /* SnodePoolResponse.swift in Sources */, @@ -5696,6 +5715,7 @@ C3D9E4F4256778AF0040E4F3 /* NSData+Image.m in Sources */, FDF222092818D2B0000A4995 /* NSAttributedString+Utilities.swift in Sources */, C32C5E0C256DDAFA003C73A2 /* NSRegularExpression+SSK.swift in Sources */, + FD29598D2A43BC0B00888A17 /* Version.swift in Sources */, FDF8487C29405906007DCAE5 /* HTTPMethod.swift in Sources */, FDF8488429405A2B007DCAE5 /* RequestInfo.swift in Sources */, C3BBE0A92554D4DE0050F1E3 /* HTTP.swift in Sources */, @@ -5892,6 +5912,7 @@ FD245C632850664600B966DD /* Configuration.swift in Sources */, C32C5DBF256DD743003C73A2 /* ClosedGroupPoller.swift in Sources */, C352A35B2557824E00338F3E /* AttachmentUploadJob.swift in Sources */, + FD2959922A4417A900888A17 /* PreparedSendData.swift in Sources */, FD5C7305284F0FF30029977D /* MessageReceiver+VisibleMessages.swift in Sources */, FD432437299DEA38008A0213 /* TypeConversion+Utilities.swift in Sources */, FD2B4B042949887A00AB4848 /* QueryInterfaceRequest+Utilities.swift in Sources */, @@ -6156,6 +6177,7 @@ FD0B77B229B82B7A009169BA /* ArrayUtilitiesSpec.swift in Sources */, FD1A94FE2900D2EA000D73D3 /* PersistableRecordUtilitiesSpec.swift in Sources */, FDC290AA27D9B6FD005DAE71 /* Mock.swift in Sources */, + FD2959902A43BE5F00888A17 /* VersionSpec.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -6395,7 +6417,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 408; + CURRENT_PROJECT_VERSION = 409; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6467,7 +6489,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 408; + CURRENT_PROJECT_VERSION = 409; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6532,7 +6554,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 408; + CURRENT_PROJECT_VERSION = 409; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6606,7 +6628,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 408; + CURRENT_PROJECT_VERSION = 409; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6671,7 +6693,7 @@ CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = dwarf; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = SUQ8J2PCT7; DYLIB_COMPATIBILITY_VERSION = 1; @@ -6808,7 +6830,7 @@ CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = dwarf; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = SUQ8J2PCT7; DYLIB_COMPATIBILITY_VERSION = 1; @@ -6959,7 +6981,7 @@ CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = dwarf; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = SUQ8J2PCT7; DYLIB_COMPATIBILITY_VERSION = 1; @@ -7096,7 +7118,7 @@ CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = dwarf; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = SUQ8J2PCT7; DYLIB_COMPATIBILITY_VERSION = 1; @@ -7235,7 +7257,7 @@ CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = dwarf; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = SUQ8J2PCT7; DYLIB_COMPATIBILITY_VERSION = 1; @@ -7514,7 +7536,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 408; + CURRENT_PROJECT_VERSION = 409; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -7585,7 +7607,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 408; + CURRENT_PROJECT_VERSION = 409; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/Session/Conversations/Context Menu/ContextMenuVC+Action.swift b/Session/Conversations/Context Menu/ContextMenuVC+Action.swift index 1c9265e90..bcef043e3 100644 --- a/Session/Conversations/Context Menu/ContextMenuVC+Action.swift +++ b/Session/Conversations/Context Menu/ContextMenuVC+Action.swift @@ -214,7 +214,10 @@ extension ContextMenuVC { let shouldShowEmojiActions: Bool = { if cellViewModel.threadVariant == .community { - return OpenGroupManager.isOpenGroupSupport(.reactions, on: cellViewModel.threadOpenGroupServer) + return OpenGroupManager.doesOpenGroupSupport( + capability: .reactions, + on: cellViewModel.threadOpenGroupServer + ) } return !currentThreadIsMessageRequest }() diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 315480a08..6c10b9da8 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -150,10 +150,17 @@ extension ConversationVC: } func sendMediaNav(_ sendMediaNavigationController: SendMediaNavigationController, didApproveAttachments attachments: [SignalAttachment], forThreadId threadId: String, messageText: String?) { - sendAttachments(attachments, with: messageText ?? "") - self.snInputView.text = "" + sendMessage(text: (messageText ?? ""), attachments: attachments) resetMentions() - dismiss(animated: true) { } + + dismiss(animated: true) { [weak self] in + if self?.isFirstResponder == false { + self?.becomeFirstResponder() + } + else { + self?.reloadInputViews() + } + } } func sendMediaNavInitialMessageText(_ sendMediaNavigationController: SendMediaNavigationController) -> String? { @@ -167,13 +174,17 @@ extension ConversationVC: // MARK: - AttachmentApprovalViewControllerDelegate func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, didApproveAttachments attachments: [SignalAttachment], forThreadId threadId: String, messageText: String?) { - sendAttachments(attachments, with: messageText ?? "") { [weak self] in - self?.dismiss(animated: true, completion: nil) - } - - scrollToBottom(isAnimated: false) - self.snInputView.text = "" + sendMessage(text: (messageText ?? ""), attachments: attachments) resetMentions() + + dismiss(animated: true) { [weak self] in + if self?.isFirstResponder == false { + self?.becomeFirstResponder() + } + else { + self?.reloadInputViews() + } + } } func attachmentApprovalDidCancel(_ attachmentApproval: AttachmentApprovalViewController) { @@ -181,7 +192,7 @@ extension ConversationVC: } func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, didChangeMessageText newMessageText: String?) { - snInputView.text = newMessageText ?? "" + snInputView.text = (newMessageText ?? "") } func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, didRemoveAttachment attachment: SignalAttachment) { @@ -348,6 +359,7 @@ extension ConversationVC: attachments: attachments, approvalDelegate: self ) + navController.modalPresentationStyle = .fullScreen present(navController, animated: true, completion: nil) } @@ -369,7 +381,7 @@ extension ConversationVC: modalActivityIndicator.dismiss { guard !attachment.hasError else { - self?.showErrorAlert(for: attachment, onDismiss: nil) + self?.showErrorAlert(for: attachment) return } @@ -385,149 +397,33 @@ extension ConversationVC: // MARK: --Message Sending func handleSendButtonTapped() { - sendMessage() - } - - func sendMessage(hasPermissionToSendSeed: Bool = false) { - guard !showBlockedModalIfNeeded() else { return } - - let text = replaceMentions(in: snInputView.text.trimmingCharacters(in: .whitespacesAndNewlines)) - - guard !text.isEmpty else { return } - - if text.contains(mnemonic) && !viewModel.threadData.threadIsNoteToSelf && !hasPermissionToSendSeed { - // Warn the user if they're about to send their seed to someone - let modal: ConfirmationModal = ConfirmationModal( - info: ConfirmationModal.Info( - title: "modal_send_seed_title".localized(), - body: .text("modal_send_seed_explanation".localized()), - confirmTitle: "modal_send_seed_send_button_title".localized(), - confirmStyle: .danger, - cancelStyle: .alert_text, - onConfirm: { [weak self] _ in self?.sendMessage(hasPermissionToSendSeed: true) } - ) - ) - - return present(modal, animated: true, completion: nil) - } - - // Clearing this out immediately to make this appear more snappy - DispatchQueue.main.async { [weak self] in - self?.snInputView.text = "" - self?.snInputView.quoteDraftInfo = nil - - self?.resetMentions() - } - - // Note: 'shouldBeVisible' is set to true the first time a thread is saved so we can - // use it to determine if the user is creating a new thread and update the 'isApproved' - // flags appropriately - let threadId: String = self.viewModel.threadData.threadId - let threadVariant: SessionThread.Variant = self.viewModel.threadData.threadVariant - let oldThreadShouldBeVisible: Bool = (self.viewModel.threadData.threadShouldBeVisible == true) - let sentTimestampMs: Int64 = SnodeAPI.currentOffsetTimestampMs() - let linkPreviewDraft: LinkPreviewDraft? = snInputView.linkPreviewInfo?.draft - let quoteModel: QuotedReplyModel? = snInputView.quoteDraftInfo?.model - - // If this was a message request then approve it - approveMessageRequestIfNeeded( - for: threadId, - threadVariant: self.viewModel.threadData.threadVariant, - isNewThread: !oldThreadShouldBeVisible, - timestampMs: (sentTimestampMs - 1) // Set 1ms earlier as this is used for sorting + sendMessage( + text: snInputView.text.trimmingCharacters(in: .whitespacesAndNewlines), + linkPreviewDraft: snInputView.linkPreviewInfo?.draft, + quoteModel: snInputView.quoteDraftInfo?.model ) - - // Send the message - Storage.shared - .writePublisher { [weak self] db in - // Let the viewModel know we are about to send a message - self?.viewModel.sentMessageBeforeUpdate = true - - // Update the thread to be visible (if it isn't already) - if self?.viewModel.threadData.threadShouldBeVisible == false { - _ = try SessionThread - .filter(id: threadId) - .updateAllAndConfig(db, SessionThread.Columns.shouldBeVisible.set(to: true)) - } - - let authorId: String = { - if let blindedId = self?.viewModel.threadData.currentUserBlindedPublicKey { - return blindedId - } - return self?.viewModel.threadData.currentUserPublicKey ?? getUserHexEncodedPublicKey(db) - }() - - // Create the interaction - let interaction: Interaction = try Interaction( - threadId: threadId, - authorId: authorId, - variant: .standardOutgoing, - body: text, - timestampMs: sentTimestampMs, - hasMention: Interaction.isUserMentioned(db, threadId: threadId, body: text), - expiresInSeconds: try? DisappearingMessagesConfiguration - .select(.durationSeconds) - .filter(id: threadId) - .filter(DisappearingMessagesConfiguration.Columns.isEnabled == true) - .asRequest(of: TimeInterval.self) - .fetchOne(db), - linkPreviewUrl: linkPreviewDraft?.urlString - ).inserted(db) - - // If there is a LinkPreview and it doesn't match an existing one then add it now - if - let linkPreviewDraft: LinkPreviewDraft = linkPreviewDraft, - (try? interaction.linkPreview.isEmpty(db)) == true - { - try LinkPreview( - url: linkPreviewDraft.urlString, - title: linkPreviewDraft.title, - attachmentId: LinkPreview.saveAttachmentIfPossible( - db, - imageData: linkPreviewDraft.jpegImageData, - mimeType: OWSMimeTypeImageJpeg - ) - ).insert(db) - } - - // If there is a Quote the insert it now - if let interactionId: Int64 = interaction.id, let quoteModel: QuotedReplyModel = quoteModel { - try Quote( - interactionId: interactionId, - authorId: quoteModel.authorId, - timestampMs: quoteModel.timestampMs, - body: quoteModel.body, - attachmentId: quoteModel.generateAttachmentThumbnailIfNeeded(db) - ).insert(db) - } - - try MessageSender.send( - db, - interaction: interaction, - threadId: threadId, - threadVariant: threadVariant - ) - } - .subscribe(on: DispatchQueue.global(qos: .userInitiated)) - .sinkUntilComplete( - receiveCompletion: { [weak self] _ in - self?.handleMessageSent() - } - ) } - func sendAttachments(_ attachments: [SignalAttachment], with text: String, hasPermissionToSendSeed: Bool = false, onComplete: (() -> ())? = nil) { + func sendMessage( + text: String, + attachments: [SignalAttachment] = [], + linkPreviewDraft: LinkPreviewDraft? = nil, + quoteModel: QuotedReplyModel? = nil, + hasPermissionToSendSeed: Bool = false + ) { guard !showBlockedModalIfNeeded() else { return } - for attachment in attachments { - if attachment.hasError { - return showErrorAlert(for: attachment, onDismiss: onComplete) - } + // Handle attachment errors if applicable + if let failedAttachment: SignalAttachment = attachments.first(where: { $0.hasError }) { + return showErrorAlert(for: failedAttachment) } - let text = replaceMentions(in: snInputView.text.trimmingCharacters(in: .whitespacesAndNewlines)) + let processedText: String = replaceMentions(in: text.trimmingCharacters(in: .whitespacesAndNewlines)) - if text.contains(mnemonic) && !viewModel.threadData.threadIsNoteToSelf && !hasPermissionToSendSeed { + // If we have no content then do nothing + guard !processedText.isEmpty || !attachments.isEmpty else { return } + + if processedText.contains(mnemonic) && !viewModel.threadData.threadIsNoteToSelf && !hasPermissionToSendSeed { // Warn the user if they're about to send their seed to someone let modal: ConfirmationModal = ConfirmationModal( info: ConfirmationModal.Info( @@ -537,7 +433,13 @@ extension ConversationVC: confirmStyle: .danger, cancelStyle: .alert_text, onConfirm: { [weak self] _ in - self?.sendAttachments(attachments, with: text, hasPermissionToSendSeed: true, onComplete: onComplete) + self?.sendMessage( + text: text, + attachments: attachments, + linkPreviewDraft: linkPreviewDraft, + quoteModel: quoteModel, + hasPermissionToSendSeed: true + ) } ) ) @@ -564,17 +466,24 @@ extension ConversationVC: // If this was a message request then approve it approveMessageRequestIfNeeded( for: threadId, - threadVariant: self.viewModel.threadData.threadVariant, + threadVariant: threadVariant, isNewThread: !oldThreadShouldBeVisible, timestampMs: (sentTimestampMs - 1) // Set 1ms earlier as this is used for sorting ) - // Send the message + // Optimistically insert the outgoing message (this will trigger a UI update) + self.viewModel.sentMessageBeforeUpdate = true + let optimisticData: ConversationViewModel.OptimisticMessageData = self.viewModel.optimisticallyAppendOutgoingMessage( + text: processedText, + sentTimestampMs: sentTimestampMs, + attachments: attachments, + linkPreviewDraft: linkPreviewDraft, + quoteModel: quoteModel + ) + + // Actually send the message Storage.shared .writePublisher { [weak self] db in - // Let the viewModel know we are about to send a message - self?.viewModel.sentMessageBeforeUpdate = true - // Update the thread to be visible (if it isn't already) if self?.viewModel.threadData.threadShouldBeVisible == false { _ = try SessionThread @@ -582,35 +491,44 @@ extension ConversationVC: .updateAllAndConfig(db, SessionThread.Columns.shouldBeVisible.set(to: true)) } - // Create the interaction - let interaction: Interaction = try Interaction( - threadId: threadId, - authorId: getUserHexEncodedPublicKey(db), - variant: .standardOutgoing, - body: text, - timestampMs: sentTimestampMs, - hasMention: Interaction.isUserMentioned(db, threadId: threadId, body: text), - expiresInSeconds: try? DisappearingMessagesConfiguration - .select(.durationSeconds) - .filter(id: threadId) - .filter(DisappearingMessagesConfiguration.Columns.isEnabled == true) - .asRequest(of: TimeInterval.self) - .fetchOne(db) - ).inserted(db) + // Insert the interaction and associated it with the optimistically inserted message so + // we can remove it once the database triggers a UI update + let insertedInteraction: Interaction = try optimisticData.interaction.inserted(db) + self?.viewModel.associate(optimisticMessageId: optimisticData.id, to: insertedInteraction.id) - guard let interactionId: Int64 = interaction.id else { return } + // If there is a LinkPreview and it doesn't match an existing one then add it now + if + let linkPreviewDraft: LinkPreviewDraft = linkPreviewDraft, + (try? insertedInteraction.linkPreview.isEmpty(db)) == true + { + try LinkPreview( + url: linkPreviewDraft.urlString, + title: linkPreviewDraft.title, + attachmentId: try optimisticData.linkPreviewAttachment?.inserted(db).id + ).insert(db) + } - // Prepare any attachments - try Attachment.prepare( + // If there is a Quote the insert it now + if let interactionId: Int64 = insertedInteraction.id, let quoteModel: QuotedReplyModel = quoteModel { + try Quote( + interactionId: interactionId, + authorId: quoteModel.authorId, + timestampMs: quoteModel.timestampMs, + body: quoteModel.body, + attachmentId: quoteModel.generateAttachmentThumbnailIfNeeded(db) + ).insert(db) + } + + // Process any attachments + try Attachment.process( db, - attachments: attachments, - for: interactionId + data: optimisticData.attachmentData, + for: insertedInteraction.id ) - // Send the message try MessageSender.send( db, - interaction: interaction, + interaction: insertedInteraction, threadId: threadId, threadVariant: threadVariant ) @@ -619,11 +537,6 @@ extension ConversationVC: .sinkUntilComplete( receiveCompletion: { [weak self] _ in self?.handleMessageSent() - - // Attachment successfully sent - dismiss the screen - DispatchQueue.main.async { - onComplete?() - } } ) } @@ -1212,7 +1125,7 @@ extension ConversationVC: guard cellViewModel.threadVariant == .community else { return } Storage.shared - .readPublisherFlatMap { db -> AnyPublisher<(OpenGroupAPI.ReactionRemoveAllResponse, OpenGroupAPI.PendingChange), Error> in + .readPublisher { db -> (OpenGroupAPI.PreparedSendData, OpenGroupAPI.PendingChange) in guard let openGroup: OpenGroup = try? OpenGroup .fetchOne(db, id: cellViewModel.threadId), @@ -1223,6 +1136,14 @@ extension ConversationVC: .fetchOne(db) else { throw StorageError.objectNotFound } + let sendData: OpenGroupAPI.PreparedSendData = try OpenGroupAPI + .preparedReactionDeleteAll( + db, + emoji: emoji, + id: openGroupServerMessageId, + in: openGroup.roomToken, + on: openGroup.server + ) let pendingChange: OpenGroupAPI.PendingChange = OpenGroupManager .addPendingReaction( emoji: emoji, @@ -1232,27 +1153,22 @@ extension ConversationVC: type: .removeAll ) - return OpenGroupAPI - .reactionDeleteAll( - db, - emoji: emoji, - id: openGroupServerMessageId, - in: openGroup.roomToken, - on: openGroup.server - ) - .map { _, response in (response, pendingChange) } - .eraseToAnyPublisher() + return (sendData, pendingChange) } .subscribe(on: DispatchQueue.global(qos: .userInitiated)) - .handleEvents( - receiveOutput: { response, pendingChange in - OpenGroupManager - .updatePendingChange( - pendingChange, - seqNo: response.seqNo - ) - } - ) + .flatMap { sendData, pendingChange in + OpenGroupAPI.send(data: sendData) + .handleEvents( + receiveOutput: { _, response in + OpenGroupManager + .updatePendingChange( + pendingChange, + seqNo: response.seqNo + ) + } + ) + .eraseToAnyPublisher() + } .sinkUntilComplete( receiveCompletion: { _ in Storage.shared.writeAsync { db in @@ -1266,14 +1182,16 @@ extension ConversationVC: } func react(_ cellViewModel: MessageViewModel, with emoji: String, remove: Bool) { - guard cellViewModel.variant == .standardIncoming || cellViewModel.variant == .standardOutgoing else { - return - } - - let threadIsMessageRequest: Bool = (self.viewModel.threadData.threadIsMessageRequest == true) - guard !threadIsMessageRequest else { return } + guard + self.viewModel.threadData.threadIsMessageRequest != true && ( + cellViewModel.variant == .standardIncoming || + cellViewModel.variant == .standardOutgoing + ) + else { return } // Perform local rate limiting (don't allow more than 20 reactions within 60 seconds) + let threadVariant: SessionThread.Variant = self.viewModel.threadData.threadVariant + let openGroupRoom: String? = self.viewModel.threadData.openGroupRoomToken let sentTimestamp: Int64 = SnodeAPI.currentOffsetTimestampMs() let recentReactionTimestamps: [Int64] = General.cache.wrappedValue.recentReactionTimestamps @@ -1299,9 +1217,38 @@ extension ConversationVC: .appending(sentTimestamp) } - // Perform the sending logic - Storage.shared - .writePublisherFlatMap { [weak self] db -> AnyPublisher in + typealias OpenGroupInfo = ( + pendingReaction: Reaction?, + pendingChange: OpenGroupAPI.PendingChange, + sendData: OpenGroupAPI.PreparedSendData + ) + + /// Perform the sending logic, we generate the pending reaction first in a deferred future closure to prevent the OpenGroup + /// cache from blocking either the main thread or the database write thread + Deferred { + Future { resolver in + guard + threadVariant == .community, + let serverMessageId: Int64 = cellViewModel.openGroupServerMessageId, + let openGroupServer: String = cellViewModel.threadOpenGroupServer, + let openGroupPublicKey: String = cellViewModel.threadOpenGroupPublicKey + else { return resolver(Result.success(nil)) } + + // Create the pending change if we have open group info + return resolver(Result.success( + OpenGroupManager.addPendingReaction( + emoji: emoji, + id: serverMessageId, + in: openGroupServer, + on: openGroupPublicKey, + type: (remove ? .remove : .add) + ) + )) + } + } + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) + .flatMap { pendingChange -> AnyPublisher<(MessageSender.PreparedSendData?, OpenGroupInfo?), Error> in + Storage.shared.writePublisher { [weak self] db -> (MessageSender.PreparedSendData?, OpenGroupInfo?) in // Update the thread to be visible (if it isn't already) if self?.viewModel.threadData.threadShouldBeVisible == false { _ = try SessionThread @@ -1310,29 +1257,29 @@ extension ConversationVC: } let pendingReaction: Reaction? = { - if remove { + guard !remove else { return try? Reaction .filter(Reaction.Columns.interactionId == cellViewModel.id) .filter(Reaction.Columns.authorId == cellViewModel.currentUserPublicKey) .filter(Reaction.Columns.emoji == emoji) .fetchOne(db) - } else { - let sortId = Reaction.getSortId( - db, - interactionId: cellViewModel.id, - emoji: emoji - ) - - return Reaction( - interactionId: cellViewModel.id, - serverHash: nil, - timestampMs: sentTimestamp, - authorId: cellViewModel.currentUserPublicKey, - emoji: emoji, - count: 1, - sortId: sortId - ) } + + let sortId: Int64 = Reaction.getSortId( + db, + interactionId: cellViewModel.id, + emoji: emoji + ) + + return Reaction( + interactionId: cellViewModel.id, + serverHash: nil, + timestampMs: sentTimestamp, + authorId: cellViewModel.currentUserPublicKey, + emoji: emoji, + count: 1, + sortId: sortId + ) }() // Update the database @@ -1350,125 +1297,108 @@ extension ConversationVC: Emoji.addRecent(db, emoji: emoji) } - // If it's not an OpenGroup then send the message directly to the thread - guard - let openGroup: OpenGroup = try? OpenGroup.fetchOne(db, id: cellViewModel.threadId), - OpenGroupManager.isOpenGroupSupport(.reactions, on: openGroup.server) - else { - let sendData: MessageSender.PreparedSendData = try MessageSender.preparedSendData( - db, - message: VisibleMessage( - sentTimestamp: UInt64(sentTimestamp), - text: nil, - reaction: VisibleMessage.VMReaction( - timestamp: UInt64(cellViewModel.timestampMs), - publicKey: { - guard cellViewModel.variant == .standardIncoming else { - return cellViewModel.currentUserPublicKey - } - - return cellViewModel.authorId - }(), - emoji: emoji, - kind: (remove ? .remove : .react) - ) - ), - to: try Message.Destination - .from(db, threadId: cellViewModel.threadId, threadVariant: cellViewModel.threadVariant), - namespace: try Message.Destination - .from(db, threadId: cellViewModel.threadId, threadVariant: cellViewModel.threadVariant) - .defaultNamespace, - interactionId: cellViewModel.id - ) - - return Just(sendData) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - } - - // Otherwise we need to make an API call to the OpenGroup - // Send reaction to open groups - guard - let openGroupServerMessageId: Int64 = try? Interaction - .select(.openGroupServerMessageId) - .filter(id: cellViewModel.id) - .asRequest(of: Int64.self) - .fetchOne(db) - else { throw MessageSenderError.invalidMessage } - - let pendingChange: OpenGroupAPI.PendingChange = OpenGroupManager - .addPendingReaction( - emoji: emoji, - id: openGroupServerMessageId, - in: openGroup.roomToken, - on: openGroup.server, - type: (remove ? .remove : .add) - ) - - let request: AnyPublisher = { - switch remove { - case true: - return OpenGroupAPI - .reactionDelete( - db, - emoji: emoji, - id: openGroupServerMessageId, - in: openGroup.roomToken, - on: openGroup.server - ) - .map { _, response in response.seqNo } - .eraseToAnyPublisher() - - case false: - return OpenGroupAPI - .reactionAdd( - db, - emoji: emoji, - id: openGroupServerMessageId, - in: openGroup.roomToken, - on: openGroup.server - ) - .map { _, response in response.seqNo } - .eraseToAnyPublisher() - } - }() - - return request - .handleEvents( - receiveOutput: { seqNo in - OpenGroupManager - .updatePendingChange( - pendingChange, - seqNo: seqNo - ) - }, - receiveCompletion: { [weak self] result in - switch result { - case .finished: break - case .failure: - OpenGroupManager.removePendingChange(pendingChange) - - self?.handleReactionSentFailure( - pendingReaction, - remove: remove + switch threadVariant { + case .community: + guard + let serverMessageId: Int64 = cellViewModel.openGroupServerMessageId, + let openGroupServer: String = cellViewModel.threadOpenGroupServer, + let openGroupRoom: String = openGroupRoom, + let pendingChange: OpenGroupAPI.PendingChange = pendingChange, + OpenGroupManager.doesOpenGroupSupport(db, capability: .reactions, on: openGroupServer) + else { throw MessageSenderError.invalidMessage } + + let sendData: OpenGroupAPI.PreparedSendData = try { + guard !remove else { + return try OpenGroupAPI + .preparedReactionDelete( + db, + emoji: emoji, + id: serverMessageId, + in: openGroupRoom, + on: openGroupServer ) + .map { _, response in response.seqNo } } - } - ) - .map { _ in nil } - .eraseToAnyPublisher() - } - .subscribe(on: DispatchQueue.global(qos: .userInitiated)) - .flatMap { maybeSendData -> AnyPublisher in - guard let sendData: MessageSender.PreparedSendData = maybeSendData else { - return Just(()) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() + + return try OpenGroupAPI + .preparedReactionAdd( + db, + emoji: emoji, + id: serverMessageId, + in: openGroupRoom, + on: openGroupServer + ) + .map { _, response in response.seqNo } + }() + + return (nil, (pendingReaction, pendingChange, sendData)) + + default: + let sendData: MessageSender.PreparedSendData = try MessageSender.preparedSendData( + db, + message: VisibleMessage( + sentTimestamp: UInt64(sentTimestamp), + text: nil, + reaction: VisibleMessage.VMReaction( + timestamp: UInt64(cellViewModel.timestampMs), + publicKey: { + guard cellViewModel.variant == .standardIncoming else { + return cellViewModel.currentUserPublicKey + } + + return cellViewModel.authorId + }(), + emoji: emoji, + kind: (remove ? .remove : .react) + ) + ), + to: try Message.Destination + .from(db, threadId: cellViewModel.threadId, threadVariant: cellViewModel.threadVariant), + namespace: try Message.Destination + .from(db, threadId: cellViewModel.threadId, threadVariant: cellViewModel.threadVariant) + .defaultNamespace, + interactionId: cellViewModel.id + ) + + return (sendData, nil) } - - return MessageSender.sendImmediate(preparedSendData: sendData) } - .sinkUntilComplete() + } + .tryFlatMap { messageSendData, openGroupInfo -> AnyPublisher in + switch (messageSendData, openGroupInfo) { + case (.some(let sendData), _): + return MessageSender.sendImmediate(preparedSendData: sendData) + + case (_, .some(let info)): + return OpenGroupAPI.send(data: info.sendData) + .handleEvents( + receiveOutput: { _, seqNo in + OpenGroupManager + .updatePendingChange( + info.pendingChange, + seqNo: seqNo + ) + }, + receiveCompletion: { [weak self] result in + switch result { + case .finished: break + case .failure: + OpenGroupManager.removePendingChange(info.pendingChange) + + self?.handleReactionSentFailure( + info.pendingReaction, + remove: remove + ) + } + } + ) + .map { _ in () } + .eraseToAnyPublisher() + + default: throw MessageSenderError.invalidMessage + } + } + .sinkUntilComplete() } func handleReactionSentFailure(_ pendingReaction: Reaction?, remove: Bool) { @@ -1891,16 +1821,18 @@ extension ConversationVC: // Delete the message from the open group deleteRemotely( from: self, - request: Storage.shared.readPublisherFlatMap { db in - OpenGroupAPI.messageDelete( - db, - id: openGroupServerMessageId, - in: openGroup.roomToken, - on: openGroup.server - ) + request: Storage.shared + .readPublisher { db in + try OpenGroupAPI.preparedMessageDelete( + db, + id: openGroupServerMessageId, + in: openGroup.roomToken, + on: openGroup.server + ) + } + .flatMap { OpenGroupAPI.send(data: $0) } .map { _ in () } .eraseToAnyPublisher() - } ) { [weak self] in self?.showInputAccessoryView() } @@ -2100,21 +2032,20 @@ extension ConversationVC: cancelStyle: .alert_text, onConfirm: { [weak self] _ in Storage.shared - .readPublisherFlatMap { db -> AnyPublisher in + .readPublisher { db -> OpenGroupAPI.PreparedSendData in guard let openGroup: OpenGroup = try OpenGroup.fetchOne(db, id: threadId) else { throw StorageError.objectNotFound } - return OpenGroupAPI - .userBan( + return try OpenGroupAPI + .preparedUserBan( db, sessionId: cellViewModel.authorId, from: [openGroup.roomToken], on: openGroup.server ) - .map { _ in () } - .eraseToAnyPublisher() } + .flatMap { OpenGroupAPI.send(data: $0) } .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .receive(on: DispatchQueue.main) .sinkUntilComplete( @@ -2316,11 +2247,11 @@ extension ConversationVC: let attachment = SignalAttachment.voiceMessageAttachment(dataSource: dataSource, dataUTI: kUTTypeMPEG4Audio as String) guard !attachment.hasError else { - return showErrorAlert(for: attachment, onDismiss: nil) + return showErrorAlert(for: attachment) } // Send attachment - sendAttachments([ attachment ], with: "") + sendMessage(text: "", attachments: [attachment]) } func cancelVoiceMessageRecording() { @@ -2360,15 +2291,14 @@ extension ConversationVC: // MARK: - Convenience - func showErrorAlert(for attachment: SignalAttachment, onDismiss: (() -> ())?) { + func showErrorAlert(for attachment: SignalAttachment) { let modal: ConfirmationModal = ConfirmationModal( targetView: self.view, info: ConfirmationModal.Info( title: "ATTACHMENT_ERROR_ALERT_TITLE".localized(), body: .text(attachment.localizedErrorDescription ?? SignalAttachment.missingDataErrorMessage), cancelTitle: "BUTTON_OK".localized(), - cancelStyle: .alert_text, - afterClosed: onDismiss + cancelStyle: .alert_text ) ) self.present(modal, animated: true) diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index c52f8d1cb..c655c3e89 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -890,6 +890,7 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers guard !didSendMessageBeforeUpdate && !wasOnlyUpdates else { self.viewModel.updateInteractionData(updatedData) self.tableView.reloadData() + self.tableView.layoutIfNeeded() // If we just sent a message then we want to jump to the bottom of the conversation instantly if didSendMessageBeforeUpdate { diff --git a/Session/Conversations/ConversationViewModel.swift b/Session/Conversations/ConversationViewModel.swift index 63ae30e30..0c898f941 100644 --- a/Session/Conversations/ConversationViewModel.swift +++ b/Session/Conversations/ConversationViewModel.swift @@ -176,9 +176,10 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { /// 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>> = setupObservableThreadData(for: self.threadId) + public typealias ThreadObservation = ValueObservation>>> + public lazy var observableThreadData: ThreadObservation = setupObservableThreadData(for: self.threadId) - private func setupObservableThreadData(for threadId: String) -> ValueObservation>> { + private func setupObservableThreadData(for threadId: String) -> ThreadObservation { return ValueObservation .trackingConstantRegion { [weak self] db -> SessionThreadViewModel? in let userPublicKey: String = getUserHexEncodedPublicKey(db) @@ -197,6 +198,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { } } .removeDuplicates() + .handleEvents(didFail: { SNLog("[ConversationViewModel] Observation failed with error: \($0)") }) } public func updateThreadData(_ updatedData: SessionThreadViewModel) { @@ -314,8 +316,16 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { ) ], onChangeUnsorted: { [weak self] updatedData, updatedPageInfo in + self?.resolveOptimisticUpdates(with: updatedData) + PagedData.processAndTriggerUpdates( - updatedData: self?.process(data: updatedData, for: updatedPageInfo), + updatedData: self?.process( + data: updatedData, + for: updatedPageInfo, + optimisticMessages: (self?.optimisticallyInsertedMessages.wrappedValue.values) + .map { Array($0) }, + initialUnreadInteractionId: self?.initialUnreadInteractionId + ), currentDataRetriever: { self?.interactionData }, onDataChange: self?.onInteractionChange, onUnobservedDataChange: { updatedData, changeset in @@ -329,11 +339,16 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { ) } - private func process(data: [MessageViewModel], for pageInfo: PagedData.PageInfo) -> [SectionModel] { - let initialUnreadInteractionId: Int64? = self.initialUnreadInteractionId + private func process( + data: [MessageViewModel], + for pageInfo: PagedData.PageInfo, + optimisticMessages: [MessageViewModel]?, + initialUnreadInteractionId: Int64? + ) -> [SectionModel] { let typingIndicator: MessageViewModel? = data.first(where: { $0.isTypingIndicator == true }) let sortedData: [MessageViewModel] = data - .filter { $0.isTypingIndicator != true } + .appending(contentsOf: (optimisticMessages ?? [])) + .filter { !$0.cellType.isPostProcessed } .sorted { lhs, rhs -> Bool in lhs.timestampMs < rhs.timestampMs } // We load messages from newest to oldest so having a pageOffset larger than zero means @@ -408,12 +423,151 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { self.interactionData = updatedData } - public func expandReactions(for interactionId: Int64) { - reactionExpandedInteractionIds.insert(interactionId) + // MARK: - Optimistic Message Handling + + public typealias OptimisticMessageData = ( + id: UUID, + interaction: Interaction, + attachmentData: Attachment.PreparedData?, + linkPreviewAttachment: Attachment? + ) + + private var optimisticallyInsertedMessages: Atomic<[UUID: MessageViewModel]> = Atomic([:]) + private var optimisticMessageAssociatedInteractionIds: Atomic<[Int64: UUID]> = Atomic([:]) + + public func optimisticallyAppendOutgoingMessage( + text: String?, + sentTimestampMs: Int64, + attachments: [SignalAttachment]?, + linkPreviewDraft: LinkPreviewDraft?, + quoteModel: QuotedReplyModel? + ) -> OptimisticMessageData { + // Generate the optimistic data + let optimisticMessageId: UUID = UUID() + let currentUserProfile: Profile = Profile.fetchOrCreateCurrentUser() + let interaction: Interaction = Interaction( + threadId: threadData.threadId, + authorId: (threadData.currentUserBlindedPublicKey ?? threadData.currentUserPublicKey), + variant: .standardOutgoing, + body: text, + timestampMs: sentTimestampMs, + hasMention: Interaction.isUserMentioned( + publicKeysToCheck: [ + threadData.currentUserPublicKey, + threadData.currentUserBlindedPublicKey + ].compactMap { $0 }, + body: text + ), + expiresInSeconds: threadData.disappearingMessagesConfiguration + .map { disappearingConfig in + guard disappearingConfig.isEnabled else { return nil } + + return disappearingConfig.durationSeconds + }, + linkPreviewUrl: linkPreviewDraft?.urlString + ) + let optimisticAttachments: Attachment.PreparedData? = attachments + .map { Attachment.prepare(attachments: $0) } + let linkPreviewAttachment: Attachment? = linkPreviewDraft.map { draft in + try? LinkPreview.generateAttachmentIfPossible( + imageData: draft.jpegImageData, + mimeType: OWSMimeTypeImageJpeg + ) + } + let optimisticData: OptimisticMessageData = ( + optimisticMessageId, + interaction, + optimisticAttachments, + linkPreviewAttachment + ) + + // Generate the actual 'MessageViewModel' + let messageViewModel: MessageViewModel = MessageViewModel( + threadId: threadData.threadId, + threadVariant: threadData.threadVariant, + threadHasDisappearingMessagesEnabled: (threadData.disappearingMessagesConfiguration?.isEnabled ?? false), + threadOpenGroupServer: threadData.openGroupServer, + threadOpenGroupPublicKey: threadData.openGroupPublicKey, + threadContactNameInternal: threadData.threadContactName(), + timestampMs: interaction.timestampMs, + receivedAtTimestampMs: interaction.receivedAtTimestampMs, + authorId: interaction.authorId, + authorNameInternal: currentUserProfile.displayName(), + body: interaction.body, + expiresStartedAtMs: interaction.expiresStartedAtMs, + expiresInSeconds: interaction.expiresInSeconds, + isSenderOpenGroupModerator: OpenGroupManager.isUserModeratorOrAdmin( + threadData.currentUserPublicKey, + for: threadData.openGroupRoomToken, + on: threadData.openGroupServer + ), + currentUserProfile: currentUserProfile, + quote: quoteModel.map { model in + // Don't care about this optimistic quote (the proper one will be generated in the database) + Quote( + interactionId: -1, // Can't save to db optimistically + authorId: model.authorId, + timestampMs: model.timestampMs, + body: model.body, + attachmentId: model.attachment?.id + ) + }, + quoteAttachment: quoteModel?.attachment, + linkPreview: linkPreviewDraft.map { draft in + LinkPreview( + url: draft.urlString, + title: draft.title, + attachmentId: nil // Can't save to db optimistically + ) + }, + linkPreviewAttachment: linkPreviewAttachment, + attachments: optimisticAttachments?.attachments + ) + + optimisticallyInsertedMessages.mutate { $0[optimisticMessageId] = messageViewModel } + + // If we can't get the current page data then don't bother trying to update (it's not going to work) + guard let currentPageInfo: PagedData.PageInfo = self.pagedDataObserver?.pageInfo.wrappedValue else { + return optimisticData + } + + /// **MUST** have the same logic as in the 'PagedDataObserver.onChangeUnsorted' above + let currentData: [SectionModel] = (unobservedInteractionDataChanges?.0 ?? interactionData) + + PagedData.processAndTriggerUpdates( + updatedData: process( + data: (currentData.first(where: { $0.model == .messages })?.elements ?? []), + for: currentPageInfo, + optimisticMessages: Array(optimisticallyInsertedMessages.wrappedValue.values), + initialUnreadInteractionId: initialUnreadInteractionId + ), + currentDataRetriever: { [weak self] in self?.interactionData }, + onDataChange: self.onInteractionChange, + onUnobservedDataChange: { [weak self] updatedData, changeset in + self?.unobservedInteractionDataChanges = (changeset.isEmpty ? + nil : + (updatedData, changeset) + ) + } + ) + + return optimisticData } - public func collapseReactions(for interactionId: Int64) { - reactionExpandedInteractionIds.remove(interactionId) + /// Record an association between an `optimisticMessageId` and a specific `interactionId` + public func associate(optimisticMessageId: UUID, to interactionId: Int64?) { + guard let interactionId: Int64 = interactionId else { return } + + optimisticMessageAssociatedInteractionIds.mutate { $0[interactionId] = optimisticMessageId } + } + + /// Remove any optimisticUpdate entries which have an associated interactionId in the provided data + private func resolveOptimisticUpdates(with data: [MessageViewModel]) { + let interactionIds: [Int64] = data.map { $0.id } + let idsToRemove: [UUID] = optimisticMessageAssociatedInteractionIds + .mutate { associatedIds in interactionIds.compactMap { associatedIds.removeValue(forKey: $0) } } + + optimisticallyInsertedMessages.mutate { messages in idsToRemove.forEach { messages.removeValue(forKey: $0) } } } // MARK: - Mentions @@ -575,6 +729,14 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { } } + public func expandReactions(for interactionId: Int64) { + reactionExpandedInteractionIds.insert(interactionId) + } + + public func collapseReactions(for interactionId: Int64) { + reactionExpandedInteractionIds.remove(interactionId) + } + // MARK: - Audio Playback public struct PlaybackInfo { diff --git a/Session/Conversations/Input View/InputView.swift b/Session/Conversations/Input View/InputView.swift index 892e3e525..5ea9ceddd 100644 --- a/Session/Conversations/Input View/InputView.swift +++ b/Session/Conversations/Input View/InputView.swift @@ -332,6 +332,7 @@ final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, M // Build the link preview LinkPreview.tryToBuildPreviewInfo(previewUrl: linkPreviewURL) + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .receive(on: DispatchQueue.main) .sink( receiveCompletion: { [weak self] result in diff --git a/Session/Conversations/Message Cells/Content Views/QuoteView.swift b/Session/Conversations/Message Cells/Content Views/QuoteView.swift index 91eda8b7e..08e300b08 100644 --- a/Session/Conversations/Message Cells/Content Views/QuoteView.swift +++ b/Session/Conversations/Message Cells/Content Views/QuoteView.swift @@ -119,17 +119,6 @@ final class QuoteView: UIView { contentView.pin([ UIView.HorizontalEdge.left, UIView.VerticalEdge.top, UIView.VerticalEdge.bottom ], to: self) contentView.rightAnchor.constraint(lessThanOrEqualTo: self.rightAnchor).isActive = true - // Line view - let lineColor: ThemeValue = { - switch mode { - case .regular: return (direction == .outgoing ? .messageBubble_outgoingText : .primary) - case .draft: return .primary - } - }() - let lineView = UIView() - lineView.themeBackgroundColor = lineColor - lineView.set(.width, to: Values.accentLineThickness) - if let attachment: Attachment = attachment { let isAudio: Bool = MIMETypeUtil.isAudio(attachment.contentType) let fallbackImageName: String = (isAudio ? "attachment_audio" : "actionsheet_document_black") @@ -181,13 +170,26 @@ final class QuoteView: UIView { } } else { + // Line view + let lineColor: ThemeValue = { + switch mode { + case .regular: return (direction == .outgoing ? .messageBubble_outgoingText : .primary) + case .draft: return .primary + } + }() + let lineView = UIView() + lineView.themeBackgroundColor = lineColor mainStackView.addArrangedSubview(lineView) + + lineView.pin(.top, to: .top, of: mainStackView) + lineView.pin(.bottom, to: .bottom, of: mainStackView) + lineView.set(.width, to: Values.accentLineThickness) } // Body label let bodyLabel = TappableLabel() - bodyLabel.numberOfLines = 0 bodyLabel.lineBreakMode = .byTruncatingTail + bodyLabel.numberOfLines = 2 let targetThemeColor: ThemeValue = { switch mode { @@ -229,7 +231,6 @@ final class QuoteView: UIView { // Label stack view let bodyLabelSize = bodyLabel.systemLayoutSizeFitting(availableSpace) - var authorLabelHeight: CGFloat? let isCurrentUser: Bool = [ currentUserPublicKey, @@ -259,16 +260,12 @@ final class QuoteView: UIView { authorLabel.themeTextColor = targetThemeColor authorLabel.lineBreakMode = .byTruncatingTail authorLabel.isHidden = (authorLabel.text == nil) - - let authorLabelSize = authorLabel.systemLayoutSizeFitting(availableSpace) - authorLabel.set(.height, to: authorLabelSize.height) - authorLabelHeight = authorLabelSize.height + authorLabel.numberOfLines = 1 let labelStackView = UIStackView(arrangedSubviews: [ authorLabel, bodyLabel ]) labelStackView.axis = .vertical labelStackView.spacing = labelStackViewSpacing labelStackView.distribution = .equalCentering - labelStackView.set(.width, to: max(bodyLabelSize.width, authorLabelSize.width)) labelStackView.isLayoutMarginsRelativeArrangement = true labelStackView.layoutMargins = UIEdgeInsets(top: labelStackViewVMargin, left: 0, bottom: labelStackViewVMargin, right: 0) mainStackView.addArrangedSubview(labelStackView) @@ -277,29 +274,6 @@ final class QuoteView: UIView { contentView.addSubview(mainStackView) mainStackView.pin(to: contentView) - if threadVariant == .contact { - bodyLabel.set(.width, to: bodyLabelSize.width) - } - - let bodyLabelHeight = bodyLabelSize.height.clamp(0, (mode == .regular ? 60 : 40)) - let contentViewHeight: CGFloat - - if attachment != nil { - contentViewHeight = thumbnailSize + 8 // Add a small amount of spacing above and below the thumbnail - bodyLabel.set(.height, to: 18) // Experimentally determined - } - else { - if let authorLabelHeight = authorLabelHeight { // Group thread - contentViewHeight = bodyLabelHeight + (authorLabelHeight + labelStackViewSpacing) + 2 * labelStackViewVMargin - } - else { - contentViewHeight = bodyLabelHeight + 2 * smallSpacing - } - } - - contentView.set(.height, to: contentViewHeight) - lineView.set(.height, to: contentViewHeight - 8) // Add a small amount of spacing above and below the line - if mode == .draft { // Cancel button let cancelButton = UIButton(type: .custom) diff --git a/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift b/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift index 685247ce4..981c05624 100644 --- a/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift +++ b/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift @@ -149,6 +149,7 @@ class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel State in try HomeViewModel.retrieveState(db) } .removeDuplicates() + .handleEvents(didFail: { SNLog("[HomeViewModel] Observation failed with error: \($0)") }) private static func retrieveState(_ db: Database) throws -> State { let hasViewedSeed: Bool = db[.hasViewedSeed] diff --git a/Session/Home/Message Requests/MessageRequestsViewModel.swift b/Session/Home/Message Requests/MessageRequestsViewModel.swift index 2f6267dd9..a7ed46b98 100644 --- a/Session/Home/Message Requests/MessageRequestsViewModel.swift +++ b/Session/Home/Message Requests/MessageRequestsViewModel.swift @@ -17,7 +17,7 @@ public class MessageRequestsViewModel { // MARK: - Variables - public static let pageSize: Int = 15 + public static let pageSize: Int = (UIDevice.current.isIPad ? 20 : 15) // MARK: - Initialization diff --git a/Session/Home/New Conversation/NewDMVC.swift b/Session/Home/New Conversation/NewDMVC.swift index 874d4987f..2ce3b92a1 100644 --- a/Session/Home/New Conversation/NewDMVC.swift +++ b/Session/Home/New Conversation/NewDMVC.swift @@ -210,6 +210,7 @@ final class NewDMVC: BaseVC, UIPageViewControllerDataSource, UIPageViewControlle .present(fromViewController: navigationController!, canCancel: false) { [weak self] modalActivityIndicator in SnodeAPI .getSessionID(for: onsNameOrPublicKey) + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .receive(on: DispatchQueue.main) .sinkUntilComplete( receiveCompletion: { result in diff --git a/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift b/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift index ca5c14ecf..c5e92d27d 100644 --- a/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift +++ b/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift @@ -360,6 +360,7 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect cell .requestRenditionForSending() + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .receive(on: DispatchQueue.main) .sink( receiveCompletion: { [weak self] result in @@ -490,6 +491,7 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect assert(searchBar.text == nil || searchBar.text?.count == 0) GiphyAPI.trending() + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .receive(on: DispatchQueue.main) .sink( receiveCompletion: { result in @@ -527,6 +529,7 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect GiphyAPI .search(query: query) + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .receive(on: DispatchQueue.main) .sink( receiveCompletion: { [weak self] result in diff --git a/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift b/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift index f8a0fa206..ec2ccddbb 100644 --- a/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift +++ b/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift @@ -291,7 +291,6 @@ enum GiphyAPI { return urlSession .dataTaskPublisher(for: url) - .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .mapError { urlError in Logger.error("search request failed: \(urlError)") @@ -340,7 +339,6 @@ enum GiphyAPI { return urlSession .dataTaskPublisher(for: request) - .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .mapError { urlError in Logger.error("search request failed: \(urlError)") diff --git a/Session/Media Viewing & Editing/MediaGalleryViewModel.swift b/Session/Media Viewing & Editing/MediaGalleryViewModel.swift index 8aeb4400a..22a1db975 100644 --- a/Session/Media Viewing & Editing/MediaGalleryViewModel.swift +++ b/Session/Media Viewing & Editing/MediaGalleryViewModel.swift @@ -360,7 +360,7 @@ public class MediaGalleryViewModel { /// 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>> + public typealias AlbumObservation = ValueObservation>>> public lazy var observableAlbumData: AlbumObservation = buildAlbumObservation(for: nil) private func buildAlbumObservation(for interactionId: Int64?) -> AlbumObservation { @@ -383,6 +383,7 @@ public class MediaGalleryViewModel { .fetchAll(db) } .removeDuplicates() + .handleEvents(didFail: { SNLog("[MediaGalleryViewModel] Observation failed with error: \($0)") }) } @discardableResult public func loadAndCacheAlbumData(for interactionId: Int64, in threadId: String) -> [Item] { diff --git a/Session/Media Viewing & Editing/PhotoCapture.swift b/Session/Media Viewing & Editing/PhotoCapture.swift index 51205b62f..37fa99901 100644 --- a/Session/Media Viewing & Editing/PhotoCapture.swift +++ b/Session/Media Viewing & Editing/PhotoCapture.swift @@ -84,7 +84,7 @@ class PhotoCapture: NSObject { func startCapture() -> AnyPublisher { return Just(()) - .subscribe(on: sessionQueue) + .subscribe(on: sessionQueue) // Must run this on a specific queue to prevent crashes .setFailureType(to: Error.self) .tryMap { [weak self] _ -> Void in self?.session.beginConfiguration() @@ -136,7 +136,7 @@ class PhotoCapture: NSObject { func stopCapture() -> AnyPublisher { return Just(()) - .subscribe(on: sessionQueue) + .subscribe(on: sessionQueue) // Must run this on a specific queue to prevent crashes .handleEvents( receiveOutput: { [weak self] in self?.session.stopRunning() } ) @@ -160,7 +160,7 @@ class PhotoCapture: NSObject { return Just(()) .setFailureType(to: Error.self) - .subscribe(on: sessionQueue) + .subscribe(on: sessionQueue) // Must run this on a specific queue to prevent crashes .tryMap { [weak self, newPosition = self.desiredPosition] _ -> Void in self?.session.beginConfiguration() defer { self?.session.commitConfiguration() } @@ -196,7 +196,7 @@ class PhotoCapture: NSObject { func switchFlashMode() -> AnyPublisher { return Just(()) - .subscribe(on: sessionQueue) + .subscribe(on: sessionQueue) // Must run this on a specific queue to prevent crashes .handleEvents( receiveOutput: { [weak self] _ in switch self?.captureOutput.flashMode { @@ -351,7 +351,7 @@ extension PhotoCapture: CaptureButtonDelegate { Logger.verbose("") Just(()) - .subscribe(on: sessionQueue) + .subscribe(on: sessionQueue) // Must run this on a specific queue to prevent crashes .sinkUntilComplete( receiveCompletion: { [weak self] _ in guard let strongSelf = self else { return } diff --git a/Session/Media Viewing & Editing/Transitions/MediaDismissAnimationController.swift b/Session/Media Viewing & Editing/Transitions/MediaDismissAnimationController.swift index d5e192e4f..76e880278 100644 --- a/Session/Media Viewing & Editing/Transitions/MediaDismissAnimationController.swift +++ b/Session/Media Viewing & Editing/Transitions/MediaDismissAnimationController.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit +import SessionUIKit class MediaDismissAnimationController: NSObject { private let mediaItem: Media @@ -46,6 +47,18 @@ extension MediaDismissAnimationController: UIViewControllerAnimatedTransitioning switch fromVC { case let contextProvider as MediaPresentationContextProvider: fromContextProvider = contextProvider + + case let topBannerController as TopBannerController: + guard + let firstChild: UIViewController = topBannerController.children.first, + let navController: UINavigationController = firstChild as? UINavigationController, + let contextProvider = navController.topViewController as? MediaPresentationContextProvider + else { + transitionContext.completeTransition(false) + return + } + + fromContextProvider = contextProvider case let navController as UINavigationController: guard let contextProvider = navController.topViewController as? MediaPresentationContextProvider else { @@ -64,6 +77,19 @@ extension MediaDismissAnimationController: UIViewControllerAnimatedTransitioning case let contextProvider as MediaPresentationContextProvider: toVC.view.layoutIfNeeded() toContextProvider = contextProvider + + case let topBannerController as TopBannerController: + guard + let firstChild: UIViewController = topBannerController.children.first, + let navController: UINavigationController = firstChild as? UINavigationController, + let contextProvider = navController.topViewController as? MediaPresentationContextProvider + else { + transitionContext.completeTransition(false) + return + } + + toVC.view.layoutIfNeeded() + toContextProvider = contextProvider case let navController as UINavigationController: guard let contextProvider = navController.topViewController as? MediaPresentationContextProvider else { diff --git a/Session/Media Viewing & Editing/Transitions/MediaZoomAnimationController.swift b/Session/Media Viewing & Editing/Transitions/MediaZoomAnimationController.swift index 83efc9a24..7dd7a4f0b 100644 --- a/Session/Media Viewing & Editing/Transitions/MediaZoomAnimationController.swift +++ b/Session/Media Viewing & Editing/Transitions/MediaZoomAnimationController.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit +import SessionUIKit class MediaZoomAnimationController: NSObject { private let mediaItem: Media @@ -34,6 +35,18 @@ extension MediaZoomAnimationController: UIViewControllerAnimatedTransitioning { switch fromVC { case let contextProvider as MediaPresentationContextProvider: fromContextProvider = contextProvider + + case let topBannerController as TopBannerController: + guard + let firstChild: UIViewController = topBannerController.children.first, + let navController: UINavigationController = firstChild as? UINavigationController, + let contextProvider = navController.topViewController as? MediaPresentationContextProvider + else { + transitionContext.completeTransition(false) + return + } + + fromContextProvider = contextProvider case let navController as UINavigationController: guard let contextProvider = navController.topViewController as? MediaPresentationContextProvider else { @@ -51,6 +64,18 @@ extension MediaZoomAnimationController: UIViewControllerAnimatedTransitioning { switch toVC { case let contextProvider as MediaPresentationContextProvider: toContextProvider = contextProvider + + case let topBannerController as TopBannerController: + guard + let firstChild: UIViewController = topBannerController.children.first, + let navController: UINavigationController = firstChild as? UINavigationController, + let contextProvider = navController.topViewController as? MediaPresentationContextProvider + else { + transitionContext.completeTransition(false) + return + } + + toContextProvider = contextProvider case let navController as UINavigationController: guard let contextProvider = navController.topViewController as? MediaPresentationContextProvider else { diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index dcab9b1d8..ca106f141 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -332,12 +332,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD } } - private func showFailedMigrationAlert(calledFrom lifecycleMethod: LifecycleMethod, error: Error?) { + private func showFailedMigrationAlert( + calledFrom lifecycleMethod: LifecycleMethod, + error: Error?, + isRestoreError: Bool = false + ) { let alert = UIAlertController( title: "Session", message: { - switch (error ?? StorageError.generic) { - case StorageError.startupFailed: return "DATABASE_STARTUP_FAILED".localized() + switch (isRestoreError, (error ?? StorageError.generic)) { + case (true, _): return "DATABASE_RESTORE_FAILED".localized() + case (_, StorageError.startupFailed): return "DATABASE_STARTUP_FAILED".localized() default: return "DATABASE_MIGRATION_FAILED".localized() } }(), @@ -348,32 +353,45 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD self?.showFailedMigrationAlert(calledFrom: lifecycleMethod, error: error) } }) - alert.addAction(UIAlertAction(title: "vc_restore_title".localized(), style: .destructive) { _ in - // Remove the legacy database and any message hashes that have been migrated to the new DB - try? SUKLegacy.deleteLegacyDatabaseFilesAndKey() - - Storage.shared.write { db in - try SnodeReceivedMessageInfo.deleteAll(db) - } - - // The re-run the migration (should succeed since there is no data) - AppSetup.runPostSetupMigrations( - migrationProgressChanged: { [weak self] progress, minEstimatedTotalTime in - self?.loadingViewController?.updateProgress( - progress: progress, - minEstimatedTotalTime: minEstimatedTotalTime - ) - }, - migrationsCompletion: { [weak self] result, needsConfigSync in - if case .failure(let error) = result { - self?.showFailedMigrationAlert(calledFrom: lifecycleMethod, error: error) - return - } + + // Only offer the 'Restore' option if the user hasn't already tried to restore + if !isRestoreError { + alert.addAction(UIAlertAction(title: "vc_restore_title".localized(), style: .destructive) { _ in + if SUKLegacy.hasLegacyDatabaseFile { + // Remove the legacy database and any message hashes that have been migrated to the new DB + try? SUKLegacy.deleteLegacyDatabaseFilesAndKey() - self?.completePostMigrationSetup(calledFrom: lifecycleMethod, needsConfigSync: needsConfigSync) + Storage.shared.write { db in + try SnodeReceivedMessageInfo.deleteAll(db) + } } - ) - }) + else { + // If we don't have a legacy database then reset the current database for a clean migration + Storage.resetForCleanMigration() + } + + // Hide the top banner if there was one + TopBannerController.hide() + + // The re-run the migration (should succeed since there is no data) + AppSetup.runPostSetupMigrations( + migrationProgressChanged: { [weak self] progress, minEstimatedTotalTime in + self?.loadingViewController?.updateProgress( + progress: progress, + minEstimatedTotalTime: minEstimatedTotalTime + ) + }, + migrationsCompletion: { [weak self] result, needsConfigSync in + if case .failure(let error) = result { + self?.showFailedMigrationAlert(calledFrom: lifecycleMethod, error: error, isRestoreError: true) + return + } + + self?.completePostMigrationSetup(calledFrom: lifecycleMethod, needsConfigSync: needsConfigSync) + } + ) + }) + } alert.addAction(UIAlertAction(title: "Close", style: .default) { _ in DDLog.flushLog() @@ -612,12 +630,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD public func startPollersIfNeeded(shouldStartGroupPollers: Bool = true) { guard Identity.userExists() else { return } - poller.start() - - guard shouldStartGroupPollers else { return } - - ClosedGroupPoller.shared.start() - OpenGroupManager.shared.startPolling() + /// There is a fun issue where if you launch without any valid paths then the pollers are guaranteed to fail their first poll due to + /// trying and failing to build paths without having the `SnodeAPI.snodePool` populated, by waiting for the + /// `JobRunner.blockingQueue` to complete we can have more confidence that paths won't fail to build incorrectly + JobRunner.afterBlockingQueue { [weak self] in + self?.poller.start() + + guard shouldStartGroupPollers else { return } + + ClosedGroupPoller.shared.start() + OpenGroupManager.shared.startPolling() + } } public func stopPollers(shouldStopUserPoller: Bool = true) { diff --git a/Session/Meta/Translations/de.lproj/Localizable.strings b/Session/Meta/Translations/de.lproj/Localizable.strings index cf306a367..178d7c6ea 100644 --- a/Session/Meta/Translations/de.lproj/Localizable.strings +++ b/Session/Meta/Translations/de.lproj/Localizable.strings @@ -415,6 +415,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index 0853ba3fb..2e5c23059 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -415,6 +415,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/es.lproj/Localizable.strings b/Session/Meta/Translations/es.lproj/Localizable.strings index 2cd107fb4..4d1257d37 100644 --- a/Session/Meta/Translations/es.lproj/Localizable.strings +++ b/Session/Meta/Translations/es.lproj/Localizable.strings @@ -415,6 +415,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/fa.lproj/Localizable.strings b/Session/Meta/Translations/fa.lproj/Localizable.strings index 721d742fe..37be0ffb3 100644 --- a/Session/Meta/Translations/fa.lproj/Localizable.strings +++ b/Session/Meta/Translations/fa.lproj/Localizable.strings @@ -415,6 +415,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "لطفا بعدا دوباره تلاش کنید"; "LOADING_CONVERSATIONS" = "درحال بارگزاری پیام ها..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "هنگام بهینه‌سازی پایگاه داده خطایی روی داد\n\nشما می‌توانید گزارش‌های برنامه خود را صادر کنید تا بتوانید برای عیب‌یابی به اشتراک بگذارید یا می‌توانید دستگاه خود را بازیابی کنید\n\nهشدار: بازیابی دستگاه شما منجر به از دست رفتن داده‌های قدیمی‌تر از دو هفته می‌شود."; "RECOVERY_PHASE_ERROR_GENERIC" = "مشکلی پیش آمد. لطفاً عبارت بازیابی خود را بررسی کنید و دوباره امتحان کنید."; "RECOVERY_PHASE_ERROR_LENGTH" = "به نظر می رسد کلمات کافی وارد نکرده اید. لطفاً عبارت بازیابی خود را بررسی کنید و دوباره امتحان کنید."; diff --git a/Session/Meta/Translations/fi.lproj/Localizable.strings b/Session/Meta/Translations/fi.lproj/Localizable.strings index e06981e6c..61f77cd4a 100644 --- a/Session/Meta/Translations/fi.lproj/Localizable.strings +++ b/Session/Meta/Translations/fi.lproj/Localizable.strings @@ -415,6 +415,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/fr.lproj/Localizable.strings b/Session/Meta/Translations/fr.lproj/Localizable.strings index 0f42d5c3b..b5bf78948 100644 --- a/Session/Meta/Translations/fr.lproj/Localizable.strings +++ b/Session/Meta/Translations/fr.lproj/Localizable.strings @@ -415,6 +415,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Veuillez réessayer plus tard"; "LOADING_CONVERSATIONS" = "Chargement des conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "Une erreur est survenue pendant l'optimisation de la base de données\n\nVous pouvez exporter votre journal d'application pour le partager et aider à régler le problème ou vous pouvez restaurer votre appareil\n\nAttention : restaurer votre appareil résultera en une perte des données des deux dernières semaines"; "RECOVERY_PHASE_ERROR_GENERIC" = "Quelque chose s'est mal passé. Vérifiez votre phrase de récupération et réessayez s'il vous plaît."; "RECOVERY_PHASE_ERROR_LENGTH" = "Il semble que vous n'avez pas saisi tous les mots. Vérifiez votre phrase de récupération et réessayez s'il vous plaît."; diff --git a/Session/Meta/Translations/hi.lproj/Localizable.strings b/Session/Meta/Translations/hi.lproj/Localizable.strings index bf51ebd6c..2b5a37525 100644 --- a/Session/Meta/Translations/hi.lproj/Localizable.strings +++ b/Session/Meta/Translations/hi.lproj/Localizable.strings @@ -415,6 +415,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/hr.lproj/Localizable.strings b/Session/Meta/Translations/hr.lproj/Localizable.strings index 5bac5abfb..c7714aca6 100644 --- a/Session/Meta/Translations/hr.lproj/Localizable.strings +++ b/Session/Meta/Translations/hr.lproj/Localizable.strings @@ -415,6 +415,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/id-ID.lproj/Localizable.strings b/Session/Meta/Translations/id-ID.lproj/Localizable.strings index 4e54eb039..62d0c3471 100644 --- a/Session/Meta/Translations/id-ID.lproj/Localizable.strings +++ b/Session/Meta/Translations/id-ID.lproj/Localizable.strings @@ -415,6 +415,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/it.lproj/Localizable.strings b/Session/Meta/Translations/it.lproj/Localizable.strings index b41f34667..acb886818 100644 --- a/Session/Meta/Translations/it.lproj/Localizable.strings +++ b/Session/Meta/Translations/it.lproj/Localizable.strings @@ -415,6 +415,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/ja.lproj/Localizable.strings b/Session/Meta/Translations/ja.lproj/Localizable.strings index f2b83382f..e5ffab88b 100644 --- a/Session/Meta/Translations/ja.lproj/Localizable.strings +++ b/Session/Meta/Translations/ja.lproj/Localizable.strings @@ -415,6 +415,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/nl.lproj/Localizable.strings b/Session/Meta/Translations/nl.lproj/Localizable.strings index 0aaa027f7..2597356f0 100644 --- a/Session/Meta/Translations/nl.lproj/Localizable.strings +++ b/Session/Meta/Translations/nl.lproj/Localizable.strings @@ -415,6 +415,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/pl.lproj/Localizable.strings b/Session/Meta/Translations/pl.lproj/Localizable.strings index dbefe0039..190af0988 100644 --- a/Session/Meta/Translations/pl.lproj/Localizable.strings +++ b/Session/Meta/Translations/pl.lproj/Localizable.strings @@ -415,6 +415,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings index 888d81746..3f9740a6e 100644 --- a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings +++ b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings @@ -415,6 +415,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/ru.lproj/Localizable.strings b/Session/Meta/Translations/ru.lproj/Localizable.strings index 77850b124..648eb63da 100644 --- a/Session/Meta/Translations/ru.lproj/Localizable.strings +++ b/Session/Meta/Translations/ru.lproj/Localizable.strings @@ -415,6 +415,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/si.lproj/Localizable.strings b/Session/Meta/Translations/si.lproj/Localizable.strings index f28501061..2e06c99a0 100644 --- a/Session/Meta/Translations/si.lproj/Localizable.strings +++ b/Session/Meta/Translations/si.lproj/Localizable.strings @@ -415,6 +415,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/sk.lproj/Localizable.strings b/Session/Meta/Translations/sk.lproj/Localizable.strings index 5a995249d..b140447f4 100644 --- a/Session/Meta/Translations/sk.lproj/Localizable.strings +++ b/Session/Meta/Translations/sk.lproj/Localizable.strings @@ -415,6 +415,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/sv.lproj/Localizable.strings b/Session/Meta/Translations/sv.lproj/Localizable.strings index 688680b2a..dfb732242 100644 --- a/Session/Meta/Translations/sv.lproj/Localizable.strings +++ b/Session/Meta/Translations/sv.lproj/Localizable.strings @@ -415,6 +415,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/th.lproj/Localizable.strings b/Session/Meta/Translations/th.lproj/Localizable.strings index 98ca3e5bb..c720946db 100644 --- a/Session/Meta/Translations/th.lproj/Localizable.strings +++ b/Session/Meta/Translations/th.lproj/Localizable.strings @@ -415,6 +415,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings index aa5527991..4efaa9b08 100644 --- a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings +++ b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings @@ -415,6 +415,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings index 8296fa917..f8735880c 100644 --- a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings @@ -415,6 +415,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings index 68076c678..b2a05e36b 100644 --- a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings @@ -415,6 +415,7 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Notifications/AppNotifications.swift b/Session/Notifications/AppNotifications.swift index dfd62083b..4c818024b 100644 --- a/Session/Notifications/AppNotifications.swift +++ b/Session/Notifications/AppNotifications.swift @@ -575,9 +575,7 @@ class NotificationActionHandler { threadVariant: thread.variant ) } - .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .flatMap { MessageSender.sendImmediate(preparedSendData: $0) } - .receive(on: DispatchQueue.main) .handleEvents( receiveCompletion: { result in switch result { diff --git a/Session/Notifications/PushRegistrationManager.swift b/Session/Notifications/PushRegistrationManager.swift index 51df226c3..50fe02510 100644 --- a/Session/Notifications/PushRegistrationManager.swift +++ b/Session/Notifications/PushRegistrationManager.swift @@ -52,8 +52,9 @@ public enum PushRegistrationError: Error { Logger.info("") return registerUserNotificationSettings() - .setFailureType(to: Error.self) + .subscribe(on: DispatchQueue.global(qos: .default)) .receive(on: DispatchQueue.main) // MUST be on main thread + .setFailureType(to: Error.self) .tryFlatMap { _ -> AnyPublisher<(pushToken: String, voipToken: String), Error> in #if targetEnvironment(simulator) throw PushRegistrationError.pushNotSupported(description: "Push not supported on simulators") diff --git a/Session/Notifications/UserNotificationsAdaptee.swift b/Session/Notifications/UserNotificationsAdaptee.swift index 3af0cccd3..4e8711b3e 100644 --- a/Session/Notifications/UserNotificationsAdaptee.swift +++ b/Session/Notifications/UserNotificationsAdaptee.swift @@ -251,6 +251,8 @@ public class UserNotificationActionHandler: NSObject { func handleNotificationResponse( _ response: UNNotificationResponse, completionHandler: @escaping () -> Void) { AssertIsOnMainThread() handleNotificationResponse(response) + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) + .receive(on: DispatchQueue.main) .sinkUntilComplete( receiveCompletion: { result in switch result { diff --git a/Session/Onboarding/DisplayNameVC.swift b/Session/Onboarding/DisplayNameVC.swift index cc1ee24fe..051b45aa1 100644 --- a/Session/Onboarding/DisplayNameVC.swift +++ b/Session/Onboarding/DisplayNameVC.swift @@ -189,7 +189,7 @@ final class DisplayNameVC: BaseVC { // Try to save the user name but ignore the result ProfileManager.updateLocal( - queue: DispatchQueue.global(qos: .default), + queue: .global(qos: .default), profileName: displayName ) diff --git a/Session/Onboarding/Onboarding.swift b/Session/Onboarding/Onboarding.swift index 914976728..5f23d32d4 100644 --- a/Session/Onboarding/Onboarding.swift +++ b/Session/Onboarding/Onboarding.swift @@ -36,14 +36,12 @@ enum Onboarding { let userPublicKey: String = getUserHexEncodedPublicKey() return SnodeAPI.getSwarm(for: userPublicKey) - .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .tryFlatMapWithRandomSnode { snode -> AnyPublisher in CurrentUserPoller .poll( namespaces: [.configUserProfile], from: snode, for: userPublicKey, - on: DispatchQueue.global(qos: .userInitiated), // Note: These values mean the received messages will be // processed immediately rather than async as part of a Job calledFromBackgroundPoller: true, @@ -67,7 +65,6 @@ enum Onboarding { namespaces: [.default], from: snode, for: userPublicKey, - on: DispatchQueue.global(qos: .userInitiated), // Note: These values mean the received messages will be // processed immediately rather than async as part of a Job calledFromBackgroundPoller: true, @@ -215,7 +212,9 @@ enum Onboarding { guard self != .register else { return } // Fetch the - Onboarding.profileNamePublisher.sinkUntilComplete() + Onboarding.profileNamePublisher + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) + .sinkUntilComplete() } func completeRegistration() { diff --git a/Session/Onboarding/PNModeVC.swift b/Session/Onboarding/PNModeVC.swift index 8c60ab51e..bf4884a29 100644 --- a/Session/Onboarding/PNModeVC.swift +++ b/Session/Onboarding/PNModeVC.swift @@ -176,6 +176,7 @@ final class PNModeVC: BaseVC, OptionViewDelegate { // If we don't have one then show a loading indicator and try to retrieve the existing name ModalActivityIndicatorViewController.present(fromViewController: self) { [weak self, flow = self.flow] viewController in Onboarding.profileNamePublisher + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .timeout(.seconds(15), scheduler: DispatchQueue.main, customError: { HTTPError.timeout }) .catch { _ -> AnyPublisher in SNLog("Onboarding failed to retrieve existing profile information") diff --git a/Session/Open Groups/OpenGroupSuggestionGrid.swift b/Session/Open Groups/OpenGroupSuggestionGrid.swift index 128cb6259..9fb666989 100644 --- a/Session/Open Groups/OpenGroupSuggestionGrid.swift +++ b/Session/Open Groups/OpenGroupSuggestionGrid.swift @@ -143,7 +143,8 @@ final class OpenGroupSuggestionGrid: UIView, UICollectionViewDataSource, UIColle widthAnchor.constraint(greaterThanOrEqualToConstant: OpenGroupSuggestionGrid.cellHeight).isActive = true OpenGroupManager.getDefaultRoomsIfNeeded() - .receive(on: DispatchQueue.main) + .subscribe(on: DispatchQueue.global(qos: .default)) + .receive(on: DispatchQueue.main, immediatelyIfMain: true) .sinkUntilComplete( receiveCompletion: { [weak self] _ in self?.update() }, receiveValue: { [weak self] rooms in self?.rooms = rooms } @@ -336,7 +337,7 @@ extension OpenGroupSuggestionGrid { .eraseToAnyPublisher() ) .subscribe(on: DispatchQueue.global(qos: .userInitiated)) - .receiveOnMain(immediately: true) + .receive(on: DispatchQueue.main, immediatelyIfMain: true) .sinkUntilComplete( receiveValue: { [weak self] imageData, hasData in guard hasData else { diff --git a/Session/Settings/ConversationSettingsViewModel.swift b/Session/Settings/ConversationSettingsViewModel.swift index 2786de2c0..1c6803913 100644 --- a/Session/Settings/ConversationSettingsViewModel.swift +++ b/Session/Settings/ConversationSettingsViewModel.swift @@ -104,6 +104,7 @@ class ConversationSettingsViewModel: SessionTableViewModel] = [] public static var isValid: Bool = false - public static func poll(completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { + public static func poll( + completionHandler: @escaping (UIBackgroundFetchResult) -> Void, + dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies( + subscribeQueue: .global(qos: .background), + receiveQueue: .main + ) + ) { Publishers .MergeMany( - [pollForMessages()] - .appending(contentsOf: pollForClosedGroupMessages()) + [pollForMessages(using: dependencies)] + .appending(contentsOf: pollForClosedGroupMessages(using: dependencies)) .appending( contentsOf: Storage.shared .read { db in - // The default room promise creates an OpenGroup with an empty - // `roomToken` value, we don't want to start a poller for this - // as the user hasn't actually joined a room + /// The default room promise creates an OpenGroup with an empty `roomToken` value, we + /// don't want to start a poller for this as the user hasn't actually joined a room + /// + /// We also want to exclude any rooms which have failed to poll too many times in a row from + /// the background poll as they are likely to fail again try OpenGroup .select(.server) - .filter(OpenGroup.Columns.roomToken != "") - .filter(OpenGroup.Columns.isActive) + .filter( + OpenGroup.Columns.roomToken != "" && + OpenGroup.Columns.isActive && + OpenGroup.Columns.pollFailureCount < OpenGroupAPI.Poller.maxRoomFailureCountForBackgroundPoll + ) .distinct() .asRequest(of: String.self) .fetchSet(db) @@ -38,13 +49,14 @@ public final class BackgroundPoller { return poller.poll( calledFromBackgroundPoller: true, isBackgroundPollerValid: { BackgroundPoller.isValid }, - isPostCapabilitiesRetry: false + isPostCapabilitiesRetry: false, + using: dependencies ) } ) ) - .subscribeOnMain(immediately: true) - .receiveOnMain(immediately: true) + .subscribe(on: dependencies.subscribeQueue, immediatelyIfMain: true) + .receive(on: dependencies.receiveQueue, immediatelyIfMain: true) .collect() .sinkUntilComplete( receiveCompletion: { result in @@ -61,27 +73,29 @@ public final class BackgroundPoller { ) } - private static func pollForMessages() -> AnyPublisher { + private static func pollForMessages( + using dependencies: OpenGroupManager.OGMDependencies + ) -> AnyPublisher { let userPublicKey: String = getUserHexEncodedPublicKey() return SnodeAPI.getSwarm(for: userPublicKey) - .subscribeOnMain(immediately: true) - .receiveOnMain(immediately: true) .tryFlatMapWithRandomSnode { snode -> AnyPublisher<[Message], Error> in CurrentUserPoller.poll( namespaces: CurrentUserPoller.namespaces, from: snode, for: userPublicKey, - on: DispatchQueue.main, calledFromBackgroundPoller: true, - isBackgroundPollValid: { BackgroundPoller.isValid } + isBackgroundPollValid: { BackgroundPoller.isValid }, + using: dependencies ) } .map { _ in () } .eraseToAnyPublisher() } - private static func pollForClosedGroupMessages() -> [AnyPublisher] { + private static func pollForClosedGroupMessages( + using dependencies: OpenGroupManager.OGMDependencies + ) -> [AnyPublisher] { // Fetch all closed groups (excluding any don't contain the current user as a // GroupMemeber as the user is no longer a member of those) return Storage.shared @@ -98,8 +112,6 @@ public final class BackgroundPoller { .defaulting(to: []) .map { groupPublicKey in SnodeAPI.getSwarm(for: groupPublicKey) - .subscribeOnMain(immediately: true) - .receiveOnMain(immediately: true) .tryFlatMap { swarm -> AnyPublisher<[Message], Error> in guard let snode: Snode = swarm.randomElement() else { throw OnionRequestAPIError.insufficientSnodes @@ -109,9 +121,9 @@ public final class BackgroundPoller { namespaces: ClosedGroupPoller.namespaces, from: snode, for: groupPublicKey, - on: DispatchQueue.main, calledFromBackgroundPoller: true, - isBackgroundPollValid: { BackgroundPoller.isValid } + isBackgroundPollValid: { BackgroundPoller.isValid }, + using: dependencies ) } .map { _ in () } diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index d6816ae8f..7d7added5 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -198,6 +198,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { ) } .flatMap { MessageSender.sendImmediate(preparedSendData: $0) } + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .sinkUntilComplete( receiveCompletion: { result in switch result { @@ -263,6 +264,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { ) } .flatMap { MessageSender.sendImmediate(preparedSendData: $0) } + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .sinkUntilComplete( receiveCompletion: { result in switch result { diff --git a/SessionMessagingKit/Database/Models/Attachment.swift b/SessionMessagingKit/Database/Models/Attachment.swift index aed812c51..bb575a6fa 100644 --- a/SessionMessagingKit/Database/Models/Attachment.swift +++ b/SessionMessagingKit/Database/Models/Attachment.swift @@ -994,11 +994,14 @@ extension Attachment { } } - public static func prepare(_ db: Database, attachments: [SignalAttachment], for interactionId: Int64) throws { - // Prepare any attachments - try attachments.enumerated() - .forEach { index, signalAttachment in - let maybeAttachment: Attachment? = Attachment( + public struct PreparedData { + public let attachments: [Attachment] + } + + public static func prepare(attachments: [SignalAttachment]) -> PreparedData { + return PreparedData( + attachments: attachments.compactMap { signalAttachment in + Attachment( variant: (signalAttachment.isVoiceMessage ? .voiceMessage : .standard @@ -1008,9 +1011,23 @@ extension Attachment { sourceFilename: signalAttachment.sourceFilename, caption: signalAttachment.captionText ) + } + ) + } + + public static func process( + _ db: Database, + data: PreparedData?, + for interactionId: Int64? + ) throws { + guard + let data: PreparedData = data, + let interactionId: Int64 = interactionId + else { return } - guard let attachment: Attachment = maybeAttachment else { return } - + try data.attachments + .enumerated() + .forEach { index, attachment in let interactionAttachment: InteractionAttachment = InteractionAttachment( albumIndex: index, interactionId: interactionId, @@ -1042,7 +1059,7 @@ extension Attachment { let attachmentId: String = self.id return Storage.shared - .writePublisherFlatMap { db -> AnyPublisher<(String?, Data?, Data?), Error> in + .writePublisher { db -> (OpenGroupAPI.PreparedSendData?, String?, Data?, Data?) in // If the attachment is a downloaded attachment, check if it came from // the server and if so just succeed immediately (no use re-uploading // an attachment that is already present on the server) - or if we want @@ -1062,9 +1079,7 @@ extension Attachment { .filter(id: attachmentId) .updateAll(db, Attachment.Columns.state.set(to: Attachment.State.uploaded)) - return Just((Attachment.fileId(for: self.downloadUrl), nil, nil)) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() + return (nil, Attachment.fileId(for: self.downloadUrl), nil, nil) } var encryptionKey: NSData = NSData() @@ -1089,42 +1104,41 @@ extension Attachment { .filter(id: attachmentId) .updateAll(db, Attachment.Columns.state.set(to: Attachment.State.uploading)) - switch destination { - case .openGroup(let openGroup): - return OpenGroupAPI - .uploadFile( - db, - bytes: data.bytes, - to: openGroup.roomToken, - on: openGroup.server - ) - .map { _, response -> (String, Data?, Data?) in - ( - response.id, - (destination.shouldEncrypt ? encryptionKey as Data : nil), - (destination.shouldEncrypt ? digest as Data : nil) + // We need database access for OpenGroup uploads so generate prepared data + let preparedSendData: OpenGroupAPI.PreparedSendData? = try { + switch destination { + case .openGroup(let openGroup): + return try OpenGroupAPI + .preparedUploadFile( + db, + bytes: data.bytes, + to: openGroup.roomToken, + on: openGroup.server ) - } - .eraseToAnyPublisher() - case .fileServer: - /// **Note:** FileServer uploads don't need database access so - return Just(( - nil, - (destination.shouldEncrypt ? encryptionKey as Data : nil), - (destination.shouldEncrypt ? digest as Data : nil) - )) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - } + default: return nil + } + }() + + return ( + preparedSendData, + nil, + (destination.shouldEncrypt ? encryptionKey as Data : nil), + (destination.shouldEncrypt ? digest as Data : nil) + ) } - .flatMap { maybeFileId, encryptionKey, digest -> AnyPublisher<(String?, Data?, Data?), Error> in + .flatMap { preparedSendData, existingFileId, encryptionKey, digest -> AnyPublisher<(String?, Data?, Data?), Error> in + // No need to upload if the file was already uploaded + if let fileId: String = existingFileId { + return Just((fileId, encryptionKey, digest)) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + switch destination { case .openGroup: - /// **Note:** OpenGroup uploads need database access so this should - /// have already been uploaded - return Just((maybeFileId, encryptionKey, digest)) - .setFailureType(to: Error.self) + return OpenGroupAPI.send(data: preparedSendData) + .map { _, response -> (String, Data?, Data?) in (response.id, encryptionKey, digest) } .eraseToAnyPublisher() case .fileServer: diff --git a/SessionMessagingKit/Database/Models/DisappearingMessageConfiguration.swift b/SessionMessagingKit/Database/Models/DisappearingMessageConfiguration.swift index 5c18b9417..a3bbb8339 100644 --- a/SessionMessagingKit/Database/Models/DisappearingMessageConfiguration.swift +++ b/SessionMessagingKit/Database/Models/DisappearingMessageConfiguration.swift @@ -5,7 +5,7 @@ import GRDB import SessionUtilitiesKit import SessionSnodeKit -public struct DisappearingMessagesConfiguration: Codable, Identifiable, Equatable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { +public struct DisappearingMessagesConfiguration: Codable, Identifiable, Equatable, Hashable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { public static var databaseTableName: String { "disappearingMessagesConfiguration" } internal static let threadForeignKey = ForeignKey([Columns.threadId], to: [SessionThread.Columns.id]) private static let thread = belongsTo(SessionThread.self, using: threadForeignKey) diff --git a/SessionMessagingKit/Database/Models/Interaction.swift b/SessionMessagingKit/Database/Models/Interaction.swift index 21fdd5456..60fe57b9a 100644 --- a/SessionMessagingKit/Database/Models/Interaction.swift +++ b/SessionMessagingKit/Database/Models/Interaction.swift @@ -319,7 +319,7 @@ public struct Interaction: Codable, Identifiable, Equatable, FetchableRecord, Mu openGroupServerMessageId: Int64? = nil, openGroupWhisperMods: Bool = false, openGroupWhisperTo: String? = nil - ) throws { + ) { self.serverHash = serverHash self.messageUuid = messageUuid self.threadId = threadId @@ -821,6 +821,18 @@ public extension Interaction { } } + return isUserMentioned( + publicKeysToCheck: publicKeysToCheck, + body: body, + quoteAuthorId: quoteAuthorId + ) + } + + static func isUserMentioned( + publicKeysToCheck: [String], + body: String?, + quoteAuthorId: String? = nil + ) -> Bool { // A user is mentioned if their public key is in the body of a message or one of their messages // was quoted return publicKeysToCheck.contains { publicKey in diff --git a/SessionMessagingKit/Database/Models/LinkPreview.swift b/SessionMessagingKit/Database/Models/LinkPreview.swift index 35018ce59..b214bc78d 100644 --- a/SessionMessagingKit/Database/Models/LinkPreview.swift +++ b/SessionMessagingKit/Database/Models/LinkPreview.swift @@ -130,7 +130,7 @@ public extension LinkPreview { return (floor(sentTimestampMs / 1000 / LinkPreview.timstampResolution) * LinkPreview.timstampResolution) } - static func saveAttachmentIfPossible(_ db: Database, imageData: Data?, mimeType: String) throws -> String? { + static func generateAttachmentIfPossible(imageData: Data?, mimeType: String) throws -> Attachment? { guard let imageData: Data = imageData, !imageData.isEmpty else { return nil } guard let fileExtension: String = MIMETypeUtil.fileExtension(forMIMEType: mimeType) else { return nil } @@ -141,9 +141,7 @@ public extension LinkPreview { return nil } - return try Attachment(contentType: mimeType, dataSource: dataSource)? - .inserted(db) - .id + return Attachment(contentType: mimeType, dataSource: dataSource) } static func isValidLinkUrl(_ urlString: String) -> Bool { @@ -355,7 +353,6 @@ public extension LinkPreview { return session .dataTaskPublisher(for: request) - .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .mapError { _ -> Error in HTTPError.generic } // URLError codes are negative values .tryMap { data, response -> (Data, URLResponse) in guard let urlResponse: HTTPURLResponse = response as? HTTPURLResponse else { diff --git a/SessionMessagingKit/File Server/FileServerAPI.swift b/SessionMessagingKit/File Server/FileServerAPI.swift index cc2bc3636..73c1ccead 100644 --- a/SessionMessagingKit/File Server/FileServerAPI.swift +++ b/SessionMessagingKit/File Server/FileServerAPI.swift @@ -89,7 +89,6 @@ public enum FileServerAPI { with: serverPublicKey, timeout: timeout ) - .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .tryMap { _, response -> Data in guard let response: Data = response else { throw HTTPError.parsingFailed } diff --git a/SessionMessagingKit/Jobs/Types/AttachmentDownloadJob.swift b/SessionMessagingKit/Jobs/Types/AttachmentDownloadJob.swift index 1afa7b6b7..d42f7078c 100644 --- a/SessionMessagingKit/Jobs/Types/AttachmentDownloadJob.swift +++ b/SessionMessagingKit/Jobs/Types/AttachmentDownloadJob.swift @@ -94,9 +94,20 @@ public enum AttachmentDownloadJob: JobExecutor { else { throw AttachmentDownloadError.invalidUrl } return Storage.shared - .readPublisher { db in try OpenGroup.fetchOne(db, id: threadId) } - .flatMap { maybeOpenGroup -> AnyPublisher in - guard let openGroup: OpenGroup = maybeOpenGroup else { + .readPublisher { db -> OpenGroupAPI.PreparedSendData? in + try OpenGroup.fetchOne(db, id: threadId) + .map { openGroup in + try OpenGroupAPI + .preparedDownloadFile( + db, + fileId: fileId, + from: openGroup.roomToken, + on: openGroup.server + ) + } + } + .flatMap { maybePreparedSendData -> AnyPublisher in + guard let preparedSendData: OpenGroupAPI.PreparedSendData = maybePreparedSendData else { return FileServerAPI .download( fileId, @@ -105,16 +116,8 @@ public enum AttachmentDownloadJob: JobExecutor { .eraseToAnyPublisher() } - return Storage.shared - .readPublisherFlatMap { db in - OpenGroupAPI - .downloadFile( - db, - fileId: fileId, - from: openGroup.roomToken, - on: openGroup.server - ) - } + return OpenGroupAPI + .send(data: preparedSendData) .map { _, data in data } .eraseToAnyPublisher() } diff --git a/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift b/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift index e7eee9f34..63297bb02 100644 --- a/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift +++ b/SessionMessagingKit/Jobs/Types/ConfigurationSyncJob.swift @@ -221,25 +221,29 @@ public extension ConfigurationSyncJob { return } - // Upsert a config sync job (if there is already an pending one then no need - // to add another one) + // Upsert a config sync job if needed JobRunner.upsert( db, - job: ConfigurationSyncJob.createOrUpdateIfNeeded(db, publicKey: publicKey) + job: ConfigurationSyncJob.createIfNeeded(db, publicKey: publicKey) ) } - @discardableResult static func createOrUpdateIfNeeded(_ db: Database, publicKey: String) -> Job { - // Try to get an existing job (if there is one that's not running) - if - let existingJobs: [Job] = try? Job + @discardableResult static func createIfNeeded(_ db: Database, publicKey: String) -> Job? { + /// The ConfigurationSyncJob will automatically reschedule itself to run again after 3 seconds so if there is an existing + /// job then there is no need to create another instance + /// + /// **Note:** Jobs with different `threadId` values can run concurrently + guard + JobRunner + .infoForCurrentlyRunningJobs(of: .configurationSync) + .filter({ _, info in info.threadId == publicKey }) + .isEmpty, + (try? Job .filter(Job.Columns.variant == Job.Variant.configurationSync) .filter(Job.Columns.threadId == publicKey) - .fetchAll(db), - let existingJob: Job = existingJobs.first(where: { !JobRunner.isCurrentlyRunning($0) }) - { - return existingJob - } + .isEmpty(db)) + .defaulting(to: false) + else { return nil } // Otherwise create a new job return Job( @@ -278,7 +282,7 @@ public extension ConfigurationSyncJob { Future { resolver in ConfigurationSyncJob.run( Job(variant: .configurationSync), - queue: DispatchQueue.global(qos: .userInitiated), + queue: .global(qos: .userInitiated), success: { _, _ in resolver(Result.success(())) }, failure: { _, error, _ in resolver(Result.failure(error ?? HTTPError.generic)) }, deferred: { _ in } diff --git a/SessionMessagingKit/Jobs/Types/GroupLeavingJob.swift b/SessionMessagingKit/Jobs/Types/GroupLeavingJob.swift index b078fe781..9b7a6e2bc 100644 --- a/SessionMessagingKit/Jobs/Types/GroupLeavingJob.swift +++ b/SessionMessagingKit/Jobs/Types/GroupLeavingJob.swift @@ -55,6 +55,7 @@ public enum GroupLeavingJob: JobExecutor { ) } .flatMap { MessageSender.sendImmediate(preparedSendData: $0) } + .subscribe(on: queue) .receive(on: queue) .sinkUntilComplete( receiveCompletion: { result in diff --git a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift index 8e97addcd..ad8f54c77 100644 --- a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift +++ b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift @@ -189,6 +189,7 @@ public enum MessageSendJob: JobExecutor { } .map { sendData in sendData.with(fileIds: messageFileIds) } .flatMap { MessageSender.sendImmediate(preparedSendData: $0) } + .subscribe(on: queue) .receive(on: queue) .sinkUntilComplete( receiveCompletion: { result in diff --git a/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift b/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift index 3ac03bdc3..0e746218e 100644 --- a/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift +++ b/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift @@ -49,6 +49,7 @@ public enum SendReadReceiptsJob: JobExecutor { ) } .flatMap { MessageSender.sendImmediate(preparedSendData: $0) } + .subscribe(on: queue) .receive(on: queue) .sinkUntilComplete( receiveCompletion: { result in diff --git a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift index aa1c56d35..59cce9d4c 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift @@ -31,9 +31,7 @@ public enum OpenGroupAPI { server: String, hasPerformedInitialPoll: Bool, timeSinceLastPoll: TimeInterval, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) + using dependencies: SMKDependencies = SMKDependencies() ) -> AnyPublisher<(info: ResponseInfoType, data: [Endpoint: Codable]), Error> { let lastInboxMessageId: Int64 = (try? OpenGroup .select(.inboxLatestMessageId) @@ -153,9 +151,7 @@ public enum OpenGroupAPI { _ db: Database, server: String, requests: [BatchRequest.Info], - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) + using dependencies: SMKDependencies = SMKDependencies() ) -> AnyPublisher<(info: ResponseInfoType, data: [Endpoint: Codable]), Error> { let responseTypes = requests.map { $0.responseType } @@ -187,9 +183,7 @@ public enum OpenGroupAPI { _ db: Database, server: String, requests: [BatchRequest.Info], - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) + using dependencies: SMKDependencies = SMKDependencies() ) -> AnyPublisher<(info: ResponseInfoType, data: [Endpoint: Codable]), Error> { let responseTypes = requests.map { $0.responseType } @@ -217,25 +211,23 @@ public enum OpenGroupAPI { /// /// Eg. `GET /capabilities` could return `{"capabilities": ["sogs", "batch"]}` `GET /capabilities?required=magic,batch` /// could return: `{"capabilities": ["sogs", "batch"], "missing": ["magic"]}` - public static func capabilities( + public static func preparedCapabilities( _ db: Database, server: String, forceBlinded: Bool = false, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) - ) -> AnyPublisher<(ResponseInfoType, Capabilities), Error> { - return OpenGroupAPI - .send( + using dependencies: SMKDependencies = SMKDependencies() + ) throws -> PreparedSendData { + return try OpenGroupAPI + .prepareSendData( db, request: Request( server: server, endpoint: .capabilities ), + responseType: Capabilities.self, forceBlinded: forceBlinded, using: dependencies ) - .decoded(as: Capabilities.self, using: dependencies) } // MARK: - Room @@ -243,23 +235,21 @@ public enum OpenGroupAPI { /// Returns a list of available rooms on the server /// /// Rooms to which the user does not have access (e.g. because they are banned, or the room has restricted access permissions) are not included - public static func rooms( + public static func preparedRooms( _ db: Database, server: String, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) - ) -> AnyPublisher<(ResponseInfoType, [Room]), Error> { - return OpenGroupAPI - .send( + using dependencies: SMKDependencies = SMKDependencies() + ) throws -> PreparedSendData<[Room]> { + return try OpenGroupAPI + .prepareSendData( db, request: Request( server: server, endpoint: .rooms ), + responseType: [Room].self, using: dependencies ) - .decoded(as: [Room].self, using: dependencies) } /// Returns the details of a single room @@ -268,24 +258,22 @@ public enum OpenGroupAPI { /// this directly remove the `@available` line and make sure to route the response of this method to the `OpenGroupManager.handlePollInfo` /// method to ensure things are processed correctly @available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead") - public static func room( + public static func preparedRoom( _ db: Database, for roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) - ) -> AnyPublisher<(ResponseInfoType, Room), Error> { - return OpenGroupAPI - .send( + using dependencies: SMKDependencies = SMKDependencies() + ) throws -> PreparedSendData { + return try OpenGroupAPI + .prepareSendData( db, request: Request( server: server, endpoint: .room(roomToken) ), + responseType: Room.self, using: dependencies ) - .decoded(as: Room.self, using: dependencies) } /// Polls a room for metadata updates @@ -297,25 +285,23 @@ public enum OpenGroupAPI { /// this directly remove the `@available` line and make sure to route the response of this method to the `OpenGroupManager.handlePollInfo` /// method to ensure things are processed correctly @available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead") - public static func roomPollInfo( + public static func preparedRoomPollInfo( _ db: Database, lastUpdated: Int64, for roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) - ) -> AnyPublisher<(ResponseInfoType, RoomPollInfo), Error> { - return OpenGroupAPI - .send( + using dependencies: SMKDependencies = SMKDependencies() + ) throws -> PreparedSendData { + return try OpenGroupAPI + .prepareSendData( db, request: Request( server: server, endpoint: .roomPollInfo(roomToken, lastUpdated) ), + responseType: RoomPollInfo.self, using: dependencies ) - .decoded(as: RoomPollInfo.self, using: dependencies) } public typealias CapabilitiesAndRoomResponse = ( @@ -332,9 +318,7 @@ public enum OpenGroupAPI { _ db: Database, for roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) + using dependencies: SMKDependencies = SMKDependencies() ) -> AnyPublisher { let requestResponseType: [BatchRequest.Info] = [ // Get the latest capabilities for the server (in case it's a new server or the cached ones are stale) @@ -398,9 +382,7 @@ public enum OpenGroupAPI { public static func capabilitiesAndRooms( _ db: Database, on server: String, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) + using dependencies: SMKDependencies = SMKDependencies() ) -> AnyPublisher<(capabilities: (info: ResponseInfoType, data: Capabilities), rooms: (info: ResponseInfoType, data: [Room])), Error> { let requestResponseType: [BatchRequest.Info] = [ // Get the latest capabilities for the server (in case it's a new server or the cached ones are stale) @@ -458,7 +440,7 @@ public enum OpenGroupAPI { // MARK: - Messages /// Posts a new message to a room - public static func send( + public static func preparedSend( _ db: Database, plaintext: Data, to roomToken: String, @@ -466,17 +448,14 @@ public enum OpenGroupAPI { whisperTo: String?, whisperMods: Bool, fileIds: [String]?, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) - ) -> AnyPublisher<(ResponseInfoType, Message), Error> { + using dependencies: SMKDependencies = SMKDependencies() + ) throws -> PreparedSendData { guard let signResult: (publicKey: String, signature: Bytes) = sign(db, messageBytes: plaintext.bytes, for: server, fallbackSigningType: .standard, using: dependencies) else { - return Fail(error: OpenGroupAPIError.signingFailed) - .eraseToAnyPublisher() + throw OpenGroupAPIError.signingFailed } - return OpenGroupAPI - .send( + return try OpenGroupAPI + .prepareSendData( db, request: Request( method: .post, @@ -490,54 +469,49 @@ public enum OpenGroupAPI { fileIds: fileIds ) ), + responseType: Message.self, using: dependencies ) - .decoded(as: Message.self, using: dependencies) } /// Returns a single message by ID - public static func message( + public static func preparedMessage( _ db: Database, id: Int64, in roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) - ) -> AnyPublisher<(ResponseInfoType, Message), Error> { - return OpenGroupAPI - .send( + using dependencies: SMKDependencies = SMKDependencies() + ) throws -> PreparedSendData { + return try OpenGroupAPI + .prepareSendData( db, request: Request( server: server, endpoint: .roomMessageIndividual(roomToken, id: id) ), + responseType: Message.self, using: dependencies ) - .decoded(as: Message.self, using: dependencies) } /// Edits a message, replacing its existing content with new content and a new signature /// /// **Note:** This edit may only be initiated by the creator of the post, and the poster must currently have write permissions in the room - public static func messageUpdate( + public static func preparedMessageUpdate( _ db: Database, id: Int64, plaintext: Data, fileIds: [Int64]?, in roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) - ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { + using dependencies: SMKDependencies = SMKDependencies() + ) throws -> PreparedSendData { guard let signResult: (publicKey: String, signature: Bytes) = sign(db, messageBytes: plaintext.bytes, for: server, fallbackSigningType: .standard, using: dependencies) else { - return Fail(error: OpenGroupAPIError.signingFailed) - .eraseToAnyPublisher() + throw OpenGroupAPIError.signingFailed } - return OpenGroupAPI - .send( + return try OpenGroupAPI + .prepareSendData( db, request: Request( method: .put, @@ -549,27 +523,27 @@ public enum OpenGroupAPI { fileIds: fileIds ) ), + responseType: NoResponse.self, using: dependencies ) } - public static func messageDelete( + public static func preparedMessageDelete( _ db: Database, id: Int64, in roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) - ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { - return OpenGroupAPI - .send( + using dependencies: SMKDependencies = SMKDependencies() + ) throws -> PreparedSendData { + return try OpenGroupAPI + .prepareSendData( db, request: Request( method: .delete, server: server, endpoint: .roomMessageIndividual(roomToken, id: id) ), + responseType: NoResponse.self, using: dependencies ) } @@ -578,66 +552,60 @@ public enum OpenGroupAPI { /// this directly remove the `@available` line and make sure to route the response of this method to the `OpenGroupManager.handleMessages` /// method to ensure things are processed correctly @available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead") - public static func recentMessages( + public static func preparedRecentMessages( _ db: Database, in roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) - ) -> AnyPublisher<(ResponseInfoType, [Message]), Error> { - return OpenGroupAPI - .send( + using dependencies: SMKDependencies = SMKDependencies() + ) throws -> PreparedSendData<[Message]> { + return try OpenGroupAPI + .prepareSendData( db, request: Request( server: server, endpoint: .roomMessagesRecent(roomToken) ), + responseType: [Message].self, using: dependencies ) - .decoded(as: [Message].self, using: dependencies) } /// **Note:** This is the direct request to retrieve recent messages before a given message and is currently unused, in order to call this directly /// remove the `@available` line and make sure to route the response of this method to the `OpenGroupManager.handleMessages` /// method to ensure things are processed correctly @available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead") - public static func messagesBefore( + public static func preparedMessagesBefore( _ db: Database, messageId: Int64, in roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) - ) -> AnyPublisher<(ResponseInfoType, [Message]), Error> { - return OpenGroupAPI - .send( + using dependencies: SMKDependencies = SMKDependencies() + ) throws -> PreparedSendData<[Message]> { + return try OpenGroupAPI + .prepareSendData( db, request: Request( server: server, endpoint: .roomMessagesBefore(roomToken, id: messageId) ), + responseType: [Message].self, using: dependencies ) - .decoded(as: [Message].self, using: dependencies) } /// **Note:** This is the direct request to retrieve messages since a given message `seqNo` so should be retrieved automatically from the /// `poll()` method, in order to call this directly remove the `@available` line and make sure to route the response of this method to the /// `OpenGroupManager.handleMessages` method to ensure things are processed correctly @available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead") - public static func messagesSince( + public static func preparedMessagesSince( _ db: Database, seqNo: Int64, in roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) - ) -> AnyPublisher<(ResponseInfoType, [Message]), Error> { - return OpenGroupAPI - .send( + using dependencies: SMKDependencies = SMKDependencies() + ) throws -> PreparedSendData<[Message]> { + return try OpenGroupAPI + .prepareSendData( db, request: Request( server: server, @@ -647,9 +615,9 @@ public enum OpenGroupAPI { .reactors: "20" ] ), + responseType: [Message].self, using: dependencies ) - .decoded(as: [Message].self, using: dependencies) } /// Deletes all messages from a given sessionId within the provided rooms (or globally) on a server @@ -665,148 +633,134 @@ public enum OpenGroupAPI { /// - server: The server to delete messages from /// /// - dependencies: Injected dependencies (used for unit testing) - public static func messagesDeleteAll( + public static func preparedMessagesDeleteAll( _ db: Database, sessionId: String, in roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) - ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { - return OpenGroupAPI - .send( + using dependencies: SMKDependencies = SMKDependencies() + ) throws -> PreparedSendData { + return try OpenGroupAPI + .prepareSendData( db, request: Request( method: .delete, server: server, endpoint: Endpoint.roomDeleteMessages(roomToken, sessionId: sessionId) ), + responseType: NoResponse.self, using: dependencies ) } // MARK: - Reactions - public static func reactors( + public static func preparedReactors( _ db: Database, emoji: String, id: Int64, in roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) - ) -> AnyPublisher { + using dependencies: SMKDependencies = SMKDependencies() + ) throws -> PreparedSendData { /// URL(String:) won't convert raw emojis, so need to do a little encoding here. /// The raw emoji will come back when calling url.path guard let encodedEmoji: String = emoji.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else { - return Fail(error: OpenGroupAPIError.invalidEmoji) - .eraseToAnyPublisher() + throw OpenGroupAPIError.invalidEmoji } - return OpenGroupAPI - .send( + return try OpenGroupAPI + .prepareSendData( db, request: Request( method: .get, server: server, endpoint: .reactors(roomToken, id: id, emoji: encodedEmoji) ), + responseType: NoResponse.self, using: dependencies ) - .map { responseInfo, _ in responseInfo } - .eraseToAnyPublisher() } - public static func reactionAdd( + public static func preparedReactionAdd( _ db: Database, emoji: String, id: Int64, in roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) - ) -> AnyPublisher<(ResponseInfoType, ReactionAddResponse), Error> { + using dependencies: SMKDependencies = SMKDependencies() + ) throws -> PreparedSendData { /// URL(String:) won't convert raw emojis, so need to do a little encoding here. /// The raw emoji will come back when calling url.path guard let encodedEmoji: String = emoji.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else { - return Fail(error: OpenGroupAPIError.invalidEmoji) - .eraseToAnyPublisher() + throw OpenGroupAPIError.invalidEmoji } - return OpenGroupAPI - .send( + return try OpenGroupAPI + .prepareSendData( db, request: Request( method: .put, server: server, endpoint: .reaction(roomToken, id: id, emoji: encodedEmoji) ), + responseType: ReactionAddResponse.self, using: dependencies ) - .decoded(as: ReactionAddResponse.self, using: dependencies) } - public static func reactionDelete( + public static func preparedReactionDelete( _ db: Database, emoji: String, id: Int64, in roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) - ) -> AnyPublisher<(ResponseInfoType, ReactionRemoveResponse), Error> { + using dependencies: SMKDependencies = SMKDependencies() + ) throws -> PreparedSendData { /// URL(String:) won't convert raw emojis, so need to do a little encoding here. /// The raw emoji will come back when calling url.path guard let encodedEmoji: String = emoji.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else { - return Fail(error: OpenGroupAPIError.invalidEmoji) - .eraseToAnyPublisher() + throw OpenGroupAPIError.invalidEmoji } - return OpenGroupAPI - .send( + return try OpenGroupAPI + .prepareSendData( db, request: Request( method: .delete, server: server, endpoint: .reaction(roomToken, id: id, emoji: encodedEmoji) ), + responseType: ReactionRemoveResponse.self, using: dependencies ) - .decoded(as: ReactionRemoveResponse.self, using: dependencies) } - public static func reactionDeleteAll( + public static func preparedReactionDeleteAll( _ db: Database, emoji: String, id: Int64, in roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) - ) -> AnyPublisher<(ResponseInfoType, ReactionRemoveAllResponse), Error> { + using dependencies: SMKDependencies = SMKDependencies() + ) throws -> PreparedSendData { /// URL(String:) won't convert raw emojis, so need to do a little encoding here. /// The raw emoji will come back when calling url.path guard let encodedEmoji: String = emoji.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else { - return Fail(error: OpenGroupAPIError.invalidEmoji) - .eraseToAnyPublisher() + throw OpenGroupAPIError.invalidEmoji } - return OpenGroupAPI - .send( + return try OpenGroupAPI + .prepareSendData( db, request: Request( method: .delete, server: server, endpoint: .reactionDelete(roomToken, id: id, emoji: encodedEmoji) ), + responseType: ReactionRemoveAllResponse.self, using: dependencies ) - .decoded(as: ReactionRemoveAllResponse.self, using: dependencies) } // MARK: - Pinning @@ -821,94 +775,83 @@ public enum OpenGroupAPI { /// Pinned messages that are already pinned will be re-pinned (that is, their pin timestamp and pinning admin user will be updated) - because pinned /// messages are returned in pinning-order this allows admins to order multiple pinned messages in a room by re-pinning (via this endpoint) in the /// order in which pinned messages should be displayed - public static func pinMessage( + public static func preparedPinMessage( _ db: Database, id: Int64, in roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) - ) -> AnyPublisher { - return OpenGroupAPI - .send( + using dependencies: SMKDependencies = SMKDependencies() + ) throws -> PreparedSendData { + return try OpenGroupAPI + .prepareSendData( db, request: Request( method: .post, server: server, endpoint: .roomPinMessage(roomToken, id: id) ), + responseType: NoResponse.self, using: dependencies ) - .map { responseInfo, _ in responseInfo } - .eraseToAnyPublisher() } /// Remove a message from this room's pinned message list /// /// The user must have `admin` (not just `moderator`) permissions in the room - public static func unpinMessage( + public static func preparedUnpinMessage( _ db: Database, id: Int64, in roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) - ) -> AnyPublisher { - return OpenGroupAPI - .send( + using dependencies: SMKDependencies = SMKDependencies() + ) throws -> PreparedSendData { + return try OpenGroupAPI + .prepareSendData( db, request: Request( method: .post, server: server, endpoint: .roomUnpinMessage(roomToken, id: id) ), + responseType: NoResponse.self, using: dependencies ) - .map { responseInfo, _ in responseInfo } - .eraseToAnyPublisher() } - + /// Removes _all_ pinned messages from this room /// /// The user must have `admin` (not just `moderator`) permissions in the room - public static func unpinAll( + public static func preparedUnpinAll( _ db: Database, in roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) - ) -> AnyPublisher { - return OpenGroupAPI - .send( + using dependencies: SMKDependencies = SMKDependencies() + ) throws -> PreparedSendData { + return try OpenGroupAPI + .prepareSendData( db, request: Request( method: .post, server: server, endpoint: .roomUnpinAll(roomToken) ), + responseType: NoResponse.self, using: dependencies ) - .map { responseInfo, _ in responseInfo } - .eraseToAnyPublisher() } // MARK: - Files - public static func uploadFile( + public static func preparedUploadFile( _ db: Database, bytes: [UInt8], fileName: String? = nil, to roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) - ) -> AnyPublisher<(ResponseInfoType, FileUploadResponse), Error> { - return OpenGroupAPI - .send( + using dependencies: SMKDependencies = SMKDependencies() + ) throws -> PreparedSendData { + return try OpenGroupAPI + .prepareSendData( db, request: Request( method: .post, @@ -922,37 +865,30 @@ public enum OpenGroupAPI { ], body: bytes ), + responseType: FileUploadResponse.self, timeout: FileServerAPI.fileUploadTimeout, using: dependencies ) - .decoded(as: FileUploadResponse.self, using: dependencies) } - public static func downloadFile( + public static func preparedDownloadFile( _ db: Database, fileId: String, from roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) - ) -> AnyPublisher<(ResponseInfoType, Data), Error> { - return OpenGroupAPI - .send( + using dependencies: SMKDependencies = SMKDependencies() + ) throws -> PreparedSendData { + return try OpenGroupAPI + .prepareSendData( db, request: Request( server: server, endpoint: .roomFileIndividual(roomToken, fileId) ), + responseType: Data.self, timeout: FileServerAPI.fileDownloadTimeout, using: dependencies ) - .tryMap { responseInfo, maybeData -> (ResponseInfoType, Data) in - guard let data: Data = maybeData else { throw HTTPError.parsingFailed } - - return (responseInfo, data) - } - .eraseToAnyPublisher() } // MARK: - Inbox/Outbox (Message Requests) @@ -963,23 +899,21 @@ public enum OpenGroupAPI { /// method, in order to call this directly remove the `@available` line and make sure to route the response of this method to the /// `OpenGroupManager.handleDirectMessages` method to ensure things are processed correctly @available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead") - public static func inbox( + public static func preparedInbox( _ db: Database, on server: String, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) - ) -> AnyPublisher<(ResponseInfoType, [DirectMessage]?), Error> { - return OpenGroupAPI - .send( + using dependencies: SMKDependencies = SMKDependencies() + ) throws -> PreparedSendData<[DirectMessage]?> { + return try OpenGroupAPI + .prepareSendData( db, request: Request( server: server, endpoint: .inbox ), + responseType: [DirectMessage]?.self, using: dependencies ) - .decoded(as: [DirectMessage]?.self, using: dependencies) } /// Polls for any DMs received since the given id, this method will return a `304` with an empty response if there are no messages @@ -988,40 +922,36 @@ public enum OpenGroupAPI { /// automatically from the `poll()` method, in order to call this directly remove the `@available` line and make sure to route the response /// of this method to the `OpenGroupManager.handleDirectMessages` method to ensure things are processed correctly @available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead") - public static func inboxSince( + public static func preparedInboxSince( _ db: Database, id: Int64, on server: String, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) - ) -> AnyPublisher<(ResponseInfoType, [DirectMessage]?), Error> { - return OpenGroupAPI - .send( + using dependencies: SMKDependencies = SMKDependencies() + ) throws -> PreparedSendData<[DirectMessage]?> { + return try OpenGroupAPI + .prepareSendData( db, request: Request( server: server, endpoint: .inboxSince(id: id) ), + responseType: [DirectMessage]?.self, using: dependencies ) - .decoded(as: [DirectMessage]?.self, using: dependencies) } /// Delivers a direct message to a user via their blinded Session ID /// /// The body of this request is a JSON object containing a message key with a value of the encrypted-then-base64-encoded message to deliver - public static func send( + public static func preparedSend( _ db: Database, ciphertext: Data, toInboxFor blindedSessionId: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) - ) -> AnyPublisher<(ResponseInfoType, SendDirectMessageResponse), Error> { - return OpenGroupAPI - .send( + using dependencies: SMKDependencies = SMKDependencies() + ) throws -> PreparedSendData { + return try OpenGroupAPI + .prepareSendData( db, request: Request( method: .post, @@ -1031,9 +961,9 @@ public enum OpenGroupAPI { message: ciphertext ) ), + responseType: SendDirectMessageResponse.self, using: dependencies ) - .decoded(as: SendDirectMessageResponse.self, using: dependencies) } /// Retrieves all of the user's sent DMs (up to limit) @@ -1042,23 +972,21 @@ public enum OpenGroupAPI { /// from the `poll()` method, in order to call this directly remove the `@available` line and make sure to route the response of /// this method to the `OpenGroupManager.handleDirectMessages` method to ensure things are processed correctly @available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead") - public static func outbox( + public static func preparedOutbox( _ db: Database, on server: String, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) - ) -> AnyPublisher<(ResponseInfoType, [DirectMessage]?), Error> { - return OpenGroupAPI - .send( + using dependencies: SMKDependencies = SMKDependencies() + ) throws -> PreparedSendData<[DirectMessage]?> { + return try OpenGroupAPI + .prepareSendData( db, request: Request( server: server, endpoint: .outbox ), + responseType: [DirectMessage]?.self, using: dependencies ) - .decoded(as: [DirectMessage]?.self, using: dependencies) } /// Polls for any DMs sent since the given id, this method will return a `304` with an empty response if there are no messages @@ -1067,24 +995,22 @@ public enum OpenGroupAPI { /// should be retrieved automatically from the `poll()` method, in order to call this directly remove the `@available` line and make sure /// to route the response of this method to the `OpenGroupManager.handleDirectMessages` method to ensure things are processed correctly @available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead") - public static func outboxSince( + public static func preparedOutboxSince( _ db: Database, id: Int64, on server: String, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) - ) -> AnyPublisher<(ResponseInfoType, [DirectMessage]?), Error> { - return OpenGroupAPI - .send( + using dependencies: SMKDependencies = SMKDependencies() + ) throws -> PreparedSendData<[DirectMessage]?> { + return try OpenGroupAPI + .prepareSendData( db, request: Request( server: server, endpoint: .outboxSince(id: id) ), + responseType: [DirectMessage]?.self, using: dependencies ) - .decoded(as: [DirectMessage]?.self, using: dependencies) } // MARK: - Users @@ -1120,18 +1046,16 @@ public enum OpenGroupAPI { /// - server: The server to delete messages from /// /// - dependencies: Injected dependencies (used for unit testing) - public static func userBan( + public static func preparedUserBan( _ db: Database, sessionId: String, for timeout: TimeInterval? = nil, from roomTokens: [String]? = nil, on server: String, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) - ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { - return OpenGroupAPI - .send( + using dependencies: SMKDependencies = SMKDependencies() + ) throws -> PreparedSendData { + return try OpenGroupAPI + .prepareSendData( db, request: Request( method: .post, @@ -1143,6 +1067,7 @@ public enum OpenGroupAPI { timeout: timeout ) ), + responseType: NoResponse.self, using: dependencies ) } @@ -1171,17 +1096,15 @@ public enum OpenGroupAPI { /// - server: The server to delete messages from /// /// - dependencies: Injected dependencies (used for unit testing) - public static func userUnban( + public static func preparedUserUnban( _ db: Database, sessionId: String, from roomTokens: [String]?, on server: String, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) - ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { - return OpenGroupAPI - .send( + using dependencies: SMKDependencies = SMKDependencies() + ) throws -> PreparedSendData { + return try OpenGroupAPI + .prepareSendData( db, request: Request( method: .post, @@ -1192,6 +1115,7 @@ public enum OpenGroupAPI { global: (roomTokens == nil ? true : nil) ) ), + responseType: NoResponse.self, using: dependencies ) } @@ -1247,7 +1171,7 @@ public enum OpenGroupAPI { /// - server: The server to perform the permission changes on /// /// - dependencies: Injected dependencies (used for unit testing) - public static func userModeratorUpdate( + public static func preparedUserModeratorUpdate( _ db: Database, sessionId: String, moderator: Bool? = nil, @@ -1255,17 +1179,14 @@ public enum OpenGroupAPI { visible: Bool, for roomTokens: [String]?, on server: String, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) - ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { + using dependencies: SMKDependencies = SMKDependencies() + ) throws -> PreparedSendData { guard (moderator != nil && admin == nil) || (moderator == nil && admin != nil) else { - return Fail(error: HTTPError.generic) - .eraseToAnyPublisher() + throw HTTPError.generic } - return OpenGroupAPI - .send( + return try OpenGroupAPI + .prepareSendData( db, request: Request( method: .post, @@ -1279,6 +1200,7 @@ public enum OpenGroupAPI { visible: visible ) ), + responseType: NoResponse.self, using: dependencies ) } @@ -1290,9 +1212,7 @@ public enum OpenGroupAPI { sessionId: String, in roomToken: String, on server: String, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) + using dependencies: SMKDependencies = SMKDependencies() ) -> AnyPublisher<(info: ResponseInfoType, data: [Endpoint: ResponseInfoType]), Error> { let banRequestBody: UserBanRequest = UserBanRequest( rooms: [roomToken], @@ -1344,9 +1264,7 @@ public enum OpenGroupAPI { for serverName: String, fallbackSigningType signingType: SessionId.Prefix, forceBlinded: Bool = false, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) + using dependencies: SMKDependencies = SMKDependencies() ) -> (publicKey: String, signature: Bytes)? { guard let userEdKeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db), @@ -1413,9 +1331,7 @@ public enum OpenGroupAPI { for serverName: String, with serverPublicKey: String, forceBlinded: Bool = false, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) + using dependencies: SMKDependencies = SMKDependencies() ) -> URLRequest? { guard let url: URL = request.url else { return nil } @@ -1472,14 +1388,44 @@ public enum OpenGroupAPI { // MARK: - Convenience + private static func prepareSendData( + _ db: Database, + request: Request, + responseType: R.Type, + forceBlinded: Bool = false, + timeout: TimeInterval = HTTP.defaultTimeout, + using dependencies: SMKDependencies = SMKDependencies() + ) throws -> PreparedSendData { + let urlRequest: URLRequest = try request.generateUrlRequest() + let maybePublicKey: String? = try? OpenGroup + .select(.publicKey) + .filter(OpenGroup.Columns.server == request.server.lowercased()) + .asRequest(of: String.self) + .fetchOne(db) + + guard let publicKey: String = maybePublicKey else { throw OpenGroupAPIError.noPublicKey } + + // Attempt to sign the request with the new auth + guard let signedRequest: URLRequest = sign(db, request: urlRequest, for: request.server, with: publicKey, forceBlinded: forceBlinded, using: dependencies) else { + throw OpenGroupAPIError.signingFailed + } + + return PreparedSendData( + request: signedRequest, + endpoint: request.endpoint, + server: request.server, + publicKey: publicKey, + responseType: responseType, + timeout: timeout + ) + } + private static func send( _ db: Database, request: Request, forceBlinded: Bool = false, timeout: TimeInterval = HTTP.defaultTimeout, - using dependencies: SMKDependencies = SMKDependencies( - queue: OpenGroupAPI.workQueue - ) + using dependencies: SMKDependencies = SMKDependencies() ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { let urlRequest: URLRequest @@ -1511,8 +1457,27 @@ public enum OpenGroupAPI { // We want to avoid blocking the db write thread so we dispatch the API call to a different thread return Just(()) .setFailureType(to: Error.self) - .subscribe(on: dependencies.queue) .flatMap { dependencies.onionApi.sendOnionRequest(signedRequest, to: request.server, with: publicKey, timeout: timeout) } .eraseToAnyPublisher() } + + public static func send( + data: PreparedSendData?, + using dependencies: SMKDependencies = SMKDependencies() + ) -> AnyPublisher<(ResponseInfoType, R), Error> { + guard let validData: PreparedSendData = data else { + return Fail(error: OpenGroupAPIError.invalidPreparedData) + .eraseToAnyPublisher() + } + + return dependencies.onionApi + .sendOnionRequest( + validData.request, + to: validData.server, + with: validData.publicKey, + timeout: validData.timeout + ) + .decoded(with: validData, using: dependencies) + .eraseToAnyPublisher() + } } diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index 0cb6f4a6b..08b585bd5 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -296,8 +296,6 @@ public final class OpenGroupManager { ) } } - .subscribe(on: OpenGroupAPI.workQueue) - .receive(on: OpenGroupAPI.workQueue) .flatMap { response -> Future in Future { resolver in dependencies.storage.write { db in @@ -347,7 +345,7 @@ public final class OpenGroupManager { _ db: Database, openGroupId: String, calledFromConfigHandling: Bool, - dependencies: OGMDependencies = OGMDependencies() + using dependencies: OGMDependencies = OGMDependencies() ) { let server: String? = try? OpenGroup .select(.server) @@ -907,26 +905,28 @@ public final class OpenGroupManager { } /// This method specifies if the given capability is supported on a specified Open Group - public static func isOpenGroupSupport( - _ capability: Capability.Variant, + public static func doesOpenGroupSupport( + _ db: Database? = nil, + capability: Capability.Variant, on server: String?, using dependencies: OGMDependencies = OGMDependencies() ) -> Bool { guard let server: String = server else { return false } + guard let db: Database = db else { + return dependencies.storage + .read { db in doesOpenGroupSupport(db, capability: capability, on: server, using: dependencies) } + .defaulting(to: false) + } - return dependencies.storage - .read { db in - let capabilities: [Capability.Variant] = (try? Capability - .select(.variant) - .filter(Capability.Columns.openGroupServer == server) - .filter(Capability.Columns.isMissing == false) - .asRequest(of: Capability.Variant.self) - .fetchAll(db)) - .defaulting(to: []) + let capabilities: [Capability.Variant] = (try? Capability + .select(.variant) + .filter(Capability.Columns.openGroupServer == server) + .filter(Capability.Columns.isMissing == false) + .asRequest(of: Capability.Variant.self) + .fetchAll(db)) + .defaulting(to: []) - return capabilities.contains(capability) - } - .defaulting(to: false) + return capabilities.contains(capability) } /// This method specifies if the given publicKey is a moderator or an admin within a specified Open Group @@ -1012,7 +1012,10 @@ public final class OpenGroupManager { } @discardableResult public static func getDefaultRoomsIfNeeded( - using dependencies: OGMDependencies = OGMDependencies() + using dependencies: OGMDependencies = OGMDependencies( + subscribeQueue: OpenGroupAPI.workQueue, + receiveQueue: OpenGroupAPI.workQueue + ) ) -> AnyPublisher<[OpenGroupAPI.Room], Error> { // Note: If we already have a 'defaultRoomsPromise' then there is no need to get it again if let existingPublisher: AnyPublisher<[OpenGroupAPI.Room], Error> = dependencies.cache.defaultRoomsPublisher { @@ -1028,8 +1031,8 @@ public final class OpenGroupManager { using: dependencies ) } - .subscribe(on: OpenGroupAPI.workQueue) - .receive(on: OpenGroupAPI.workQueue) + .subscribe(on: dependencies.subscribeQueue, immediatelyIfMain: true) + .receive(on: dependencies.receiveQueue, immediatelyIfMain: true) .retry(8) .map { response in dependencies.storage.writeAsync { db in @@ -1077,7 +1080,6 @@ public final class OpenGroupManager { on: OpenGroupAPI.defaultServer, using: dependencies ) - .sinkUntilComplete() } } @@ -1107,13 +1109,13 @@ public final class OpenGroupManager { return publisher } - public static func roomImage( + @discardableResult public static func roomImage( _ db: Database, fileId: String, for roomToken: String, on server: String, using dependencies: OGMDependencies = OGMDependencies( - queue: DispatchQueue.global(qos: .background) + subscribeQueue: .global(qos: .background) ) ) -> AnyPublisher { // Normally the image for a given group is stored with the group thread, so it's only @@ -1149,37 +1151,63 @@ public final class OpenGroupManager { return publisher } - // Trigger the download on a background queue - let publisher: AnyPublisher = OpenGroupAPI - .downloadFile( - db, - fileId: fileId, - from: roomToken, - on: server, - using: dependencies - ) - .map { _, imageData in - if server.lowercased() == OpenGroupAPI.defaultServer { - dependencies.storage.write { db in - _ = try OpenGroup - .filter(id: threadId) - .updateAll(db, OpenGroup.Columns.imageData.set(to: imageData)) - } - dependencies.standardUserDefaults[.lastOpenGroupImageUpdate] = now + let sendData: OpenGroupAPI.PreparedSendData + + do { + sendData = try OpenGroupAPI + .preparedDownloadFile( + db, + fileId: fileId, + from: roomToken, + on: server, + using: dependencies + ) + } + catch { + return Fail(error: error) + .eraseToAnyPublisher() + } + + // Defer the actual download and run it on a separate thread to avoid blocking the calling thread + let publisher: AnyPublisher = Deferred { + Future { resolver in + dependencies.subscribeQueue.async { + // Hold on to the publisher until it has completed at least once + OpenGroupAPI + .send( + data: sendData, + using: dependencies + ) + .sinkUntilComplete( + receiveCompletion: { result in + switch result { + case .finished: break + case .failure(let error): resolver(Result.failure(error)) + } + }, + receiveValue: { _, imageData in + if server.lowercased() == OpenGroupAPI.defaultServer { + dependencies.storage.write { db in + _ = try OpenGroup + .filter(id: threadId) + .updateAll(db, OpenGroup.Columns.imageData.set(to: imageData)) + } + dependencies.standardUserDefaults[.lastOpenGroupImageUpdate] = now + } + + resolver(Result.success(imageData)) + } + ) } - - return imageData } - .shareReplay(1) - .eraseToAnyPublisher() + } + .shareReplay(1) + .eraseToAnyPublisher() dependencies.mutableCache.mutate { cache in cache.groupImagePublishers[threadId] = publisher } - // Hold on to the publisher until it has completed at least once - publisher.sinkUntilComplete() - return publisher } } @@ -1198,7 +1226,8 @@ extension OpenGroupManager { public var cache: OGMCacheType { return mutableCache.wrappedValue } public init( - queue: DispatchQueue? = nil, + subscribeQueue: DispatchQueue? = nil, + receiveQueue: DispatchQueue? = nil, cache: Atomic? = nil, onionApi: OnionRequestAPIType.Type? = nil, generalCache: Atomic? = nil, @@ -1218,7 +1247,8 @@ extension OpenGroupManager { _mutableCache = Atomic(cache) super.init( - queue: queue, + subscribeQueue: subscribeQueue, + receiveQueue: receiveQueue, onionApi: onionApi, generalCache: generalCache, storage: storage, diff --git a/SessionMessagingKit/Open Groups/Types/OpenGroupAPIError.swift b/SessionMessagingKit/Open Groups/Types/OpenGroupAPIError.swift index fa427f86f..a86383993 100644 --- a/SessionMessagingKit/Open Groups/Types/OpenGroupAPIError.swift +++ b/SessionMessagingKit/Open Groups/Types/OpenGroupAPIError.swift @@ -7,6 +7,7 @@ public enum OpenGroupAPIError: LocalizedError { case signingFailed case noPublicKey case invalidEmoji + case invalidPreparedData public var errorDescription: String? { switch self { @@ -14,6 +15,7 @@ public enum OpenGroupAPIError: LocalizedError { case .signingFailed: return "Couldn't sign message." case .noPublicKey: return "Couldn't find server public key." case .invalidEmoji: return "The emoji is invalid." + case .invalidPreparedData: return "Invalid PreparedSendData provided." } } } diff --git a/SessionMessagingKit/Open Groups/Types/PreparedSendData.swift b/SessionMessagingKit/Open Groups/Types/PreparedSendData.swift new file mode 100644 index 000000000..f2798326f --- /dev/null +++ b/SessionMessagingKit/Open Groups/Types/PreparedSendData.swift @@ -0,0 +1,125 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import Combine +import SessionUtilitiesKit + +public extension OpenGroupAPI { + struct PreparedSendData { + internal let request: URLRequest + internal let endpoint: Endpoint + internal let server: String + internal let publicKey: String + internal let originalType: Decodable.Type + internal let responseType: R.Type + internal let timeout: TimeInterval + internal let responseConverter: ((ResponseInfoType, Any) throws -> R) + + internal init( + request: URLRequest, + endpoint: Endpoint, + server: String, + publicKey: String, + responseType: R.Type, + timeout: TimeInterval + ) where R: Decodable { + self.request = request + self.endpoint = endpoint + self.server = server + self.publicKey = publicKey + self.originalType = responseType + self.responseType = responseType + self.timeout = timeout + self.responseConverter = { _, response in + guard let validResponse: R = response as? R else { throw HTTPError.invalidResponse } + + return validResponse + } + } + + private init( + request: URLRequest, + endpoint: Endpoint, + server: String, + publicKey: String, + originalType: U.Type, + responseType: R.Type, + timeout: TimeInterval, + responseConverter: @escaping (ResponseInfoType, Any) throws -> R + ) { + self.request = request + self.endpoint = endpoint + self.server = server + self.publicKey = publicKey + self.originalType = originalType + self.responseType = responseType + self.timeout = timeout + self.responseConverter = responseConverter + } + } +} + +public extension OpenGroupAPI.PreparedSendData { + func map(transform: @escaping (ResponseInfoType, R) throws -> O) -> OpenGroupAPI.PreparedSendData { + return OpenGroupAPI.PreparedSendData( + request: request, + endpoint: endpoint, + server: server, + publicKey: publicKey, + originalType: originalType, + responseType: O.self, + timeout: timeout, + responseConverter: { info, response in + let validResponse: R = try responseConverter(info, response) + + return try transform(info, validResponse) + } + ) + } +} + +// MARK: - Convenience + +public extension Publisher where Output == (ResponseInfoType, Data?), Failure == Error { + func decoded( + with preparedData: OpenGroupAPI.PreparedSendData, + using dependencies: Dependencies = Dependencies() + ) -> AnyPublisher<(ResponseInfoType, R), Error> { + self + .tryMap { responseInfo, maybeData -> (ResponseInfoType, R) in + // Depending on the 'originalType' we need to process the response differently + let targetData: Any = try { + switch preparedData.originalType { + case is NoResponse.Type: return NoResponse() + case is Optional.Type: return maybeData as Any + case is Data.Type: return try maybeData ?? { throw HTTPError.parsingFailed }() + + case is _OptionalProtocol.Type: + guard let data: Data = maybeData else { return maybeData as Any } + + return try preparedData.originalType.decoded(from: data, using: dependencies) + + default: + guard let data: Data = maybeData else { throw HTTPError.parsingFailed } + + return try preparedData.originalType.decoded(from: data, using: dependencies) + } + }() + + // Generate and return the converted data + let convertedData: R = try preparedData.responseConverter(responseInfo, targetData) + + return (responseInfo, convertedData) + } + .eraseToAnyPublisher() + } +} + +// MARK: - _OptionalProtocol + +/// This protocol should only be used within this file and is used to distinguish between `Any.Type` and `Optional.Type` as +/// it seems that `is Optional.Type` doesn't work nicely but this protocol works nicely as long as the case is under any explicit +/// `Optional` handling that we need +private protocol _OptionalProtocol {} + +extension Optional: _OptionalProtocol {} diff --git a/SessionMessagingKit/SMKDependencies.swift b/SessionMessagingKit/SMKDependencies.swift index fa66b4b6a..8b763356e 100644 --- a/SessionMessagingKit/SMKDependencies.swift +++ b/SessionMessagingKit/SMKDependencies.swift @@ -1,4 +1,5 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + import Foundation import GRDB import Sodium @@ -57,7 +58,8 @@ public class SMKDependencies: SSKDependencies { // MARK: - Initialization public init( - queue: DispatchQueue? = nil, + subscribeQueue: DispatchQueue? = nil, + receiveQueue: DispatchQueue? = nil, onionApi: OnionRequestAPIType.Type? = nil, generalCache: Atomic? = nil, storage: Storage? = nil, @@ -83,7 +85,8 @@ public class SMKDependencies: SSKDependencies { _nonceGenerator24 = Atomic(nonceGenerator24) super.init( - queue: queue, + subscribeQueue: subscribeQueue, + receiveQueue: receiveQueue, onionApi: onionApi, generalCache: generalCache, storage: storage, diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift index 8737f2643..6497f7e3e 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift @@ -64,12 +64,14 @@ extension MessageReceiver { let thread: SessionThread = try SessionThread .fetchOrCreate(db, id: sender, variant: .contact, shouldBeVisible: nil) - Environment.shared?.notificationsManager.wrappedValue? - .notifyUser( - db, - forIncomingCall: interaction, - in: thread - ) + if !interaction.wasRead { + Environment.shared?.notificationsManager.wrappedValue? + .notifyUser( + db, + forIncomingCall: interaction, + in: thread + ) + } } return } @@ -79,12 +81,14 @@ extension MessageReceiver { let thread: SessionThread = try SessionThread .fetchOrCreate(db, id: sender, variant: .contact, shouldBeVisible: nil) - Environment.shared?.notificationsManager.wrappedValue? - .notifyUser( - db, - forIncomingCall: interaction, - in: thread - ) + if !interaction.wasRead { + Environment.shared?.notificationsManager.wrappedValue? + .notifyUser( + db, + forIncomingCall: interaction, + in: thread + ) + } // Trigger the missed call UI if needed NotificationCenter.default.post( @@ -196,6 +200,10 @@ extension MessageReceiver { SNLog("[Calls] Sending end call message because there is an ongoing call.") + let messageSentTimestamp: Int64 = ( + message.sentTimestamp.map { Int64($0) } ?? + SnodeAPI.currentOffsetTimestampMs() + ) _ = try Interaction( serverHash: message.serverHash, messageUuid: message.uuid, @@ -203,9 +211,13 @@ extension MessageReceiver { authorId: caller, variant: .infoCall, body: String(data: messageInfoData, encoding: .utf8), - timestampMs: ( - message.sentTimestamp.map { Int64($0) } ?? - SnodeAPI.currentOffsetTimestampMs() + timestampMs: messageSentTimestamp, + wasRead: SessionUtil.timestampAlreadyRead( + threadId: thread.id, + threadVariant: thread.variant, + timestampMs: (messageSentTimestamp * 1000), + userPublicKey: getUserHexEncodedPublicKey(db), + openGroup: nil ) ) .inserted(db) @@ -227,6 +239,7 @@ extension MessageReceiver { interactionId: nil // Explicitly nil as it's a separate message from above ) ) + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .sinkUntilComplete() } @@ -246,9 +259,10 @@ extension MessageReceiver { !thread.isMessageRequest(db) else { return nil } + let currentUserPublicKey: String = getUserHexEncodedPublicKey(db) let messageInfo: CallMessage.MessageInfo = CallMessage.MessageInfo( state: state.defaulting( - to: (sender == getUserHexEncodedPublicKey(db) ? + to: (sender == currentUserPublicKey ? .outgoing : .incoming ) @@ -268,7 +282,14 @@ extension MessageReceiver { authorId: sender, variant: .infoCall, body: String(data: messageInfoData, encoding: .utf8), - timestampMs: timestampMs + timestampMs: timestampMs, + wasRead: SessionUtil.timestampAlreadyRead( + threadId: thread.id, + threadVariant: thread.variant, + timestampMs: (timestampMs * 1000), + userPublicKey: currentUserPublicKey, + openGroup: nil + ) ).inserted(db) } } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+DataExtractionNotification.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+DataExtractionNotification.swift index 48b91fba4..db91fb8ea 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+DataExtractionNotification.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+DataExtractionNotification.swift @@ -3,6 +3,7 @@ import Foundation import GRDB import SessionSnodeKit +import SessionUtilitiesKit extension MessageReceiver { internal static func handleDataExtractionNotification( @@ -17,6 +18,10 @@ extension MessageReceiver { let messageKind: DataExtractionNotification.Kind = message.kind else { throw MessageReceiverError.invalidMessage } + let timestampMs: Int64 = ( + message.sentTimestamp.map { Int64($0) } ?? + SnodeAPI.currentOffsetTimestampMs() + ) _ = try Interaction( serverHash: message.serverHash, threadId: threadId, @@ -27,9 +32,13 @@ extension MessageReceiver { case .mediaSaved: return .infoMediaSavedNotification } }(), - timestampMs: ( - message.sentTimestamp.map { Int64($0) } ?? - SnodeAPI.currentOffsetTimestampMs() + timestampMs: timestampMs, + wasRead: SessionUtil.timestampAlreadyRead( + threadId: threadId, + threadVariant: threadVariant, + timestampMs: (timestampMs * 1000), + userPublicKey: getUserHexEncodedPublicKey(db), + openGroup: nil ) ).inserted(db) } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ExpirationTimers.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ExpirationTimers.swift index fbec8536b..60a95f4ca 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ExpirationTimers.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ExpirationTimers.swift @@ -71,18 +71,26 @@ extension MessageReceiver { } // Add an info message for the user + let currentUserPublicKey: String = getUserHexEncodedPublicKey(db) _ = try Interaction( serverHash: nil, // Intentionally null so sync messages are seen as duplicates threadId: threadId, authorId: sender, variant: .infoDisappearingMessagesUpdate, body: config.messageInfoString( - with: (sender != getUserHexEncodedPublicKey(db) ? + with: (sender != currentUserPublicKey ? Profile.displayName(db, id: sender) : nil ) ), - timestampMs: timestampMs + timestampMs: timestampMs, + wasRead: SessionUtil.timestampAlreadyRead( + threadId: threadId, + threadVariant: threadVariant, + timestampMs: (timestampMs * 1000), + userPublicKey: currentUserPublicKey, + openGroup: nil + ) ).inserted(db) // Only save the updated config if we can perform the change diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+UnsendRequests.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+UnsendRequests.swift index 433c1734f..ea80c22ff 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+UnsendRequests.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+UnsendRequests.swift @@ -48,6 +48,7 @@ extension MessageReceiver { publicKey: author, serverHashes: [serverHash] ) + .subscribe(on: DispatchQueue.global(qos: .background)) .sinkUntilComplete() } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift index 1fc33de52..2958b3548 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift @@ -117,7 +117,8 @@ extension MessageReceiver { message: message, associatedWithProto: proto, sender: sender, - messageSentTimestamp: messageSentTimestamp + messageSentTimestamp: messageSentTimestamp, + openGroup: maybeOpenGroup ) { return interactionId } @@ -323,7 +324,7 @@ extension MessageReceiver { } // Notify the user if needed - guard variant == .standardIncoming else { return interactionId } + guard variant == .standardIncoming && !interaction.wasRead else { return interactionId } // Use the same identifier for notifications when in backgroud polling to prevent spam Environment.shared?.notificationsManager.wrappedValue? @@ -342,7 +343,8 @@ extension MessageReceiver { message: VisibleMessage, associatedWithProto proto: SNProtoContent, sender: String, - messageSentTimestamp: TimeInterval + messageSentTimestamp: TimeInterval, + openGroup: OpenGroup? ) throws -> Int64? { guard let reaction: VisibleMessage.VMReaction = message.reaction, @@ -370,17 +372,28 @@ extension MessageReceiver { switch reaction.kind { case .react: + let timestampMs: Int64 = Int64(messageSentTimestamp * 1000) + let currentUserPublicKey: String = getUserHexEncodedPublicKey(db) let reaction: Reaction = try Reaction( interactionId: interactionId, serverHash: message.serverHash, - timestampMs: Int64(messageSentTimestamp * 1000), + timestampMs: timestampMs, authorId: sender, emoji: reaction.emoji, count: 1, sortId: sortId ).inserted(db) + let timestampAlreadyRead: Bool = SessionUtil.timestampAlreadyRead( + threadId: thread.id, + threadVariant: thread.variant, + timestampMs: timestampMs, + userPublicKey: currentUserPublicKey, + openGroup: openGroup + ) - if sender != getUserHexEncodedPublicKey(db) { + // Don't notify if the reaction was added before the lastest read timestamp for + // the conversation + if sender != currentUserPublicKey && !timestampAlreadyRead { Environment.shared?.notificationsManager.wrappedValue? .notifyUser( db, diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift b/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift index 4c6421b34..f0e139d35 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift @@ -108,10 +108,7 @@ extension MessageSender { ) } - public static func performUploadsIfNeeded( - queue: DispatchQueue, - preparedSendData: PreparedSendData - ) -> AnyPublisher { + public static func performUploadsIfNeeded(preparedSendData: PreparedSendData) -> AnyPublisher { // We need an interactionId in order for a message to have uploads guard let interactionId: Int64 = preparedSendData.interactionId else { return Just(preparedSendData) diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index 1da13d3ec..9005bf1b8 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -643,7 +643,6 @@ public final class MessageSender { snodeMessage, in: namespace ) - .subscribe(on: DispatchQueue.global(qos: .default)) .flatMap { response -> AnyPublisher in let updatedMessage: Message = message updatedMessage.serverHash = response.1.hash @@ -703,7 +702,7 @@ public final class MessageSender { Future { resolver in NotifyPushServerJob.run( job, - queue: DispatchQueue.global(qos: .default), + queue: .global(qos: .default), success: { _, _ in resolver(Result.success(())) }, failure: { _, _, _ in // Always fulfill because the notify PN server job isn't critical. @@ -760,9 +759,9 @@ public final class MessageSender { // Send the result return dependencies.storage - .readPublisherFlatMap { db in - OpenGroupAPI - .send( + .readPublisher { db in + try OpenGroupAPI + .preparedSend( db, plaintext: plaintext, to: roomToken, @@ -773,7 +772,7 @@ public final class MessageSender { using: dependencies ) } - .subscribe(on: DispatchQueue.global(qos: .default)) + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .flatMap { (responseInfo, responseData) -> AnyPublisher in let serverTimestampMs: UInt64? = responseData.posted.map { UInt64(floor($0 * 1000)) } let updatedMessage: Message = message @@ -828,9 +827,9 @@ public final class MessageSender { // Send the result return dependencies.storage - .readPublisherFlatMap { db in - return OpenGroupAPI - .send( + .readPublisher { db in + try OpenGroupAPI + .preparedSend( db, ciphertext: ciphertext, toInboxFor: recipientBlindedPublicKey, @@ -838,7 +837,7 @@ public final class MessageSender { using: dependencies ) } - .subscribe(on: DispatchQueue.global(qos: .default)) + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .flatMap { (responseInfo, responseData) -> AnyPublisher in let updatedMessage: Message = message updatedMessage.openGroupServerMessageId = UInt64(responseData.id) diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift index c84258b4a..b752b6464 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift @@ -92,12 +92,16 @@ public final class ClosedGroupPoller: Poller { .eraseToAnyPublisher() } - override func handlePollError(_ error: Error, for publicKey: String) { + override func handlePollError( + _ error: Error, + for publicKey: String, + using dependencies: SMKDependencies = SMKDependencies() + ) { SNLog("Polling failed for closed group with public key: \(publicKey) due to error: \(error).") // Try to restart the poller from scratch Threading.pollerQueue.async { [weak self] in - self?.setUpPolling(for: publicKey) + self?.setUpPolling(for: publicKey, using: dependencies) } } } diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift index e4b07e678..b144f9479 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift @@ -100,7 +100,11 @@ public final class CurrentUserPoller: Poller { .eraseToAnyPublisher() } - override func handlePollError(_ error: Error, for publicKey: String) { + override func handlePollError( + _ error: Error, + for publicKey: String, + using dependencies: SMKDependencies = SMKDependencies() + ) { if UserDefaults.sharedLokiProject?[.isMainAppActive] != true { // Do nothing when an error gets throws right after returning from the background (happens frequently) } @@ -115,7 +119,7 @@ public final class CurrentUserPoller: Poller { // Try to restart the poller from scratch Threading.pollerQueue.async { [weak self] in - self?.setUpPolling(for: publicKey) + self?.setUpPolling(for: publicKey, using: dependencies) } } } diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift index 7458ca7a8..975a31770 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift @@ -18,8 +18,16 @@ extension OpenGroupAPI { // MARK: - Settings private static let minPollInterval: TimeInterval = 3 - private static let maxPollInterval: Double = (60 * 60) - internal static let maxInactivityPeriod: Double = (14 * 24 * 60 * 60) + private static let maxPollInterval: TimeInterval = (60 * 60) + internal static let maxInactivityPeriod: TimeInterval = (14 * 24 * 60 * 60) + + /// If there are hidden rooms that we poll and they fail too many times we want to prune them (as it likely means they no longer + /// exist, and since they are already hidden it's unlikely that the user will notice that we stopped polling for them) + internal static let maxHiddenRoomFailureCount: Int64 = 10 + + /// When doing a background poll we want to only fetch from rooms which are unlikely to timeout, in order to do this we exclude + /// any rooms which have failed more than this threashold + public static let maxRoomFailureCountForBackgroundPoll: Int64 = 15 // MARK: - Lifecycle @@ -41,7 +49,12 @@ extension OpenGroupAPI { // MARK: - Polling - private func pollRecursively(using dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies()) { + private func pollRecursively( + using dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies( + subscribeQueue: Threading.pollerQueue, + receiveQueue: OpenGroupAPI.workQueue + ) + ) { guard hasStarted else { return } let minPollFailureCount: TimeInterval = Storage.shared @@ -59,6 +72,8 @@ extension OpenGroupAPI { // Wait until the last poll completes before polling again ensuring we don't poll any faster than // the 'nextPollInterval' value poll(using: dependencies) + .subscribe(on: dependencies.subscribeQueue, immediatelyIfMain: true) + .receive(on: dependencies.receiveQueue, immediatelyIfMain: true) .sinkUntilComplete( receiveCompletion: { [weak self] _ in let currentTime: TimeInterval = Date().timeIntervalSince1970 @@ -129,8 +144,6 @@ extension OpenGroupAPI { .map { response in (failureCount, response) } .eraseToAnyPublisher() } - .subscribe(on: Threading.pollerQueue) - .receive(on: Threading.pollerQueue) .handleEvents( receiveOutput: { [weak self] failureCount, response in guard !calledFromBackgroundPoller || isBackgroundPollerValid() else { @@ -179,7 +192,8 @@ extension OpenGroupAPI { calledFromBackgroundPoller: calledFromBackgroundPoller, isBackgroundPollerValid: isBackgroundPollerValid, isPostCapabilitiesRetry: isPostCapabilitiesRetry, - error: error + error: error, + using: dependencies ) .handleEvents( receiveOutput: { [weak self] didHandleError in @@ -232,7 +246,7 @@ extension OpenGroupAPI { /// If the polling has failed 10+ times then try to prune any invalid rooms that /// aren't visible (they would have been added via config messages and will /// likely always fail but the user has no way to delete them) - guard pollFailureCount > 10 else { return } + guard pollFailureCount > Poller.maxHiddenRoomFailureCount else { return } prunedIds = roomsAreVisible .filter { !$0.shouldBeVisible } @@ -247,19 +261,20 @@ extension OpenGroupAPI { /// not be in an invalid state on other devices - one of the other devices /// will eventually trigger a new config update which will re-add this room /// and hopefully at that time it'll work again - calledFromConfigHandling: true + calledFromConfigHandling: true, + using: dependencies ) } } - SNLog("Open group polling failed due to error: \(error). Setting failure count to \(pollFailureCount).") + SNLog("Open group polling to \(server) failed due to error: \(error). Setting failure count to \(pollFailureCount).") // Add a note to the logs that this happened if !prunedIds.isEmpty { let rooms: String = prunedIds .compactMap { $0.components(separatedBy: server).last } .joined(separator: ", ") - SNLog("Hidden open group failure count surpassed 10, removed hidden rooms \(rooms).") + SNLog("Hidden open group failure count surpassed \(Poller.maxHiddenRoomFailureCount), removed hidden rooms \(rooms).") } } @@ -299,16 +314,15 @@ extension OpenGroupAPI { } return dependencies.storage - .readPublisherFlatMap { db in - OpenGroupAPI.capabilities( + .readPublisher { db in + try OpenGroupAPI.preparedCapabilities( db, server: server, forceBlinded: true, using: dependencies ) } - .subscribe(on: OpenGroupAPI.workQueue) - .receive(on: OpenGroupAPI.workQueue) + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .flatMap { [weak self] _, responseBody -> AnyPublisher in guard let strongSelf = self, isBackgroundPollerValid() else { return Just(()) diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift index 8dbcbdd91..e69afe618 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift @@ -59,7 +59,7 @@ public class Poller { preconditionFailure("abstract class - override in subclass") } - internal func handlePollError(_ error: Error, for publicKey: String) { + internal func handlePollError(_ error: Error, for publicKey: String, using dependencies: SMKDependencies) { preconditionFailure("abstract class - override in subclass") } @@ -81,37 +81,46 @@ public class Poller { /// We want to initially trigger a poll against the target service node and then run the recursive polling, /// if an error is thrown during the poll then this should automatically restart the polling - internal func setUpPolling(for publicKey: String) { + internal func setUpPolling( + for publicKey: String, + using dependencies: SMKDependencies = SMKDependencies( + subscribeQueue: Threading.pollerQueue, + receiveQueue: Threading.pollerQueue + ) + ) { guard isPolling.wrappedValue[publicKey] == true else { return } let namespaces: [SnodeAPI.Namespace] = self.namespaces getSnodeForPolling(for: publicKey) - .subscribe(on: Threading.pollerQueue) + .subscribe(on: dependencies.subscribeQueue, immediatelyIfMain: true) .flatMap { snode -> AnyPublisher<[Message], Error> in Poller.poll( namespaces: namespaces, from: snode, for: publicKey, - on: Threading.pollerQueue, - poller: self + poller: self, + using: dependencies ) } - .receive(on: Threading.pollerQueue) + .receive(on: dependencies.receiveQueue, immediatelyIfMain: true) .sinkUntilComplete( receiveCompletion: { [weak self] result in switch result { - case .finished: self?.pollRecursively(for: publicKey) + case .finished: self?.pollRecursively(for: publicKey, using: dependencies) case .failure(let error): guard self?.isPolling.wrappedValue[publicKey] == true else { return } - self?.handlePollError(error, for: publicKey) + self?.handlePollError(error, for: publicKey, using: dependencies) } } ) } - private func pollRecursively(for publicKey: String) { + private func pollRecursively( + for publicKey: String, + using dependencies: SMKDependencies = SMKDependencies() + ) { guard isPolling.wrappedValue[publicKey] == true else { return } let namespaces: [SnodeAPI.Namespace] = self.namespaces @@ -125,21 +134,21 @@ public class Poller { timer.invalidate() self?.getSnodeForPolling(for: publicKey) - .subscribe(on: Threading.pollerQueue) + .subscribe(on: dependencies.subscribeQueue, immediatelyIfMain: true) .flatMap { snode -> AnyPublisher<[Message], Error> in Poller.poll( namespaces: namespaces, from: snode, for: publicKey, - on: Threading.pollerQueue, - poller: self + poller: self, + using: dependencies ) } - .receive(on: Threading.pollerQueue) + .receive(on: dependencies.receiveQueue, immediatelyIfMain: true) .sinkUntilComplete( receiveCompletion: { result in switch result { - case .failure(let error): self?.handlePollError(error, for: publicKey) + case .failure(let error): self?.handlePollError(error, for: publicKey, using: dependencies) case .finished: let maxNodePollCount: UInt = (self?.maxNodePollCount ?? 0) @@ -161,7 +170,7 @@ public class Poller { timer.invalidate() self?.pollCount.mutate { $0[publicKey] = 0 } - self?.setUpPolling(for: publicKey) + self?.setUpPolling(for: publicKey, using: dependencies) } } return @@ -169,7 +178,7 @@ public class Poller { } // Otherwise just loop - self?.pollRecursively(for: publicKey) + self?.pollRecursively(for: publicKey, using: dependencies) } } ) @@ -186,10 +195,12 @@ public class Poller { namespaces: [SnodeAPI.Namespace], from snode: Snode, for publicKey: String, - on queue: DispatchQueue, calledFromBackgroundPoller: Bool = false, isBackgroundPollValid: @escaping (() -> Bool) = { true }, - poller: Poller? = nil + poller: Poller? = nil, + using dependencies: SMKDependencies = SMKDependencies( + receiveQueue: Threading.pollerQueue + ) ) -> AnyPublisher<[Message], Error> { // If the polling has been cancelled then don't continue guard @@ -213,7 +224,8 @@ public class Poller { namespaces: namespaces, refreshingConfigHashes: configHashes, from: snode, - associatedWith: publicKey + associatedWith: publicKey, + using: dependencies ) .flatMap { namespacedResults -> AnyPublisher<[Message], Error> in guard @@ -391,7 +403,7 @@ public class Poller { // Note: In the background we just want jobs to fail silently ConfigMessageReceiveJob.run( job, - queue: queue, + queue: dependencies.receiveQueue, success: { _, _ in resolver(Result.success(())) }, failure: { _, _, _ in resolver(Result.success(())) }, deferred: { _ in resolver(Result.success(())) } @@ -411,7 +423,7 @@ public class Poller { // Note: In the background we just want jobs to fail silently MessageReceiveJob.run( job, - queue: queue, + queue: dependencies.receiveQueue, success: { _, _ in resolver(Result.success(())) }, failure: { _, _, _ in resolver(Result.success(())) }, deferred: { _ in resolver(Result.success(())) } diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift index 47964c504..677f5bd81 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift @@ -328,7 +328,7 @@ public extension SessionUtil { return false } - return (oneToOne.last_read > timestampMs) + return (oneToOne.last_read >= timestampMs) case .legacyGroup: var cThreadId: [CChar] = threadId.cArray.nullTerminated() @@ -338,7 +338,7 @@ public extension SessionUtil { return false } - return (legacyGroup.last_read > timestampMs) + return (legacyGroup.last_read >= timestampMs) case .community: guard let openGroup: OpenGroup = openGroup else { return false } @@ -351,7 +351,7 @@ public extension SessionUtil { return false } - return (convoCommunity.last_read > timestampMs) + return (convoCommunity.last_read >= timestampMs) case .group: return false } diff --git a/SessionMessagingKit/SessionUtil/SessionUtil.swift b/SessionMessagingKit/SessionUtil/SessionUtil.swift index adca3e50e..44eb2f20b 100644 --- a/SessionMessagingKit/SessionUtil/SessionUtil.swift +++ b/SessionMessagingKit/SessionUtil/SessionUtil.swift @@ -355,7 +355,6 @@ public enum SessionUtil { ) let seqNo: Int64 = cPushData.pointee.seqno cPushData.deallocate() - SNLog("[libSession - DEBUG] Push data for \(variant) config data, size: \(configCountInfo), bytes: \(pushData.count)") return OutgoingConfResult( message: SharedConfigMessage( diff --git a/SessionMessagingKit/Shared Models/MessageViewModel.swift b/SessionMessagingKit/Shared Models/MessageViewModel.swift index bc143b0e5..67d719d74 100644 --- a/SessionMessagingKit/Shared Models/MessageViewModel.swift +++ b/SessionMessagingKit/Shared Models/MessageViewModel.swift @@ -56,6 +56,15 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable, case typingIndicator case dateHeader case unreadMarker + + /// A number of the `CellType` entries are dynamically added to the dataset after processing, this flag indicates + /// whether the given type is one of them + public var isPostProcessed: Bool { + switch self { + case .typingIndicator, .dateHeader, .unreadMarker: return true + default: return false + } + } } public var differenceIdentifier: Int64 { id } @@ -74,6 +83,7 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable, public let rowId: Int64 public let id: Int64 + public let openGroupServerMessageId: Int64? public let variant: Interaction.Variant public let timestampMs: Int64 public let receivedAtTimestampMs: Int64 @@ -171,6 +181,7 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable, threadContactNameInternal: self.threadContactNameInternal, rowId: self.rowId, id: self.id, + openGroupServerMessageId: self.openGroupServerMessageId, variant: self.variant, timestampMs: self.timestampMs, receivedAtTimestampMs: self.receivedAtTimestampMs, @@ -335,6 +346,7 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable, threadContactNameInternal: self.threadContactNameInternal, rowId: self.rowId, id: self.id, + openGroupServerMessageId: self.openGroupServerMessageId, variant: self.variant, timestampMs: self.timestampMs, receivedAtTimestampMs: self.receivedAtTimestampMs, @@ -516,7 +528,7 @@ public extension MessageViewModel { static let genericId: Int64 = -1 static let typingIndicatorId: Int64 = -2 - // Note: This init method is only used system-created cells or empty states + /// This init method is only used for system-created cells or empty states init( variant: Interaction.Variant = .standardOutgoing, timestampMs: Int64 = Int64.max, @@ -546,6 +558,7 @@ public extension MessageViewModel { }() self.rowId = targetId self.id = targetId + self.openGroupServerMessageId = nil self.variant = variant self.timestampMs = timestampMs self.receivedAtTimestampMs = receivedAtTimestampMs @@ -567,11 +580,11 @@ public extension MessageViewModel { self.linkPreview = nil self.linkPreviewAttachment = nil self.currentUserPublicKey = "" + self.attachments = nil + self.reactionInfo = nil // Post-Query Processing Data - self.attachments = nil - self.reactionInfo = nil self.cellType = cellType self.authorName = "" self.senderName = nil @@ -587,6 +600,84 @@ public extension MessageViewModel { self.isLastOutgoing = isLastOutgoing self.currentUserBlindedPublicKey = nil } + + /// This init method is only used for optimistic outgoing messages + init( + threadId: String, + threadVariant: SessionThread.Variant, + threadHasDisappearingMessagesEnabled: Bool, + threadOpenGroupServer: String?, + threadOpenGroupPublicKey: String?, + threadContactNameInternal: String, + timestampMs: Int64, + receivedAtTimestampMs: Int64, + authorId: String, + authorNameInternal: String, + body: String?, + expiresStartedAtMs: Double?, + expiresInSeconds: TimeInterval?, + isSenderOpenGroupModerator: Bool, + currentUserProfile: Profile, + quote: Quote?, + quoteAttachment: Attachment?, + linkPreview: LinkPreview?, + linkPreviewAttachment: Attachment?, + attachments: [Attachment]? + ) { + self.threadId = threadId + self.threadVariant = threadVariant + self.threadIsTrusted = false + self.threadHasDisappearingMessagesEnabled = threadHasDisappearingMessagesEnabled + self.threadOpenGroupServer = threadOpenGroupServer + self.threadOpenGroupPublicKey = threadOpenGroupPublicKey + self.threadContactNameInternal = threadContactNameInternal + + // Interaction Info + + self.rowId = -1 + self.id = -1 + self.openGroupServerMessageId = nil + self.variant = .standardOutgoing + self.timestampMs = timestampMs + self.receivedAtTimestampMs = receivedAtTimestampMs + self.authorId = authorId + self.authorNameInternal = authorNameInternal + self.body = body + self.rawBody = body + self.expiresStartedAtMs = expiresStartedAtMs + self.expiresInSeconds = expiresInSeconds + + self.state = .sending + self.hasAtLeastOneReadReceipt = false + self.mostRecentFailureText = nil + self.isSenderOpenGroupModerator = isSenderOpenGroupModerator + self.isTypingIndicator = false + self.profile = currentUserProfile + self.quote = quote + self.quoteAttachment = quoteAttachment + self.linkPreview = linkPreview + self.linkPreviewAttachment = linkPreviewAttachment + self.currentUserPublicKey = currentUserProfile.id + self.attachments = attachments + self.reactionInfo = nil + + // Post-Query Processing Data + + self.cellType = .textOnlyMessage + self.authorName = "" + self.senderName = nil + self.canHaveProfile = false + self.shouldShowProfile = false + self.shouldShowDateHeader = false + self.containsOnlyEmoji = nil + self.glyphCount = nil + self.previousVariant = nil + self.positionInCluster = .middle + self.isOnlyMessageInCluster = true + self.isLast = false + self.isLastOutgoing = false + self.currentUserBlindedPublicKey = nil + } } // MARK: - Convenience @@ -688,7 +779,7 @@ public extension MessageViewModel { let interactionAttachmentAttachmentIdColumn: SQL = SQL(stringLiteral: InteractionAttachment.Columns.attachmentId.name) let interactionAttachmentAlbumIndexColumn: SQL = SQL(stringLiteral: InteractionAttachment.Columns.albumIndex.name) - let numColumnsBeforeLinkedRecords: Int = 21 + let numColumnsBeforeLinkedRecords: Int = 22 let finalGroupSQL: SQL = (groupSQL ?? "") let request: SQLRequest = """ SELECT @@ -704,6 +795,7 @@ public extension MessageViewModel { \(interaction.alias[Column.rowID]) AS \(ViewModel.rowIdKey), \(interaction[.id]), + \(interaction[.openGroupServerMessageId]), \(interaction[.variant]), \(interaction[.timestampMs]), \(interaction[.receivedAtTimestampMs]), @@ -977,7 +1069,7 @@ public extension MessageViewModel.ReactionInfo { items: pagedRowIdsWithNoReactions .compactMap { rowId -> ViewModel? in updatedPagedDataCache.data[rowId] } .filter { viewModel -> Bool in (viewModel.reactionInfo?.isEmpty == false) } - .map { viewModel -> ViewModel in viewModel.with(reactionInfo: nil) } + .map { viewModel -> ViewModel in viewModel.with(reactionInfo: []) } ) return updatedPagedDataCache diff --git a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift index 20508b0a6..c11af914e 100644 --- a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift +++ b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift @@ -33,6 +33,7 @@ public struct SessionThreadViewModel: FetchableRecordWithRowId, Decodable, Equat public static let threadWasMarkedUnreadKey: SQL = SQL(stringLiteral: CodingKeys.threadWasMarkedUnread.stringValue) public static let threadUnreadCountKey: SQL = SQL(stringLiteral: CodingKeys.threadUnreadCount.stringValue) public static let threadUnreadMentionCountKey: SQL = SQL(stringLiteral: CodingKeys.threadUnreadMentionCount.stringValue) + public static let disappearingMessagesConfigurationKey: SQL = SQL(stringLiteral: CodingKeys.disappearingMessagesConfiguration.stringValue) public static let contactProfileKey: SQL = SQL(stringLiteral: CodingKeys.contactProfile.stringValue) public static let closedGroupNameKey: SQL = SQL(stringLiteral: CodingKeys.closedGroupName.stringValue) public static let closedGroupUserCountKey: SQL = SQL(stringLiteral: CodingKeys.closedGroupUserCount.stringValue) @@ -66,6 +67,7 @@ public struct SessionThreadViewModel: FetchableRecordWithRowId, Decodable, Equat public static let threadUnreadMentionCountString: String = CodingKeys.threadUnreadMentionCount.stringValue public static let closedGroupUserCountString: String = CodingKeys.closedGroupUserCount.stringValue public static let openGroupUserCountString: String = CodingKeys.openGroupUserCount.stringValue + public static let disappearingMessagesConfigurationString: String = CodingKeys.disappearingMessagesConfiguration.stringValue public static let contactProfileString: String = CodingKeys.contactProfile.stringValue public static let closedGroupProfileFrontString: String = CodingKeys.closedGroupProfileFront.stringValue public static let closedGroupProfileBackString: String = CodingKeys.closedGroupProfileBack.stringValue @@ -116,6 +118,8 @@ public struct SessionThreadViewModel: FetchableRecordWithRowId, Decodable, Equat // Thread display info + public let disappearingMessagesConfiguration: DisappearingMessagesConfiguration? + private let contactProfile: Profile? private let closedGroupProfileFront: Profile? private let closedGroupProfileBack: Profile? @@ -343,7 +347,8 @@ public extension SessionThreadViewModel { contactProfile: Profile? = nil, currentUserIsClosedGroupMember: Bool? = nil, openGroupPermissions: OpenGroup.Permissions? = nil, - unreadCount: UInt = 0 + unreadCount: UInt = 0, + disappearingMessagesConfiguration: DisappearingMessagesConfiguration? = nil ) { self.rowId = -1 self.threadId = threadId @@ -368,6 +373,8 @@ public extension SessionThreadViewModel { // Thread display info + self.disappearingMessagesConfiguration = disappearingMessagesConfiguration + self.contactProfile = contactProfile self.closedGroupProfileFront = nil self.closedGroupProfileBack = nil @@ -430,6 +437,7 @@ public extension SessionThreadViewModel { threadWasMarkedUnread: self.threadWasMarkedUnread, threadUnreadCount: self.threadUnreadCount, threadUnreadMentionCount: self.threadUnreadMentionCount, + disappearingMessagesConfiguration: self.disappearingMessagesConfiguration, contactProfile: self.contactProfile, closedGroupProfileFront: self.closedGroupProfileFront, closedGroupProfileBack: self.closedGroupProfileBack, @@ -486,6 +494,7 @@ public extension SessionThreadViewModel { threadWasMarkedUnread: self.threadWasMarkedUnread, threadUnreadCount: self.threadUnreadCount, threadUnreadMentionCount: self.threadUnreadMentionCount, + disappearingMessagesConfiguration: self.disappearingMessagesConfiguration, contactProfile: self.contactProfile, closedGroupProfileFront: self.closedGroupProfileFront, closedGroupProfileBack: self.closedGroupProfileBack, @@ -839,6 +848,7 @@ public extension SessionThreadViewModel { /// but including this warning just in case there is a discrepancy) static func conversationQuery(threadId: String, userPublicKey: String) -> AdaptedFetchRequest> { let thread: TypedTableAlias = TypedTableAlias() + let disappearingMessagesConfiguration: TypedTableAlias = TypedTableAlias() let contact: TypedTableAlias = TypedTableAlias() let closedGroup: TypedTableAlias = TypedTableAlias() let groupMember: TypedTableAlias = TypedTableAlias() @@ -883,6 +893,8 @@ public extension SessionThreadViewModel { \(thread[.markedAsUnread]) AS \(ViewModel.threadWasMarkedUnreadKey), \(aggregateInteractionLiteral).\(ViewModel.threadUnreadCountKey), + + \(ViewModel.disappearingMessagesConfigurationKey).*, \(ViewModel.contactProfileKey).*, \(closedGroup[.name]) AS \(ViewModel.closedGroupNameKey), @@ -911,6 +923,7 @@ public extension SessionThreadViewModel { \(SQL("\(userPublicKey)")) AS \(ViewModel.currentUserPublicKeyKey) FROM \(SessionThread.self) + LEFT JOIN \(DisappearingMessagesConfiguration.self) ON \(disappearingMessagesConfiguration[.threadId]) = \(thread[.id]) LEFT JOIN \(Contact.self) ON \(contact[.id]) = \(thread[.id]) LEFT JOIN ( SELECT @@ -945,11 +958,13 @@ public extension SessionThreadViewModel { return request.adapted { db in let adapters = try splittingRowAdapters(columnCounts: [ numColumnsBeforeProfiles, + DisappearingMessagesConfiguration.numberOfSelectedColumns(db), Profile.numberOfSelectedColumns(db) ]) return ScopeAdapter([ - ViewModel.contactProfileString: adapters[1] + ViewModel.disappearingMessagesConfigurationString: adapters[1], + ViewModel.contactProfileString: adapters[2] ]) } } diff --git a/SessionMessagingKit/Utilities/ProfileManager.swift b/SessionMessagingKit/Utilities/ProfileManager.swift index ffbff0aeb..ea0753690 100644 --- a/SessionMessagingKit/Utilities/ProfileManager.swift +++ b/SessionMessagingKit/Utilities/ProfileManager.swift @@ -51,7 +51,10 @@ public struct ProfileManager { } if let profilePictureUrl: String = profile.profilePictureUrl, !profilePictureUrl.isEmpty { - downloadAvatar(for: profile) + // FIXME: Refactor avatar downloading to be a proper Job so we can avoid this + JobRunner.afterBlockingQueue { + ProfileManager.downloadAvatar(for: profile) + } } return nil @@ -78,7 +81,10 @@ public struct ProfileManager { completion: { _, _ in // Try to re-download the avatar if it has a URL if let profilePictureUrl: String = profile.profilePictureUrl, !profilePictureUrl.isEmpty { - downloadAvatar(for: profile) + // FIXME: Refactor avatar downloading to be a proper Job so we can avoid this + JobRunner.afterBlockingQueue { + ProfileManager.downloadAvatar(for: profile) + } } } ) @@ -214,7 +220,8 @@ public struct ProfileManager { FileServerAPI .download(fileId, useOldServer: useOldServer) - .receive(on: DispatchQueue.global(qos: .default)) + .subscribe(on: DispatchQueue.global(qos: .background)) + .receive(on: DispatchQueue.global(qos: .background)) .sinkUntilComplete( receiveCompletion: { _ in currentAvatarDownloads.mutate { $0.remove(profile.id) } @@ -451,6 +458,7 @@ public struct ProfileManager { // Upload the avatar to the FileServer FileServerAPI .upload(encryptedAvatarData) + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .receive(on: queue) .sinkUntilComplete( receiveCompletion: { result in @@ -590,7 +598,12 @@ public struct ProfileManager { db.afterNextTransactionNestedOnce(dedupeId: dedupeIdentifier) { db in // Need to refetch to ensure the db changes have occurred - ProfileManager.downloadAvatar(for: Profile.fetchOrCreate(db, id: publicKey)) + let targetProfile: Profile = Profile.fetchOrCreate(db, id: publicKey) + + // FIXME: Refactor avatar downloading to be a proper Job so we can avoid this + JobRunner.afterBlockingQueue { + ProfileManager.downloadAvatar(for: targetProfile) + } } } } diff --git a/SessionMessagingKit/Utilities/Threading.swift b/SessionMessagingKit/Utilities/Threading.swift index b7e1cab79..ef06b3c6b 100644 --- a/SessionMessagingKit/Utilities/Threading.swift +++ b/SessionMessagingKit/Utilities/Threading.swift @@ -1,6 +1,5 @@ import Foundation -internal enum Threading { - - internal static let pollerQueue = DispatchQueue(label: "SessionMessagingKit.pollerQueue") +public enum Threading { + public static let pollerQueue = DispatchQueue(label: "SessionMessagingKit.pollerQueue") } diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift b/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift index 379c634eb..0697d65a1 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift +++ b/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift @@ -825,13 +825,14 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Capabilities)? mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI.capabilities( + .readPublisher { db in + try OpenGroupAPI.preparedCapabilities( db, server: "testserver", using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -895,13 +896,14 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: [OpenGroupAPI.Room])? mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI.rooms( + .readPublisher { db in + try OpenGroupAPI.preparedRooms( db, server: "testserver", using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -1261,9 +1263,9 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .send( + .readPublisher { db in + try OpenGroupAPI + .preparedSend( db, plaintext: "test".data(using: .utf8)!, to: "testRoom", @@ -1274,6 +1276,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -1306,9 +1309,9 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .send( + .readPublisher { db in + try OpenGroupAPI + .preparedSend( db, plaintext: "test".data(using: .utf8)!, to: "testRoom", @@ -1319,6 +1322,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -1346,9 +1350,9 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .send( + .readPublisher { db in + try OpenGroupAPI + .preparedSend( db, plaintext: "test".data(using: .utf8)!, to: "testRoom", @@ -1359,6 +1363,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -1381,9 +1386,9 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .send( + .readPublisher { db in + try OpenGroupAPI + .preparedSend( db, plaintext: "test".data(using: .utf8)!, to: "testRoom", @@ -1394,6 +1399,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -1414,9 +1420,9 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .send( + .readPublisher { db in + try OpenGroupAPI + .preparedSend( db, plaintext: "test".data(using: .utf8)!, to: "testRoom", @@ -1427,6 +1433,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -1454,9 +1461,9 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .send( + .readPublisher { db in + try OpenGroupAPI + .preparedSend( db, plaintext: "test".data(using: .utf8)!, to: "testRoom", @@ -1467,6 +1474,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -1494,9 +1502,9 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .send( + .readPublisher { db in + try OpenGroupAPI + .preparedSend( db, plaintext: "test".data(using: .utf8)!, to: "testRoom", @@ -1507,6 +1515,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -1529,9 +1538,9 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .send( + .readPublisher { db in + try OpenGroupAPI + .preparedSend( db, plaintext: "test".data(using: .utf8)!, to: "testRoom", @@ -1542,6 +1551,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -1570,9 +1580,9 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .send( + .readPublisher { db in + try OpenGroupAPI + .preparedSend( db, plaintext: "test".data(using: .utf8)!, to: "testRoom", @@ -1583,6 +1593,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -1623,9 +1634,9 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.Message)? mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .message( + .readPublisher { db in + try OpenGroupAPI + .preparedMessage( db, id: 123, in: "testRoom", @@ -1633,6 +1644,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -1672,12 +1684,12 @@ class OpenGroupAPISpec: QuickSpec { } it("correctly sends the update") { - var response: (info: ResponseInfoType, data: Data?)? + var response: (info: ResponseInfoType, data: NoResponse)? mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .messageUpdate( + .readPublisher { db in + try OpenGroupAPI + .preparedMessageUpdate( db, id: 123, plaintext: "test".data(using: .utf8)!, @@ -1687,6 +1699,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -1713,12 +1726,12 @@ class OpenGroupAPISpec: QuickSpec { } it("signs the message correctly") { - var response: (info: ResponseInfoType, data: Data?)? + var response: (info: ResponseInfoType, data: NoResponse)? mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .messageUpdate( + .readPublisher { db in + try OpenGroupAPI + .preparedMessageUpdate( db, id: 123, plaintext: "test".data(using: .utf8)!, @@ -1728,6 +1741,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -1752,12 +1766,12 @@ class OpenGroupAPISpec: QuickSpec { _ = try OpenGroup.deleteAll(db) } - var response: (info: ResponseInfoType, data: Data?)? + var response: (info: ResponseInfoType, data: NoResponse)? mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .messageUpdate( + .readPublisher { db in + try OpenGroupAPI + .preparedMessageUpdate( db, id: 123, plaintext: "test".data(using: .utf8)!, @@ -1767,6 +1781,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -1786,12 +1801,12 @@ class OpenGroupAPISpec: QuickSpec { _ = try Identity.filter(id: .x25519PrivateKey).deleteAll(db) } - var response: (info: ResponseInfoType, data: Data?)? + var response: (info: ResponseInfoType, data: NoResponse)? mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .messageUpdate( + .readPublisher { db in + try OpenGroupAPI + .preparedMessageUpdate( db, id: 123, plaintext: "test".data(using: .utf8)!, @@ -1801,6 +1816,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -1818,12 +1834,12 @@ class OpenGroupAPISpec: QuickSpec { mockEd25519.reset() // The 'keyPair' value doesn't equate so have to explicitly reset mockEd25519.when { try $0.sign(data: anyArray(), keyPair: any()) }.thenReturn(nil) - var response: (info: ResponseInfoType, data: Data?)? + var response: (info: ResponseInfoType, data: NoResponse)? mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .messageUpdate( + .readPublisher { db in + try OpenGroupAPI + .preparedMessageUpdate( db, id: 123, plaintext: "test".data(using: .utf8)!, @@ -1833,6 +1849,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -1857,12 +1874,12 @@ class OpenGroupAPISpec: QuickSpec { } it("signs the message correctly") { - var response: (info: ResponseInfoType, data: Data?)? + var response: (info: ResponseInfoType, data: NoResponse)? mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .messageUpdate( + .readPublisher { db in + try OpenGroupAPI + .preparedMessageUpdate( db, id: 123, plaintext: "test".data(using: .utf8)!, @@ -1872,6 +1889,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -1896,12 +1914,12 @@ class OpenGroupAPISpec: QuickSpec { _ = try OpenGroup.deleteAll(db) } - var response: (info: ResponseInfoType, data: Data?)? + var response: (info: ResponseInfoType, data: NoResponse)? mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .messageUpdate( + .readPublisher { db in + try OpenGroupAPI + .preparedMessageUpdate( db, id: 123, plaintext: "test".data(using: .utf8)!, @@ -1911,6 +1929,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -1930,12 +1949,12 @@ class OpenGroupAPISpec: QuickSpec { _ = try Identity.filter(id: .ed25519SecretKey).deleteAll(db) } - var response: (info: ResponseInfoType, data: Data?)? + var response: (info: ResponseInfoType, data: NoResponse)? mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .messageUpdate( + .readPublisher { db in + try OpenGroupAPI + .preparedMessageUpdate( db, id: 123, plaintext: "test".data(using: .utf8)!, @@ -1945,6 +1964,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -1970,12 +1990,12 @@ class OpenGroupAPISpec: QuickSpec { } .thenReturn(nil) - var response: (info: ResponseInfoType, data: Data?)? + var response: (info: ResponseInfoType, data: NoResponse)? mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .messageUpdate( + .readPublisher { db in + try OpenGroupAPI + .preparedMessageUpdate( db, id: 123, plaintext: "test".data(using: .utf8)!, @@ -1985,6 +2005,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -2007,12 +2028,12 @@ class OpenGroupAPISpec: QuickSpec { } dependencies = dependencies.with(onionApi: TestApi.self) - var response: (info: ResponseInfoType, data: Data?)? + var response: (info: ResponseInfoType, data: NoResponse)? mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .messageDelete( + .readPublisher { db in + try OpenGroupAPI + .preparedMessageDelete( db, id: 123, in: "testRoom", @@ -2020,6 +2041,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -2039,7 +2061,7 @@ class OpenGroupAPISpec: QuickSpec { } context("when deleting all messages for a user") { - var response: (info: ResponseInfoType, data: Data?)? + var response: (info: ResponseInfoType, data: NoResponse)? beforeEach { class TestApi: TestOnionRequestAPI { @@ -2054,9 +2076,9 @@ class OpenGroupAPISpec: QuickSpec { it("generates the request and handles the response correctly") { mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .messagesDeleteAll( + .readPublisher { db in + try OpenGroupAPI + .preparedMessagesDeleteAll( db, sessionId: "testUserId", in: "testRoom", @@ -2064,6 +2086,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -2091,12 +2114,12 @@ class OpenGroupAPISpec: QuickSpec { } dependencies = dependencies.with(onionApi: TestApi.self) - var response: ResponseInfoType? + var response: (info: ResponseInfoType, data: NoResponse)? mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .pinMessage( + .readPublisher { db in + try OpenGroupAPI + .preparedPinMessage( db, id: 123, in: "testRoom", @@ -2104,6 +2127,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -2116,7 +2140,7 @@ class OpenGroupAPISpec: QuickSpec { expect(error?.localizedDescription).to(beNil()) // Validate request data - let requestData: TestOnionRequestAPI.RequestData? = (response as? TestOnionRequestAPI.ResponseInfo)?.requestData + let requestData: TestOnionRequestAPI.RequestData? = (response?.info as? TestOnionRequestAPI.ResponseInfo)?.requestData expect(requestData?.httpMethod).to(equal("POST")) expect(requestData?.urlString).to(equal("testserver/room/testRoom/pin/123")) } @@ -2129,12 +2153,12 @@ class OpenGroupAPISpec: QuickSpec { } dependencies = dependencies.with(onionApi: TestApi.self) - var response: ResponseInfoType? + var response: (info: ResponseInfoType, data: NoResponse)? mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .unpinMessage( + .readPublisher { db in + try OpenGroupAPI + .preparedUnpinMessage( db, id: 123, in: "testRoom", @@ -2142,6 +2166,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -2154,7 +2179,7 @@ class OpenGroupAPISpec: QuickSpec { expect(error?.localizedDescription).to(beNil()) // Validate request data - let requestData: TestOnionRequestAPI.RequestData? = (response as? TestOnionRequestAPI.ResponseInfo)?.requestData + let requestData: TestOnionRequestAPI.RequestData? = (response?.info as? TestOnionRequestAPI.ResponseInfo)?.requestData expect(requestData?.httpMethod).to(equal("POST")) expect(requestData?.urlString).to(equal("testserver/room/testRoom/unpin/123")) } @@ -2167,18 +2192,19 @@ class OpenGroupAPISpec: QuickSpec { } dependencies = dependencies.with(onionApi: TestApi.self) - var response: ResponseInfoType? + var response: (info: ResponseInfoType, data: NoResponse)? mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .unpinAll( + .readPublisher { db in + try OpenGroupAPI + .preparedUnpinAll( db, in: "testRoom", on: "testserver", using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -2191,7 +2217,7 @@ class OpenGroupAPISpec: QuickSpec { expect(error?.localizedDescription).to(beNil()) // Validate request data - let requestData: TestOnionRequestAPI.RequestData? = (response as? TestOnionRequestAPI.ResponseInfo)?.requestData + let requestData: TestOnionRequestAPI.RequestData? = (response?.info as? TestOnionRequestAPI.ResponseInfo)?.requestData expect(requestData?.httpMethod).to(equal("POST")) expect(requestData?.urlString).to(equal("testserver/room/testRoom/unpin/all")) } @@ -2209,9 +2235,9 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .uploadFile( + .readPublisher { db in + try OpenGroupAPI + .preparedUploadFile( db, bytes: [], to: "testRoom", @@ -2219,6 +2245,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -2245,9 +2272,9 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .uploadFile( + .readPublisher { db in + try OpenGroupAPI + .preparedUploadFile( db, bytes: [], to: "testRoom", @@ -2255,6 +2282,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -2281,9 +2309,9 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .uploadFile( + .readPublisher { db in + try OpenGroupAPI + .preparedUploadFile( db, bytes: [], fileName: "TestFileName", @@ -2292,6 +2320,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -2319,9 +2348,9 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .downloadFile( + .readPublisher { db in + try OpenGroupAPI + .preparedDownloadFile( db, fileId: "1", from: "testRoom", @@ -2329,6 +2358,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -2376,9 +2406,9 @@ class OpenGroupAPISpec: QuickSpec { var response: (info: ResponseInfoType, data: OpenGroupAPI.SendDirectMessageResponse)? mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .send( + .readPublisher { db in + try OpenGroupAPI + .preparedSend( db, ciphertext: "test".data(using: .utf8)!, toInboxFor: "testUserId", @@ -2386,6 +2416,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -2410,7 +2441,7 @@ class OpenGroupAPISpec: QuickSpec { // MARK: - Users context("when banning a user") { - var response: (info: ResponseInfoType, data: Data?)? + var response: (info: ResponseInfoType, data: NoResponse)? beforeEach { class TestApi: TestOnionRequestAPI { @@ -2425,9 +2456,9 @@ class OpenGroupAPISpec: QuickSpec { it("generates the request and handles the response correctly") { mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .userBan( + .readPublisher { db in + try OpenGroupAPI + .preparedUserBan( db, sessionId: "testUserId", for: nil, @@ -2436,6 +2467,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -2455,9 +2487,9 @@ class OpenGroupAPISpec: QuickSpec { it("does a global ban if no room tokens are provided") { mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .userBan( + .readPublisher { db in + try OpenGroupAPI + .preparedUserBan( db, sessionId: "testUserId", for: nil, @@ -2466,6 +2498,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -2487,9 +2520,9 @@ class OpenGroupAPISpec: QuickSpec { it("does room specific bans if room tokens are provided") { mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .userBan( + .readPublisher { db in + try OpenGroupAPI + .preparedUserBan( db, sessionId: "testUserId", for: nil, @@ -2498,6 +2531,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -2519,7 +2553,7 @@ class OpenGroupAPISpec: QuickSpec { } context("when unbanning a user") { - var response: (info: ResponseInfoType, data: Data?)? + var response: (info: ResponseInfoType, data: NoResponse)? beforeEach { class TestApi: TestOnionRequestAPI { @@ -2534,9 +2568,9 @@ class OpenGroupAPISpec: QuickSpec { it("generates the request and handles the response correctly") { mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .userUnban( + .readPublisher { db in + try OpenGroupAPI + .preparedUserUnban( db, sessionId: "testUserId", from: nil, @@ -2544,6 +2578,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -2563,9 +2598,9 @@ class OpenGroupAPISpec: QuickSpec { it("does a global ban if no room tokens are provided") { mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .userUnban( + .readPublisher { db in + try OpenGroupAPI + .preparedUserUnban( db, sessionId: "testUserId", from: nil, @@ -2573,6 +2608,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -2594,9 +2630,9 @@ class OpenGroupAPISpec: QuickSpec { it("does room specific bans if room tokens are provided") { mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .userUnban( + .readPublisher { db in + try OpenGroupAPI + .preparedUserUnban( db, sessionId: "testUserId", from: ["testRoom"], @@ -2604,6 +2640,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -2625,7 +2662,7 @@ class OpenGroupAPISpec: QuickSpec { } context("when updating a users permissions") { - var response: (info: ResponseInfoType, data: Data?)? + var response: (info: ResponseInfoType, data: NoResponse)? beforeEach { class TestApi: TestOnionRequestAPI { @@ -2640,9 +2677,9 @@ class OpenGroupAPISpec: QuickSpec { it("generates the request and handles the response correctly") { mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .userModeratorUpdate( + .readPublisher { db in + try OpenGroupAPI + .preparedUserModeratorUpdate( db, sessionId: "testUserId", moderator: true, @@ -2653,6 +2690,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -2672,9 +2710,9 @@ class OpenGroupAPISpec: QuickSpec { it("does a global update if no room tokens are provided") { mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .userModeratorUpdate( + .readPublisher { db in + try OpenGroupAPI + .preparedUserModeratorUpdate( db, sessionId: "testUserId", moderator: true, @@ -2685,6 +2723,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -2706,9 +2745,9 @@ class OpenGroupAPISpec: QuickSpec { it("does room specific updates if room tokens are provided") { mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .userModeratorUpdate( + .readPublisher { db in + try OpenGroupAPI + .preparedUserModeratorUpdate( db, sessionId: "testUserId", moderator: true, @@ -2719,6 +2758,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -2740,9 +2780,9 @@ class OpenGroupAPISpec: QuickSpec { it("fails if neither moderator or admin are set") { mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .userModeratorUpdate( + .readPublisher { db in + try OpenGroupAPI + .preparedUserModeratorUpdate( db, sessionId: "testUserId", moderator: nil, @@ -2753,6 +2793,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -2890,14 +2931,15 @@ class OpenGroupAPISpec: QuickSpec { } mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .rooms( + .readPublisher { db in + try OpenGroupAPI + .preparedRooms( db, server: "testserver", using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -2917,14 +2959,15 @@ class OpenGroupAPISpec: QuickSpec { } mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .rooms( + .readPublisher { db in + try OpenGroupAPI + .preparedRooms( db, server: "testserver", using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -2944,14 +2987,15 @@ class OpenGroupAPISpec: QuickSpec { } mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .rooms( + .readPublisher { db in + try OpenGroupAPI + .preparedRooms( db, server: "testserver", using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -2975,14 +3019,15 @@ class OpenGroupAPISpec: QuickSpec { it("signs correctly") { mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .rooms( + .readPublisher { db in + try OpenGroupAPI + .preparedRooms( db, server: "testserver", using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -3011,14 +3056,15 @@ class OpenGroupAPISpec: QuickSpec { mockSign.when { $0.signature(message: anyArray(), secretKey: anyArray()) }.thenReturn(nil) mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .rooms( + .readPublisher { db in + try OpenGroupAPI + .preparedRooms( db, server: "testserver", using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -3044,14 +3090,15 @@ class OpenGroupAPISpec: QuickSpec { it("signs correctly") { mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .rooms( + .readPublisher { db in + try OpenGroupAPI + .preparedRooms( db, server: "testserver", using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -3081,14 +3128,15 @@ class OpenGroupAPISpec: QuickSpec { .thenReturn(nil) mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .rooms( + .readPublisher { db in + try OpenGroupAPI + .preparedRooms( db, server: "testserver", using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -3108,14 +3156,15 @@ class OpenGroupAPISpec: QuickSpec { .thenReturn(nil) mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .rooms( + .readPublisher { db in + try OpenGroupAPI + .preparedRooms( db, server: "testserver", using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift index dfcad2e76..8beeb921b 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift +++ b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift @@ -116,7 +116,8 @@ class OpenGroupManagerSpec: QuickSpec { mockNonce24Generator = MockNonce24Generator() mockUserDefaults = MockUserDefaults() dependencies = OpenGroupManager.OGMDependencies( - queue: DispatchQueue.main, + subscribeQueue: DispatchQueue.main, + receiveQueue: DispatchQueue.main, cache: Atomic(mockOGMCache), onionApi: TestCapabilitiesAndRoomApi.self, generalCache: Atomic(mockGeneralCache), @@ -991,7 +992,7 @@ class OpenGroupManagerSpec: QuickSpec { db, openGroupId: OpenGroup.idFor(roomToken: "testRoom", server: "testServer"), calledFromConfigHandling: true, // Don't trigger SessionUtil logic - dependencies: dependencies + using: dependencies ) } @@ -1006,7 +1007,7 @@ class OpenGroupManagerSpec: QuickSpec { db, openGroupId: OpenGroup.idFor(roomToken: "testRoom", server: "testServer"), calledFromConfigHandling: true, // Don't trigger SessionUtil logic - dependencies: dependencies + using: dependencies ) } @@ -1024,7 +1025,7 @@ class OpenGroupManagerSpec: QuickSpec { db, openGroupId: OpenGroup.idFor(roomToken: "testRoom", server: "testServer"), calledFromConfigHandling: true, // Don't trigger SessionUtil logic - dependencies: dependencies + using: dependencies ) } @@ -1038,7 +1039,7 @@ class OpenGroupManagerSpec: QuickSpec { db, openGroupId: OpenGroup.idFor(roomToken: "testRoom", server: "testServer"), calledFromConfigHandling: true, // Don't trigger SessionUtil logic - dependencies: dependencies + using: dependencies ) } @@ -1077,7 +1078,7 @@ class OpenGroupManagerSpec: QuickSpec { db, openGroupId: OpenGroup.idFor(roomToken: "testRoom", server: "testServer"), calledFromConfigHandling: true, // Don't trigger SessionUtil logic - dependencies: dependencies + using: dependencies ) } @@ -1130,7 +1131,7 @@ class OpenGroupManagerSpec: QuickSpec { db, openGroupId: OpenGroup.idFor(roomToken: "testRoom", server: OpenGroupAPI.defaultServer), calledFromConfigHandling: true, // Don't trigger SessionUtil logic - dependencies: dependencies + using: dependencies ) } @@ -1145,7 +1146,7 @@ class OpenGroupManagerSpec: QuickSpec { db, openGroupId: OpenGroup.idFor(roomToken: "testRoom", server: OpenGroupAPI.defaultServer), calledFromConfigHandling: true, // Don't trigger SessionUtil logic - dependencies: dependencies + using: dependencies ) } diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index e423e2784..3a943047e 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -136,7 +136,14 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension guard case .preOffer = callMessage.kind else { return self.completeSilenty() } if !db[.areCallsEnabled] { - if let sender: String = callMessage.sender, let interaction: Interaction = try MessageReceiver.insertCallInfoMessage(db, for: callMessage, state: .permissionDenied) { + if + let sender: String = callMessage.sender, + let interaction: Interaction = try MessageReceiver.insertCallInfoMessage( + db, + for: callMessage, + state: .permissionDenied + ) + { let thread: SessionThread = try SessionThread .fetchOrCreate( db, @@ -145,12 +152,15 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension shouldBeVisible: nil ) - Environment.shared?.notificationsManager.wrappedValue? - .notifyUser( - db, - forIncomingCall: interaction, - in: thread - ) + // Notify the user if the call message wasn't already read + if !interaction.wasRead { + Environment.shared?.notificationsManager.wrappedValue? + .notifyUser( + db, + forIncomingCall: interaction, + in: thread + ) + } } break } diff --git a/SessionShareExtension/ThreadPickerVC.swift b/SessionShareExtension/ThreadPickerVC.swift index a00a4c020..4013936b7 100644 --- a/SessionShareExtension/ThreadPickerVC.swift +++ b/SessionShareExtension/ThreadPickerVC.swift @@ -153,7 +153,8 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView tableView.deselectRow(at: indexPath, animated: true) ShareNavController.attachmentPrepPublisher? - .receiveOnMain(immediately: true) + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) + .receive(on: DispatchQueue.main, immediatelyIfMain: true) .sinkUntilComplete( receiveValue: { [weak self] attachments in guard let strongSelf = self else { return } @@ -232,18 +233,20 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView try LinkPreview( url: linkPreviewDraft.urlString, title: linkPreviewDraft.title, - attachmentId: LinkPreview.saveAttachmentIfPossible( - db, - imageData: linkPreviewDraft.jpegImageData, - mimeType: OWSMimeTypeImageJpeg - ) + attachmentId: LinkPreview + .generateAttachmentIfPossible( + imageData: linkPreviewDraft.jpegImageData, + mimeType: OWSMimeTypeImageJpeg + )? + .inserted(db) + .id ).insert(db) } // Prepare any attachments - try Attachment.prepare( + try Attachment.process( db, - attachments: finalAttachments, + data: Attachment.prepare(attachments: finalAttachments), for: interactionId ) @@ -257,13 +260,9 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView ) } .subscribe(on: DispatchQueue.global(qos: .userInitiated)) - .flatMap { - MessageSender.performUploadsIfNeeded( - queue: DispatchQueue.global(qos: .userInitiated), - preparedSendData: $0 - ) - } + .flatMap { MessageSender.performUploadsIfNeeded(preparedSendData: $0) } .flatMap { MessageSender.sendImmediate(preparedSendData: $0) } + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .receive(on: DispatchQueue.main) .sinkUntilComplete( receiveCompletion: { [weak self] result in diff --git a/SessionShareExtension/ThreadPickerViewModel.swift b/SessionShareExtension/ThreadPickerViewModel.swift index 93035647f..2d07a43cd 100644 --- a/SessionShareExtension/ThreadPickerViewModel.swift +++ b/SessionShareExtension/ThreadPickerViewModel.swift @@ -29,6 +29,7 @@ public class ThreadPickerViewModel { .fetchAll(db) } .removeDuplicates() + .handleEvents(didFail: { SNLog("[ThreadPickerViewModel] Observation failed with error: \($0)") }) // MARK: - Functions diff --git a/SessionSnodeKit/Jobs/GetSnodePoolJob.swift b/SessionSnodeKit/Jobs/GetSnodePoolJob.swift index 2d0c5e827..79041941a 100644 --- a/SessionSnodeKit/Jobs/GetSnodePoolJob.swift +++ b/SessionSnodeKit/Jobs/GetSnodePoolJob.swift @@ -56,7 +56,7 @@ public enum GetSnodePoolJob: JobExecutor { public static func run() { GetSnodePoolJob.run( Job(variant: .getSnodePool), - queue: DispatchQueue.global(qos: .background), + queue: .global(qos: .background), success: { _, _ in }, failure: { _, _, _ in }, deferred: { _ in } diff --git a/SessionSnodeKit/Models/GetStatsResponse.swift b/SessionSnodeKit/Models/GetStatsResponse.swift new file mode 100644 index 000000000..5e33cd03b --- /dev/null +++ b/SessionSnodeKit/Models/GetStatsResponse.swift @@ -0,0 +1,16 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import SessionUtilitiesKit + +extension SnodeAPI { + public struct GetStatsResponse: Codable { + private enum CodingKeys: String, CodingKey { + case versionString = "version" + } + + let versionString: String? + + var version: Version? { versionString.map { Version.from($0) } } + } +} diff --git a/SessionSnodeKit/Networking/OnionRequestAPI.swift b/SessionSnodeKit/Networking/OnionRequestAPI.swift index 538733eea..926c49008 100644 --- a/SessionSnodeKit/Networking/OnionRequestAPI.swift +++ b/SessionSnodeKit/Networking/OnionRequestAPI.swift @@ -71,18 +71,12 @@ public enum OnionRequestAPI: OnionRequestAPIType { let timeout: TimeInterval = 3 // Use a shorter timeout for testing return HTTP.execute(.get, url, timeout: timeout) - .subscribe(on: DispatchQueue.global(qos: .userInitiated)) - .tryMap { responseData -> Void in - // TODO: Remove JSON usage - guard let responseJson: JSON = try? JSONSerialization.jsonObject(with: responseData, options: [ .fragmentsAllowed ]) as? JSON else { - throw HTTPError.invalidJSON - } - guard let version = responseJson["version"] as? String else { - throw OnionRequestAPIError.missingSnodeVersion - } - guard version >= "2.0.7" else { - SNLog("Unsupported snode version: \(version).") - throw OnionRequestAPIError.unsupportedSnodeVersion(version) + .decoded(as: SnodeAPI.GetStatsResponse.self) + .tryMap { response -> Void in + guard let version: Version = response.version else { throw OnionRequestAPIError.missingSnodeVersion } + guard version >= Version(major: 2, minor: 0, patch: 7) else { + SNLog("Unsupported snode version: \(version.stringValue).") + throw OnionRequestAPIError.unsupportedSnodeVersion(version.stringValue) } return () @@ -154,63 +148,82 @@ public enum OnionRequestAPI: OnionRequestAPIType { return existingBuildPathsPublisher } - SNLog("Building onion request paths.") - DispatchQueue.main.async { - NotificationCenter.default.post(name: .buildingPaths, object: nil) - } - let reusableGuardSnodes = reusablePaths.map { $0[0] } - let publisher: AnyPublisher<[[Snode]], Error> = getGuardSnodes(reusing: reusableGuardSnodes) - .flatMap { guardSnodes -> AnyPublisher<[[Snode]], Error> in - var unusedSnodes = SnodeAPI.snodePool.wrappedValue - .subtracting(guardSnodes) - .subtracting(reusablePaths.flatMap { $0 }) - let reusableGuardSnodeCount = UInt(reusableGuardSnodes.count) - let pathSnodeCount = (targetGuardSnodeCount - reusableGuardSnodeCount) * pathSize - (targetGuardSnodeCount - reusableGuardSnodeCount) - - guard unusedSnodes.count >= pathSnodeCount else { - return Fail<[[Snode]], Error>(error: OnionRequestAPIError.insufficientSnodes) - .eraseToAnyPublisher() - } - - // Don't test path snodes as this would reveal the user's IP to them - return Just( - guardSnodes + return buildPathsPublisher.mutate { result in + /// It was possible for multiple threads to call this at the same time resulting in duplicate promises getting created, while + /// this should no longer be possible (as the `wrappedValue` should now properly be blocked) this is a sanity check + /// to make sure we don't create an additional promise when one already exists + if let previouslyBlockedPublisher: AnyPublisher<[[Snode]], Error> = result { + return previouslyBlockedPublisher + } + + SNLog("Building onion request paths.") + DispatchQueue.main.async { + NotificationCenter.default.post(name: .buildingPaths, object: nil) + } + + /// Need to include the post-request code and a `shareReplay` within the publisher otherwise it can still be executed + /// multiple times as a result of multiple subscribers + let reusableGuardSnodes = reusablePaths.map { $0[0] } + let publisher: AnyPublisher<[[Snode]], Error> = getGuardSnodes(reusing: reusableGuardSnodes) + .flatMap { (guardSnodes: Set) -> AnyPublisher<[[Snode]], Error> in + var unusedSnodes: Set = SnodeAPI.snodePool.wrappedValue + .subtracting(guardSnodes) + .subtracting(reusablePaths.flatMap { $0 }) + let reusableGuardSnodeCount: UInt = UInt(reusableGuardSnodes.count) + let pathSnodeCount: UInt = (targetGuardSnodeCount - reusableGuardSnodeCount) * pathSize - (targetGuardSnodeCount - reusableGuardSnodeCount) + + guard unusedSnodes.count >= pathSnodeCount else { + return Fail<[[Snode]], Error>(error: OnionRequestAPIError.insufficientSnodes) + .eraseToAnyPublisher() + } + + // Don't test path snodes as this would reveal the user's IP to them + let paths: [[Snode]] = guardSnodes .subtracting(reusableGuardSnodes) - .map { guardSnode in - let result = [ guardSnode ] + (0..<(pathSize - 1)).map { _ in - // randomElement() uses the system's default random generator, which is cryptographically secure - let pathSnode = unusedSnodes.randomElement()! // Safe because of the pathSnodeCount check above - unusedSnodes.remove(pathSnode) // All used snodes should be unique - return pathSnode - } + .map { (guardSnode: Snode) in + let result: [Snode] = [guardSnode] + .appending( + contentsOf: (0..<(pathSize - 1)) + .map { _ in + // randomElement() uses the system's default random generator, + // which is cryptographically secure + let pathSnode: Snode = unusedSnodes.randomElement()! // Safe because of the pathSnodeCount check above + unusedSnodes.remove(pathSnode) // All used snodes should be unique + return pathSnode + } + ) SNLog("Built new onion request path: \(result.prettifiedDescription).") return result } + + return Just(paths) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + .handleEvents( + receiveOutput: { output in + OnionRequestAPI.paths = (output + reusablePaths) + + Storage.shared.write { db in + SNLog("Persisting onion request paths to database.") + try? output.save(db) + } + + DispatchQueue.main.async { + NotificationCenter.default.post(name: .pathsBuilt, object: nil) + } + }, + receiveCompletion: { _ in buildPathsPublisher.mutate { $0 = nil } } ) - .setFailureType(to: Error.self) + .shareReplay(1) .eraseToAnyPublisher() - } - .handleEvents( - receiveOutput: { output in - OnionRequestAPI.paths = (output + reusablePaths) - - Storage.shared.write { db in - SNLog("Persisting onion request paths to database.") - try? output.save(db) - } - - DispatchQueue.main.async { - NotificationCenter.default.post(name: .pathsBuilt, object: nil) - } - }, - receiveCompletion: { _ in buildPathsPublisher.mutate { $0 = nil } } - ) - .eraseToAnyPublisher() - - buildPathsPublisher.mutate { $0 = publisher } - - return publisher + + /// Actually assign the atomic value + result = publisher + + return publisher + } } /// Returns a `Path` to be used for building an onion request. Builds new paths as needed. @@ -245,6 +258,7 @@ public enum OnionRequestAPI: OnionRequestAPIType { if let snode = snode { if let path = paths.first(where: { !$0.contains(snode) }) { buildPaths(reusing: paths) // Re-build paths in the background + .subscribe(on: DispatchQueue.global(qos: .background)) .sink(receiveCompletion: { _ in cancellable = [] }, receiveValue: { _ in }) .store(in: &cancellable) @@ -269,6 +283,7 @@ public enum OnionRequestAPI: OnionRequestAPIType { } else { buildPaths(reusing: paths) // Re-build paths in the background + .subscribe(on: DispatchQueue.global(qos: .background)) .sink(receiveCompletion: { _ in cancellable = [] }, receiveValue: { _ in }) .store(in: &cancellable) @@ -480,7 +495,6 @@ public enum OnionRequestAPI: OnionRequestAPIType { var guardSnode: Snode? return buildOnion(around: payload, targetedAt: destination) - .subscribe(on: Threading.workQueue) .flatMap { intermediate -> AnyPublisher<(ResponseInfoType, Data?), Error> in guardSnode = intermediate.guardSnode let url = "\(guardSnode!.address):\(guardSnode!.port)/onion_req/v2" diff --git a/SessionSnodeKit/Networking/SnodeAPI.swift b/SessionSnodeKit/Networking/SnodeAPI.swift index 6e2abafbb..de81c675c 100644 --- a/SessionSnodeKit/Networking/SnodeAPI.swift +++ b/SessionSnodeKit/Networking/SnodeAPI.swift @@ -162,18 +162,17 @@ public final class SnodeAPI { return previouslyBlockedPublisher } - let publisher: AnyPublisher, Error> = { + let targetPublisher: AnyPublisher, Error> = { guard snodePool.count >= minSnodePoolCount else { return getSnodePoolFromSeedNode() } return getSnodePoolFromSnode() .catch { _ in getSnodePoolFromSeedNode() } .eraseToAnyPublisher() }() - - /// Actually assign the atomic value - result = publisher - return publisher + /// Need to include the post-request code and a `shareReplay` within the publisher otherwise it can still be executed + /// multiple times as a result of multiple subscribers + let publisher: AnyPublisher, Error> = targetPublisher .tryFlatMap { snodePool -> AnyPublisher, Error> in guard !snodePool.isEmpty else { throw SnodeAPIError.snodePoolUpdatingFailed } @@ -189,7 +188,14 @@ public final class SnodeAPI { .handleEvents( receiveCompletion: { _ in getSnodePoolPublisher.mutate { $0 = nil } } ) + .shareReplay(1) .eraseToAnyPublisher() + + /// Actually assign the atomic value + result = publisher + + return publisher + } } @@ -245,7 +251,6 @@ public final class SnodeAPI { } } ) - .subscribe(on: Threading.workQueue) .collect() .tryMap { results -> String in guard results.count == validationCount, Set(results).count == 1 else { @@ -758,7 +763,6 @@ public final class SnodeAPI { } return getSwarm(for: publicKey) - .subscribe(on: Threading.workQueue) .tryFlatMapWithRandomSnode(retry: maxRetryCount) { snode -> AnyPublisher<[String: [(hash: String, expiry: UInt64)]], Error> in SnodeAPI .send( @@ -800,7 +804,6 @@ public final class SnodeAPI { } return getSwarm(for: publicKey) - .subscribe(on: Threading.workQueue) .tryFlatMapWithRandomSnode(retry: maxRetryCount) { snode -> AnyPublisher in SnodeAPI .send( @@ -846,7 +849,6 @@ public final class SnodeAPI { let userX25519PublicKey: String = getUserHexEncodedPublicKey() return getSwarm(for: publicKey) - .subscribe(on: Threading.workQueue) .tryFlatMapWithRandomSnode(retry: maxRetryCount) { snode -> AnyPublisher<[String: Bool], Error> in SnodeAPI .send( @@ -902,7 +904,6 @@ public final class SnodeAPI { let userX25519PublicKey: String = getUserHexEncodedPublicKey() return getSwarm(for: userX25519PublicKey) - .subscribe(on: Threading.workQueue) .tryFlatMapWithRandomSnode(retry: maxRetryCount) { snode -> AnyPublisher<[String: Bool], Error> in getNetworkTime(from: snode) .flatMap { timestampMs -> AnyPublisher<[String: Bool], Error> in @@ -950,7 +951,6 @@ public final class SnodeAPI { let userX25519PublicKey: String = getUserHexEncodedPublicKey() return getSwarm(for: userX25519PublicKey) - .subscribe(on: Threading.workQueue) .tryFlatMapWithRandomSnode(retry: maxRetryCount) { snode -> AnyPublisher<[String: Bool], Error> in getNetworkTime(from: snode) .flatMap { timestampMs -> AnyPublisher<[String: Bool], Error> in @@ -1048,7 +1048,6 @@ public final class SnodeAPI { useSeedNodeURLSession: true ) .decoded(as: SnodePoolResponse.self, using: dependencies) - .subscribe(on: Threading.workQueue) .mapError { error in switch error { case HTTPError.parsingFailed: return SnodeAPIError.snodePoolUpdatingFailed diff --git a/SessionSnodeKit/SSKDependencies.swift b/SessionSnodeKit/SSKDependencies.swift index e61cb7311..01faa2289 100644 --- a/SessionSnodeKit/SSKDependencies.swift +++ b/SessionSnodeKit/SSKDependencies.swift @@ -14,7 +14,8 @@ open class SSKDependencies: Dependencies { // MARK: - Initialization public init( - queue: DispatchQueue? = nil, + subscribeQueue: DispatchQueue? = nil, + receiveQueue: DispatchQueue? = nil, onionApi: OnionRequestAPIType.Type? = nil, generalCache: Atomic? = nil, storage: Storage? = nil, @@ -25,7 +26,8 @@ open class SSKDependencies: Dependencies { _onionApi = Atomic(onionApi) super.init( - queue: queue, + subscribeQueue: subscribeQueue, + receiveQueue: receiveQueue, generalCache: generalCache, storage: storage, scheduler: scheduler, diff --git a/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift b/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift index c5638934f..862a650e8 100644 --- a/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift +++ b/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift @@ -49,7 +49,7 @@ class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec { ) cancellables.append( viewModel.observableTableData - .receiveOnMain(immediately: true) + .receive(on: DispatchQueue.main, immediatelyIfMain: true) .sink( receiveCompletion: { _ in }, receiveValue: { viewModel.updateTableData($0.0) } @@ -132,7 +132,7 @@ class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec { ) cancellables.append( viewModel.observableTableData - .receiveOnMain(immediately: true) + .receive(on: DispatchQueue.main, immediatelyIfMain: true) .sink( receiveCompletion: { _ in }, receiveValue: { viewModel.updateTableData($0.0) } @@ -178,7 +178,7 @@ class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec { cancellables.append( viewModel.rightNavItems - .receiveOnMain(immediately: true) + .receive(on: DispatchQueue.main, immediatelyIfMain: true) .sink( receiveCompletion: { _ in }, receiveValue: { navItems in items = navItems } @@ -194,7 +194,7 @@ class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec { beforeEach { cancellables.append( viewModel.rightNavItems - .receiveOnMain(immediately: true) + .receive(on: DispatchQueue.main, immediatelyIfMain: true) .sink( receiveCompletion: { _ in }, receiveValue: { navItems in items = navItems } @@ -221,7 +221,7 @@ class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec { cancellables.append( viewModel.dismissScreen - .receiveOnMain(immediately: true) + .receive(on: DispatchQueue.main, immediatelyIfMain: true) .sink( receiveCompletion: { _ in }, receiveValue: { _ in didDismissScreen = true } diff --git a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift index 5d2b08362..e05de6c9a 100644 --- a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift +++ b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift @@ -75,7 +75,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { ) disposables.append( viewModel.observableTableData - .receiveOnMain(immediately: true) + .receive(on: DispatchQueue.main, immediatelyIfMain: true) .sink( receiveCompletion: { _ in }, receiveValue: { viewModel.updateTableData($0.0) } @@ -173,7 +173,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { ) disposables.append( viewModel.observableTableData - .receiveOnMain(immediately: true) + .receive(on: DispatchQueue.main, immediatelyIfMain: true) .sink( receiveCompletion: { _ in }, receiveValue: { viewModel.updateTableData($0.0) } @@ -447,7 +447,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { ) disposables.append( viewModel.observableTableData - .receiveOnMain(immediately: true) + .receive(on: DispatchQueue.main, immediatelyIfMain: true) .sink( receiveCompletion: { _ in }, receiveValue: { viewModel.updateTableData($0.0) } @@ -489,7 +489,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { ) disposables.append( viewModel.observableTableData - .receiveOnMain(immediately: true) + .receive(on: DispatchQueue.main, immediatelyIfMain: true) .sink( receiveCompletion: { _ in }, receiveValue: { viewModel.updateTableData($0.0) } diff --git a/SessionTests/Settings/NotificationContentViewModelSpec.swift b/SessionTests/Settings/NotificationContentViewModelSpec.swift index 04f11d74b..6538c95b0 100644 --- a/SessionTests/Settings/NotificationContentViewModelSpec.swift +++ b/SessionTests/Settings/NotificationContentViewModelSpec.swift @@ -31,7 +31,7 @@ class NotificationContentViewModelSpec: QuickSpec { ) viewModel = NotificationContentViewModel(storage: mockStorage, scheduling: .immediate) dataChangeCancellable = viewModel.observableTableData - .receiveOnMain(immediately: true) + .receive(on: DispatchQueue.main, immediatelyIfMain: true) .sink( receiveCompletion: { _ in }, receiveValue: { viewModel.updateTableData($0.0) } @@ -99,7 +99,7 @@ class NotificationContentViewModelSpec: QuickSpec { } viewModel = NotificationContentViewModel(storage: mockStorage, scheduling: .immediate) dataChangeCancellable = viewModel.observableTableData - .receiveOnMain(immediately: true) + .receive(on: DispatchQueue.main, immediatelyIfMain: true) .sink( receiveCompletion: { _ in }, receiveValue: { viewModel.updateTableData($0.0) } @@ -148,7 +148,7 @@ class NotificationContentViewModelSpec: QuickSpec { var didDismissScreen: Bool = false dismissCancellable = viewModel.dismissScreen - .receiveOnMain(immediately: true) + .receive(on: DispatchQueue.main, immediatelyIfMain: true) .sink( receiveCompletion: { _ in }, receiveValue: { _ in didDismissScreen = true } diff --git a/SessionUIKit/Components/HighlightMentionBackgroundView.swift b/SessionUIKit/Components/HighlightMentionBackgroundView.swift index 450636c53..289787c21 100644 --- a/SessionUIKit/Components/HighlightMentionBackgroundView.swift +++ b/SessionUIKit/Components/HighlightMentionBackgroundView.swift @@ -92,7 +92,7 @@ public class HighlightMentionBackgroundView: UIView { var ascent: CGFloat = 0 var descent: CGFloat = 0 var leading: CGFloat = 0 - let lineWidth = CGFloat(CTLineGetTypographicBounds(line, &ascent, &descent, &leading)) + _ = CGFloat(CTLineGetTypographicBounds(line, &ascent, &descent, &leading)) for run in runs { let attributes: NSDictionary = CTRunGetAttributes(run) diff --git a/SessionUIKit/Components/TopBannerController.swift b/SessionUIKit/Components/TopBannerController.swift index 6264471f4..f00e53115 100644 --- a/SessionUIKit/Components/TopBannerController.swift +++ b/SessionUIKit/Components/TopBannerController.swift @@ -163,7 +163,7 @@ public class TopBannerController: UIViewController { return } - // Not an ideal approach but should allow + // Not an ideal approach but should allow us to have a single banner guard let instance: TopBannerController = ((view?.window?.rootViewController as? TopBannerController) ?? TopBannerController.lastInstance) else { return } @@ -185,4 +185,20 @@ public class TopBannerController: UIViewController { instance?.contentStackView.layoutIfNeeded() } } + + public static func hide(inWindowFor view: UIView? = nil) { + guard Thread.isMainThread else { + DispatchQueue.main.async { + TopBannerController.hide(inWindowFor: view) + } + return + } + + // Not an ideal approach but should allow us to have a single banner + guard let instance: TopBannerController = ((view?.window?.rootViewController as? TopBannerController) ?? TopBannerController.lastInstance) else { + return + } + + UIView.performWithoutAnimation { instance.dismissBanner() } + } } diff --git a/SessionUIKit/Database/Migrations/_001_ThemePreferences.swift b/SessionUIKit/Database/Migrations/_001_ThemePreferences.swift index 39ca24f5e..2951dca42 100644 --- a/SessionUIKit/Database/Migrations/_001_ThemePreferences.swift +++ b/SessionUIKit/Database/Migrations/_001_ThemePreferences.swift @@ -35,10 +35,12 @@ enum _001_ThemePreferences: Migration { db[.themePrimaryColor] = targetPrimaryColor // Looks like the ThemeManager will load it's default values before this migration gets run - // as a result we need to update the ThemeManage to ensure the correct theme is applied - ThemeManager.currentTheme = targetTheme - ThemeManager.primaryColor = targetPrimaryColor - ThemeManager.matchSystemNightModeSetting = matchSystemNightModeSetting + // as a result we need to update the ThemeManager to ensure the correct theme is applied + ThemeManager.setInitialThemeState( + theme: targetTheme, + primaryColor: targetPrimaryColor, + matchSystemNightModeSetting: matchSystemNightModeSetting + ) Storage.update(progress: 1, for: self, in: target) // In case this is the last migration } diff --git a/SessionUIKit/Style Guide/ThemeManager.swift b/SessionUIKit/Style Guide/ThemeManager.swift index 5926a82d1..a38356ebb 100644 --- a/SessionUIKit/Style Guide/ThemeManager.swift +++ b/SessionUIKit/Style Guide/ThemeManager.swift @@ -30,8 +30,12 @@ public enum ThemeManager { /// Unfortunately if we don't do this the `ThemeApplier` is immediately deallocated and we can't use it to update the theme private static var uiRegistry: NSMapTable = NSMapTable.weakToStrongObjects() + private static var _initialTheme: Theme? + private static var _initialPrimaryColor: Theme.PrimaryColor? + private static var _initialMatchSystemNightModeSetting: Bool? + public static var currentTheme: Theme = { - Storage.shared[.theme].defaulting(to: Theme.classicDark) + (_initialTheme ?? Storage.shared[.theme].defaulting(to: Theme.classicDark)) }() { didSet { // Only update if it was changed @@ -55,7 +59,7 @@ public enum ThemeManager { } public static var primaryColor: Theme.PrimaryColor = { - Storage.shared[.themePrimaryColor].defaulting(to: Theme.PrimaryColor.green) + (_initialPrimaryColor ?? Storage.shared[.themePrimaryColor].defaulting(to: Theme.PrimaryColor.green)) }() { didSet { // Only update if it was changed @@ -70,7 +74,7 @@ public enum ThemeManager { } public static var matchSystemNightModeSetting: Bool = { - Storage.shared[.themeMatchSystemDayNightCycle] + (_initialMatchSystemNightModeSetting ?? Storage.shared[.themeMatchSystemDayNightCycle]) }() { didSet { // Only update if it was changed @@ -99,6 +103,16 @@ public enum ThemeManager { // MARK: - Functions + public static func setInitialThemeState( + theme: Theme, + primaryColor: Theme.PrimaryColor, + matchSystemNightModeSetting: Bool + ) { + _initialTheme = theme + _initialPrimaryColor = primaryColor + _initialMatchSystemNightModeSetting = matchSystemNightModeSetting + } + public static func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { let currentUserInterfaceStyle: UIUserInterfaceStyle = UITraitCollection.current.userInterfaceStyle diff --git a/SessionUtilitiesKit/Combine/Publisher+Utilities.swift b/SessionUtilitiesKit/Combine/Publisher+Utilities.swift index 4df5f143f..ddc059a85 100644 --- a/SessionUtilitiesKit/Combine/Publisher+Utilities.swift +++ b/SessionUtilitiesKit/Combine/Publisher+Utilities.swift @@ -28,21 +28,25 @@ public extension Publisher { /// The standard `.subscribe(on: DispatchQueue.main)` seems to ocassionally dispatch to the /// next run loop before actually subscribing, this method checks if it's running on the main thread already and /// if so just subscribes directly rather than routing via `.receive(on:)` - func subscribeOnMain(immediately receiveImmediately: Bool = false, options: DispatchQueue.SchedulerOptions? = nil) -> AnyPublisher { - guard receiveImmediately else { - return self.subscribe(on: DispatchQueue.main, options: options) + func subscribe( + on scheduler: S, + immediatelyIfMain: Bool, + options: S.SchedulerOptions? = nil + ) -> AnyPublisher where S: Scheduler { + guard immediatelyIfMain && ((scheduler as? DispatchQueue) == DispatchQueue.main) else { + return self.subscribe(on: scheduler, options: options) .eraseToAnyPublisher() } - + return self .flatMap { value -> AnyPublisher in guard Thread.isMainThread else { return Just(value) .setFailureType(to: Failure.self) - .subscribe(on: DispatchQueue.main, options: options) + .subscribe(on: scheduler, options: options) .eraseToAnyPublisher() } - + return Just(value) .setFailureType(to: Failure.self) .eraseToAnyPublisher() @@ -53,21 +57,25 @@ public extension Publisher { /// The standard `.receive(on: DispatchQueue.main)` seems to ocassionally dispatch to the /// next run loop before emitting data, this method checks if it's running on the main thread already and /// if so just emits directly rather than routing via `.receive(on:)` - func receiveOnMain(immediately receiveImmediately: Bool = false) -> AnyPublisher { - guard receiveImmediately else { - return self.receive(on: DispatchQueue.main) + func receive( + on scheduler: S, + immediatelyIfMain: Bool, + options: S.SchedulerOptions? = nil + ) -> AnyPublisher where S: Scheduler { + guard immediatelyIfMain && ((scheduler as? DispatchQueue) == DispatchQueue.main) else { + return self.receive(on: scheduler, options: options) .eraseToAnyPublisher() } - + return self .flatMap { value -> AnyPublisher in guard Thread.isMainThread else { return Just(value) .setFailureType(to: Failure.self) - .receive(on: DispatchQueue.main) + .receive(on: scheduler, options: options) .eraseToAnyPublisher() } - + return Just(value) .setFailureType(to: Failure.self) .eraseToAnyPublisher() diff --git a/SessionUtilitiesKit/Database/Storage.swift b/SessionUtilitiesKit/Database/Storage.swift index 40b0380e9..ce25d5b86 100644 --- a/SessionUtilitiesKit/Database/Storage.swift +++ b/SessionUtilitiesKit/Database/Storage.swift @@ -46,6 +46,13 @@ open class Storage { public init( customWriter: DatabaseWriter? = nil, customMigrations: [TargetMigrations]? = nil + ) { + configureDatabase(customWriter: customWriter, customMigrations: customMigrations) + } + + private func configureDatabase( + customWriter: DatabaseWriter? = nil, + customMigrations: [TargetMigrations]? = nil ) { // Create the database directory if needed and ensure it's protection level is set before attempting to // create the database KeySpec or the database itself @@ -330,8 +337,16 @@ open class Storage { Storage.shared.migrationsCompleted.mutate { $0 = false } Storage.shared.dbWriter = nil - self.deleteDatabaseFiles() - try? self.deleteDbKeys() + deleteDatabaseFiles() + try? deleteDbKeys() + } + + public static func resetForCleanMigration() { + // Clear existing content + resetAllStorage() + + // Reconfigure + Storage.shared.configureDatabase() } private static func deleteDatabaseFiles() { diff --git a/SessionUtilitiesKit/Database/Types/PagedDatabaseObserver.swift b/SessionUtilitiesKit/Database/Types/PagedDatabaseObserver.swift index 30d8fb877..26920940d 100644 --- a/SessionUtilitiesKit/Database/Types/PagedDatabaseObserver.swift +++ b/SessionUtilitiesKit/Database/Types/PagedDatabaseObserver.swift @@ -663,49 +663,53 @@ public class PagedDatabaseObserver: TransactionObserver where } // Fetch the desired data - let pageRowIds: [Int64] = PagedData.rowIds( - db, - tableName: pagedTableName, - requiredJoinSQL: joinSQL, - filterSQL: filterSQL, - groupSQL: groupSQL, - orderSQL: orderSQL, - limit: queryInfo.limit, - offset: queryInfo.offset - ) + let pageRowIds: [Int64] let newData: [T] + let updatedLimitInfo: PagedData.PageInfo - do { newData = try dataQuery(pageRowIds).fetchAll(db) } - catch { - SNLog("PagedDatabaseObserver threw exception: \(error)") - throw error - } - - let updatedLimitInfo: PagedData.PageInfo = PagedData.PageInfo( - pageSize: currentPageInfo.pageSize, - pageOffset: queryInfo.updatedCacheOffset, - currentCount: { - switch target { - case .reloadCurrent: return currentPageInfo.currentCount - default: return (currentPageInfo.currentCount + newData.count) - } - }(), - totalCount: totalCount - ) - - // Update the associatedRecords for the newly retrieved data - self?.associatedRecords.forEach { record in - record.updateCache( + do { + pageRowIds = try PagedData.rowIds( db, - rowIds: PagedData.associatedRowIds( - db, - tableName: record.databaseTableName, - pagedTableName: pagedTableName, - pagedTypeRowIds: newData.map { $0.rowId }, - joinToPagedType: record.joinToPagedType - ), - hasOtherChanges: false + tableName: pagedTableName, + requiredJoinSQL: joinSQL, + filterSQL: filterSQL, + groupSQL: groupSQL, + orderSQL: orderSQL, + limit: queryInfo.limit, + offset: queryInfo.offset ) + newData = try dataQuery(pageRowIds).fetchAll(db) + updatedLimitInfo = PagedData.PageInfo( + pageSize: currentPageInfo.pageSize, + pageOffset: queryInfo.updatedCacheOffset, + currentCount: { + switch target { + case .reloadCurrent: return currentPageInfo.currentCount + default: return (currentPageInfo.currentCount + newData.count) + } + }(), + totalCount: totalCount + ) + + // Update the associatedRecords for the newly retrieved data + let newDataRowIds: [Int64] = newData.map { $0.rowId } + try self?.associatedRecords.forEach { record in + record.updateCache( + db, + rowIds: try PagedData.associatedRowIds( + db, + tableName: record.databaseTableName, + pagedTableName: pagedTableName, + pagedTypeRowIds: newDataRowIds, + joinToPagedType: record.joinToPagedType + ), + hasOtherChanges: false + ) + } + } + catch { + SNLog("[PagedDatabaseObserver] Error loading data: \(error)") + throw error } return (newData, updatedLimitInfo, nil) @@ -1072,7 +1076,7 @@ public enum PagedData { orderSQL: SQL, limit: Int, offset: Int - ) -> [Int64] { + ) throws -> [Int64] { let tableNameLiteral: SQL = SQL(stringLiteral: tableName) let finalJoinSQL: SQL = (requiredJoinSQL ?? "") let finalGroupSQL: SQL = (groupSQL ?? "") @@ -1086,8 +1090,7 @@ public enum PagedData { LIMIT \(limit) OFFSET \(offset) """ - return (try? request.fetchAll(db)) - .defaulting(to: []) + return try request.fetchAll(db) } fileprivate static func index( @@ -1160,7 +1163,7 @@ public enum PagedData { pagedTableName: String, pagedTypeRowIds: [Int64], joinToPagedType: SQL - ) -> [Int64] { + ) throws -> [Int64] { guard !pagedTypeRowIds.isEmpty else { return [] } let tableNameLiteral: SQL = SQL(stringLiteral: tableName) @@ -1172,8 +1175,7 @@ public enum PagedData { WHERE \(pagedTableNameLiteral).rowId IN \(pagedTypeRowIds) """ - return (try? request.fetchAll(db)) - .defaulting(to: []) + return try request.fetchAll(db) } /// Returns the rowIds for the paged type based on the specified relatedRowIds diff --git a/SessionUtilitiesKit/General/Dependencies.swift b/SessionUtilitiesKit/General/Dependencies.swift index 30e443be1..64e2d5f3e 100644 --- a/SessionUtilitiesKit/General/Dependencies.swift +++ b/SessionUtilitiesKit/General/Dependencies.swift @@ -4,10 +4,16 @@ import Foundation import GRDB open class Dependencies { - public var _queue: Atomic - public var queue: DispatchQueue { - get { Dependencies.getValueSettingIfNull(&_queue) { DispatchQueue.global(qos: .default) } } - set { _queue.mutate { $0 = newValue } } + public var _subscribeQueue: Atomic + public var subscribeQueue: DispatchQueue { + get { Dependencies.getValueSettingIfNull(&_subscribeQueue) { DispatchQueue.global(qos: .default) } } + set { _subscribeQueue.mutate { $0 = newValue } } + } + + public var _receiveQueue: Atomic + public var receiveQueue: DispatchQueue { + get { Dependencies.getValueSettingIfNull(&_receiveQueue) { DispatchQueue.global(qos: .default) } } + set { _receiveQueue.mutate { $0 = newValue } } } public var _generalCache: Atomic?> @@ -43,14 +49,16 @@ open class Dependencies { // MARK: - Initialization public init( - queue: DispatchQueue? = nil, + subscribeQueue: DispatchQueue? = nil, + receiveQueue: DispatchQueue? = nil, generalCache: Atomic? = nil, storage: Storage? = nil, scheduler: ValueObservationScheduler? = nil, standardUserDefaults: UserDefaultsType? = nil, date: Date? = nil ) { - _queue = Atomic(queue) + _subscribeQueue = Atomic(subscribeQueue) + _receiveQueue = Atomic(receiveQueue) _generalCache = Atomic(generalCache) _storage = Atomic(storage) _scheduler = Atomic(scheduler) diff --git a/SessionUtilitiesKit/JobRunner/JobRunner.swift b/SessionUtilitiesKit/JobRunner/JobRunner.swift index 48752c5bf..a562cc961 100644 --- a/SessionUtilitiesKit/JobRunner/JobRunner.swift +++ b/SessionUtilitiesKit/JobRunner/JobRunner.swift @@ -57,6 +57,10 @@ public final class JobRunner { // Once all blocking jobs have been completed we want to start running // the remaining job queues queues.wrappedValue.forEach { _, queue in queue.start() } + blockingQueueDrainCallback.mutate { + $0.forEach { $0() } + $0 = [] + } } ) ) @@ -120,6 +124,12 @@ public final class JobRunner { private static var hasCompletedInitialBecomeActive: Atomic = Atomic(false) private static var shutdownBackgroundTask: Atomic = Atomic(nil) fileprivate static var canStartQueues: Atomic = Atomic(false) + private static var blockingQueueDrainCallback: Atomic<[() -> ()]> = Atomic([]) + + fileprivate static var canStartNonBlockingQueue: Bool { + blockingQueue.wrappedValue?.hasStartedAtLeastOnce.wrappedValue == true && + blockingQueue.wrappedValue?.isRunning.wrappedValue != true + } // MARK: - Configuration @@ -127,6 +137,15 @@ public final class JobRunner { executorMap.mutate { $0[variant] = executor } } + public static func afterBlockingQueue(callback: @escaping () -> ()) { + guard + (blockingQueue.wrappedValue?.hasStartedAtLeastOnce.wrappedValue != true) || + (blockingQueue.wrappedValue?.isRunning.wrappedValue == true) + else { return callback() } + + blockingQueueDrainCallback.mutate { $0.append(callback) } + } + // MARK: - Execution /// Add a job onto the queue, if the queue isn't currently running and 'canStartJob' is true then this will start @@ -444,8 +463,8 @@ public final class JobRunner { // MARK: - JobQueue -private final class JobQueue { - fileprivate enum QueueType: Hashable { +public final class JobQueue { + public enum QueueType: Hashable { case blocking case general(number: Int) case messageSend @@ -530,6 +549,7 @@ private final class JobQueue { }() private var nextTrigger: Atomic = Atomic(nil) + fileprivate var hasStartedAtLeastOnce: Atomic = Atomic(false) fileprivate var isRunning: Atomic = Atomic(false) private var queue: Atomic<[Job]> = Atomic([]) private var jobCallbacks: Atomic<[Int64: [(JobRunner.JobResult) -> ()]]> = Atomic([:]) @@ -541,7 +561,7 @@ private final class JobQueue { // MARK: - Initialization - init( + fileprivate init( type: QueueType, executionType: ExecutionType = .serial, qos: DispatchQoS, @@ -754,7 +774,11 @@ private final class JobQueue { HasAppContext() && CurrentAppContext().isMainApp && !CurrentAppContext().isRunningTests && - JobRunner.canStartQueues.wrappedValue + JobRunner.canStartQueues.wrappedValue && + ( + type == .blocking || + JobRunner.canStartNonBlockingQueue + ) else { return } guard force || !isRunning.wrappedValue else { return } @@ -774,6 +798,7 @@ private final class JobQueue { wasAlreadyRunning = isRunning isRunning = true } + hasStartedAtLeastOnce.mutate { $0 = true } // Get any pending jobs let jobIdsAlreadyRunning: Set = currentlyRunningJobIds.wrappedValue diff --git a/SessionUtilitiesKit/Networking/Request.swift b/SessionUtilitiesKit/Networking/Request.swift index df2a04e78..943f2a1fa 100644 --- a/SessionUtilitiesKit/Networking/Request.swift +++ b/SessionUtilitiesKit/Networking/Request.swift @@ -2,7 +2,9 @@ import Foundation // MARK: - Convenience Types -public struct Empty: Codable {} +public struct Empty: Codable { + public init() {} +} public typealias NoBody = Empty public typealias NoResponse = Empty diff --git a/SessionUtilitiesKit/Utilities/Version.swift b/SessionUtilitiesKit/Utilities/Version.swift new file mode 100644 index 000000000..38daf00bf --- /dev/null +++ b/SessionUtilitiesKit/Utilities/Version.swift @@ -0,0 +1,55 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public struct Version: Comparable { + public let major: Int + public let minor: Int + public let patch: Int + + public var stringValue: String { "\(major).\(minor).\(patch)" } + + // MARK: - Initialization + + public init( + major: Int, + minor: Int, + patch: Int + ) { + self.major = major + self.minor = minor + self.patch = patch + } + + // MARK: - Functions + + public static func from(_ versionString: String) -> Version { + var tokens: [Int] = versionString + .split(separator: ".") + .map { (Int($0) ?? 0) } + + // Extend to '{major}.{minor}.{patch}' if any parts were omitted + while tokens.count < 3 { + tokens.append(0) + } + + return Version(major: tokens[0], minor: tokens[1], patch: tokens[2]) + } + + // MARK: - Comparable + + public static func == (lhs: Version, rhs: Version) -> Bool { + return ( + lhs.major == rhs.major && + lhs.minor == rhs.minor && + lhs.patch == rhs.patch + ) + } + + public static func < (lhs: Version, rhs: Version) -> Bool { + guard lhs.major >= rhs.major else { return true } + guard lhs.minor >= rhs.minor else { return true } + + return (lhs.patch < rhs.patch) + } +} diff --git a/SessionUtilitiesKitTests/Utilities/VersionSpec.swift b/SessionUtilitiesKitTests/Utilities/VersionSpec.swift new file mode 100644 index 000000000..2d46e2f3d --- /dev/null +++ b/SessionUtilitiesKitTests/Utilities/VersionSpec.swift @@ -0,0 +1,98 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +import Quick +import Nimble + +@testable import SessionUtilitiesKit + +class VersionSpec: QuickSpec { + // MARK: - Spec + + override func spec() { + describe("a Version") { + it("can be created from a string") { + let version: Version = Version.from("1.20.3") + + expect(version.major).to(equal(1)) + expect(version.minor).to(equal(20)) + expect(version.patch).to(equal(3)) + } + + it("correctly exposes a string value") { + let version: Version = Version(major: 1, minor: 20, patch: 3) + + expect(version.stringValue).to(equal("1.20.3")) + } + + context("when checking equality") { + it("returns true if the values match") { + let version1: Version = Version.from("1.0.0") + let version2: Version = Version.from("1.0.0") + + expect(version1 == version2) + .to(beTrue()) + } + + it("returns false if the values do not match") { + let version1: Version = Version.from("1.0.0") + let version2: Version = Version.from("1.0.1") + + expect(version1 == version2) + .to(beFalse()) + } + } + + context("when comparing versions") { + it("returns correctly for a simple major difference") { + let version1: Version = Version.from("1.0.0") + let version2: Version = Version.from("2.0.0") + + expect(version1 < version2).to(beTrue()) + expect(version2 > version1).to(beTrue()) + } + + it("returns correctly for a complex major difference") { + let version1: Version = Version.from("2.90.90") + let version2: Version = Version.from("10.0.0") + + expect(version1 < version2).to(beTrue()) + expect(version2 > version1).to(beTrue()) + } + + it("returns correctly for a simple minor difference") { + let version1: Version = Version.from("1.0.0") + let version2: Version = Version.from("1.1.0") + + expect(version1 < version2).to(beTrue()) + expect(version2 > version1).to(beTrue()) + } + + it("returns correctly for a complex minor difference") { + let version1: Version = Version.from("90.2.90") + let version2: Version = Version.from("90.10.0") + + expect(version1 < version2).to(beTrue()) + expect(version2 > version1).to(beTrue()) + } + + it("returns correctly for a simple patch difference") { + let version1: Version = Version.from("1.0.0") + let version2: Version = Version.from("1.0.1") + + expect(version1 < version2).to(beTrue()) + expect(version2 > version1).to(beTrue()) + } + + it("returns correctly for a complex patch difference") { + let version1: Version = Version.from("90.90.2") + let version2: Version = Version.from("90.90.10") + + expect(version1 < version2).to(beTrue()) + expect(version2 > version1).to(beTrue()) + } + } + } + } +} diff --git a/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift b/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift index 11f829932..5884bb82d 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift @@ -567,6 +567,7 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate { loadingView.startAnimating() LinkPreview.tryToBuildPreviewInfo(previewUrl: linkPreviewURL) + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .receive(on: DispatchQueue.main) .sink( receiveCompletion: { [weak self] result in diff --git a/_SharedTestUtilities/CombineExtensions.swift b/_SharedTestUtilities/CombineExtensions.swift index 1c8ac8505..f040a6008 100644 --- a/_SharedTestUtilities/CombineExtensions.swift +++ b/_SharedTestUtilities/CombineExtensions.swift @@ -7,8 +7,8 @@ import SessionUtilitiesKit public extension Publisher { func sinkAndStore(in storage: inout C) where C: RangeReplaceableCollection, C.Element == AnyCancellable { self - .subscribeOnMain(immediately: true) - .receiveOnMain(immediately: true) + .subscribe(on: DispatchQueue.main, immediatelyIfMain: true) + .receive(on: DispatchQueue.main, immediatelyIfMain: true) .sink( receiveCompletion: { _ in }, receiveValue: { _ in } @@ -22,7 +22,7 @@ public extension AnyPublisher { var value: Output? _ = self - .receiveOnMain(immediately: true) + .receive(on: DispatchQueue.main, immediatelyIfMain: true) .sink( receiveCompletion: { _ in }, receiveValue: { result in value = result } From d8ae9669c86e5672b5adb9541bb3d86b1573243f Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Mon, 26 Jun 2023 18:03:40 +1000 Subject: [PATCH 095/135] Fixed a breaking issue and a few other minor bugs Fixed a busted version comparison Fixed an issue where the config dump population wasn't setting the 'created' timestamp for contacts Fixed an issue where the 'SyncPushTokensJob' could run logic on the wrong thread Fixed a bug where the 'scroll to bottom' button wouldn't initial be visible in some cases Fixed a bug where the 'scroll to bottom' button would fade out when there were subsequent pages Fixed a bug where an open group image might not get downloaded in some cases Fixed an issue where we would incorrectly append a wildcard character to the end of a search term that ended in a quotation mark Finished refactoring the OpenGroupAPI to use PreparedSendData --- Session.xcodeproj/project.pbxproj | 12 +- .../ConversationVC+Interaction.swift | 9 +- Session/Conversations/ConversationVC.swift | 23 +- Session/Notifications/SyncPushTokensJob.swift | 1 + Session/Shared/FullConversationCell.swift | 3 +- .../_014_GenerateInitialUserConfigDumps.swift | 3 +- .../Jobs/Types/ConfigurationSyncJob.swift | 2 +- .../Open Groups/Models/SOGSBatchRequest.swift | 132 ++--- .../Open Groups/OpenGroupAPI.swift | 476 +++++++----------- .../Open Groups/OpenGroupManager.swift | 30 +- .../Open Groups/Types/PreparedSendData.swift | 148 +++++- .../Pollers/OpenGroupPoller.swift | 47 +- .../SessionUtil+Contacts.swift | 11 +- .../SessionThreadViewModel.swift | 24 +- .../Models/BatchRequestInfoSpec.swift | 185 +++---- .../Open Groups/OpenGroupAPISpec.swift | 175 ++++--- .../Models/SnodeBatchRequest.swift | 2 +- .../Crypto/CryptoKit+Utilities.swift | 16 +- .../Networking/BatchResponse.swift | 101 ++-- SessionUtilitiesKit/Utilities/Version.swift | 4 +- .../Utilities/VersionSpec.swift | 36 +- 21 files changed, 751 insertions(+), 689 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 0a5a4e81a..211bfa740 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -6417,7 +6417,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 409; + CURRENT_PROJECT_VERSION = 410; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6489,7 +6489,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 409; + CURRENT_PROJECT_VERSION = 410; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6554,7 +6554,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 409; + CURRENT_PROJECT_VERSION = 410; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6628,7 +6628,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 409; + CURRENT_PROJECT_VERSION = 410; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -7536,7 +7536,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 409; + CURRENT_PROJECT_VERSION = 410; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -7607,7 +7607,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 409; + CURRENT_PROJECT_VERSION = 410; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 6c10b9da8..504067efd 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -2088,21 +2088,20 @@ extension ConversationVC: cancelStyle: .alert_text, onConfirm: { [weak self] _ in Storage.shared - .readPublisherFlatMap { db -> AnyPublisher in + .readPublisher { db in guard let openGroup: OpenGroup = try OpenGroup.fetchOne(db, id: threadId) else { throw StorageError.objectNotFound } - return OpenGroupAPI - .userBanAndDeleteAllMessages( + return try OpenGroupAPI + .preparedUserBanAndDeleteAllMessages( db, sessionId: cellViewModel.authorId, in: openGroup.roomToken, on: openGroup.server ) - .map { _ in () } - .eraseToAnyPublisher() } + .flatMap { OpenGroupAPI.send(data: $0) } .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .receive(on: DispatchQueue.main) .sinkUntilComplete( diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index c655c3e89..ddadaed3e 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -1632,10 +1632,19 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers unreadCountView.isHidden = (unreadCount == 0) } - public func updateScrollToBottom() { - // The initial scroll can trigger this logic but we already mark the initially focused message - // as read so don't run the below until the user actually scrolls after the initial layout - guard self.didFinishInitialLayout else { return } + public func updateScrollToBottom(force: Bool = false) { + // Don't update the scroll button until we have actually setup the initial scroll position to avoid + // any odd flickering or incorrect appearance + guard self.didFinishInitialLayout || force else { return } + + // If we have a 'loadNewer' item in the interaction data then there are subsequent pages and the + // 'scrollToBottom' actions should always be visible to allow the user to jump to the bottom (without + // this the button will fade out as the user gets close to the bottom of the current page) + guard !self.viewModel.interactionData.contains(where: { $0.model == .loadNewer }) else { + self.scrollButton.alpha = 1 + self.unreadCountView.alpha = 1 + return + } // Calculate the target opacity for the scroll button let contentOffsetY: CGFloat = tableView.contentOffset.y @@ -1848,17 +1857,13 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers animated: (self.didFinishInitialLayout && isAnimated) ) - // Need to explicitly call 'scrollViewDidScroll' here as it won't get triggered - // by 'scrollToRow' if a scroll doesn't occur (eg. if there is less than 1 screen - // of messages) - self.scrollViewDidScroll(self.tableView) - // If we haven't finished the initial layout then we want to delay the highlight/markRead slightly // so it doesn't look buggy with the push transition and we know for sure the correct visible cells // have been loaded DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(self.didFinishInitialLayout ? 0 : 150)) { [weak self] in self?.markFullyVisibleAndOlderCellsAsRead(interactionInfo: interactionInfo) self?.highlightCellIfNeeded(interactionId: interactionInfo.id, behaviour: focusBehaviour) + self?.updateScrollToBottom(force: true) } self.shouldHighlightNextScrollToInteraction = false diff --git a/Session/Notifications/SyncPushTokensJob.swift b/Session/Notifications/SyncPushTokensJob.swift index c11ec93d3..36b32e56a 100644 --- a/Session/Notifications/SyncPushTokensJob.swift +++ b/Session/Notifications/SyncPushTokensJob.swift @@ -193,6 +193,7 @@ extension SyncPushTokensJob { return Fail(error: error) .eraseToAnyPublisher() } + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .sinkUntilComplete( receiveCompletion: { result in switch result { diff --git a/Session/Shared/FullConversationCell.swift b/Session/Shared/FullConversationCell.swift index c22bcb3b2..9a8d7fa83 100644 --- a/Session/Shared/FullConversationCell.swift +++ b/Session/Shared/FullConversationCell.swift @@ -651,8 +651,7 @@ public final class FullConversationCell: UITableViewCell, SwipeActionOptimisticC .map { part -> String in guard part.hasPrefix("\"") && part.hasSuffix("\"") else { return part } - let partRange = (part.index(after: part.startIndex)..(request: Request, responseType: R.Type) { - self.endpoint = request.endpoint - self.responseType = HTTP.BatchSubResponse.self - self.child = Child(request: request) - } - - public init(request: Request) { - self.init( - request: request, - responseType: NoResponse.self - ) - } - } - // MARK: - BatchRequest.Child struct Child: Encodable { @@ -51,76 +32,43 @@ internal extension OpenGroupAPI { case bytes } - let method: HTTPMethod - let path: String - let headers: [String: String]? - - /// The `jsonBodyEncoder` is used to avoid having to make `Child` a generic type (haven't found a good way - /// to keep `Child` encodable using protocols unfortunately so need this work around) - private let jsonBodyEncoder: ((inout KeyedEncodingContainer, CodingKeys) throws -> ())? - private let b64: String? - private let bytes: [UInt8]? - - internal init(request: Request) { - self.method = request.method - self.path = request.urlPathAndParamsString - self.headers = (request.headers.isEmpty ? nil : request.headers.toHTTPHeaders()) - - // Note: Need to differentiate between JSON, b64 string and bytes body values to ensure - // they are encoded correctly so the server knows how to handle them - switch request.body { - case let bodyString as String: - self.jsonBodyEncoder = nil - self.b64 = bodyString - self.bytes = nil - - case let bodyBytes as [UInt8]: - self.jsonBodyEncoder = nil - self.b64 = nil - self.bytes = bodyBytes - - default: - self.jsonBodyEncoder = { [body = request.body] container, key in - try container.encodeIfPresent(body, forKey: key) - } - self.b64 = nil - self.bytes = nil - } - } + let request: ErasedPreparedSendData func encode(to encoder: Encoder) throws { - var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(method, forKey: .method) - try container.encode(path, forKey: .path) - try container.encodeIfPresent(headers, forKey: .headers) - try jsonBodyEncoder?(&container, .json) - try container.encodeIfPresent(b64, forKey: .b64) - try container.encodeIfPresent(bytes, forKey: .bytes) + try request.encodeForBatchRequest(to: encoder) } } } -} - -// MARK: - Convenience - -internal extension AnyPublisher where Output == HTTP.BatchResponse, Failure == Error { - func map( - requests: [OpenGroupAPI.BatchRequest.Info], - toHashMapFor endpointType: E.Type - ) -> AnyPublisher<(info: ResponseInfoType, data: [E: Codable]), Error> { - return self - .map { result -> (info: ResponseInfoType, data: [E: Codable]) in - ( - info: result.info, - data: result.responses.enumerated() - .reduce(into: [:]) { prev, next in - guard let endpoint: E = requests[next.offset].endpoint as? E else { return } - - prev[endpoint] = next.element - } - ) - } - .eraseToAnyPublisher() + + struct BatchResponse: Decodable { + let info: ResponseInfoType + let data: [Endpoint: Decodable] + + public subscript(position: Endpoint) -> Decodable? { + get { return data[position] } + } + + public var count: Int { data.count } + public var keys: Dictionary.Keys { data.keys } + public var values: Dictionary.Values { data.values } + + // MARK: - Initialization + + internal init( + info: ResponseInfoType, + data: [Endpoint: Decodable] + ) { + self.info = info + self.data = data + } + + public init(from decoder: Decoder) throws { +#if DEBUG + preconditionFailure("The `OpenGroupAPI.BatchResponse` type cannot be decoded directly, this is simply here to allow for `PreparedSendData` support") +#else + data = [:] +#endif + + } } } diff --git a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift index 59cce9d4c..6f51415f8 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift @@ -26,13 +26,13 @@ public enum OpenGroupAPI { /// - Messages (includes additions and deletions) /// - Inbox for the server /// - Outbox for the server - public static func poll( + public static func preparedPoll( _ db: Database, server: String, hasPerformedInitialPoll: Bool, timeSinceLastPoll: TimeInterval, using dependencies: SMKDependencies = SMKDependencies() - ) -> AnyPublisher<(info: ResponseInfoType, data: [Endpoint: Codable]), Error> { + ) throws -> PreparedSendData { let lastInboxMessageId: Int64 = (try? OpenGroup .select(.inboxLatestMessageId) .filter(OpenGroup.Columns.server == server) @@ -51,26 +51,23 @@ public enum OpenGroupAPI { .asRequest(of: Capability.Variant.self) .fetchSet(db)) .defaulting(to: []) + let openGroupRooms: [OpenGroup] = (try? OpenGroup + .filter(OpenGroup.Columns.server == server.lowercased()) // Note: The `OpenGroup` type converts to lowercase in init + .filter(OpenGroup.Columns.isActive == true) + .filter(OpenGroup.Columns.roomToken != "") + .fetchAll(db)) + .defaulting(to: []) - // Generate the requests - let requestResponseType: [BatchRequest.Info] = [ - BatchRequest.Info( - request: Request( - server: server, - endpoint: .capabilities - ), - responseType: Capabilities.self + let preparedRequests: [ErasedPreparedSendData] = [ + try preparedCapabilities( + db, + server: server, + using: dependencies ) - ] - .appending( + ].appending( // Per-room requests - contentsOf: (try? OpenGroup - .filter(OpenGroup.Columns.server == server.lowercased()) // Note: The `OpenGroup` type converts to lowercase in init - .filter(OpenGroup.Columns.isActive == true) - .filter(OpenGroup.Columns.roomToken != "") - .fetchAll(db)) - .defaulting(to: []) - .flatMap { openGroup -> [BatchRequest.Info] in + contentsOf: try openGroupRooms + .flatMap { openGroup -> [ErasedPreparedSendData] in let shouldRetrieveRecentMessages: Bool = ( openGroup.sequenceNumber == 0 || ( // If it's the first poll for this launch and it's been longer than @@ -82,26 +79,27 @@ public enum OpenGroupAPI { ) return [ - BatchRequest.Info( - request: Request( - server: server, - endpoint: .roomPollInfo(openGroup.roomToken, openGroup.infoUpdates) - ), - responseType: RoomPollInfo.self + try preparedRoomPollInfo( + db, + lastUpdated: openGroup.infoUpdates, + for: openGroup.roomToken, + on: openGroup.server, + using: dependencies ), - BatchRequest.Info( - request: Request( - server: server, - endpoint: (shouldRetrieveRecentMessages ? - .roomMessagesRecent(openGroup.roomToken) : - .roomMessagesSince(openGroup.roomToken, seqNo: openGroup.sequenceNumber) - ), - queryParameters: [ - .updateTypes: UpdateTypes.reaction.rawValue, - .reactors: "5" - ] - ), - responseType: [Failable].self + (shouldRetrieveRecentMessages ? + try preparedRecentMessages( + db, + in: openGroup.roomToken, + on: openGroup.server, + using: dependencies + ) : + try preparedMessagesSince( + db, + seqNo: openGroup.sequenceNumber, + in: openGroup.roomToken, + on: openGroup.server, + using: dependencies + ) ) ] } @@ -112,83 +110,73 @@ public enum OpenGroupAPI { !capabilities.contains(.blind) ? [] : [ // Inbox - BatchRequest.Info( - request: Request( - server: server, - endpoint: (lastInboxMessageId == 0 ? - .inbox : - .inboxSince(id: lastInboxMessageId) - ) - ), - responseType: [DirectMessage]?.self // 'inboxSince' will return a `304` with an empty response if no messages + (lastInboxMessageId == 0 ? + try preparedInbox(db, on: server, using: dependencies) : + try preparedInboxSince(db, id: lastInboxMessageId, on: server, using: dependencies) ), // Outbox - BatchRequest.Info( - request: Request( - server: server, - endpoint: (lastOutboxMessageId == 0 ? - .outbox : - .outboxSince(id: lastOutboxMessageId) - ) - ), - responseType: [DirectMessage]?.self // 'outboxSince' will return a `304` with an empty response if no messages - ) + (lastOutboxMessageId == 0 ? + try preparedOutbox(db, on: server, using: dependencies) : + try preparedOutboxSince(db, id: lastOutboxMessageId, on: server, using: dependencies) + ), ] ) ) - return OpenGroupAPI.batch(db, server: server, requests: requestResponseType, using: dependencies) + return try OpenGroupAPI.preparedBatch( + db, + server: server, + requests: preparedRequests, + using: dependencies + ) } /// Submits multiple requests wrapped up in a single request, runs them all, then returns the result of each one /// - /// Requests are performed independently, that is, if one fails the others will still be attempted - there is no guarantee on the order in which requests will be - /// carried out (for sequential, related requests invoke via `/sequence` instead) + /// Requests are performed independently, that is, if one fails the others will still be attempted - there is no guarantee on the order in which + /// requests will be carried out (for sequential, related requests invoke via `/sequence` instead) /// - /// For contained subrequests that specify a body (i.e. POST or PUT requests) exactly one of `json`, `b64`, or `bytes` must be provided with the request body. - private static func batch( + /// For contained subrequests that specify a body (i.e. POST or PUT requests) exactly one of `json`, `b64`, or `bytes` must be provided + /// with the request body. + private static func preparedBatch( _ db: Database, server: String, - requests: [BatchRequest.Info], + requests: [ErasedPreparedSendData], using dependencies: SMKDependencies = SMKDependencies() - ) -> AnyPublisher<(info: ResponseInfoType, data: [Endpoint: Codable]), Error> { - let responseTypes = requests.map { $0.responseType } - - return OpenGroupAPI - .send( + ) throws -> PreparedSendData { + return try OpenGroupAPI + .prepareSendData( db, request: Request( method: .post, server: server, - endpoint: Endpoint.batch, + endpoint: .batch, body: BatchRequest(requests: requests) ), + responseType: BatchResponse.self, using: dependencies ) - .decoded(as: responseTypes, using: dependencies) - .map(requests: requests, toHashMapFor: Endpoint.self) } - /// This is like `/batch`, except that it guarantees to perform requests sequentially in the order provided and will stop processing requests if the previous request - /// returned a non-`2xx` response + /// This is like `/batch`, except that it guarantees to perform requests sequentially in the order provided and will stop processing requests + /// if the previous request returned a non-`2xx` response /// - /// For example, this can be used to ban and delete all of a user's messages by sequencing the ban followed by the `delete_all`: if the ban fails (e.g. because - /// permission is denied) then the `delete_all` will not occur. The batch body and response are identical to the `/batch` endpoint; requests that are not - /// carried out because of an earlier failure will have a response code of `412` (Precondition Failed)." + /// For example, this can be used to ban and delete all of a user's messages by sequencing the ban followed by the `delete_all`: if the + /// ban fails (e.g. because permission is denied) then the `delete_all` will not occur. The batch body and response are identical to the + /// `/batch` endpoint; requests that are not carried out because of an earlier failure will have a response code of `412` (Precondition Failed)." /// - /// Like `/batch`, responses are returned in the same order as requests, but unlike `/batch` there may be fewer elements in the response list (if requests were - /// stopped because of a non-2xx response) - In such a case, the final, non-2xx response is still included as the final response value - private static func sequence( + /// Like `/batch`, responses are returned in the same order as requests, but unlike `/batch` there may be fewer elements in the response + /// list (if requests were stopped because of a non-2xx response) - In such a case, the final, non-2xx response is still included as the final + /// response value + private static func preparedSequence( _ db: Database, server: String, - requests: [BatchRequest.Info], + requests: [ErasedPreparedSendData], using dependencies: SMKDependencies = SMKDependencies() - ) -> AnyPublisher<(info: ResponseInfoType, data: [Endpoint: Codable]), Error> { - let responseTypes = requests.map { $0.responseType } - - return OpenGroupAPI - .send( + ) throws -> PreparedSendData { + return try OpenGroupAPI + .prepareSendData( db, request: Request( method: .post, @@ -196,18 +184,17 @@ public enum OpenGroupAPI { endpoint: Endpoint.sequence, body: BatchRequest(requests: requests) ), + responseType: BatchResponse.self, using: dependencies ) - .decoded(as: responseTypes, using: dependencies) - .map(requests: requests, toHashMapFor: Endpoint.self) } // MARK: - Capabilities /// Return the list of server features/capabilities /// - /// Optionally takes a `required` parameter containing a comma-separated list of capabilites; if any are not satisfied a 412 (Precondition Failed) response - /// will be returned with missing requested capabilities in the `missing` key + /// Optionally takes a `required` parameter containing a comma-separated list of capabilites; if any are not satisfied a 412 (Precondition Failed) + /// response will be returned with missing requested capabilities in the `missing` key /// /// Eg. `GET /capabilities` could return `{"capabilities": ["sogs", "batch"]}` `GET /capabilities?required=magic,batch` /// could return: `{"capabilities": ["sogs", "batch"], "missing": ["magic"]}` @@ -253,11 +240,6 @@ public enum OpenGroupAPI { } /// Returns the details of a single room - /// - /// **Note:** This is the direct request to retrieve a room so should only be called from either the `poll()` or `joinRoom()` methods, in order to call - /// this directly remove the `@available` line and make sure to route the response of this method to the `OpenGroupManager.handlePollInfo` - /// method to ensure things are processed correctly - @available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead") public static func preparedRoom( _ db: Database, for roomToken: String, @@ -280,11 +262,6 @@ public enum OpenGroupAPI { /// /// The endpoint polls room metadata for this room, always including the instantaneous room details (such as the user's permission and current /// number of active users), and including the full room metadata if the room's info_updated counter has changed from the provided value - /// - /// **Note:** This is the direct request to retrieve room updates so should be retrieved automatically from the `poll()` method, in order to call - /// this directly remove the `@available` line and make sure to route the response of this method to the `OpenGroupManager.handlePollInfo` - /// method to ensure things are processed correctly - @available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead") public static func preparedRoomPollInfo( _ db: Database, lastUpdated: Int64, @@ -305,51 +282,33 @@ public enum OpenGroupAPI { } public typealias CapabilitiesAndRoomResponse = ( - info: ResponseInfoType, - data: ( - capabilities: (info: ResponseInfoType, data: Capabilities), - room: (info: ResponseInfoType, data: Room) - ) + capabilities: (info: ResponseInfoType, data: Capabilities), + room: (info: ResponseInfoType, data: Room) ) /// This is a convenience method which constructs a `/sequence` of the `capabilities` and `room` requests, refer to those /// methods for the documented behaviour of each method - public static func capabilitiesAndRoom( + public static func preparedCapabilitiesAndRoom( _ db: Database, for roomToken: String, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> AnyPublisher { - let requestResponseType: [BatchRequest.Info] = [ - // Get the latest capabilities for the server (in case it's a new server or the cached ones are stale) - BatchRequest.Info( - request: Request( - server: server, - endpoint: .capabilities - ), - responseType: Capabilities.self - ), - - // And the room info - BatchRequest.Info( - request: Request( - server: server, - endpoint: .room(roomToken) - ), - responseType: Room.self - ) - ] - - return OpenGroupAPI - .sequence( + ) throws -> PreparedSendData { + return try OpenGroupAPI + .preparedSequence( db, server: server, - requests: requestResponseType, + requests: [ + // Get the latest capabilities for the server (in case it's a new server or the + // cached ones are stale) + preparedCapabilities(db, server: server, using: dependencies), + preparedRoom(db, for: roomToken, on: server, using: dependencies) + ], using: dependencies ) - .tryMap { (info: ResponseInfoType, data: [Endpoint: Codable]) -> CapabilitiesAndRoomResponse in - let maybeCapabilities: HTTP.BatchSubResponse? = (data[.capabilities] as? HTTP.BatchSubResponse) - let maybeRoomResponse: Codable? = data + .map { (info: ResponseInfoType, response: BatchResponse) -> CapabilitiesAndRoomResponse in + let maybeCapabilities: HTTP.BatchSubResponse? = (response[.capabilities] as? HTTP.BatchSubResponse) + let maybeRoomResponse: Decodable? = response.data .first(where: { key, _ in switch key { case .room: return true @@ -367,53 +326,34 @@ public enum OpenGroupAPI { else { throw HTTPError.parsingFailed } return ( - info: info, - data: ( - capabilities: (info: capabilitiesInfo, data: capabilities), - room: (info: roomInfo, data: room) - ) + capabilities: (info: capabilitiesInfo, data: capabilities), + room: (info: roomInfo, data: room) ) } - .eraseToAnyPublisher() } /// This is a convenience method which constructs a `/sequence` of the `capabilities` and `rooms` requests, refer to those /// methods for the documented behaviour of each method - public static func capabilitiesAndRooms( + public static func preparedCapabilitiesAndRooms( _ db: Database, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> AnyPublisher<(capabilities: (info: ResponseInfoType, data: Capabilities), rooms: (info: ResponseInfoType, data: [Room])), Error> { - let requestResponseType: [BatchRequest.Info] = [ - // Get the latest capabilities for the server (in case it's a new server or the cached ones are stale) - BatchRequest.Info( - request: Request( - server: server, - endpoint: .capabilities - ), - responseType: Capabilities.self - ), - - // And the room info - BatchRequest.Info( - request: Request( - server: server, - endpoint: .rooms - ), - responseType: [Room].self - ) - ] - - return OpenGroupAPI - .sequence( + ) throws -> PreparedSendData<(capabilities: (info: ResponseInfoType, data: Capabilities), rooms: (info: ResponseInfoType, data: [Room]))> { + return try OpenGroupAPI + .preparedSequence( db, server: server, - requests: requestResponseType, + requests: [ + // Get the latest capabilities for the server (in case it's a new server or the + // cached ones are stale) + preparedCapabilities(db, server: server, using: dependencies), + preparedRooms(db, server: server, using: dependencies) + ], using: dependencies ) - .tryMap { (info: ResponseInfoType, data: [Endpoint: Codable]) -> (capabilities: (info: ResponseInfoType, data: Capabilities), rooms: (info: ResponseInfoType, data: [Room])) in - let maybeCapabilities: HTTP.BatchSubResponse? = (data[.capabilities] as? HTTP.BatchSubResponse) - let maybeRooms: HTTP.BatchSubResponse<[Room]>? = data + .map { (info: ResponseInfoType, response: BatchResponse) -> (capabilities: (info: ResponseInfoType, data: Capabilities), rooms: (info: ResponseInfoType, data: [Room])) in + let maybeCapabilities: HTTP.BatchSubResponse? = (response[.capabilities] as? HTTP.BatchSubResponse) + let maybeRooms: HTTP.BatchSubResponse<[Room]>? = response.data .first(where: { key, _ in switch key { case .rooms: return true @@ -434,7 +374,6 @@ public enum OpenGroupAPI { rooms: (info: roomsInfo, data: rooms) ) } - .eraseToAnyPublisher() } // MARK: - Messages @@ -528,6 +467,7 @@ public enum OpenGroupAPI { ) } + /// Remove a message by its message id public static func preparedMessageDelete( _ db: Database, id: Int64, @@ -548,62 +488,75 @@ public enum OpenGroupAPI { ) } - /// **Note:** This is the direct request to retrieve recent messages so should be retrieved automatically from the `poll()` method, in order to call - /// this directly remove the `@available` line and make sure to route the response of this method to the `OpenGroupManager.handleMessages` - /// method to ensure things are processed correctly - @available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead") + /// Retrieves recent messages posted to this room + /// + /// Returns the most recent limit messages (100 if no limit is given). This only returns extant messages, and always returns the latest + /// versions: that is, deleted message indicators and pre-editing versions of messages are not returned. Messages are returned in order + /// from most recent to least recent public static func preparedRecentMessages( _ db: Database, in roomToken: String, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) throws -> PreparedSendData<[Message]> { + ) throws -> PreparedSendData<[Failable]> { return try OpenGroupAPI .prepareSendData( db, request: Request( server: server, - endpoint: .roomMessagesRecent(roomToken) + endpoint: .roomMessagesRecent(roomToken), + queryParameters: [ + .updateTypes: UpdateTypes.reaction.rawValue, + .reactors: "5" + ] ), - responseType: [Message].self, + responseType: [Failable].self, using: dependencies ) } - /// **Note:** This is the direct request to retrieve recent messages before a given message and is currently unused, in order to call this directly - /// remove the `@available` line and make sure to route the response of this method to the `OpenGroupManager.handleMessages` - /// method to ensure things are processed correctly - @available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead") + /// Retrieves messages from the room preceding a given id. + /// + /// This endpoint is intended to be used with .../recent to allow a client to retrieve the most recent messages and then walk backwards + /// through batches of ever-older messages. As with .../recent, messages are returned in order from most recent to least recent. + /// + /// As with .../recent, this endpoint does not include deleted messages and always returns the current version, for edited messages. public static func preparedMessagesBefore( _ db: Database, messageId: Int64, in roomToken: String, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) throws -> PreparedSendData<[Message]> { + ) throws -> PreparedSendData<[Failable]> { return try OpenGroupAPI .prepareSendData( db, request: Request( server: server, - endpoint: .roomMessagesBefore(roomToken, id: messageId) + endpoint: .roomMessagesBefore(roomToken, id: messageId), + queryParameters: [ + .updateTypes: UpdateTypes.reaction.rawValue, + .reactors: "5" + ] ), - responseType: [Message].self, + responseType: [Failable].self, using: dependencies ) } - /// **Note:** This is the direct request to retrieve messages since a given message `seqNo` so should be retrieved automatically from the - /// `poll()` method, in order to call this directly remove the `@available` line and make sure to route the response of this method to the - /// `OpenGroupManager.handleMessages` method to ensure things are processed correctly - @available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead") + /// Retrieves message updates from a room. This is the main message polling endpoint in SOGS. + /// + /// This endpoint retrieves new, edited, and deleted messages or message reactions posted to this room since the given message + /// sequence counter. Returns limit messages at a time (100 if no limit is given). Returned messages include any new messages, updates + /// to existing messages (i.e. edits), and message deletions made to the room since the given update id. Messages are returned in "update" + /// order, that is, in the order in which the change was applied to the room, from oldest the newest. public static func preparedMessagesSince( _ db: Database, seqNo: Int64, in roomToken: String, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) throws -> PreparedSendData<[Message]> { + ) throws -> PreparedSendData<[Failable]> { return try OpenGroupAPI .prepareSendData( db, @@ -612,10 +565,10 @@ public enum OpenGroupAPI { endpoint: .roomMessagesSince(roomToken, seqNo: seqNo), queryParameters: [ .updateTypes: UpdateTypes.reaction.rawValue, - .reactors: "20" + .reactors: "5" ] ), - responseType: [Message].self, + responseType: [Failable].self, using: dependencies ) } @@ -655,6 +608,7 @@ public enum OpenGroupAPI { // MARK: - Reactions + /// Returns the list of all reactors who have added a particular reaction to a particular message. public static func preparedReactors( _ db: Database, emoji: String, @@ -682,6 +636,10 @@ public enum OpenGroupAPI { ) } + /// Adds a reaction to the given message in this room. The user must have read access in the room. + /// + /// Reactions are short strings of 1-12 unicode codepoints, typically emoji (or character sequences to produce an emoji variant, + /// such as 👨🏿‍🦰, which is composed of 4 unicode "characters" but usually renders as a single emoji "Man: Dark Skin Tone, Red Hair"). public static func preparedReactionAdd( _ db: Database, emoji: String, @@ -709,6 +667,8 @@ public enum OpenGroupAPI { ) } + /// Removes a reaction from a post this room. The user must have read access in the room. This only removes the user's own reaction + /// but does not affect the reactions of other users. public static func preparedReactionDelete( _ db: Database, emoji: String, @@ -736,6 +696,9 @@ public enum OpenGroupAPI { ) } + /// Removes all reactions of all users from a post in this room. The calling must have moderator permissions in the room. This endpoint + /// can either remove a single reaction (e.g. remove all 🍆 reactions) by specifying it after the message id (following a /), or remove all + /// reactions from the post by not including the / suffix of the URL. public static func preparedReactionDeleteAll( _ db: Database, emoji: String, @@ -842,6 +805,12 @@ public enum OpenGroupAPI { // MARK: - Files + /// Uploads a file to a room. + /// + /// Takes the request as binary in the body and takes other properties (specifically the suggested filename) via submitted headers. + /// + /// The user must have upload and posting permissions for the room. The file will have a default lifetime of 1 hour, which is extended + /// to 15 days (by default) when a post referencing the uploaded file is posted or edited. public static func preparedUploadFile( _ db: Database, bytes: [UInt8], @@ -871,6 +840,10 @@ public enum OpenGroupAPI { ) } + /// Retrieves a file uploaded to the room. + /// + /// Retrieves a file via its numeric id from the room, returning the file content directly as the binary response body. The file's suggested + /// filename (as provided by the uploader) is provided in the Content-Disposition header, if available. public static func preparedDownloadFile( _ db: Database, fileId: String, @@ -895,10 +868,7 @@ public enum OpenGroupAPI { /// Retrieves all of the user's current DMs (up to limit) /// - /// **Note:** This is the direct request to retrieve DMs for a specific Open Group so should be retrieved automatically from the `poll()` - /// method, in order to call this directly remove the `@available` line and make sure to route the response of this method to the - /// `OpenGroupManager.handleDirectMessages` method to ensure things are processed correctly - @available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead") + /// **Note:** `inbox` will return a `304` with an empty response if no messages (hence the optional return type) public static func preparedInbox( _ db: Database, on server: String, @@ -918,10 +888,7 @@ public enum OpenGroupAPI { /// Polls for any DMs received since the given id, this method will return a `304` with an empty response if there are no messages /// - /// **Note:** This is the direct request to retrieve messages requests for a specific Open Group since a given messages so should be retrieved - /// automatically from the `poll()` method, in order to call this directly remove the `@available` line and make sure to route the response - /// of this method to the `OpenGroupManager.handleDirectMessages` method to ensure things are processed correctly - @available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead") + /// **Note:** `inboxSince` will return a `304` with an empty response if no messages (hence the optional return type) public static func preparedInboxSince( _ db: Database, id: Int64, @@ -968,10 +935,7 @@ public enum OpenGroupAPI { /// Retrieves all of the user's sent DMs (up to limit) /// - /// **Note:** This is the direct request to retrieve DMs sent by the user for a specific Open Group so should be retrieved automatically - /// from the `poll()` method, in order to call this directly remove the `@available` line and make sure to route the response of - /// this method to the `OpenGroupManager.handleDirectMessages` method to ensure things are processed correctly - @available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead") + /// **Note:** `outbox` will return a `304` with an empty response if no messages (hence the optional return type) public static func preparedOutbox( _ db: Database, on server: String, @@ -991,10 +955,7 @@ public enum OpenGroupAPI { /// Polls for any DMs sent since the given id, this method will return a `304` with an empty response if there are no messages /// - /// **Note:** This is the direct request to retrieve messages requests sent by the user for a specific Open Group since a given messages so - /// should be retrieved automatically from the `poll()` method, in order to call this directly remove the `@available` line and make sure - /// to route the response of this method to the `OpenGroupManager.handleDirectMessages` method to ensure things are processed correctly - @available(*, unavailable, message: "Avoid using this directly, use the pre-built `poll()` method instead") + /// **Note:** `outboxSince` will return a `304` with an empty response if no messages (hence the optional return type) public static func preparedOutboxSince( _ db: Database, id: Int64, @@ -1207,52 +1168,35 @@ public enum OpenGroupAPI { /// This is a convenience method which constructs a `/sequence` of the `userBan` and `userDeleteMessages` requests, refer to those /// methods for the documented behaviour of each method - public static func userBanAndDeleteAllMessages( + public static func preparedUserBanAndDeleteAllMessages( _ db: Database, sessionId: String, in roomToken: String, on server: String, using dependencies: SMKDependencies = SMKDependencies() - ) -> AnyPublisher<(info: ResponseInfoType, data: [Endpoint: ResponseInfoType]), Error> { - let banRequestBody: UserBanRequest = UserBanRequest( - rooms: [roomToken], - global: nil, - timeout: nil - ) - - // Generate the requests - let requestResponseType: [BatchRequest.Info] = [ - BatchRequest.Info( - request: Request( - method: .post, - server: server, - endpoint: .userBan(sessionId), - body: banRequestBody - ) - ), - BatchRequest.Info( - request: Request( - method: .delete, - server: server, - endpoint: Endpoint.roomDeleteMessages(roomToken, sessionId: sessionId) - ) - ) - ] - - return OpenGroupAPI - .sequence( + ) throws -> PreparedSendData { + return try OpenGroupAPI + .preparedSequence( db, server: server, - requests: requestResponseType, + requests: [ + preparedUserBan( + db, + sessionId: sessionId, + from: [roomToken], + on: server, + using: dependencies + ), + preparedMessagesDeleteAll( + db, + sessionId: sessionId, + in: roomToken, + on: server, + using: dependencies + ) + ], using: dependencies ) - .map { info, data -> (info: ResponseInfoType, data: [Endpoint: ResponseInfoType]) in - ( - info, - data.compactMapValues { ($0 as? BatchSubResponseType)?.responseInfo } - ) - } - .eraseToAnyPublisher() } // MARK: - Authentication @@ -1388,6 +1332,9 @@ public enum OpenGroupAPI { // MARK: - Convenience + /// Takes the reuqest information and generates a signed `PreparedSendData` pbject which is ready for sending to the API, this + /// method is mainly here so we can separate the preparation of a request, which requires access to the database for signing, from the + /// actual sending of the reuqest to ensure we don't run into any unexpected blocking of the database write thread private static func prepareSendData( _ db: Database, request: Request, @@ -1411,56 +1358,15 @@ public enum OpenGroupAPI { } return PreparedSendData( - request: signedRequest, - endpoint: request.endpoint, - server: request.server, + request: request, + urlRequest: signedRequest, publicKey: publicKey, responseType: responseType, timeout: timeout ) } - private static func send( - _ db: Database, - request: Request, - forceBlinded: Bool = false, - timeout: TimeInterval = HTTP.defaultTimeout, - using dependencies: SMKDependencies = SMKDependencies() - ) -> AnyPublisher<(ResponseInfoType, Data?), Error> { - let urlRequest: URLRequest - - do { - urlRequest = try request.generateUrlRequest() - } - catch { - return Fail(error: error) - .eraseToAnyPublisher() - } - - let maybePublicKey: String? = try? OpenGroup - .select(.publicKey) - .filter(OpenGroup.Columns.server == request.server.lowercased()) - .asRequest(of: String.self) - .fetchOne(db) - - guard let publicKey: String = maybePublicKey else { - return Fail(error: OpenGroupAPIError.noPublicKey) - .eraseToAnyPublisher() - } - - // Attempt to sign the request with the new auth - guard let signedRequest: URLRequest = sign(db, request: urlRequest, for: request.server, with: publicKey, forceBlinded: forceBlinded, using: dependencies) else { - return Fail(error: OpenGroupAPIError.signingFailed) - .eraseToAnyPublisher() - } - - // We want to avoid blocking the db write thread so we dispatch the API call to a different thread - return Just(()) - .setFailureType(to: Error.self) - .flatMap { dependencies.onionApi.sendOnionRequest(signedRequest, to: request.server, with: publicKey, timeout: timeout) } - .eraseToAnyPublisher() - } - + /// This method takes in the `PreparedSendData` and actually sends it to the API public static func send( data: PreparedSendData?, using dependencies: SMKDependencies = SMKDependencies() diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index 08b585bd5..fee25ffba 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -282,13 +282,9 @@ public final class OpenGroupManager { } .flatMap { _ in dependencies.storage - .readPublisherFlatMap { db in - // Note: The initial request for room info and it's capabilities should NOT be - // authenticated (this is because if the server requires blinding and the auth - // headers aren't blinded it will error - these endpoints do support unauthenticated - // retrieval so doing so prevents the error) - OpenGroupAPI - .capabilitiesAndRoom( + .readPublisher { db in + try OpenGroupAPI + .preparedCapabilitiesAndRoom( db, for: roomToken, on: targetServer, @@ -296,7 +292,8 @@ public final class OpenGroupManager { ) } } - .flatMap { response -> Future in + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } + .flatMap { info, response -> Future in Future { resolver in dependencies.storage.write { db in // Add the new open group to libSession @@ -312,14 +309,14 @@ public final class OpenGroupManager { // Store the capabilities first OpenGroupManager.handleCapabilities( db, - capabilities: response.data.capabilities.data, + capabilities: response.capabilities.data, on: targetServer ) // Then the room try OpenGroupManager.handlePollInfo( db, - pollInfo: OpenGroupAPI.RoomPollInfo(room: response.data.room.data), + pollInfo: OpenGroupAPI.RoomPollInfo(room: response.room.data), publicKey: publicKey, for: roomToken, on: targetServer, @@ -1024,17 +1021,18 @@ public final class OpenGroupManager { // Try to retrieve the default rooms 8 times let publisher: AnyPublisher<[OpenGroupAPI.Room], Error> = dependencies.storage - .readPublisherFlatMap { db in - OpenGroupAPI.capabilitiesAndRooms( + .readPublisher { db in + try OpenGroupAPI.preparedCapabilitiesAndRooms( db, on: OpenGroupAPI.defaultServer, using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .subscribe(on: dependencies.subscribeQueue, immediatelyIfMain: true) .receive(on: dependencies.receiveQueue, immediatelyIfMain: true) .retry(8) - .map { response in + .map { info, response in dependencies.storage.writeAsync { db in // Store the capabilities first OpenGroupManager.handleCapabilities( @@ -1204,6 +1202,12 @@ public final class OpenGroupManager { .shareReplay(1) .eraseToAnyPublisher() + // Automatically subscribe for the roomImage download (want to download regardless of + // whether the upstream subscribes) + publisher + .subscribe(on: dependencies.subscribeQueue) + .sinkUntilComplete() + dependencies.mutableCache.mutate { cache in cache.groupImagePublishers[threadId] = publisher } diff --git a/SessionMessagingKit/Open Groups/Types/PreparedSendData.swift b/SessionMessagingKit/Open Groups/Types/PreparedSendData.swift index f2798326f..c8b7c4d00 100644 --- a/SessionMessagingKit/Open Groups/Types/PreparedSendData.swift +++ b/SessionMessagingKit/Open Groups/Types/PreparedSendData.swift @@ -4,28 +4,48 @@ import Foundation import Combine import SessionUtilitiesKit +// MARK: - ErasedPreparedSendData + +public protocol ErasedPreparedSendData { + var endpoint: OpenGroupAPI.Endpoint { get } + var batchResponseTypes: [Decodable.Type] { get } + + func encodeForBatchRequest(to encoder: Encoder) throws +} + +// MARK: - PreparedSendData + public extension OpenGroupAPI { - struct PreparedSendData { + struct PreparedSendData: ErasedPreparedSendData { internal let request: URLRequest - internal let endpoint: Endpoint internal let server: String internal let publicKey: String internal let originalType: Decodable.Type internal let responseType: R.Type internal let timeout: TimeInterval - internal let responseConverter: ((ResponseInfoType, Any) throws -> R) + fileprivate let responseConverter: ((ResponseInfoType, Any) throws -> R) - internal init( - request: URLRequest, - endpoint: Endpoint, - server: String, + // The following types are needed for `BatchRequest` handling + private let method: HTTPMethod + private let path: String + public let endpoint: Endpoint + fileprivate let batchEndpoints: [Endpoint] + public let batchResponseTypes: [Decodable.Type] + + /// The `jsonBodyEncoder` is used to simplify the encoding for `BatchRequest` + private let jsonBodyEncoder: ((inout KeyedEncodingContainer, BatchRequest.Child.CodingKeys) throws -> ())? + private let b64: String? + private let bytes: [UInt8]? + + internal init( + request: Request, + urlRequest: URLRequest, publicKey: String, responseType: R.Type, timeout: TimeInterval ) where R: Decodable { - self.request = request - self.endpoint = endpoint - self.server = server + self.request = urlRequest + self.server = request.server self.publicKey = publicKey self.originalType = responseType self.responseType = responseType @@ -35,26 +55,101 @@ public extension OpenGroupAPI { return validResponse } + + // The following data is needed in this type for handling batch requests + self.method = request.method + self.endpoint = request.endpoint + self.path = request.urlPathAndParamsString + self.batchEndpoints = ((request.body as? BatchRequest)? + .requests + .map { $0.request.endpoint }) + .defaulting(to: []) + self.batchResponseTypes = ((request.body as? BatchRequest)? + .requests + .flatMap { $0.request.batchResponseTypes }) + .defaulting(to: [HTTP.BatchSubResponse.self]) + + // Note: Need to differentiate between JSON, b64 string and bytes body values to ensure + // they are encoded correctly so the server knows how to handle them + switch request.body { + case let bodyString as String: + self.jsonBodyEncoder = nil + self.b64 = bodyString + self.bytes = nil + + case let bodyBytes as [UInt8]: + self.jsonBodyEncoder = nil + self.b64 = nil + self.bytes = bodyBytes + + default: + self.jsonBodyEncoder = { [body = request.body] container, key in + try container.encodeIfPresent(body, forKey: key) + } + self.b64 = nil + self.bytes = nil + } } private init( request: URLRequest, - endpoint: Endpoint, server: String, publicKey: String, originalType: U.Type, responseType: R.Type, timeout: TimeInterval, - responseConverter: @escaping (ResponseInfoType, Any) throws -> R + responseConverter: @escaping (ResponseInfoType, Any) throws -> R, + method: HTTPMethod, + endpoint: Endpoint, + path: String, + batchEndpoints: [Endpoint], + batchResponseTypes: [Decodable.Type], + jsonBodyEncoder: ((inout KeyedEncodingContainer, BatchRequest.Child.CodingKeys) throws -> ())?, + b64: String?, + bytes: [UInt8]? ) { self.request = request - self.endpoint = endpoint self.server = server self.publicKey = publicKey self.originalType = originalType self.responseType = responseType self.timeout = timeout self.responseConverter = responseConverter + + // The following data is needed in this type for handling batch requests + self.method = method + self.endpoint = endpoint + self.path = path + self.batchEndpoints = batchEndpoints + self.batchResponseTypes = batchResponseTypes + self.jsonBodyEncoder = jsonBodyEncoder + self.b64 = b64 + self.bytes = bytes + } + + // MARK: - ErasedPreparedSendData + + public func encodeForBatchRequest(to encoder: Encoder) throws { + var container: KeyedEncodingContainer = encoder.container(keyedBy: BatchRequest.Child.CodingKeys.self) + + // Exclude request signature headers (not used for sub-requests) + let batchRequestHeaders: [String: String] = (request.allHTTPHeaderFields ?? [:]) + .filter { key, _ in + key.lowercased() != HTTPHeader.sogsPubKey.lowercased() && + key.lowercased() != HTTPHeader.sogsTimestamp.lowercased() && + key.lowercased() != HTTPHeader.sogsNonce.lowercased() && + key.lowercased() != HTTPHeader.sogsSignature.lowercased() + } + + if !batchRequestHeaders.isEmpty { + try container.encode(batchRequestHeaders, forKey: .headers) + } + + try container.encode(method, forKey: .method) + try container.encode(path, forKey: .path) + try jsonBodyEncoder?(&container, .json) + try container.encodeIfPresent(b64, forKey: .b64) + try container.encodeIfPresent(bytes, forKey: .bytes) } } } @@ -63,7 +158,6 @@ public extension OpenGroupAPI.PreparedSendData { func map(transform: @escaping (ResponseInfoType, R) throws -> O) -> OpenGroupAPI.PreparedSendData { return OpenGroupAPI.PreparedSendData( request: request, - endpoint: endpoint, server: server, publicKey: publicKey, originalType: originalType, @@ -73,7 +167,15 @@ public extension OpenGroupAPI.PreparedSendData { let validResponse: R = try responseConverter(info, response) return try transform(info, validResponse) - } + }, + method: method, + endpoint: endpoint, + path: path, + batchEndpoints: batchEndpoints, + batchResponseTypes: batchResponseTypes, + jsonBodyEncoder: jsonBodyEncoder, + b64: b64, + bytes: bytes ) } } @@ -90,6 +192,22 @@ public extension Publisher where Output == (ResponseInfoType, Data?), Failure == // Depending on the 'originalType' we need to process the response differently let targetData: Any = try { switch preparedData.originalType { + case is OpenGroupAPI.BatchResponse.Type: + let responses: [Decodable] = try HTTP.BatchResponse.decodingResponses( + from: maybeData, + as: preparedData.batchResponseTypes, + requireAllResults: true, + using: dependencies + ) + + return OpenGroupAPI.BatchResponse( + info: responseInfo, + data: Swift.zip(preparedData.batchEndpoints, responses) + .reduce(into: [:]) { result, next in + result[next.0] = next.1 + } + ) + case is NoResponse.Type: return NoResponse() case is Optional.Type: return maybeData as Any case is Data.Type: return try maybeData ?? { throw HTTPError.parsingFailed }() diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift index 975a31770..873319035 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift @@ -8,7 +8,7 @@ import SessionUtilitiesKit extension OpenGroupAPI { public final class Poller { - typealias PollResponse = (info: ResponseInfoType, data: [OpenGroupAPI.Endpoint: Codable]) + typealias PollResponse = (info: ResponseInfoType, data: [OpenGroupAPI.Endpoint: Decodable]) private let server: String private var timer: Timer? = nil @@ -122,7 +122,7 @@ extension OpenGroupAPI { let server: String = self.server return dependencies.storage - .readPublisherFlatMap { db -> AnyPublisher<(Int64, PollResponse), Error> in + .readPublisher { db -> (Int64, PreparedSendData) in let failureCount: Int64 = (try? OpenGroup .filter(OpenGroup.Columns.server == server) .select(max(OpenGroup.Columns.pollFailureCount)) @@ -130,22 +130,27 @@ extension OpenGroupAPI { .fetchOne(db)) .defaulting(to: 0) - return OpenGroupAPI - .poll( - db, - server: server, - hasPerformedInitialPoll: dependencies.cache.hasPerformedInitialPoll[server] == true, - timeSinceLastPoll: ( - dependencies.cache.timeSinceLastPoll[server] ?? - dependencies.cache.getTimeSinceLastOpen(using: dependencies) - ), - using: dependencies - ) - .map { response in (failureCount, response) } - .eraseToAnyPublisher() + return ( + failureCount, + try OpenGroupAPI + .preparedPoll( + db, + server: server, + hasPerformedInitialPoll: dependencies.cache.hasPerformedInitialPoll[server] == true, + timeSinceLastPoll: ( + dependencies.cache.timeSinceLastPoll[server] ?? + dependencies.cache.getTimeSinceLastOpen(using: dependencies) + ), + using: dependencies + ) + ) + } + .flatMap { failureCount, sendData in + OpenGroupAPI.send(data: sendData, using: dependencies) + .map { info, response in (failureCount, info, response) } } .handleEvents( - receiveOutput: { [weak self] failureCount, response in + receiveOutput: { [weak self] failureCount, info, response in guard !calledFromBackgroundPoller || isBackgroundPollerValid() else { // If this was a background poll and the background poll is no longer valid // then just stop @@ -155,7 +160,8 @@ extension OpenGroupAPI { self?.isPolling = false self?.handlePollResponse( - response, + info: info, + response: response, failureCount: failureCount, using: dependencies ) @@ -363,12 +369,13 @@ extension OpenGroupAPI { } private func handlePollResponse( - _ response: PollResponse, + info: ResponseInfoType, + response: BatchResponse, failureCount: Int64, using dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies() ) { let server: String = self.server - let validResponses: [OpenGroupAPI.Endpoint: Codable] = response.data + let validResponses: [OpenGroupAPI.Endpoint: Decodable] = response.data .filter { endpoint, data in switch endpoint { case .capabilities: @@ -467,7 +474,7 @@ extension OpenGroupAPI { return (capabilities, groups) } - let changedResponses: [OpenGroupAPI.Endpoint: Codable] = validResponses + let changedResponses: [OpenGroupAPI.Endpoint: Decodable] = validResponses .filter { endpoint, data in switch endpoint { case .capabilities: diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift index f3d07f91d..9487b7b68 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift @@ -287,6 +287,12 @@ internal extension SessionUtil { contact.approved_me = updatedContact.didApproveMe contact.blocked = updatedContact.isBlocked + // If we were given a `created` timestamp then set it to the min between the current + // setting and the value (as long as the current setting isn't `0`) + if let created: Int64 = info.created.map({ Int64(floor($0)) }) { + contact.created = (contact.created > 0 ? min(contact.created, created) : created) + } + // Store the updated contact (needs to happen before variables go out of scope) contacts_set(conf, &contact) } @@ -494,17 +500,20 @@ extension SessionUtil { let contact: Contact? let profile: Profile? let priority: Int32? + let created: TimeInterval? init( id: String, contact: Contact? = nil, profile: Profile? = nil, - priority: Int32? = nil + priority: Int32? = nil, + created: TimeInterval? = nil ) { self.id = id self.contact = contact self.profile = profile self.priority = priority + self.created = created } } } diff --git a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift index c11af914e..5fd4f3857 100644 --- a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift +++ b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift @@ -1111,8 +1111,8 @@ public extension SessionThreadViewModel { /// Step 1 - Keep any "quoted" sections as stand-alone search /// Step 2 - Separate any words outside of quotes /// Step 3 - Join the different search term parts with 'OR" (include results for each individual term) - /// Step 4 - Append a wild-card character to the final word - return searchTerm + /// Step 4 - Append a wild-card character to the final word (as long as the last word doesn't end in a quote) + return standardQuotes(searchTerm) .split(separator: "\"") .enumerated() .flatMap { index, value -> [String] in @@ -1127,6 +1127,13 @@ public extension SessionThreadViewModel { .filter { !$0.isEmpty } } + static func standardQuotes(_ term: String) -> String { + // Apple like to use the special '”“' quote characters when typing so replace them with normal ones + return term + .replacingOccurrences(of: "”", with: "\"") + .replacingOccurrences(of: "“", with: "\"") + } + static func pattern(_ db: Database, searchTerm: String) throws -> FTS5Pattern { return try pattern(db, searchTerm: searchTerm, forTable: Interaction.self) } @@ -1134,9 +1141,16 @@ public extension SessionThreadViewModel { static func pattern(_ db: Database, searchTerm: String, forTable table: T.Type) throws -> FTS5Pattern where T: TableRecord, T: ColumnExpressible { // Note: FTS doesn't support both prefix/suffix wild cards so don't bother trying to // add a prefix one - let rawPattern: String = searchTermParts(searchTerm) - .joined(separator: " OR ") - .appending("*") + let rawPattern: String = { + let result: String = searchTermParts(searchTerm) + .joined(separator: " OR ") + + // If the last character is a quotation mark then assume the user doesn't want to append + // a wildcard character + guard !standardQuotes(searchTerm).hasSuffix("\"") else { return result } + + return "\(result)*" + }() let fallbackTerm: String = "\(searchSafeTerm(searchTerm))*" /// There are cases where creating a pattern can fail, we want to try and recover from those cases diff --git a/SessionMessagingKitTests/Open Groups/Models/BatchRequestInfoSpec.swift b/SessionMessagingKitTests/Open Groups/Models/BatchRequestInfoSpec.swift index c6ea98995..345b3044d 100644 --- a/SessionMessagingKitTests/Open Groups/Models/BatchRequestInfoSpec.swift +++ b/SessionMessagingKitTests/Open Groups/Models/BatchRequestInfoSpec.swift @@ -24,47 +24,11 @@ class BatchRequestInfoSpec: QuickSpec { describe("a BatchRequest.Child") { var request: OpenGroupAPI.BatchRequest! - context("when initializing") { - it("sets the headers to nil if there aren't any") { - request = OpenGroupAPI.BatchRequest( - requests: [ - OpenGroupAPI.BatchRequest.Info( - request: Request( - server: "testServer", - endpoint: .batch - ) - ) - ] - ) - - expect(request.requests.first?.headers).to(beNil()) - } - - it("converts the headers to HTTP headers") { - request = OpenGroupAPI.BatchRequest( - requests: [ - OpenGroupAPI.BatchRequest.Info( - request: Request( - method: .get, - server: "testServer", - endpoint: .batch, - queryParameters: [:], - headers: [.authorization: "testAuth"], - body: nil - ) - ) - ] - ) - - expect(request.requests.first?.headers).to(equal(["Authorization": "testAuth"])) - } - } - context("when encoding") { it("successfully encodes a string body") { request = OpenGroupAPI.BatchRequest( requests: [ - OpenGroupAPI.BatchRequest.Info( + OpenGroupAPI.PreparedSendData( request: Request( method: .get, server: "testServer", @@ -72,21 +36,25 @@ class BatchRequestInfoSpec: QuickSpec { queryParameters: [:], headers: [:], body: "testBody" - ) + ), + urlRequest: URLRequest(url: URL(string: "https://www.oxen.io")!), + publicKey: "", + responseType: NoResponse.self, + timeout: 0 ) ] ) - let childRequestData: Data = try! JSONEncoder().encode(request.requests[0]) - let childRequestString: String? = String(data: childRequestData, encoding: .utf8) + let requestData: Data = try! JSONEncoder().encode(request) + let requestString: String? = String(data: requestData, encoding: .utf8) - expect(childRequestString) - .to(equal("{\"path\":\"\\/batch\",\"method\":\"GET\",\"b64\":\"testBody\"}")) + expect(requestString) + .to(equal("[{\"path\":\"\\/batch\",\"method\":\"GET\",\"b64\":\"testBody\"}]")) } it("successfully encodes a byte body") { request = OpenGroupAPI.BatchRequest( requests: [ - OpenGroupAPI.BatchRequest.Info( + OpenGroupAPI.PreparedSendData( request: Request<[UInt8], OpenGroupAPI.Endpoint>( method: .get, server: "testServer", @@ -94,21 +62,25 @@ class BatchRequestInfoSpec: QuickSpec { queryParameters: [:], headers: [:], body: [1, 2, 3] - ) + ), + urlRequest: URLRequest(url: URL(string: "https://www.oxen.io")!), + publicKey: "", + responseType: NoResponse.self, + timeout: 0 ) ] ) - let childRequestData: Data = try! JSONEncoder().encode(request.requests[0]) - let childRequestString: String? = String(data: childRequestData, encoding: .utf8) + let requestData: Data = try! JSONEncoder().encode(request) + let requestString: String? = String(data: requestData, encoding: .utf8) - expect(childRequestString) - .to(equal("{\"path\":\"\\/batch\",\"method\":\"GET\",\"bytes\":[1,2,3]}")) + expect(requestString) + .to(equal("[{\"path\":\"\\/batch\",\"method\":\"GET\",\"bytes\":[1,2,3]}]")) } it("successfully encodes a JSON body") { request = OpenGroupAPI.BatchRequest( requests: [ - OpenGroupAPI.BatchRequest.Info( + OpenGroupAPI.PreparedSendData( request: Request( method: .get, server: "testServer", @@ -116,64 +88,93 @@ class BatchRequestInfoSpec: QuickSpec { queryParameters: [:], headers: [:], body: TestType(stringValue: "testValue") - ) + ), + urlRequest: URLRequest(url: URL(string: "https://www.oxen.io")!), + publicKey: "", + responseType: NoResponse.self, + timeout: 0 ) ] ) - let childRequestData: Data = try! JSONEncoder().encode(request.requests[0]) - let childRequestString: String? = String(data: childRequestData, encoding: .utf8) + let requestData: Data = try! JSONEncoder().encode(request) + let requestString: String? = String(data: requestData, encoding: .utf8) - expect(childRequestString) - .to(equal("{\"path\":\"\\/batch\",\"method\":\"GET\",\"json\":{\"stringValue\":\"testValue\"}}")) + expect(requestString) + .to(equal("[{\"path\":\"\\/batch\",\"method\":\"GET\",\"json\":{\"stringValue\":\"testValue\"}}]")) + } + + it("strips authentication headers") { + let httpRequest: Request = Request( + method: .get, + server: "testServer", + endpoint: .batch, + queryParameters: [:], + headers: [ + "TestHeader": "Test", + HTTPHeader.sogsPubKey: "A", + HTTPHeader.sogsTimestamp: "B", + HTTPHeader.sogsNonce: "C", + HTTPHeader.sogsSignature: "D" + ], + body: nil + ) + request = OpenGroupAPI.BatchRequest( + requests: [ + OpenGroupAPI.PreparedSendData( + request: httpRequest, + urlRequest: try! httpRequest.generateUrlRequest(), + publicKey: "", + responseType: NoResponse.self, + timeout: 0 + ) + ] + ) + + let requestData: Data = try! JSONEncoder().encode(request) + let requestString: String? = String(data: requestData, encoding: .utf8) + + expect(requestString) + .toNot(contain([ + HTTPHeader.sogsPubKey, + HTTPHeader.sogsTimestamp, + HTTPHeader.sogsNonce, + HTTPHeader.sogsSignature + ])) } } - } - - // MARK: - BatchRequest.Info - - describe("a BatchRequest.Info") { - var request: Request! - beforeEach { - request = Request( + it("does not strip non authentication headers") { + let httpRequest: Request = Request( method: .get, server: "testServer", endpoint: .batch, queryParameters: [:], - headers: [:], - body: TestType(stringValue: "testValue") + headers: [ + "TestHeader": "Test", + HTTPHeader.sogsPubKey: "A", + HTTPHeader.sogsTimestamp: "B", + HTTPHeader.sogsNonce: "C", + HTTPHeader.sogsSignature: "D" + ], + body: nil ) - } - - it("initializes correctly when given a request") { - let requestInfo: OpenGroupAPI.BatchRequest.Info = OpenGroupAPI.BatchRequest.Info( - request: request + request = OpenGroupAPI.BatchRequest( + requests: [ + OpenGroupAPI.PreparedSendData( + request: httpRequest, + urlRequest: try! httpRequest.generateUrlRequest(), + publicKey: "", + responseType: NoResponse.self, + timeout: 0 + ) + ] ) - expect(requestInfo.endpoint.path).to(equal(request.endpoint.path)) - expect(requestInfo.responseType == HTTP.BatchSubResponse.self).to(beTrue()) - } - - it("initializes correctly when given a request and a response type") { - let requestInfo: OpenGroupAPI.BatchRequest.Info = OpenGroupAPI.BatchRequest.Info( - request: request, - responseType: TestType.self - ) + let requestData: Data = try! JSONEncoder().encode(request) + let requestString: String? = String(data: requestData, encoding: .utf8) - expect(requestInfo.endpoint.path).to(equal(request.endpoint.path)) - expect(requestInfo.responseType == HTTP.BatchSubResponse.self).to(beTrue()) - } - } - - // MARK: - Convenience - // MARK: --Decodable - - describe("a Decodable") { - it("decodes correctly") { - let jsonData: Data = "{\"stringValue\":\"testValue\"}".data(using: .utf8)! - let result: TestType? = try? TestType.decoded(from: jsonData) - - expect(result).to(equal(TestType(stringValue: "testValue"))) + expect(requestString) + .to(contain("\"TestHeader\":\"Test\"")) } } } diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift b/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift index 0697d65a1..c74bf5c2c 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift +++ b/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift @@ -28,7 +28,7 @@ class OpenGroupAPISpec: QuickSpec { var disposables: [AnyCancellable] = [] var response: (ResponseInfoType, Codable)? = nil - var pollResponse: (info: ResponseInfoType, data: [OpenGroupAPI.Endpoint: Codable])? + var pollResponse: (info: ResponseInfoType, data: OpenGroupAPI.BatchResponse)? var error: Error? describe("an OpenGroupAPI") { @@ -186,8 +186,8 @@ class OpenGroupAPISpec: QuickSpec { it("generates the correct request") { mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI.poll( + .readPublisher { db in + try OpenGroupAPI.preparedPoll( db, server: "testserver", hasPerformedInitialPoll: false, @@ -195,6 +195,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in pollResponse = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -221,8 +222,8 @@ class OpenGroupAPISpec: QuickSpec { it("retrieves recent messages if there was no last message") { mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI.poll( + .readPublisher { db in + try OpenGroupAPI.preparedPoll( db, server: "testserver", hasPerformedInitialPoll: false, @@ -230,6 +231,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in pollResponse = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -250,8 +252,8 @@ class OpenGroupAPISpec: QuickSpec { } mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI.poll( + .readPublisher { db in + try OpenGroupAPI.preparedPoll( db, server: "testserver", hasPerformedInitialPoll: false, @@ -259,6 +261,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in pollResponse = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -279,8 +282,8 @@ class OpenGroupAPISpec: QuickSpec { } mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI.poll( + .readPublisher { db in + try OpenGroupAPI.preparedPoll( db, server: "testserver", hasPerformedInitialPoll: false, @@ -288,6 +291,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in pollResponse = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -308,8 +312,8 @@ class OpenGroupAPISpec: QuickSpec { } mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI.poll( + .readPublisher { db in + try OpenGroupAPI.preparedPoll( db, server: "testserver", hasPerformedInitialPoll: true, @@ -317,6 +321,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in pollResponse = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -340,8 +345,8 @@ class OpenGroupAPISpec: QuickSpec { it("does not call the inbox and outbox endpoints") { mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI.poll( + .readPublisher { db in + try OpenGroupAPI.preparedPoll( db, server: "testserver", hasPerformedInitialPoll: false, @@ -349,6 +354,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in pollResponse = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -439,8 +445,8 @@ class OpenGroupAPISpec: QuickSpec { it("includes the inbox and outbox endpoints") { mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI.poll( + .readPublisher { db in + try OpenGroupAPI.preparedPoll( db, server: "testserver", hasPerformedInitialPoll: false, @@ -448,6 +454,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in pollResponse = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -466,8 +473,8 @@ class OpenGroupAPISpec: QuickSpec { it("retrieves recent inbox messages if there was no last message") { mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI.poll( + .readPublisher { db in + try OpenGroupAPI.preparedPoll( db, server: "testserver", hasPerformedInitialPoll: true, @@ -475,6 +482,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in pollResponse = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -495,8 +503,8 @@ class OpenGroupAPISpec: QuickSpec { } mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI.poll( + .readPublisher { db in + try OpenGroupAPI.preparedPoll( db, server: "testserver", hasPerformedInitialPoll: true, @@ -504,6 +512,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in pollResponse = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -519,8 +528,8 @@ class OpenGroupAPISpec: QuickSpec { it("retrieves recent outbox messages if there was no last message") { mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI.poll( + .readPublisher { db in + try OpenGroupAPI.preparedPoll( db, server: "testserver", hasPerformedInitialPoll: true, @@ -528,6 +537,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in pollResponse = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -548,8 +558,8 @@ class OpenGroupAPISpec: QuickSpec { } mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI.poll( + .readPublisher { db in + try OpenGroupAPI.preparedPoll( db, server: "testserver", hasPerformedInitialPoll: true, @@ -557,6 +567,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in pollResponse = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -609,8 +620,8 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI.poll( + .readPublisher { db in + try OpenGroupAPI.preparedPoll( db, server: "testserver", hasPerformedInitialPoll: false, @@ -618,6 +629,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in pollResponse = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -639,8 +651,8 @@ class OpenGroupAPISpec: QuickSpec { it("errors when no data is returned") { mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI.poll( + .readPublisher { db in + try OpenGroupAPI.preparedPoll( db, server: "testserver", hasPerformedInitialPoll: false, @@ -648,6 +660,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in pollResponse = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -668,8 +681,8 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI.poll( + .readPublisher { db in + try OpenGroupAPI.preparedPoll( db, server: "testserver", hasPerformedInitialPoll: false, @@ -677,6 +690,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in pollResponse = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -697,8 +711,8 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI.poll( + .readPublisher { db in + try OpenGroupAPI.preparedPoll( db, server: "testserver", hasPerformedInitialPoll: false, @@ -706,6 +720,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in pollResponse = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -726,8 +741,8 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI.poll( + .readPublisher { db in + try OpenGroupAPI.preparedPoll( db, server: "testserver", hasPerformedInitialPoll: false, @@ -735,6 +750,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in pollResponse = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -787,8 +803,8 @@ class OpenGroupAPISpec: QuickSpec { dependencies = dependencies.with(onionApi: TestApi.self) mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI.poll( + .readPublisher { db in + try OpenGroupAPI.preparedPoll( db, server: "testserver", hasPerformedInitialPoll: false, @@ -796,6 +812,7 @@ class OpenGroupAPISpec: QuickSpec { using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in pollResponse = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -985,17 +1002,18 @@ class OpenGroupAPISpec: QuickSpec { } dependencies = dependencies.with(onionApi: TestApi.self) - var response: OpenGroupAPI.CapabilitiesAndRoomResponse? + var response: (info: ResponseInfoType, data: OpenGroupAPI.CapabilitiesAndRoomResponse)? mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI.capabilitiesAndRoom( + .readPublisher { db in + try OpenGroupAPI.preparedCapabilitiesAndRoom( db, for: "testRoom", on: "testserver", using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -1040,18 +1058,18 @@ class OpenGroupAPISpec: QuickSpec { } dependencies = dependencies.with(onionApi: TestApi.self) - var response: OpenGroupAPI.CapabilitiesAndRoomResponse? + var response: (info: ResponseInfoType, data: OpenGroupAPI.CapabilitiesAndRoomResponse)? mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .capabilitiesAndRoom( - db, - for: "testRoom", - on: "testserver", - using: dependencies - ) + .readPublisher { db in + try OpenGroupAPI.preparedCapabilitiesAndRoom( + db, + for: "testRoom", + on: "testserver", + using: dependencies + ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -1112,18 +1130,18 @@ class OpenGroupAPISpec: QuickSpec { } dependencies = dependencies.with(onionApi: TestApi.self) - var response: OpenGroupAPI.CapabilitiesAndRoomResponse? + var response: (info: ResponseInfoType, data: OpenGroupAPI.CapabilitiesAndRoomResponse)? mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .capabilitiesAndRoom( - db, - for: "testRoom", - on: "testserver", - using: dependencies - ) + .readPublisher { db in + try OpenGroupAPI.preparedCapabilitiesAndRoom( + db, + for: "testRoom", + on: "testserver", + using: dependencies + ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -1201,17 +1219,18 @@ class OpenGroupAPISpec: QuickSpec { } dependencies = dependencies.with(onionApi: TestApi.self) - var response: OpenGroupAPI.CapabilitiesAndRoomResponse? + var response: (info: ResponseInfoType, data: OpenGroupAPI.CapabilitiesAndRoomResponse)? mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI.capabilitiesAndRoom( + .readPublisher { db in + try OpenGroupAPI.preparedCapabilitiesAndRoom( db, for: "testRoom", on: "testserver", using: dependencies ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -2809,7 +2828,7 @@ class OpenGroupAPISpec: QuickSpec { } context("when banning and deleting all messages for a user") { - var response: (info: ResponseInfoType, data: [OpenGroupAPI.Endpoint: ResponseInfoType])? + var response: (info: ResponseInfoType, data: OpenGroupAPI.BatchResponse)? beforeEach { class TestApi: TestOnionRequestAPI { @@ -2845,16 +2864,16 @@ class OpenGroupAPISpec: QuickSpec { it("generates the request and handles the response correctly") { mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .userBanAndDeleteAllMessages( - db, - sessionId: "testUserId", - in: "testRoom", - on: "testserver", - using: dependencies - ) + .readPublisher { db in + try OpenGroupAPI.preparedUserBanAndDeleteAllMessages( + db, + sessionId: "testUserId", + in: "testRoom", + on: "testserver", + using: dependencies + ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) @@ -2874,16 +2893,16 @@ class OpenGroupAPISpec: QuickSpec { it("bans the user from the specified room rather than globally") { mockStorage - .readPublisherFlatMap { db in - OpenGroupAPI - .userBanAndDeleteAllMessages( - db, - sessionId: "testUserId", - in: "testRoom", - on: "testserver", - using: dependencies - ) + .readPublisher { db in + try OpenGroupAPI.preparedUserBanAndDeleteAllMessages( + db, + sessionId: "testUserId", + in: "testRoom", + on: "testserver", + using: dependencies + ) } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) diff --git a/SessionSnodeKit/Models/SnodeBatchRequest.swift b/SessionSnodeKit/Models/SnodeBatchRequest.swift index 92c88ef4c..7824ca44b 100644 --- a/SessionSnodeKit/Models/SnodeBatchRequest.swift +++ b/SessionSnodeKit/Models/SnodeBatchRequest.swift @@ -14,7 +14,7 @@ internal extension SnodeAPI { // MARK: - BatchRequest.Info struct Info { - public let responseType: Codable.Type + public let responseType: Decodable.Type fileprivate let child: Child public init(request: SnodeRequest, responseType: R.Type) { diff --git a/SessionUtilitiesKit/Crypto/CryptoKit+Utilities.swift b/SessionUtilitiesKit/Crypto/CryptoKit+Utilities.swift index c3af90b73..3967e9eed 100644 --- a/SessionUtilitiesKit/Crypto/CryptoKit+Utilities.swift +++ b/SessionUtilitiesKit/Crypto/CryptoKit+Utilities.swift @@ -38,11 +38,11 @@ public extension AES.GCM { /// - Note: Sync. Don't call from the main thread. static func generateSymmetricKey(x25519PublicKey: Data, x25519PrivateKey: Data) throws -> Data { + #if DEBUG if Thread.isMainThread { - #if DEBUG preconditionFailure("It's illegal to call encrypt(_:forSnode:) from the main thread.") - #endif } + #endif guard let sharedSecret: Data = try? Curve25519.generateSharedSecret(fromPublicKey: x25519PublicKey, privateKey: x25519PrivateKey) else { throw Error.sharedSecretGenerationFailed } @@ -58,11 +58,11 @@ public extension AES.GCM { /// - Note: Sync. Don't call from the main thread. static func decrypt(_ nonceAndCiphertext: Data, with symmetricKey: Data) throws -> Data { + #if DEBUG if Thread.isMainThread { - #if DEBUG preconditionFailure("It's illegal to call decrypt(_:usingAESGCMWithSymmetricKey:) from the main thread.") - #endif } + #endif return try AES.GCM.open( try AES.GCM.SealedBox(combined: nonceAndCiphertext), @@ -72,11 +72,11 @@ public extension AES.GCM { /// - Note: Sync. Don't call from the main thread. static func encrypt(_ plaintext: Data, with symmetricKey: Data) throws -> Data { + #if DEBUG if Thread.isMainThread { - #if DEBUG preconditionFailure("It's illegal to call encrypt(_:usingAESGCMWithSymmetricKey:) from the main thread.") - #endif } + #endif let nonceData: Data = try Randomness.generateRandomBytes(numberBytes: ivSize) let sealedData: AES.GCM.SealedBox = try AES.GCM.seal( @@ -94,11 +94,11 @@ public extension AES.GCM { /// - Note: Sync. Don't call from the main thread. static func encrypt(_ plaintext: Data, for hexEncodedX25519PublicKey: String) throws -> EncryptionResult { + #if DEBUG if Thread.isMainThread { - #if DEBUG preconditionFailure("It's illegal to call encrypt(_:forSnode:) from the main thread.") - #endif } + #endif let x25519PublicKey = Data(hex: hexEncodedX25519PublicKey) let ephemeralKeyPair = Curve25519.generateKeyPair() let symmetricKey = try generateSymmetricKey(x25519PublicKey: x25519PublicKey, x25519PrivateKey: ephemeralKeyPair.privateKey) diff --git a/SessionUtilitiesKit/Networking/BatchResponse.swift b/SessionUtilitiesKit/Networking/BatchResponse.swift index 4b0e244e8..600b82982 100644 --- a/SessionUtilitiesKit/Networking/BatchResponse.swift +++ b/SessionUtilitiesKit/Networking/BatchResponse.swift @@ -4,18 +4,63 @@ import Foundation import Combine public extension HTTP { - typealias BatchResponseTypes = [Codable.Type] - // MARK: - BatchResponse struct BatchResponse { public let info: ResponseInfoType - public let responses: [Codable] + public let responses: [Decodable] + + public static func decodingResponses( + from data: Data?, + as types: [Decodable.Type], + requireAllResults: Bool, + using dependencies: Dependencies = Dependencies() + ) throws -> [Decodable] { + // Need to split the data into an array of data so each item can be Decoded correctly + guard let data: Data = data else { throw HTTPError.parsingFailed } + guard let jsonObject: Any = try? JSONSerialization.jsonObject(with: data, options: [.fragmentsAllowed]) else { + throw HTTPError.parsingFailed + } + + let dataArray: [Data] + + switch jsonObject { + case let anyArray as [Any]: + dataArray = anyArray.compactMap { try? JSONSerialization.data(withJSONObject: $0) } + + guard !requireAllResults || dataArray.count == types.count else { + throw HTTPError.parsingFailed + } + + case let anyDict as [String: Any]: + guard + let resultsArray: [Data] = (anyDict["results"] as? [Any])? + .compactMap({ try? JSONSerialization.data(withJSONObject: $0) }), + ( + !requireAllResults || + resultsArray.count == types.count + ) + else { throw HTTPError.parsingFailed } + + dataArray = resultsArray + + default: throw HTTPError.parsingFailed + } + + return try zip(dataArray, types) + .map { data, type in try type.decoded(from: data, using: dependencies) } + } } // MARK: - BatchSubResponse - struct BatchSubResponse: BatchSubResponseType { + struct BatchSubResponse: BatchSubResponseType { + public enum CodingKeys: String, CodingKey { + case code + case headers + case body + } + /// The numeric http response code (e.g. 200 for success) public let code: Int @@ -42,7 +87,7 @@ public extension HTTP { } } -public protocol BatchSubResponseType: Codable { +public protocol BatchSubResponseType: Decodable { var code: Int { get } var headers: [String: String] { get } var failedToParseBody: Bool { get } @@ -52,6 +97,8 @@ extension BatchSubResponseType { public var responseInfo: ResponseInfoType { HTTP.ResponseInfo(code: code, headers: headers) } } +extension HTTP.BatchSubResponse: Encodable where T: Encodable {} + public extension HTTP.BatchSubResponse { init(from decoder: Decoder) throws { let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) @@ -80,48 +127,20 @@ public extension Decodable { public extension Publisher where Output == (ResponseInfoType, Data?), Failure == Error { func decoded( - as types: HTTP.BatchResponseTypes, + as types: [Decodable.Type], requireAllResults: Bool = true, using dependencies: Dependencies = Dependencies() ) -> AnyPublisher { self .tryMap { responseInfo, maybeData -> HTTP.BatchResponse in - // Need to split the data into an array of data so each item can be Decoded correctly - guard let data: Data = maybeData else { throw HTTPError.parsingFailed } - guard let jsonObject: Any = try? JSONSerialization.jsonObject(with: data, options: [.fragmentsAllowed]) else { - throw HTTPError.parsingFailed - } - - let dataArray: [Data] - - switch jsonObject { - case let anyArray as [Any]: - dataArray = anyArray.compactMap { try? JSONSerialization.data(withJSONObject: $0) } - - guard !requireAllResults || dataArray.count == types.count else { - throw HTTPError.parsingFailed - } - - case let anyDict as [String: Any]: - guard - let resultsArray: [Data] = (anyDict["results"] as? [Any])? - .compactMap({ try? JSONSerialization.data(withJSONObject: $0) }), - ( - !requireAllResults || - resultsArray.count == types.count - ) - else { throw HTTPError.parsingFailed } - - dataArray = resultsArray - - default: throw HTTPError.parsingFailed - } - - // TODO: Remove the 'Swift.' - return HTTP.BatchResponse( + HTTP.BatchResponse( info: responseInfo, - responses: try Swift.zip(dataArray, types) - .map { data, type in try type.decoded(from: data, using: dependencies) } + responses: try HTTP.BatchResponse.decodingResponses( + from: maybeData, + as: types, + requireAllResults: requireAllResults, + using: dependencies + ) ) } .eraseToAnyPublisher() diff --git a/SessionUtilitiesKit/Utilities/Version.swift b/SessionUtilitiesKit/Utilities/Version.swift index 38daf00bf..dadc37c04 100644 --- a/SessionUtilitiesKit/Utilities/Version.swift +++ b/SessionUtilitiesKit/Utilities/Version.swift @@ -47,8 +47,8 @@ public struct Version: Comparable { } public static func < (lhs: Version, rhs: Version) -> Bool { - guard lhs.major >= rhs.major else { return true } - guard lhs.minor >= rhs.minor else { return true } + guard lhs.major == rhs.major else { return (lhs.major < rhs.major) } + guard lhs.minor == rhs.minor else { return (lhs.minor < rhs.minor) } return (lhs.patch < rhs.patch) } diff --git a/SessionUtilitiesKitTests/Utilities/VersionSpec.swift b/SessionUtilitiesKitTests/Utilities/VersionSpec.swift index 2d46e2f3d..55c25ba4d 100644 --- a/SessionUtilitiesKitTests/Utilities/VersionSpec.swift +++ b/SessionUtilitiesKitTests/Utilities/VersionSpec.swift @@ -54,11 +54,15 @@ class VersionSpec: QuickSpec { } it("returns correctly for a complex major difference") { - let version1: Version = Version.from("2.90.90") - let version2: Version = Version.from("10.0.0") + let version1a: Version = Version.from("2.90.90") + let version2a: Version = Version.from("10.0.0") + let version1b: Version = Version.from("0.7.2") + let version2b: Version = Version.from("5.0.2") - expect(version1 < version2).to(beTrue()) - expect(version2 > version1).to(beTrue()) + expect(version1a < version2a).to(beTrue()) + expect(version2a > version1a).to(beTrue()) + expect(version1b < version2b).to(beTrue()) + expect(version2b > version1b).to(beTrue()) } it("returns correctly for a simple minor difference") { @@ -70,11 +74,15 @@ class VersionSpec: QuickSpec { } it("returns correctly for a complex minor difference") { - let version1: Version = Version.from("90.2.90") - let version2: Version = Version.from("90.10.0") + let version1a: Version = Version.from("90.2.90") + let version2a: Version = Version.from("90.10.0") + let version1b: Version = Version.from("2.0.7") + let version2b: Version = Version.from("2.5.0") - expect(version1 < version2).to(beTrue()) - expect(version2 > version1).to(beTrue()) + expect(version1a < version2a).to(beTrue()) + expect(version2a > version1a).to(beTrue()) + expect(version1b < version2b).to(beTrue()) + expect(version2b > version1b).to(beTrue()) } it("returns correctly for a simple patch difference") { @@ -86,11 +94,15 @@ class VersionSpec: QuickSpec { } it("returns correctly for a complex patch difference") { - let version1: Version = Version.from("90.90.2") - let version2: Version = Version.from("90.90.10") + let version1a: Version = Version.from("90.90.2") + let version2a: Version = Version.from("90.90.10") + let version1b: Version = Version.from("2.5.0") + let version2b: Version = Version.from("2.5.7") - expect(version1 < version2).to(beTrue()) - expect(version2 > version1).to(beTrue()) + expect(version1a < version2a).to(beTrue()) + expect(version2a > version1a).to(beTrue()) + expect(version1b < version2b).to(beTrue()) + expect(version2b > version1b).to(beTrue()) } } } From 244fe9d7ae221ceb3737efefe69c102e0cb88a23 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Mon, 26 Jun 2023 18:07:54 +1000 Subject: [PATCH 096/135] Fixed a production build issue --- SessionMessagingKit/Open Groups/Models/SOGSBatchRequest.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/SessionMessagingKit/Open Groups/Models/SOGSBatchRequest.swift b/SessionMessagingKit/Open Groups/Models/SOGSBatchRequest.swift index 4c31bf6c4..efe990e89 100644 --- a/SessionMessagingKit/Open Groups/Models/SOGSBatchRequest.swift +++ b/SessionMessagingKit/Open Groups/Models/SOGSBatchRequest.swift @@ -66,6 +66,7 @@ public extension OpenGroupAPI { #if DEBUG preconditionFailure("The `OpenGroupAPI.BatchResponse` type cannot be decoded directly, this is simply here to allow for `PreparedSendData` support") #else + info = HTTP.ResponseInfo(code: 0, headers: [:]) data = [:] #endif From b6328f79b99b9c4de1fd962f39f37be58d6812da Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 27 Jun 2023 18:01:00 +1000 Subject: [PATCH 097/135] Reworked the app startup process Shifted the initial HomeVC population to a background thread to avoid blocking launch processing Added some logging for database 'ABORT' errors to better identify cases of deadlocks Added a launch timeout modal to allow users to share their logs if the startup process happens to hang Updated the notification handling (and cancelling) so it could run on background threads (seemed to take up a decent chunk of main thread time) Fixed an issue where the IP2Country population was running sync which could cause a hang on startup Fixed an issue where the code checking if the UIPasteBoard contained an image was explicitly advised against by the documentation (caused some reported hangs) Fixed a hang which could be caused by a redundant function when the ImagePickerController appeared --- Session/Conversations/ConversationVC.swift | 30 +- .../Conversations/ConversationViewModel.swift | 10 +- .../Input View/InputTextView.swift | 2 +- Session/Home/HomeVC.swift | 29 +- Session/Home/HomeViewModel.swift | 34 +- .../MessageRequestsViewController.swift | 11 +- .../MessageRequestsViewModel.swift | 10 +- .../ImagePickerController.swift | 29 +- .../MediaGalleryViewModel.swift | 10 +- .../MediaPageViewController.swift | 22 +- Session/Meta/AppDelegate.swift | 362 ++++++++++++------ Session/Meta/SessionApp.swift | 24 ++ .../Translations/de.lproj/Localizable.strings | 2 + .../Translations/en.lproj/Localizable.strings | 2 + .../Translations/es.lproj/Localizable.strings | 2 + .../Translations/fa.lproj/Localizable.strings | 2 + .../Translations/fi.lproj/Localizable.strings | 2 + .../Translations/fr.lproj/Localizable.strings | 2 + .../Translations/hi.lproj/Localizable.strings | 2 + .../Translations/hr.lproj/Localizable.strings | 2 + .../id-ID.lproj/Localizable.strings | 2 + .../Translations/it.lproj/Localizable.strings | 2 + .../Translations/ja.lproj/Localizable.strings | 2 + .../Translations/nl.lproj/Localizable.strings | 2 + .../Translations/pl.lproj/Localizable.strings | 2 + .../pt_BR.lproj/Localizable.strings | 2 + .../Translations/ru.lproj/Localizable.strings | 2 + .../Translations/si.lproj/Localizable.strings | 2 + .../Translations/sk.lproj/Localizable.strings | 2 + .../Translations/sv.lproj/Localizable.strings | 2 + .../Translations/th.lproj/Localizable.strings | 2 + .../vi-VN.lproj/Localizable.strings | 2 + .../zh-Hant.lproj/Localizable.strings | 2 + .../zh_CN.lproj/Localizable.strings | 2 + Session/Notifications/AppNotifications.swift | 240 ++++++------ .../UserNotificationsAdaptee.swift | 70 ++-- Session/Onboarding/Onboarding.swift | 19 + Session/Settings/HelpViewModel.swift | 21 +- Session/Utilities/IP2Country.swift | 15 +- .../MessageReceiver+Calls.swift | 10 +- .../MessageReceiver+VisibleMessages.swift | 9 +- .../Notifications/NotificationsProtocol.swift | 6 +- .../Utilities/DeviceSleepManager.swift | 6 +- .../NSENotificationPresenter.swift | 6 +- .../NotificationServiceExtension.swift | 3 +- SessionShareExtension/ThreadPickerVC.swift | 16 +- SessionUtilitiesKit/Database/Storage.swift | 36 +- SignalUtilitiesKit/Utilities/AppSetup.swift | 10 +- .../Utilities/NoopNotificationsManager.swift | 6 +- 49 files changed, 687 insertions(+), 403 deletions(-) diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index ddadaed3e..6e95bce32 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -13,7 +13,9 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers private static let loadingHeaderHeight: CGFloat = 40 internal let viewModel: ConversationViewModel - private var dataChangeObservable: DatabaseCancellable? + private var dataChangeObservable: DatabaseCancellable? { + didSet { oldValue?.cancel() } // Cancel the old observable if there was one + } private var hasLoadedInitialThreadData: Bool = false private var hasLoadedInitialInteractionData: Bool = false private var currentTargetOffset: CGPoint? @@ -518,6 +520,16 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) + /// When the `ConversationVC` is on the screen we want to store it so we can avoid sending notification without accessing the + /// main thread (we don't currently care if it's still in the nav stack though - so if a user is on a conversation settings screen this should + /// get cleared within `viewWillDisappear`) + /// + /// **Note:** We do this on an async queue because `Atomic` can block if something else is mutating it and we want to avoid + /// the risk of blocking the conversation transition + DispatchQueue.global(qos: .userInitiated).async { [weak self] in + SessionApp.currentlyOpenConversationViewController.mutate { $0 = self } + } + if delayFirstResponder || isShowingSearchUI { delayFirstResponder = false @@ -540,6 +552,16 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) + /// When the `ConversationVC` is on the screen we want to store it so we can avoid sending notification without accessing the + /// main thread (we don't currently care if it's still in the nav stack though - so if a user leaves a conversation settings screen we clear + /// it, and if a user moves to a different `ConversationVC` this will get updated to that one within `viewDidAppear`) + /// + /// **Note:** We do this on an async queue because `Atomic` can block if something else is mutating it and we want to avoid + /// the risk of blocking the conversation transition + DispatchQueue.global(qos: .userInitiated).async { + SessionApp.currentlyOpenConversationViewController.mutate { $0 = nil } + } + viewIsDisappearing = true // Don't set the draft or resign the first responder if we are replacing the thread (want the keyboard @@ -605,7 +627,8 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers // MARK: - Updating private func startObservingChanges(didReturnFromBackground: Bool = false) { - // Start observing for data changes + guard dataChangeObservable == nil else { return } + dataChangeObservable = Storage.shared.start( viewModel.observableThreadData, onError: { _ in }, @@ -675,8 +698,7 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers } func stopObservingChanges() { - // Stop observing database changes - dataChangeObservable?.cancel() + self.dataChangeObservable = nil self.viewModel.onInteractionChange = nil } diff --git a/Session/Conversations/ConversationViewModel.swift b/Session/Conversations/ConversationViewModel.swift index 0c898f941..a5dcf5086 100644 --- a/Session/Conversations/ConversationViewModel.swift +++ b/Session/Conversations/ConversationViewModel.swift @@ -217,8 +217,14 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { didSet { // When starting to observe interaction changes we want to trigger a UI update just in case the // data was changed while we weren't observing - if let unobservedInteractionDataChanges: ([SectionModel], StagedChangeset<[SectionModel]>) = self.unobservedInteractionDataChanges { - onInteractionChange?(unobservedInteractionDataChanges.0, unobservedInteractionDataChanges.1) + if let changes: ([SectionModel], StagedChangeset<[SectionModel]>) = self.unobservedInteractionDataChanges { + let performChange: (([SectionModel], StagedChangeset<[SectionModel]>) -> ())? = onInteractionChange + + switch Thread.isMainThread { + case true: performChange?(changes.0, changes.1) + case false: DispatchQueue.main.async { performChange?(changes.0, changes.1) } + } + self.unobservedInteractionDataChanges = nil } } diff --git a/Session/Conversations/Input View/InputTextView.swift b/Session/Conversations/Input View/InputTextView.swift index aaaf0e3f0..cba081e8e 100644 --- a/Session/Conversations/Input View/InputTextView.swift +++ b/Session/Conversations/Input View/InputTextView.swift @@ -50,7 +50,7 @@ public final class InputTextView: UITextView, UITextViewDelegate { public override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { if action == #selector(paste(_:)) { - if let _ = UIPasteboard.general.image { + if UIPasteboard.general.hasImages { return true } } diff --git a/Session/Home/HomeVC.swift b/Session/Home/HomeVC.swift index eb7d34ec5..d62e359f0 100644 --- a/Session/Home/HomeVC.swift +++ b/Session/Home/HomeVC.swift @@ -13,7 +13,9 @@ final class HomeVC: BaseVC, SessionUtilRespondingViewController, UITableViewData public static let newConversationButtonSize: CGFloat = 60 private let viewModel: HomeViewModel = HomeViewModel() - private var dataChangeObservable: DatabaseCancellable? + private var dataChangeObservable: DatabaseCancellable? { + didSet { oldValue?.cancel() } // Cancel the old observable if there was one + } private var hasLoadedInitialStateData: Bool = false private var hasLoadedInitialThreadData: Bool = false private var isLoadingMore: Bool = false @@ -327,26 +329,31 @@ final class HomeVC: BaseVC, SessionUtilRespondingViewController, UITableViewData // MARK: - Updating - private func startObservingChanges(didReturnFromBackground: Bool = false) { - // Start observing for data changes + public func startObservingChanges(didReturnFromBackground: Bool = false, onReceivedInitialChange: (() -> ())? = nil) { + guard dataChangeObservable == nil else { return } + + var runAndClearInitialChangeCallback: (() -> ())? = nil + + runAndClearInitialChangeCallback = { [weak self] in + guard self?.hasLoadedInitialStateData == true && self?.hasLoadedInitialThreadData == true else { return } + + onReceivedInitialChange?() + runAndClearInitialChangeCallback = nil + } + dataChangeObservable = Storage.shared.start( viewModel.observableState, - // If we haven't done the initial load the trigger it immediately (blocking the main - // thread so we remain on the launch screen until it completes to be consistent with - // the old behaviour) - scheduling: (hasLoadedInitialStateData ? - .async(onQueue: .main) : - .immediate - ), onError: { _ in }, onChange: { [weak self] state in // The default scheduler emits changes on the main thread self?.handleUpdates(state) + runAndClearInitialChangeCallback?() } ) self.viewModel.onThreadChange = { [weak self] updatedThreadData, changeset in self?.handleThreadUpdates(updatedThreadData, changeset: changeset) + runAndClearInitialChangeCallback?() } // Note: When returning from the background we could have received notifications but the @@ -361,7 +368,7 @@ final class HomeVC: BaseVC, SessionUtilRespondingViewController, UITableViewData private func stopObservingChanges() { // Stop observing database changes - dataChangeObservable?.cancel() + self.dataChangeObservable = nil self.viewModel.onThreadChange = nil } diff --git a/Session/Home/HomeViewModel.swift b/Session/Home/HomeViewModel.swift index d55d186b6..675317605 100644 --- a/Session/Home/HomeViewModel.swift +++ b/Session/Home/HomeViewModel.swift @@ -208,12 +208,16 @@ public class HomeViewModel { ) } ) + + self?.hasReceivedInitialThreadData = true } ) - // Run the initial query on the main thread so we prevent the app from leaving the loading screen - // until we have data (Note: the `.pageBefore` will query from a `0` offset loading the first page) - self.pagedDataObserver?.load(.pageBefore) + // Run the initial query on a background thread so we don't block the main thread + DispatchQueue.global(qos: .userInitiated).async { [weak self] in + // The `.pageBefore` will query from a `0` offset loading the first page + self?.pagedDataObserver?.load(.pageBefore) + } } // MARK: - State @@ -254,8 +258,10 @@ public class HomeViewModel { let oldState: State = self.state self.state = updatedState - // If the messageRequest content changed then we need to re-process the thread data + // If the messageRequest content changed then we need to re-process the thread data (assuming + // we've received the initial thread data) guard + self.hasReceivedInitialThreadData, ( oldState.hasHiddenMessageRequests != updatedState.hasHiddenMessageRequests || oldState.unreadMessageRequestThreadCount != updatedState.unreadMessageRequestThreadCount @@ -272,11 +278,7 @@ public class HomeViewModel { PagedData.processAndTriggerUpdates( updatedData: updatedThreadData, - currentDataRetriever: { [weak self] in - guard self?.hasProcessedInitialThreadData == true else { return nil } - - return (self?.unobservedThreadDataChanges?.0 ?? self?.threadData) - }, + currentDataRetriever: { [weak self] in (self?.unobservedThreadDataChanges?.0 ?? self?.threadData) }, onDataChange: onThreadChange, onUnobservedDataChange: { [weak self] updatedData, changeset in self?.unobservedThreadDataChanges = (changeset.isEmpty ? @@ -289,19 +291,23 @@ public class HomeViewModel { // MARK: - Thread Data - private var hasProcessedInitialThreadData: Bool = false + private var hasReceivedInitialThreadData: Bool = false public private(set) var unobservedThreadDataChanges: ([SectionModel], StagedChangeset<[SectionModel]>)? public private(set) var threadData: [SectionModel] = [] public private(set) var pagedDataObserver: PagedDatabaseObserver? public var onThreadChange: (([SectionModel], StagedChangeset<[SectionModel]>) -> ())? { didSet { - self.hasProcessedInitialThreadData = (onThreadChange != nil || hasProcessedInitialThreadData) - // When starting to observe interaction changes we want to trigger a UI update just in case the // data was changed while we weren't observing - if let unobservedThreadDataChanges: ([SectionModel], StagedChangeset<[SectionModel]>) = self.unobservedThreadDataChanges { - onThreadChange?(unobservedThreadDataChanges.0, unobservedThreadDataChanges.1) + if let changes: ([SectionModel], StagedChangeset<[SectionModel]>) = self.unobservedThreadDataChanges { + let performChange: (([SectionModel], StagedChangeset<[SectionModel]>) -> ())? = onThreadChange + + switch Thread.isMainThread { + case true: performChange?(changes.0, changes.1) + case false: DispatchQueue.main.async { performChange?(changes.0, changes.1) } + } + self.unobservedThreadDataChanges = nil } } diff --git a/Session/Home/Message Requests/MessageRequestsViewController.swift b/Session/Home/Message Requests/MessageRequestsViewController.swift index 7d6b158e0..b7c63b57d 100644 --- a/Session/Home/Message Requests/MessageRequestsViewController.swift +++ b/Session/Home/Message Requests/MessageRequestsViewController.swift @@ -11,7 +11,6 @@ class MessageRequestsViewController: BaseVC, SessionUtilRespondingViewController private static let loadingHeaderHeight: CGFloat = 40 private let viewModel: MessageRequestsViewModel = MessageRequestsViewModel() - private var dataChangeObservable: DatabaseCancellable? private var hasLoadedInitialThreadData: Bool = false private var isLoadingMore: Bool = false private var isAutoLoadingNextPage: Bool = false @@ -161,8 +160,7 @@ class MessageRequestsViewController: BaseVC, SessionUtilRespondingViewController override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - // Stop observing database changes - dataChangeObservable?.cancel() + stopObservingChanges() } @objc func applicationDidBecomeActive(_ notification: Notification) { @@ -173,8 +171,7 @@ class MessageRequestsViewController: BaseVC, SessionUtilRespondingViewController } @objc func applicationDidResignActive(_ notification: Notification) { - // Stop observing database changes - dataChangeObservable?.cancel() + stopObservingChanges() } // MARK: - Layout @@ -223,6 +220,10 @@ class MessageRequestsViewController: BaseVC, SessionUtilRespondingViewController } } + private func stopObservingChanges() { + self.viewModel.onThreadChange = nil + } + private func handleThreadUpdates( _ updatedData: [MessageRequestsViewModel.SectionModel], changeset: StagedChangeset<[MessageRequestsViewModel.SectionModel]>, diff --git a/Session/Home/Message Requests/MessageRequestsViewModel.swift b/Session/Home/Message Requests/MessageRequestsViewModel.swift index a7ed46b98..27c426335 100644 --- a/Session/Home/Message Requests/MessageRequestsViewModel.swift +++ b/Session/Home/Message Requests/MessageRequestsViewModel.swift @@ -129,8 +129,14 @@ public class MessageRequestsViewModel { didSet { // When starting to observe interaction changes we want to trigger a UI update just in case the // data was changed while we weren't observing - if let unobservedThreadDataChanges: ([SectionModel], StagedChangeset<[SectionModel]>) = self.unobservedThreadDataChanges { - self.onThreadChange?(unobservedThreadDataChanges.0, unobservedThreadDataChanges.1) + if let changes: ([SectionModel], StagedChangeset<[SectionModel]>) = self.unobservedThreadDataChanges { + let performChange: (([SectionModel], StagedChangeset<[SectionModel]>) -> ())? = onThreadChange + + switch Thread.isMainThread { + case true: performChange?(changes.0, changes.1) + case false: DispatchQueue.main.async { performChange?(changes.0, changes.1) } + } + self.unobservedThreadDataChanges = nil } } diff --git a/Session/Media Viewing & Editing/ImagePickerController.swift b/Session/Media Viewing & Editing/ImagePickerController.swift index 504465c31..e7837f15a 100644 --- a/Session/Media Viewing & Editing/ImagePickerController.swift +++ b/Session/Media Viewing & Editing/ImagePickerController.swift @@ -203,8 +203,7 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat let scale = UIScreen.main.scale let cellSize = collectionViewFlowLayout.itemSize photoMediaSize.thumbnailSize = CGSize(width: cellSize.width * scale, height: cellSize.height * scale) - - reloadDataAndRestoreSelection() + if !hasEverAppeared { scrollToBottom(animated: false) } @@ -291,30 +290,6 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat } } - private func reloadDataAndRestoreSelection() { - guard let collectionView = collectionView else { - owsFailDebug("Missing collectionView.") - return - } - - guard let delegate = delegate else { - owsFailDebug("delegate was unexpectedly nil") - return - } - - collectionView.reloadData() - collectionView.layoutIfNeeded() - - let count = photoCollectionContents.assetCount - for index in 0..) = self.unobservedGalleryDataChanges { - onGalleryChange?(unobservedGalleryDataChanges.0, unobservedGalleryDataChanges.1) + if let changes: ([SectionModel], StagedChangeset<[SectionModel]>) = self.unobservedGalleryDataChanges { + let performChange: (([SectionModel], StagedChangeset<[SectionModel]>) -> ())? = onGalleryChange + + switch Thread.isMainThread { + case true: performChange?(changes.0, changes.1) + case false: DispatchQueue.main.async { performChange?(changes.0, changes.1) } + } + self.unobservedGalleryDataChanges = nil } } diff --git a/Session/Media Viewing & Editing/MediaPageViewController.swift b/Session/Media Viewing & Editing/MediaPageViewController.swift index 1125f5eaa..59cf1a58f 100644 --- a/Session/Media Viewing & Editing/MediaPageViewController.swift +++ b/Session/Media Viewing & Editing/MediaPageViewController.swift @@ -15,7 +15,9 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou fileprivate var mediaInteractiveDismiss: MediaInteractiveDismiss? public let viewModel: MediaGalleryViewModel - private var dataChangeObservable: DatabaseCancellable? + private var dataChangeObservable: DatabaseCancellable? { + didSet { oldValue?.cancel() } // Cancel the old observable if there was one + } private var initialPage: MediaDetailViewController private var cachedPages: [Int64: [MediaGalleryViewModel.Item: MediaDetailViewController]] = [:] @@ -40,7 +42,7 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou ) // Swap out the database observer - dataChangeObservable?.cancel() + stopObservingChanges() viewModel.replaceAlbumObservation(toObservationFor: item.interactionId) startObservingChanges() @@ -238,8 +240,7 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou public override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - // Stop observing database changes - dataChangeObservable?.cancel() + stopObservingChanges() resignFirstResponder() } @@ -252,8 +253,7 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou } @objc func applicationDidResignActive(_ notification: Notification) { - // Stop observing database changes - dataChangeObservable?.cancel() + stopObservingChanges() } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { @@ -388,6 +388,8 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou // MARK: - Updating private func startObservingChanges() { + guard dataChangeObservable == nil else { return } + // Start observing for data changes dataChangeObservable = Storage.shared.start( viewModel.observableAlbumData, @@ -399,6 +401,10 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou ) } + private func stopObservingChanges() { + dataChangeObservable = nil + } + private func handleUpdates(_ updatedViewData: [MediaGalleryViewModel.Item]) { // Determine if we swapped albums (if so we don't need to do anything else) guard updatedViewData.contains(where: { $0.interactionId == currentItem.interactionId }) else { @@ -710,7 +716,7 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou } // Swap out the database observer - dataChangeObservable?.cancel() + stopObservingChanges() viewModel.replaceAlbumObservation(toObservationFor: interactionIdAfter) startObservingChanges() @@ -755,7 +761,7 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou } // Swap out the database observer - dataChangeObservable?.cancel() + stopObservingChanges() viewModel.replaceAlbumObservation(toObservationFor: interactionIdBefore) startObservingChanges() diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index ca106f141..ae7592fef 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -13,23 +13,25 @@ import SignalCoreKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate { + private static let maxRootViewControllerInitialQueryDuration: TimeInterval = 5 + var window: UIWindow? var backgroundSnapshotBlockerWindow: UIWindow? var appStartupWindow: UIWindow? var hasInitialRootViewController: Bool = false + var startTime: CFTimeInterval = 0 private var loadingViewController: LoadingViewController? - enum LifecycleMethod { - case finishLaunching - case enterForeground - } - /// This needs to be a lazy variable to ensure it doesn't get initialized before it actually needs to be used lazy var poller: CurrentUserPoller = CurrentUserPoller() // MARK: - Lifecycle func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Log something immediately to make it easier to track app launches (and crashes during launch) + SNLog("Launching \(SessionApp.versionInfo)") + startTime = CACurrentMediaTime() + // These should be the first things we do (the startup process can fail without them) SetCurrentAppContext(MainAppContext()) verifyDBKeysAvailableBeforeBackgroundLaunch() @@ -71,7 +73,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD }, migrationsCompletion: { [weak self] result, needsConfigSync in if case .failure(let error) = result { - self?.showFailedMigrationAlert(calledFrom: .finishLaunching, error: error) + self?.showFailedStartupAlert(calledFrom: .finishLaunching, error: .migrationError(error)) return } @@ -147,7 +149,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD }, migrationsCompletion: { [weak self] result, needsConfigSync in if case .failure(let error) = result { - self?.showFailedMigrationAlert(calledFrom: .enterForeground, error: error) + DispatchQueue.main.async { + self?.showFailedStartupAlert(calledFrom: .enterForeground, error: .migrationError(error)) + } return } @@ -187,7 +191,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD UserDefaults.sharedLokiProject?[.isMainAppActive] = true - ensureRootViewController() + ensureRootViewController(calledFrom: .didBecomeActive) AppReadiness.runNowOrWhenAppDidBecomeReady { [weak self] in self?.handleActivation() @@ -283,122 +287,139 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD Configuration.performMainSetup() JobRunner.add(executor: SyncPushTokensJob.self, for: .syncPushTokens) - /// Setup the UI - /// - /// **Note:** This **MUST** be run before calling: - /// - `AppReadiness.setAppIsReady()`: - /// If we are launching the app from a push notification the HomeVC won't be setup yet - /// and it won't open the related thread - /// - /// - `JobRunner.appDidFinishLaunching()`: - /// The jobs which run on launch (eg. DisappearingMessages job) can impact the interactions - /// which get fetched to display on the home screen, if the PagedDatabaseObserver hasn't - /// been setup yet then the home screen can show stale (ie. deleted) interactions incorrectly - self.ensureRootViewController(isPreAppReadyCall: true) - - // Trigger any launch-specific jobs and start the JobRunner - if lifecycleMethod == .finishLaunching { - JobRunner.appDidFinishLaunching() - } - - // Note that this does much more than set a flag; - // it will also run all deferred blocks (including the JobRunner - // 'appDidBecomeActive' method) - AppReadiness.setAppIsReady() - - DeviceSleepManager.sharedInstance.removeBlock(blockObject: self) - AppVersion.sharedInstance().mainAppLaunchDidComplete() - Environment.shared?.audioSession.setup() - Environment.shared?.reachabilityManager.setup() - - Storage.shared.writeAsync { db in - // Disable the SAE until the main app has successfully completed launch process - // at least once in the post-SAE world. - db[.isReadyForAppExtensions] = true + // Setup the UI and trigger any post-UI setup actions + self.ensureRootViewController(calledFrom: lifecycleMethod) { [weak self] in + /// Trigger any launch-specific jobs and start the JobRunner with `JobRunner.appDidFinishLaunching()` some + /// of these jobs (eg. DisappearingMessages job) can impact the interactions which get fetched to display on the home + /// screen, if the PagedDatabaseObserver hasn't been setup yet then the home screen can show stale (ie. deleted) + /// interactions incorrectly + if lifecycleMethod == .finishLaunching { + JobRunner.appDidFinishLaunching() + } - if Identity.userCompletedRequiredOnboarding(db) { - let appVersion: AppVersion = AppVersion.sharedInstance() + /// Flag that the app is ready via `AppReadiness.setAppIsReady()` + /// + /// If we are launching the app from a push notification we need to ensure we wait until after the `HomeVC` is setup + /// otherwise it won't open the related thread + /// + /// **Note:** This this does much more than set a flag - it will also run all deferred blocks (including the JobRunner + /// `appDidBecomeActive` method hence why it **must** also come after calling + /// `JobRunner.appDidFinishLaunching()`) + AppReadiness.setAppIsReady() + + /// Remove the sleep blocking once the startup is done (needs to run on the main thread and sleeping while + /// doing the startup could suspend the database causing errors/crashes + DeviceSleepManager.sharedInstance.removeBlock(blockObject: self) + + /// App launch hasn't really completed until the main screen is loaded so wait until then to register it + AppVersion.sharedInstance().mainAppLaunchDidComplete() + + /// App won't be ready for extensions and no need to enqueue a config sync unless we successfully completed startup + Storage.shared.writeAsync { db in + // Disable the SAE until the main app has successfully completed launch process + // at least once in the post-SAE world. + db[.isReadyForAppExtensions] = true - // If the device needs to sync config or the user updated to a new version - if - needsConfigSync || ( - (appVersion.lastAppVersion?.count ?? 0) > 0 && - appVersion.lastAppVersion != appVersion.currentAppVersion - ) - { - ConfigurationSyncJob.enqueue(db, publicKey: getUserHexEncodedPublicKey(db)) + if Identity.userCompletedRequiredOnboarding(db) { + let appVersion: AppVersion = AppVersion.sharedInstance() + + // If the device needs to sync config or the user updated to a new version + if + needsConfigSync || ( + (appVersion.lastAppVersion?.count ?? 0) > 0 && + appVersion.lastAppVersion != appVersion.currentAppVersion + ) + { + ConfigurationSyncJob.enqueue(db, publicKey: getUserHexEncodedPublicKey(db)) + } } } + + // Add a log to track the proper startup time of the app so we know whether we need to + // improve it in the future from user logs + let endTime: CFTimeInterval = CACurrentMediaTime() + SNLog("Launch completed in \((self?.startTime).map { ceil((endTime - $0) * 1000) } ?? -1)ms") } + + // May as well run these on the background thread + Environment.shared?.audioSession.setup() + Environment.shared?.reachabilityManager.setup() } - private func showFailedMigrationAlert( + private func showFailedStartupAlert( calledFrom lifecycleMethod: LifecycleMethod, - error: Error?, - isRestoreError: Bool = false + error: StartupError, + animated: Bool = true, + presentationCompletion: (() -> ())? = nil ) { - let alert = UIAlertController( + /// This **must** be a standard `UIAlertController` instead of a `ConfirmationModal` because we may not + /// have access to the database when displaying this so can't extract theme information for styling purposes + let alert: UIAlertController = UIAlertController( title: "Session", - message: { - switch (isRestoreError, (error ?? StorageError.generic)) { - case (true, _): return "DATABASE_RESTORE_FAILED".localized() - case (_, StorageError.startupFailed): return "DATABASE_STARTUP_FAILED".localized() - default: return "DATABASE_MIGRATION_FAILED".localized() - } - }(), + message: error.message, preferredStyle: .alert ) alert.addAction(UIAlertAction(title: "HELP_REPORT_BUG_ACTION_TITLE".localized(), style: .default) { _ in HelpViewModel.shareLogs(viewControllerToDismiss: alert) { [weak self] in - self?.showFailedMigrationAlert(calledFrom: lifecycleMethod, error: error) + // Don't bother showing the "Failed Startup" modal again if we happen to now + // have an initial view controller (this most likely means that the startup + // completed while the user was sharing logs so we can just let the user use + // the app) + guard self?.hasInitialRootViewController == false else { return } + + self?.showFailedStartupAlert(calledFrom: lifecycleMethod, error: error) } }) - // Only offer the 'Restore' option if the user hasn't already tried to restore - if !isRestoreError { - alert.addAction(UIAlertAction(title: "vc_restore_title".localized(), style: .destructive) { _ in - if SUKLegacy.hasLegacyDatabaseFile { - // Remove the legacy database and any message hashes that have been migrated to the new DB - try? SUKLegacy.deleteLegacyDatabaseFilesAndKey() - - Storage.shared.write { db in - try SnodeReceivedMessageInfo.deleteAll(db) - } - } - else { - // If we don't have a legacy database then reset the current database for a clean migration - Storage.resetForCleanMigration() - } - - // Hide the top banner if there was one - TopBannerController.hide() - - // The re-run the migration (should succeed since there is no data) - AppSetup.runPostSetupMigrations( - migrationProgressChanged: { [weak self] progress, minEstimatedTotalTime in - self?.loadingViewController?.updateProgress( - progress: progress, - minEstimatedTotalTime: minEstimatedTotalTime - ) - }, - migrationsCompletion: { [weak self] result, needsConfigSync in - if case .failure(let error) = result { - self?.showFailedMigrationAlert(calledFrom: lifecycleMethod, error: error, isRestoreError: true) - return - } + switch error { + // Offer the 'Restore' option if it was a migration error + case .migrationError: + alert.addAction(UIAlertAction(title: "vc_restore_title".localized(), style: .destructive) { _ in + if SUKLegacy.hasLegacyDatabaseFile { + // Remove the legacy database and any message hashes that have been migrated to the new DB + try? SUKLegacy.deleteLegacyDatabaseFilesAndKey() - self?.completePostMigrationSetup(calledFrom: lifecycleMethod, needsConfigSync: needsConfigSync) + Storage.shared.write { db in + try SnodeReceivedMessageInfo.deleteAll(db) + } } - ) - }) + else { + // If we don't have a legacy database then reset the current database for a clean migration + Storage.resetForCleanMigration() + } + + // Hide the top banner if there was one + TopBannerController.hide() + + // The re-run the migration (should succeed since there is no data) + AppSetup.runPostSetupMigrations( + migrationProgressChanged: { [weak self] progress, minEstimatedTotalTime in + self?.loadingViewController?.updateProgress( + progress: progress, + minEstimatedTotalTime: minEstimatedTotalTime + ) + }, + migrationsCompletion: { [weak self] result, needsConfigSync in + switch result { + case .failure: + self?.showFailedStartupAlert(calledFrom: lifecycleMethod, error: .failedToRestore) + + case .success: + self?.completePostMigrationSetup(calledFrom: lifecycleMethod, needsConfigSync: needsConfigSync) + } + } + ) + }) + + default: break } - alert.addAction(UIAlertAction(title: "Close", style: .default) { _ in + alert.addAction(UIAlertAction(title: "APP_STARTUP_EXIT".localized(), style: .default) { _ in DDLog.flushLog() exit(0) }) - self.window?.rootViewController?.present(alert, animated: true, completion: nil) + self.window?.rootViewController?.present(alert, animated: animated, completion: presentationCompletion) } /// The user must unlock the device once after reboot before the database encryption key can be accessed. @@ -452,36 +473,101 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD } } - private func ensureRootViewController(isPreAppReadyCall: Bool = false) { - guard (AppReadiness.isAppReady() || isPreAppReadyCall) && Storage.shared.isValid && !hasInitialRootViewController else { + private func ensureRootViewController( + calledFrom lifecycleMethod: LifecycleMethod, + onComplete: (() -> ())? = nil + ) { + guard (AppReadiness.isAppReady() || lifecycleMethod == .finishLaunching) && Storage.shared.isValid && !hasInitialRootViewController else { return } - self.hasInitialRootViewController = true - self.window?.rootViewController = TopBannerController( - child: StyledNavigationController( - rootViewController: { - guard Identity.userExists() else { return LandingVC() } - guard !Profile.fetchOrCreateCurrentUser().name.isEmpty else { - // If we have no display name then collect one (this can happen if the - // app crashed during onboarding which would leave the user in an invalid - // state with no display name) - return DisplayNameVC(flow: .register) - } - - return HomeVC() - }() - ), - cachedWarning: UserDefaults.sharedLokiProject?[.topBannerWarningToShow] - .map { rawValue in TopBannerController.Warning(rawValue: rawValue) } - ) - UIViewController.attemptRotationToDeviceOrientation() + /// Start a timeout for the creation of the rootViewController setup process (if it takes too long then we want to give the user + /// the option to export their logs) + let populateHomeScreenTimer: Timer = Timer.scheduledTimerOnMainThread( + withTimeInterval: AppDelegate.maxRootViewControllerInitialQueryDuration, + repeats: false + ) { [weak self] timer in + timer.invalidate() + self?.showFailedStartupAlert(calledFrom: lifecycleMethod, error: .startupTimeout) + } - /// **Note:** There is an annoying case when starting the app by interacting with a push notification where - /// the `HomeVC` won't have completed loading it's view which means the `SessionApp.homeViewController` - /// won't have been set - we set the value directly here to resolve this edge case - if let homeViewController: HomeVC = (self.window?.rootViewController as? UINavigationController)?.viewControllers.first as? HomeVC { - SessionApp.homeViewController.mutate { $0 = homeViewController } + // All logic which needs to run after the 'rootViewController' is created + let rootViewControllerSetupComplete: (UIViewController) -> () = { [weak self] rootViewController in + let presentedViewController: UIViewController? = self?.window?.rootViewController?.presentedViewController + let targetRootViewController: UIViewController = TopBannerController( + child: StyledNavigationController(rootViewController: rootViewController), + cachedWarning: UserDefaults.sharedLokiProject?[.topBannerWarningToShow] + .map { rawValue in TopBannerController.Warning(rawValue: rawValue) } + ) + + /// Insert the `targetRootViewController` below the current view and trigger a layout without animation before properly + /// swapping the `rootViewController` over so we can avoid any weird initial layout behaviours + UIView.performWithoutAnimation { + self?.window?.rootViewController = targetRootViewController + } + + self?.hasInitialRootViewController = true + UIViewController.attemptRotationToDeviceOrientation() + + /// **Note:** There is an annoying case when starting the app by interacting with a push notification where + /// the `HomeVC` won't have completed loading it's view which means the `SessionApp.homeViewController` + /// won't have been set - we set the value directly here to resolve this edge case + if let homeViewController: HomeVC = rootViewController as? HomeVC { + SessionApp.homeViewController.mutate { $0 = homeViewController } + } + + /// If we were previously presenting a viewController but are no longer preseting it then present it again + /// + /// **Note:** Looks like the OS will throw an exception if we try to present a screen which is already (or + /// was previously?) presented, even if it's not attached to the screen it seems... + switch presentedViewController { + case is UIAlertController, is ConfirmationModal: + /// If the viewController we were presenting happened to be the "failed startup" modal then we can dismiss it + /// automatically (while this seems redundant it's less jarring for the user than just instantly having it disappear) + self?.showFailedStartupAlert(calledFrom: lifecycleMethod, error: .startupTimeout, animated: false) { + self?.window?.rootViewController?.dismiss(animated: true) + } + + case is UIActivityViewController: HelpViewModel.shareLogs(animated: false) + default: break + } + + // Setup is completed so run any post-setup tasks + onComplete?() + } + + // Navigate to the approriate screen depending on the onboarding state + switch Onboarding.State.current { + case .newUser: + DispatchQueue.main.async { + let viewController: LandingVC = LandingVC() + populateHomeScreenTimer.invalidate() + rootViewControllerSetupComplete(viewController) + } + + case .missingName: + DispatchQueue.main.async { + let viewController: DisplayNameVC = DisplayNameVC(flow: .register) + populateHomeScreenTimer.invalidate() + rootViewControllerSetupComplete(viewController) + } + + case .completed: + DispatchQueue.main.async { + let viewController: HomeVC = HomeVC() + + /// We want to start observing the changes for the 'HomeVC' and want to wait until we actually get data back before we + /// continue as we don't want to show a blank home screen + DispatchQueue.global(qos: .userInitiated).async { + viewController.startObservingChanges() { + populateHomeScreenTimer.invalidate() + + DispatchQueue.main.async { + rootViewControllerSetupComplete(viewController) + } + } + } + } } } @@ -750,3 +836,29 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD ) } } + +// MARK: - LifecycleMethod + +private enum LifecycleMethod { + case finishLaunching + case enterForeground + case didBecomeActive +} + +// MARK: - StartupError + +private enum StartupError: Error { + case databaseStartupError + case migrationError(Error) + case failedToRestore + case startupTimeout + + var message: String { + switch self { + case .databaseStartupError: return "DATABASE_STARTUP_FAILED".localized() + case .failedToRestore: return "DATABASE_RESTORE_FAILED".localized() + case .migrationError: return "DATABASE_MIGRATION_FAILED".localized() + case .startupTimeout: return "APP_STARTUP_TIMEOUT".localized() + } + } +} diff --git a/Session/Meta/SessionApp.swift b/Session/Meta/SessionApp.swift index 2adb9eb7c..5dfc97351 100644 --- a/Session/Meta/SessionApp.swift +++ b/Session/Meta/SessionApp.swift @@ -6,7 +6,31 @@ import SessionMessagingKit import SignalCoreKit public struct SessionApp { + // FIXME: Refactor this to be protocol based for unit testing (or even dynamic based on view hierarchy - do want to avoid needing to use the main thread to access them though) static let homeViewController: Atomic = Atomic(nil) + static let currentlyOpenConversationViewController: Atomic = Atomic(nil) + + static var versionInfo: String { + let buildNumber: String = (Bundle.main.infoDictionary?["CFBundleVersion"] as? String) + .map { " (\($0))" } + .defaulting(to: "") + let appVersion: String? = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) + .map { "App: \($0)\(buildNumber)" } + #if DEBUG + let commitInfo: String? = (Bundle.main.infoDictionary?["GitCommitHash"] as? String).map { "Commit: \($0)" } + #else + let commitInfo: String? = nil + #endif + + let versionInfo: [String] = [ + "iOS \(UIDevice.current.systemVersion)", + appVersion, + "libSession: \(SessionUtil.libSessionVersion)", + commitInfo + ].compactMap { $0 } + + return versionInfo.joined(separator: ", ") + } // MARK: - View Convenience Methods diff --git a/Session/Meta/Translations/de.lproj/Localizable.strings b/Session/Meta/Translations/de.lproj/Localizable.strings index 178d7c6ea..688070b13 100644 --- a/Session/Meta/Translations/de.lproj/Localizable.strings +++ b/Session/Meta/Translations/de.lproj/Localizable.strings @@ -415,6 +415,8 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; +"APP_STARTUP_EXIT" = "Exit"; "DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index 2e5c23059..8f0e8f077 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -415,6 +415,8 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; +"APP_STARTUP_EXIT" = "Exit"; "DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/es.lproj/Localizable.strings b/Session/Meta/Translations/es.lproj/Localizable.strings index 4d1257d37..81652e295 100644 --- a/Session/Meta/Translations/es.lproj/Localizable.strings +++ b/Session/Meta/Translations/es.lproj/Localizable.strings @@ -415,6 +415,8 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; +"APP_STARTUP_EXIT" = "Exit"; "DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/fa.lproj/Localizable.strings b/Session/Meta/Translations/fa.lproj/Localizable.strings index 37be0ffb3..9ca92b73c 100644 --- a/Session/Meta/Translations/fa.lproj/Localizable.strings +++ b/Session/Meta/Translations/fa.lproj/Localizable.strings @@ -415,6 +415,8 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "لطفا بعدا دوباره تلاش کنید"; "LOADING_CONVERSATIONS" = "درحال بارگزاری پیام ها..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; +"APP_STARTUP_EXIT" = "Exit"; "DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "هنگام بهینه‌سازی پایگاه داده خطایی روی داد\n\nشما می‌توانید گزارش‌های برنامه خود را صادر کنید تا بتوانید برای عیب‌یابی به اشتراک بگذارید یا می‌توانید دستگاه خود را بازیابی کنید\n\nهشدار: بازیابی دستگاه شما منجر به از دست رفتن داده‌های قدیمی‌تر از دو هفته می‌شود."; "RECOVERY_PHASE_ERROR_GENERIC" = "مشکلی پیش آمد. لطفاً عبارت بازیابی خود را بررسی کنید و دوباره امتحان کنید."; diff --git a/Session/Meta/Translations/fi.lproj/Localizable.strings b/Session/Meta/Translations/fi.lproj/Localizable.strings index 61f77cd4a..c63940d0a 100644 --- a/Session/Meta/Translations/fi.lproj/Localizable.strings +++ b/Session/Meta/Translations/fi.lproj/Localizable.strings @@ -415,6 +415,8 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; +"APP_STARTUP_EXIT" = "Exit"; "DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/fr.lproj/Localizable.strings b/Session/Meta/Translations/fr.lproj/Localizable.strings index b5bf78948..1b1005b26 100644 --- a/Session/Meta/Translations/fr.lproj/Localizable.strings +++ b/Session/Meta/Translations/fr.lproj/Localizable.strings @@ -415,6 +415,8 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Veuillez réessayer plus tard"; "LOADING_CONVERSATIONS" = "Chargement des conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; +"APP_STARTUP_EXIT" = "Exit"; "DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "Une erreur est survenue pendant l'optimisation de la base de données\n\nVous pouvez exporter votre journal d'application pour le partager et aider à régler le problème ou vous pouvez restaurer votre appareil\n\nAttention : restaurer votre appareil résultera en une perte des données des deux dernières semaines"; "RECOVERY_PHASE_ERROR_GENERIC" = "Quelque chose s'est mal passé. Vérifiez votre phrase de récupération et réessayez s'il vous plaît."; diff --git a/Session/Meta/Translations/hi.lproj/Localizable.strings b/Session/Meta/Translations/hi.lproj/Localizable.strings index 2b5a37525..534ff51ef 100644 --- a/Session/Meta/Translations/hi.lproj/Localizable.strings +++ b/Session/Meta/Translations/hi.lproj/Localizable.strings @@ -415,6 +415,8 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; +"APP_STARTUP_EXIT" = "Exit"; "DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/hr.lproj/Localizable.strings b/Session/Meta/Translations/hr.lproj/Localizable.strings index c7714aca6..39506e93f 100644 --- a/Session/Meta/Translations/hr.lproj/Localizable.strings +++ b/Session/Meta/Translations/hr.lproj/Localizable.strings @@ -415,6 +415,8 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; +"APP_STARTUP_EXIT" = "Exit"; "DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/id-ID.lproj/Localizable.strings b/Session/Meta/Translations/id-ID.lproj/Localizable.strings index 62d0c3471..a19d65df2 100644 --- a/Session/Meta/Translations/id-ID.lproj/Localizable.strings +++ b/Session/Meta/Translations/id-ID.lproj/Localizable.strings @@ -415,6 +415,8 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; +"APP_STARTUP_EXIT" = "Exit"; "DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/it.lproj/Localizable.strings b/Session/Meta/Translations/it.lproj/Localizable.strings index acb886818..101c5112a 100644 --- a/Session/Meta/Translations/it.lproj/Localizable.strings +++ b/Session/Meta/Translations/it.lproj/Localizable.strings @@ -415,6 +415,8 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; +"APP_STARTUP_EXIT" = "Exit"; "DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/ja.lproj/Localizable.strings b/Session/Meta/Translations/ja.lproj/Localizable.strings index e5ffab88b..6de3a1708 100644 --- a/Session/Meta/Translations/ja.lproj/Localizable.strings +++ b/Session/Meta/Translations/ja.lproj/Localizable.strings @@ -415,6 +415,8 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; +"APP_STARTUP_EXIT" = "Exit"; "DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/nl.lproj/Localizable.strings b/Session/Meta/Translations/nl.lproj/Localizable.strings index 2597356f0..3f071ad99 100644 --- a/Session/Meta/Translations/nl.lproj/Localizable.strings +++ b/Session/Meta/Translations/nl.lproj/Localizable.strings @@ -415,6 +415,8 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; +"APP_STARTUP_EXIT" = "Exit"; "DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/pl.lproj/Localizable.strings b/Session/Meta/Translations/pl.lproj/Localizable.strings index 190af0988..dd56d5cd4 100644 --- a/Session/Meta/Translations/pl.lproj/Localizable.strings +++ b/Session/Meta/Translations/pl.lproj/Localizable.strings @@ -415,6 +415,8 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; +"APP_STARTUP_EXIT" = "Exit"; "DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings index 3f9740a6e..bdd6a7f9a 100644 --- a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings +++ b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings @@ -415,6 +415,8 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; +"APP_STARTUP_EXIT" = "Exit"; "DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/ru.lproj/Localizable.strings b/Session/Meta/Translations/ru.lproj/Localizable.strings index 648eb63da..bfaf6bb8c 100644 --- a/Session/Meta/Translations/ru.lproj/Localizable.strings +++ b/Session/Meta/Translations/ru.lproj/Localizable.strings @@ -415,6 +415,8 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; +"APP_STARTUP_EXIT" = "Exit"; "DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/si.lproj/Localizable.strings b/Session/Meta/Translations/si.lproj/Localizable.strings index 2e06c99a0..c35173c5b 100644 --- a/Session/Meta/Translations/si.lproj/Localizable.strings +++ b/Session/Meta/Translations/si.lproj/Localizable.strings @@ -415,6 +415,8 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; +"APP_STARTUP_EXIT" = "Exit"; "DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/sk.lproj/Localizable.strings b/Session/Meta/Translations/sk.lproj/Localizable.strings index b140447f4..8e5f5db93 100644 --- a/Session/Meta/Translations/sk.lproj/Localizable.strings +++ b/Session/Meta/Translations/sk.lproj/Localizable.strings @@ -415,6 +415,8 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; +"APP_STARTUP_EXIT" = "Exit"; "DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/sv.lproj/Localizable.strings b/Session/Meta/Translations/sv.lproj/Localizable.strings index dfb732242..dfdb07bd1 100644 --- a/Session/Meta/Translations/sv.lproj/Localizable.strings +++ b/Session/Meta/Translations/sv.lproj/Localizable.strings @@ -415,6 +415,8 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; +"APP_STARTUP_EXIT" = "Exit"; "DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/th.lproj/Localizable.strings b/Session/Meta/Translations/th.lproj/Localizable.strings index c720946db..0431a2c30 100644 --- a/Session/Meta/Translations/th.lproj/Localizable.strings +++ b/Session/Meta/Translations/th.lproj/Localizable.strings @@ -415,6 +415,8 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; +"APP_STARTUP_EXIT" = "Exit"; "DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings index 4efaa9b08..cbe4e9604 100644 --- a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings +++ b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings @@ -415,6 +415,8 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; +"APP_STARTUP_EXIT" = "Exit"; "DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings index f8735880c..2c1c6c1d0 100644 --- a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings @@ -415,6 +415,8 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; +"APP_STARTUP_EXIT" = "Exit"; "DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings index b2a05e36b..d69d20a71 100644 --- a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings @@ -415,6 +415,8 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; "DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; +"APP_STARTUP_EXIT" = "Exit"; "DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; diff --git a/Session/Notifications/AppNotifications.swift b/Session/Notifications/AppNotifications.swift index 4c818024b..314fee3b5 100644 --- a/Session/Notifications/AppNotifications.swift +++ b/Session/Notifications/AppNotifications.swift @@ -99,6 +99,7 @@ protocol NotificationPresenterAdaptee: AnyObject { sound: Preferences.Sound?, threadVariant: SessionThread.Variant, threadName: String, + applicationState: UIApplication.State, replacingIdentifier: String? ) @@ -116,7 +117,8 @@ extension NotificationPresenterAdaptee { previewType: Preferences.NotificationPreviewType, sound: Preferences.Sound?, threadVariant: SessionThread.Variant, - threadName: String + threadName: String, + applicationState: UIApplication.State ) { notify( category: category, @@ -127,22 +129,16 @@ extension NotificationPresenterAdaptee { sound: sound, threadVariant: threadVariant, threadName: threadName, + applicationState: applicationState, replacingIdentifier: nil ) } } -@objc(OWSNotificationPresenter) -public class NotificationPresenter: NSObject, NotificationsProtocol { - - private let adaptee: NotificationPresenterAdaptee - - @objc - public override init() { - self.adaptee = UserNotificationPresenterAdaptee() - - super.init() +public class NotificationPresenter: NotificationsProtocol { + private let adaptee: NotificationPresenterAdaptee = UserNotificationPresenterAdaptee() + public init() { SwiftSingletons.register(self) } @@ -152,7 +148,12 @@ public class NotificationPresenter: NSObject, NotificationsProtocol { return adaptee.registerNotificationSettings() } - public func notifyUser(_ db: Database, for interaction: Interaction, in thread: SessionThread) { + public func notifyUser( + _ db: Database, + for interaction: Interaction, + in thread: SessionThread, + applicationState: UIApplication.State + ) { let isMessageRequest: Bool = thread.isMessageRequest(db, includeNonVisible: true) // Ensure we should be showing a notification for the thread @@ -244,34 +245,39 @@ public class NotificationPresenter: NSObject, NotificationsProtocol { let fallbackSound: Preferences.Sound = db[.defaultNotificationSound] .defaulting(to: Preferences.Sound.defaultNotificationSound) - DispatchQueue.main.async { - let sound: Preferences.Sound? = self.requestSound( - thread: thread, - fallbackSound: fallbackSound - ) - - notificationBody = MentionUtilities.highlightMentionsNoAttributes( - in: (notificationBody ?? ""), - threadVariant: thread.variant, - currentUserPublicKey: userPublicKey, - currentUserBlindedPublicKey: userBlindedKey - ) - - self.adaptee.notify( - category: category, - title: notificationTitle, - body: (notificationBody ?? ""), - userInfo: userInfo, - previewType: previewType, - sound: sound, - threadVariant: thread.variant, - threadName: groupName, - replacingIdentifier: identifier - ) - } + let sound: Preferences.Sound? = requestSound( + thread: thread, + fallbackSound: fallbackSound, + applicationState: applicationState + ) + + notificationBody = MentionUtilities.highlightMentionsNoAttributes( + in: (notificationBody ?? ""), + threadVariant: thread.variant, + currentUserPublicKey: userPublicKey, + currentUserBlindedPublicKey: userBlindedKey + ) + + self.adaptee.notify( + category: category, + title: notificationTitle, + body: (notificationBody ?? ""), + userInfo: userInfo, + previewType: previewType, + sound: sound, + threadVariant: thread.variant, + threadName: groupName, + applicationState: applicationState, + replacingIdentifier: identifier + ) } - public func notifyUser(_ db: Database, forIncomingCall interaction: Interaction, in thread: SessionThread) { + public func notifyUser( + _ db: Database, + forIncomingCall interaction: Interaction, + in thread: SessionThread, + applicationState: UIApplication.State + ) { // No call notifications for muted or group threads guard Date().timeIntervalSince1970 > (thread.mutedUntilTimestamp ?? 0) else { return } guard @@ -320,28 +326,32 @@ public class NotificationPresenter: NSObject, NotificationsProtocol { let fallbackSound: Preferences.Sound = db[.defaultNotificationSound] .defaulting(to: Preferences.Sound.defaultNotificationSound) + let sound = self.requestSound( + thread: thread, + fallbackSound: fallbackSound, + applicationState: applicationState + ) - DispatchQueue.main.async { - let sound = self.requestSound( - thread: thread, - fallbackSound: fallbackSound - ) - - self.adaptee.notify( - category: category, - title: notificationTitle, - body: (notificationBody ?? ""), - userInfo: userInfo, - previewType: previewType, - sound: sound, - threadVariant: thread.variant, - threadName: senderName, - replacingIdentifier: UUID().uuidString - ) - } + self.adaptee.notify( + category: category, + title: notificationTitle, + body: (notificationBody ?? ""), + userInfo: userInfo, + previewType: previewType, + sound: sound, + threadVariant: thread.variant, + threadName: senderName, + applicationState: applicationState, + replacingIdentifier: UUID().uuidString + ) } - public func notifyUser(_ db: Database, forReaction reaction: Reaction, in thread: SessionThread) { + public func notifyUser( + _ db: Database, + forReaction reaction: Reaction, + in thread: SessionThread, + applicationState: UIApplication.State + ) { let isMessageRequest: Bool = thread.isMessageRequest(db, includeNonVisible: true) // No reaction notifications for muted, group threads or message requests @@ -380,28 +390,31 @@ public class NotificationPresenter: NSObject, NotificationsProtocol { ) let fallbackSound: Preferences.Sound = db[.defaultNotificationSound] .defaulting(to: Preferences.Sound.defaultNotificationSound) - - DispatchQueue.main.async { - let sound = self.requestSound( - thread: thread, - fallbackSound: fallbackSound - ) - - self.adaptee.notify( - category: category, - title: notificationTitle, - body: notificationBody, - userInfo: userInfo, - previewType: previewType, - sound: sound, - threadVariant: thread.variant, - threadName: threadName, - replacingIdentifier: UUID().uuidString - ) - } + let sound = self.requestSound( + thread: thread, + fallbackSound: fallbackSound, + applicationState: applicationState + ) + + self.adaptee.notify( + category: category, + title: notificationTitle, + body: notificationBody, + userInfo: userInfo, + previewType: previewType, + sound: sound, + threadVariant: thread.variant, + threadName: threadName, + applicationState: applicationState, + replacingIdentifier: UUID().uuidString + ) } - public func notifyForFailedSend(_ db: Database, in thread: SessionThread) { + public func notifyForFailedSend( + _ db: Database, + in thread: SessionThread, + applicationState: UIApplication.State + ) { let notificationTitle: String? let previewType: Preferences.NotificationPreviewType = db[.preferencesNotificationPreviewType] .defaulting(to: .defaultPreviewType) @@ -432,24 +445,23 @@ public class NotificationPresenter: NSObject, NotificationsProtocol { ] let fallbackSound: Preferences.Sound = db[.defaultNotificationSound] .defaulting(to: Preferences.Sound.defaultNotificationSound) - - DispatchQueue.main.async { - let sound: Preferences.Sound? = self.requestSound( - thread: thread, - fallbackSound: fallbackSound - ) - - self.adaptee.notify( - category: .errorMessage, - title: notificationTitle, - body: notificationBody, - userInfo: userInfo, - previewType: previewType, - sound: sound, - threadVariant: thread.variant, - threadName: threadName - ) - } + let sound: Preferences.Sound? = self.requestSound( + thread: thread, + fallbackSound: fallbackSound, + applicationState: applicationState + ) + + self.adaptee.notify( + category: .errorMessage, + title: notificationTitle, + body: notificationBody, + userInfo: userInfo, + previewType: previewType, + sound: sound, + threadVariant: thread.variant, + threadName: threadName, + applicationState: applicationState + ) } @objc @@ -471,32 +483,30 @@ public class NotificationPresenter: NSObject, NotificationsProtocol { // MARK: - - var mostRecentNotifications = TruncatedList(maxLength: kAudioNotificationsThrottleCount) + var mostRecentNotifications: Atomic> = Atomic(TruncatedList(maxLength: kAudioNotificationsThrottleCount)) - private func requestSound(thread: SessionThread, fallbackSound: Preferences.Sound) -> Preferences.Sound? { - guard checkIfShouldPlaySound() else { - return nil - } + private func requestSound( + thread: SessionThread, + fallbackSound: Preferences.Sound, + applicationState: UIApplication.State + ) -> Preferences.Sound? { + guard checkIfShouldPlaySound(applicationState: applicationState) else { return nil } return (thread.notificationSound ?? fallbackSound) } - private func checkIfShouldPlaySound() -> Bool { - AssertIsOnMainThread() - - guard UIApplication.shared.applicationState == .active else { return true } + private func checkIfShouldPlaySound(applicationState: UIApplication.State) -> Bool { + guard applicationState == .active else { return true } guard Storage.shared[.playNotificationSoundInForeground] else { return false } let nowMs: UInt64 = UInt64(floor(Date().timeIntervalSince1970 * 1000)) let recentThreshold = nowMs - UInt64(kAudioNotificationsThrottleInterval * Double(kSecondInMs)) - let recentNotifications = mostRecentNotifications.filter { $0 > recentThreshold } + let recentNotifications = mostRecentNotifications.wrappedValue.filter { $0 > recentThreshold } - guard recentNotifications.count < kAudioNotificationsThrottleCount else { - return false - } + guard recentNotifications.count < kAudioNotificationsThrottleCount else { return false } - mostRecentNotifications.append(nowMs) + mostRecentNotifications.mutate { $0.append(nowMs) } return true } } @@ -527,7 +537,11 @@ class NotificationActionHandler { return markAsRead(threadId: threadId) } - func reply(userInfo: [AnyHashable: Any], replyText: String) -> AnyPublisher { + func reply( + userInfo: [AnyHashable: Any], + replyText: String, + applicationState: UIApplication.State + ) -> AnyPublisher { guard let threadId = userInfo[AppNotificationUserInfoKey.threadId] as? String else { return Fail(error: NotificationError.failDebug("threadId was unexpectedly nil")) .eraseToAnyPublisher() @@ -582,7 +596,11 @@ class NotificationActionHandler { case .finished: break case .failure: Storage.shared.read { [weak self] db in - self?.notificationPresenter.notifyForFailedSend(db, in: thread) + self?.notificationPresenter.notifyForFailedSend( + db, + in: thread, + applicationState: applicationState + ) } } } diff --git a/Session/Notifications/UserNotificationsAdaptee.swift b/Session/Notifications/UserNotificationsAdaptee.swift index 4e8711b3e..15c14a53a 100644 --- a/Session/Notifications/UserNotificationsAdaptee.swift +++ b/Session/Notifications/UserNotificationsAdaptee.swift @@ -61,7 +61,7 @@ class UserNotificationConfig { class UserNotificationPresenterAdaptee: NSObject, UNUserNotificationCenterDelegate { private let notificationCenter: UNUserNotificationCenter - private var notifications: [String: UNNotificationRequest] = [:] + private var notifications: Atomic<[String: UNNotificationRequest]> = Atomic([:]) override init() { self.notificationCenter = UNUserNotificationCenter.current() @@ -105,10 +105,9 @@ extension UserNotificationPresenterAdaptee: NotificationPresenterAdaptee { sound: Preferences.Sound?, threadVariant: SessionThread.Variant, threadName: String, + applicationState: UIApplication.State, replacingIdentifier: String? ) { - AssertIsOnMainThread() - let threadIdentifier: String? = (userInfo[AppNotificationUserInfoKey.threadId] as? String) let content = UNMutableNotificationContent() content.categoryIdentifier = category.identifier @@ -119,16 +118,21 @@ extension UserNotificationPresenterAdaptee: NotificationPresenterAdaptee { threadVariant == .community && replacingIdentifier == threadIdentifier ) - let isAppActive = UIApplication.shared.applicationState == .active if let sound = sound, sound != .none { - content.sound = sound.notificationSound(isQuiet: isAppActive) + content.sound = sound.notificationSound(isQuiet: (applicationState == .active)) } let notificationIdentifier: String = (replacingIdentifier ?? UUID().uuidString) - let isReplacingNotification: Bool = (notifications[notificationIdentifier] != nil) + let isReplacingNotification: Bool = (notifications.wrappedValue[notificationIdentifier] != nil) + let shouldPresentNotification: Bool = shouldPresentNotification( + category: category, + applicationState: applicationState, + frontMostViewController: SessionApp.currentlyOpenConversationViewController.wrappedValue, + userInfo: userInfo + ) var trigger: UNNotificationTrigger? - if shouldPresentNotification(category: category, userInfo: userInfo) { + if shouldPresentNotification { if let displayableTitle = title?.filterForDisplay { content.title = displayableTitle } @@ -142,7 +146,7 @@ extension UserNotificationPresenterAdaptee: NotificationPresenterAdaptee { repeats: false ) - let numberExistingNotifications: Int? = notifications[notificationIdentifier]? + let numberExistingNotifications: Int? = notifications.wrappedValue[notificationIdentifier]? .content .userInfo[AppNotificationUserInfoKey.threadNotificationCounter] .asType(Int.self) @@ -180,47 +184,48 @@ extension UserNotificationPresenterAdaptee: NotificationPresenterAdaptee { if isReplacingNotification { cancelNotifications(identifiers: [notificationIdentifier]) } notificationCenter.add(request) - notifications[notificationIdentifier] = request + notifications.mutate { $0[notificationIdentifier] = request } } func cancelNotifications(identifiers: [String]) { - AssertIsOnMainThread() - identifiers.forEach { notifications.removeValue(forKey: $0) } + notifications.mutate { notifications in + identifiers.forEach { notifications.removeValue(forKey: $0) } + } notificationCenter.removeDeliveredNotifications(withIdentifiers: identifiers) notificationCenter.removePendingNotificationRequests(withIdentifiers: identifiers) } func cancelNotification(_ notification: UNNotificationRequest) { - AssertIsOnMainThread() cancelNotifications(identifiers: [notification.identifier]) } func cancelNotifications(threadId: String) { - AssertIsOnMainThread() - for notification in notifications.values { - guard let notificationThreadId = notification.content.userInfo[AppNotificationUserInfoKey.threadId] as? String else { - continue + let notificationsIdsToCancel: [String] = notifications.wrappedValue + .values + .compactMap { notification in + guard + let notificationThreadId: String = notification.content.userInfo[AppNotificationUserInfoKey.threadId] as? String, + notificationThreadId == threadId + else { return nil } + + return notification.identifier } - - guard notificationThreadId == threadId else { - continue - } - - cancelNotification(notification) - } + + cancelNotifications(identifiers: notificationsIdsToCancel) } func clearAllNotifications() { - AssertIsOnMainThread() notificationCenter.removeAllPendingNotificationRequests() notificationCenter.removeAllDeliveredNotifications() } - func shouldPresentNotification(category: AppNotificationCategory, userInfo: [AnyHashable: Any]) -> Bool { - AssertIsOnMainThread() - guard UIApplication.shared.applicationState == .active else { - return true - } + func shouldPresentNotification( + category: AppNotificationCategory, + applicationState: UIApplication.State, + frontMostViewController: UIViewController?, + userInfo: [AnyHashable: Any] + ) -> Bool { + guard applicationState == .active else { return true } guard category == .incomingMessage || category == .errorMessage else { return true @@ -231,7 +236,7 @@ extension UserNotificationPresenterAdaptee: NotificationPresenterAdaptee { return true } - guard let conversationViewController = UIApplication.shared.frontmostViewController as? ConversationVC else { + guard let conversationViewController: ConversationVC = frontMostViewController as? ConversationVC else { return true } @@ -271,7 +276,8 @@ public class UserNotificationActionHandler: NSObject { AssertIsOnMainThread() assert(AppReadiness.isAppReady()) - let userInfo = response.notification.request.content.userInfo + let userInfo: [AnyHashable: Any] = response.notification.request.content.userInfo + let applicationState: UIApplication.State = UIApplication.shared.applicationState switch response.actionIdentifier { case UNNotificationDefaultActionIdentifier: @@ -307,7 +313,7 @@ public class UserNotificationActionHandler: NSObject { .eraseToAnyPublisher() } - return actionHandler.reply(userInfo: userInfo, replyText: textInputResponse.userText) + return actionHandler.reply(userInfo: userInfo, replyText: textInputResponse.userText, applicationState: applicationState) case .showThread: return actionHandler.showThread(userInfo: userInfo) diff --git a/Session/Onboarding/Onboarding.swift b/Session/Onboarding/Onboarding.swift index 5f23d32d4..b09fae7ef 100644 --- a/Session/Onboarding/Onboarding.swift +++ b/Session/Onboarding/Onboarding.swift @@ -123,6 +123,25 @@ enum Onboarding { .eraseToAnyPublisher() } + enum State { + case newUser + case missingName + case completed + + static var current: State { + // If we have no identify information then the user needs to register + guard Identity.userExists() else { return .newUser } + + // If we have no display name then collect one (this can happen if the + // app crashed during onboarding which would leave the user in an invalid + // state with no display name) + guard !Profile.fetchOrCreateCurrentUser().name.isEmpty else { return .missingName } + + // Otherwise we have enough for a full user and can start the app + return .completed + } + } + enum Flow { case register, recover, link diff --git a/Session/Settings/HelpViewModel.swift b/Session/Settings/HelpViewModel.swift index 387f064bc..b413e0fc2 100644 --- a/Session/Settings/HelpViewModel.swift +++ b/Session/Settings/HelpViewModel.swift @@ -177,23 +177,10 @@ class HelpViewModel: SessionTableViewModel ())? = nil ) { - let version: String = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) - .defaulting(to: "") - #if DEBUG - let commitInfo: String? = (Bundle.main.infoDictionary?["GitCommitHash"] as? String).map { "Commit: \($0)" } - #else - let commitInfo: String? = nil - #endif - - let versionInfo: [String] = [ - "iOS \(UIDevice.current.systemVersion)", - "App: \(version)", - "libSession: \(SessionUtil.libSessionVersion)", - commitInfo - ].compactMap { $0 } - OWSLogger.info("[Version] \(versionInfo.joined(separator: ", "))") + OWSLogger.info("[Version] \(SessionApp.versionInfo)") DDLog.flushLog() let logFilePaths: [String] = AppEnvironment.shared.fileLogger.logFileManager.sortedLogFilePaths @@ -216,7 +203,7 @@ class HelpViewModel: SessionTableViewModel = Atomic([:]) - - - private static let workQueue = DispatchQueue(label: "IP2Country.workQueue", qos: .utility) // It's important that this is a serial queue static var isInitialized = false - // MARK: Tables + var countryNamesCache: Atomic<[String: String]> = Atomic([:]) + + // MARK: - Tables /// This table has two columns: the "network" column and the "registered_country_geoname_id" column. The network column contains /// the **lower** bound of an IP range and the "registered_country_geoname_id" column contains the ID of the country corresponding /// to that range. We look up an IP by finding the first index in the network column where the value is greater than the IP we're looking @@ -58,13 +56,12 @@ final class IP2Country { } @objc func populateCacheIfNeededAsync() { - // This has to be sync since the `countryNamesCache` dict doesn't like async access - IP2Country.workQueue.sync { [weak self] in - _ = self?.populateCacheIfNeeded() + DispatchQueue.global(qos: .utility).async { [weak self] in + self?.populateCacheIfNeeded() } } - func populateCacheIfNeeded() -> Bool { + @discardableResult func populateCacheIfNeeded() -> Bool { guard let pathToDisplay: [Snode] = OnionRequestAPI.paths.first else { return false } countryNamesCache.mutate { [weak self] cache in diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift index 6497f7e3e..cd85c095b 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift @@ -46,6 +46,10 @@ extension MessageReceiver { private static func handleNewCallMessage(_ db: Database, message: CallMessage) throws { SNLog("[Calls] Received pre-offer message.") + // Determine whether the app is active based on the prefs rather than the UIApplication state to avoid + // requiring main-thread execution + let isMainAppActive: Bool = (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) + // It is enough just ignoring the pre offers, other call messages // for this call would be dropped because of no Session call instance guard @@ -69,7 +73,8 @@ extension MessageReceiver { .notifyUser( db, forIncomingCall: interaction, - in: thread + in: thread, + applicationState: (isMainAppActive ? .active : .background) ) } } @@ -86,7 +91,8 @@ extension MessageReceiver { .notifyUser( db, forIncomingCall: interaction, - in: thread + in: thread, + applicationState: (isMainAppActive ? .active : .background) ) } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift index 2958b3548..34ce81e73 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift @@ -331,7 +331,8 @@ extension MessageReceiver { .notifyUser( db, for: interaction, - in: thread + in: thread, + applicationState: (isMainAppActive ? .active : .background) ) return interactionId @@ -372,6 +373,9 @@ extension MessageReceiver { switch reaction.kind { case .react: + // Determine whether the app is active based on the prefs rather than the UIApplication state to avoid + // requiring main-thread execution + let isMainAppActive: Bool = (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) let timestampMs: Int64 = Int64(messageSentTimestamp * 1000) let currentUserPublicKey: String = getUserHexEncodedPublicKey(db) let reaction: Reaction = try Reaction( @@ -398,7 +402,8 @@ extension MessageReceiver { .notifyUser( db, forReaction: reaction, - in: thread + in: thread, + applicationState: (isMainAppActive ? .active : .background) ) } case .remove: diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/NotificationsProtocol.swift b/SessionMessagingKit/Sending & Receiving/Notifications/NotificationsProtocol.swift index 51e2951ac..f946eb476 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/NotificationsProtocol.swift +++ b/SessionMessagingKit/Sending & Receiving/Notifications/NotificationsProtocol.swift @@ -4,9 +4,9 @@ import Foundation import GRDB public protocol NotificationsProtocol { - func notifyUser(_ db: Database, for interaction: Interaction, in thread: SessionThread) - func notifyUser(_ db: Database, forIncomingCall interaction: Interaction, in thread: SessionThread) - func notifyUser(_ db: Database, forReaction reaction: Reaction, in thread: SessionThread) + func notifyUser(_ db: Database, for interaction: Interaction, in thread: SessionThread, applicationState: UIApplication.State) + func notifyUser(_ db: Database, forIncomingCall interaction: Interaction, in thread: SessionThread, applicationState: UIApplication.State) + func notifyUser(_ db: Database, forReaction reaction: Reaction, in thread: SessionThread, applicationState: UIApplication.State) func cancelNotifications(identifiers: [String]) func clearAllNotifications() } diff --git a/SessionMessagingKit/Utilities/DeviceSleepManager.swift b/SessionMessagingKit/Utilities/DeviceSleepManager.swift index ff0d470b8..11ec81bc0 100644 --- a/SessionMessagingKit/Utilities/DeviceSleepManager.swift +++ b/SessionMessagingKit/Utilities/DeviceSleepManager.swift @@ -24,7 +24,7 @@ public class DeviceSleepManager: NSObject { return "SleepBlock(\(String(reflecting: blockObject)))" } - init(blockObject: NSObject) { + init(blockObject: NSObject?) { self.blockObject = blockObject } } @@ -51,14 +51,14 @@ public class DeviceSleepManager: NSObject { } @objc - public func addBlock(blockObject: NSObject) { + public func addBlock(blockObject: NSObject?) { blocks.append(SleepBlock(blockObject: blockObject)) ensureSleepBlocking() } @objc - public func removeBlock(blockObject: NSObject) { + public func removeBlock(blockObject: NSObject?) { blocks = blocks.filter { $0.blockObject != nil && $0.blockObject != blockObject } diff --git a/SessionNotificationServiceExtension/NSENotificationPresenter.swift b/SessionNotificationServiceExtension/NSENotificationPresenter.swift index 9c625f9b6..acff494bb 100644 --- a/SessionNotificationServiceExtension/NSENotificationPresenter.swift +++ b/SessionNotificationServiceExtension/NSENotificationPresenter.swift @@ -9,7 +9,7 @@ import SessionMessagingKit public class NSENotificationPresenter: NSObject, NotificationsProtocol { private var notifications: [String: UNNotificationRequest] = [:] - public func notifyUser(_ db: Database, for interaction: Interaction, in thread: SessionThread) { + public func notifyUser(_ db: Database, for interaction: Interaction, in thread: SessionThread, applicationState: UIApplication.State) { let isMessageRequest: Bool = thread.isMessageRequest(db, includeNonVisible: true) // Ensure we should be showing a notification for the thread @@ -124,7 +124,7 @@ public class NSENotificationPresenter: NSObject, NotificationsProtocol { ) } - public func notifyUser(_ db: Database, forIncomingCall interaction: Interaction, in thread: SessionThread) { + public func notifyUser(_ db: Database, forIncomingCall interaction: Interaction, in thread: SessionThread, applicationState: UIApplication.State) { // No call notifications for muted or group threads guard Date().timeIntervalSince1970 > (thread.mutedUntilTimestamp ?? 0) else { return } guard @@ -180,7 +180,7 @@ public class NSENotificationPresenter: NSObject, NotificationsProtocol { ) } - public func notifyUser(_ db: Database, forReaction reaction: Reaction, in thread: SessionThread) { + public func notifyUser(_ db: Database, forReaction reaction: Reaction, in thread: SessionThread, applicationState: UIApplication.State) { let isMessageRequest: Bool = thread.isMessageRequest(db, includeNonVisible: true) // No reaction notifications for muted, group threads or message requests diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index 3a943047e..59b31366b 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -158,7 +158,8 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension .notifyUser( db, forIncomingCall: interaction, - in: thread + in: thread, + applicationState: .background ) } } diff --git a/SessionShareExtension/ThreadPickerVC.swift b/SessionShareExtension/ThreadPickerVC.swift index 4013936b7..2facdea62 100644 --- a/SessionShareExtension/ThreadPickerVC.swift +++ b/SessionShareExtension/ThreadPickerVC.swift @@ -10,7 +10,9 @@ import SessionMessagingKit final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableViewDelegate, AttachmentApprovalViewControllerDelegate { private let viewModel: ThreadPickerViewModel = ThreadPickerViewModel() - private var dataChangeObservable: DatabaseCancellable? + private var dataChangeObservable: DatabaseCancellable? { + didSet { oldValue?.cancel() } // Cancel the old observable if there was one + } private var hasLoadedInitialData: Bool = false var shareNavController: ShareNavController? @@ -79,8 +81,7 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - // Stop observing database changes - dataChangeObservable?.cancel() + stopObservingChanges() } @objc func applicationDidBecomeActive(_ notification: Notification) { @@ -91,8 +92,7 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView } @objc func applicationDidResignActive(_ notification: Notification) { - // Stop observing database changes - dataChangeObservable?.cancel() + stopObservingChanges() } // MARK: Layout @@ -104,6 +104,8 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView // MARK: - Updating private func startObservingChanges() { + guard dataChangeObservable == nil else { return } + // Start observing for data changes dataChangeObservable = Storage.shared.start( viewModel.observableViewData, @@ -115,6 +117,10 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView ) } + private func stopObservingChanges() { + dataChangeObservable = nil + } + private func handleUpdates(_ updatedViewData: [SessionThreadViewModel]) { // Ensure the first load runs without animations (if we don't do this the cells will animate // in from a frame of CGRect.zero) diff --git a/SessionUtilitiesKit/Database/Storage.swift b/SessionUtilitiesKit/Database/Storage.swift index ce25d5b86..4cd35ccce 100644 --- a/SessionUtilitiesKit/Database/Storage.swift +++ b/SessionUtilitiesKit/Database/Storage.swift @@ -361,10 +361,26 @@ open class Storage { // MARK: - Functions + private static func logIfNeeded(_ error: Error, isWrite: Bool) { + switch error { + case DatabaseError.SQLITE_ABORT: + let message: String = ((error as? DatabaseError)?.message ?? "Unknown") + SNLog("[Storage] Database \(isWrite ? "write" : "read") failed due to error: \(message)") + + default: break + } + } + + private static func logIfNeeded(_ error: Error, isWrite: Bool) -> T? { + logIfNeeded(error, isWrite: isWrite) + return nil + } + @discardableResult public final func write(updates: (Database) throws -> T?) -> T? { guard isValid, let dbWriter: DatabaseWriter = dbWriter else { return nil } - return try? dbWriter.write(updates) + do { return try dbWriter.write(updates) } + catch { return Storage.logIfNeeded(error, isWrite: true) } } open func writeAsync(updates: @escaping (Database) throws -> T) { @@ -377,6 +393,11 @@ open class Storage { dbWriter.asyncWrite( updates, completion: { db, result in + switch result { + case .failure(let error): Storage.logIfNeeded(error, isWrite: true) + default: break + } + try? completion(db, result) } ) @@ -400,7 +421,10 @@ open class Storage { return Deferred { Future { resolver in do { resolver(Result.success(try dbWriter.write(updates))) } - catch { resolver(Result.failure(error)) } + catch { + Storage.logIfNeeded(error, isWrite: true) + resolver(Result.failure(error)) + } } }.eraseToAnyPublisher() } @@ -423,7 +447,10 @@ open class Storage { return Deferred { Future { resolver in do { resolver(Result.success(try dbWriter.read(value))) } - catch { resolver(Result.failure(error)) } + catch { + Storage.logIfNeeded(error, isWrite: false) + resolver(Result.failure(error)) + } } }.eraseToAnyPublisher() } @@ -431,7 +458,8 @@ open class Storage { @discardableResult public final func read(_ value: (Database) throws -> T?) -> T? { guard isValid, let dbWriter: DatabaseWriter = dbWriter else { return nil } - return try? dbWriter.read(value) + do { return try dbWriter.read(value) } + catch { return Storage.logIfNeeded(error, isWrite: false) } } /// Rever to the `ValueObservation.start` method for full documentation diff --git a/SignalUtilitiesKit/Utilities/AppSetup.swift b/SignalUtilitiesKit/Utilities/AppSetup.swift index 9e89ed175..efc6d1c25 100644 --- a/SignalUtilitiesKit/Utilities/AppSetup.swift +++ b/SignalUtilitiesKit/Utilities/AppSetup.swift @@ -90,12 +90,10 @@ public enum AppSetup { // method when calling within a database read/write closure) Storage.shared.read { db in SessionUtil.refreshingUserConfigsEnabled(db) } - DispatchQueue.main.async { - migrationsCompletion(result, (needsConfigSync || SessionUtil.needsSync)) - - // The 'if' is only there to prevent the "variable never read" warning from showing - if backgroundTask != nil { backgroundTask = nil } - } + migrationsCompletion(result, (needsConfigSync || SessionUtil.needsSync)) + + // The 'if' is only there to prevent the "variable never read" warning from showing + if backgroundTask != nil { backgroundTask = nil } } ) } diff --git a/SignalUtilitiesKit/Utilities/NoopNotificationsManager.swift b/SignalUtilitiesKit/Utilities/NoopNotificationsManager.swift index 775319890..a832873b2 100644 --- a/SignalUtilitiesKit/Utilities/NoopNotificationsManager.swift +++ b/SignalUtilitiesKit/Utilities/NoopNotificationsManager.swift @@ -8,15 +8,15 @@ import SignalCoreKit public class NoopNotificationsManager: NotificationsProtocol { public init() {} - public func notifyUser(_ db: Database, for interaction: Interaction, in thread: SessionThread) { + public func notifyUser(_ db: Database, for interaction: Interaction, in thread: SessionThread, applicationState: UIApplication.State) { owsFailDebug("") } - public func notifyUser(_ db: Database, forIncomingCall interaction: Interaction, in thread: SessionThread) { + public func notifyUser(_ db: Database, forIncomingCall interaction: Interaction, in thread: SessionThread, applicationState: UIApplication.State) { owsFailDebug("") } - public func notifyUser(_ db: Database, forReaction reaction: Reaction, in thread: SessionThread) { + public func notifyUser(_ db: Database, forReaction reaction: Reaction, in thread: SessionThread, applicationState: UIApplication.State) { owsFailDebug("") } From 6cf7cc42abcea4ae15e9528efd3821135e144f9b Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 28 Jun 2023 18:03:40 +1000 Subject: [PATCH 098/135] Fixed up the remaining reported internal testing issues Removed the 'readPublisherFlatMap/writePublisherFlatMap' functions as they easily resulted in behaviours which held up database threads Tweaked the logic around starting the open group pollers to avoid an unlikely atomic lock blocks Updated some logic to avoid accessing database read threads for longer than needed Updated the OpenGroupManager to only update the 'seqNo' value for valid messages Cleaned up some double Atomic wrapped instances which had some weird access behaviours Fixed an issue where a database read thread could have been started within a database write thread Fixed an issue where the ReplaySubject might not emit values in some cases --- Session.xcodeproj/project.pbxproj | 12 +- .../Calls/Call Management/SessionCall.swift | 16 +- .../Views & Modals/IncomingCallBanner.swift | 2 +- Session/Closed Groups/EditClosedGroupVC.swift | 25 +- Session/Closed Groups/NewClosedGroupVC.swift | 6 +- .../ConversationVC+Interaction.swift | 22 +- .../Conversations/ConversationViewModel.swift | 9 +- Session/Home/HomeVC.swift | 10 +- Session/Home/HomeViewModel.swift | 37 +- Session/Meta/AppDelegate.swift | 26 +- .../Translations/de.lproj/Localizable.strings | 4 +- .../Translations/en.lproj/Localizable.strings | 4 +- .../Translations/es.lproj/Localizable.strings | 4 +- .../Translations/fa.lproj/Localizable.strings | 4 +- .../Translations/fi.lproj/Localizable.strings | 4 +- .../Translations/fr.lproj/Localizable.strings | 4 +- .../Translations/hi.lproj/Localizable.strings | 4 +- .../Translations/hr.lproj/Localizable.strings | 4 +- .../id-ID.lproj/Localizable.strings | 4 +- .../Translations/it.lproj/Localizable.strings | 4 +- .../Translations/ja.lproj/Localizable.strings | 4 +- .../Translations/nl.lproj/Localizable.strings | 4 +- .../Translations/pl.lproj/Localizable.strings | 4 +- .../pt_BR.lproj/Localizable.strings | 4 +- .../Translations/ru.lproj/Localizable.strings | 4 +- .../Translations/si.lproj/Localizable.strings | 4 +- .../Translations/sk.lproj/Localizable.strings | 4 +- .../Translations/sv.lproj/Localizable.strings | 4 +- .../Translations/th.lproj/Localizable.strings | 4 +- .../vi-VN.lproj/Localizable.strings | 4 +- .../zh-Hant.lproj/Localizable.strings | 4 +- .../zh_CN.lproj/Localizable.strings | 4 +- Session/Open Groups/JoinOpenGroupVC.swift | 11 +- .../Open Groups/OpenGroupSuggestionGrid.swift | 39 +- Session/Settings/NukeDataModal.swift | 7 +- SessionMessagingKit/Calls/WebRTCSession.swift | 8 +- .../Database/Models/Profile.swift | 12 - .../Open Groups/OpenGroupManager.swift | 438 ++++++------ .../Open Groups/Types/OpenGroupAPIError.swift | 2 + SessionMessagingKit/SMKDependencies.swift | 2 +- ...essageReceiver+ConfigurationMessages.swift | 17 +- .../MessageSender+ClosedGroups.swift | 626 +++++++++--------- .../Pollers/OpenGroupPoller.swift | 63 +- .../SessionUtil+UserGroups.swift | 17 +- .../SessionUtil/SessionUtil.swift | 49 +- .../Utilities/ProfileManager.swift | 2 +- .../Open Groups/OpenGroupManagerSpec.swift | 4 +- .../_TestUtilities/MockOGMCache.swift | 6 +- SessionSnodeKit/SSKDependencies.swift | 2 +- .../Combine/ReplaySubject.swift | 23 +- SessionUtilitiesKit/Database/Storage.swift | 18 - .../General/Dependencies.swift | 41 +- SessionUtilitiesKit/General/General.swift | 31 +- 53 files changed, 906 insertions(+), 765 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 211bfa740..16cef78c5 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -6417,7 +6417,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 410; + CURRENT_PROJECT_VERSION = 411; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6489,7 +6489,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 410; + CURRENT_PROJECT_VERSION = 411; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6554,7 +6554,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 410; + CURRENT_PROJECT_VERSION = 411; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6628,7 +6628,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 410; + CURRENT_PROJECT_VERSION = 411; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -7536,7 +7536,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 410; + CURRENT_PROJECT_VERSION = 411; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -7607,7 +7607,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 410; + CURRENT_PROJECT_VERSION = 411; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index 070545ef9..53b392657 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -246,11 +246,7 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate { ) // Start the timeout timer for the call .handleEvents(receiveOutput: { [weak self] _ in self?.setupTimeoutTimer() }) - .flatMap { _ in - Storage.shared.writePublisherFlatMap { db -> AnyPublisher in - webRTCSession.sendOffer(db, to: sessionId) - } - } + .flatMap { _ in webRTCSession.sendOffer(to: thread) } .sinkUntilComplete() } @@ -431,10 +427,12 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate { let sessionId: String = self.sessionId let webRTCSession: WebRTCSession = self.webRTCSession - Storage.shared - .readPublisherFlatMap { db in - webRTCSession.sendOffer(db, to: sessionId, isRestartingICEConnection: true) - } + guard let thread: SessionThread = Storage.shared.read({ db in try SessionThread.fetchOne(db, id: sessionId) }) else { + return + } + + webRTCSession + .sendOffer(to: thread, isRestartingICEConnection: true) .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .sinkUntilComplete() } diff --git a/Session/Calls/Views & Modals/IncomingCallBanner.swift b/Session/Calls/Views & Modals/IncomingCallBanner.swift index b6e7ba8b9..98fdbf424 100644 --- a/Session/Calls/Views & Modals/IncomingCallBanner.swift +++ b/Session/Calls/Views & Modals/IncomingCallBanner.swift @@ -114,7 +114,7 @@ final class IncomingCallBanner: UIView, UIGestureRecognizerDelegate { publicKey: call.sessionId, threadVariant: .contact, customImageData: nil, - profile: Profile.fetchOrCreate(id: call.sessionId), + profile: Storage.shared.read { db in Profile.fetchOrCreate(db, id: call.sessionId) }, additionalProfile: nil ) displayNameLabel.text = call.contactName diff --git a/Session/Closed Groups/EditClosedGroupVC.swift b/Session/Closed Groups/EditClosedGroupVC.swift index 88f87fbc9..11161e534 100644 --- a/Session/Closed Groups/EditClosedGroupVC.swift +++ b/Session/Closed Groups/EditClosedGroupVC.swift @@ -464,21 +464,18 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat ModalActivityIndicatorViewController.present(fromViewController: navigationController) { _ in Storage.shared - .writePublisherFlatMap { db -> AnyPublisher in - if !updatedMemberIds.contains(userPublicKey) { - try MessageSender.leave( - db, - groupPublicKey: threadId, - deleteThread: true - ) - - return Just(()) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - } - - return MessageSender.update( + .writePublisher { db in + guard updatedMemberIds.contains(userPublicKey) else { return } + + try MessageSender.leave( db, + groupPublicKey: threadId, + deleteThread: true + ) + + } + .flatMap { + MessageSender.update( groupPublicKey: threadId, with: updatedMemberIds, name: updatedName diff --git a/Session/Closed Groups/NewClosedGroupVC.swift b/Session/Closed Groups/NewClosedGroupVC.swift index 3970e52c2..deb2165f7 100644 --- a/Session/Closed Groups/NewClosedGroupVC.swift +++ b/Session/Closed Groups/NewClosedGroupVC.swift @@ -332,10 +332,8 @@ final class NewClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegate let selectedContacts = self.selectedContacts let message: String? = (selectedContacts.count > 20 ? "GROUP_CREATION_PLEASE_WAIT".localized() : nil) ModalActivityIndicatorViewController.present(fromViewController: navigationController!, message: message) { [weak self] _ in - Storage.shared - .writePublisherFlatMap { db in - try MessageSender.createClosedGroup(db, name: name, members: selectedContacts) - } + MessageSender + .createClosedGroup(name: name, members: selectedContacts) .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .receive(on: DispatchQueue.main) .sinkUntilComplete( diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 504067efd..c3073d9ce 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -1181,7 +1181,12 @@ extension ConversationVC: ) } - func react(_ cellViewModel: MessageViewModel, with emoji: String, remove: Bool) { + func react( + _ cellViewModel: MessageViewModel, + with emoji: String, + remove: Bool, + using dependencies: Dependencies = Dependencies() + ) { guard self.viewModel.threadData.threadIsMessageRequest != true && ( cellViewModel.variant == .standardIncoming || @@ -1193,7 +1198,7 @@ extension ConversationVC: let threadVariant: SessionThread.Variant = self.viewModel.threadData.threadVariant let openGroupRoom: String? = self.viewModel.threadData.openGroupRoomToken let sentTimestamp: Int64 = SnodeAPI.currentOffsetTimestampMs() - let recentReactionTimestamps: [Int64] = General.cache.wrappedValue.recentReactionTimestamps + let recentReactionTimestamps: [Int64] = dependencies.generalCache.recentReactionTimestamps guard recentReactionTimestamps.count < 20 || @@ -1211,7 +1216,7 @@ extension ConversationVC: return } - General.cache.mutate { + dependencies.mutableGeneralCache.mutate { $0.recentReactionTimestamps = Array($0.recentReactionTimestamps .suffix(19)) .appending(sentTimestamp) @@ -1522,7 +1527,7 @@ extension ConversationVC: } Storage.shared - .writePublisherFlatMap { db in + .writePublisher { db in OpenGroupManager.shared.add( db, roomToken: room, @@ -1531,6 +1536,15 @@ extension ConversationVC: calledFromConfigHandling: false ) } + .flatMap { successfullyAddedGroup in + OpenGroupManager.shared.performInitialRequestsAfterAdd( + successfullyAddedGroup: successfullyAddedGroup, + roomToken: room, + server: server, + publicKey: publicKey, + calledFromConfigHandling: false + ) + } .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .receive(on: DispatchQueue.main) .sinkUntilComplete( diff --git a/Session/Conversations/ConversationViewModel.swift b/Session/Conversations/ConversationViewModel.swift index a5dcf5086..cd9c8b8a5 100644 --- a/Session/Conversations/ConversationViewModel.swift +++ b/Session/Conversations/ConversationViewModel.swift @@ -63,6 +63,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { init(threadId: String, threadVariant: SessionThread.Variant, focusedInteractionInfo: Interaction.TimestampInfo?) { typealias InitialData = ( + currentUserPublicKey: String, initialUnreadInteractionInfo: Interaction.TimestampInfo?, threadIsBlocked: Bool, currentUserIsClosedGroupMember: Bool?, @@ -73,6 +74,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { let initialData: InitialData? = Storage.shared.read { db -> InitialData in let interaction: TypedTableAlias = TypedTableAlias() let groupMember: TypedTableAlias = TypedTableAlias() + let currentUserPublicKey: String = getUserHexEncodedPublicKey(db) // If we have a specified 'focusedInteractionInfo' then use that, otherwise retrieve the oldest // unread interaction and start focused around that one @@ -94,7 +96,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { let currentUserIsClosedGroupMember: Bool? = (![.legacyGroup, .group].contains(threadVariant) ? nil : GroupMember .filter(groupMember[.groupId] == threadId) - .filter(groupMember[.profileId] == getUserHexEncodedPublicKey(db)) + .filter(groupMember[.profileId] == currentUserPublicKey) .filter(groupMember[.role] == GroupMember.Role.standard) .isNotEmpty(db) ) @@ -112,6 +114,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { ) return ( + currentUserPublicKey, initialUnreadInteractionInfo, threadIsBlocked, currentUserIsClosedGroupMember, @@ -128,7 +131,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { self.threadData = SessionThreadViewModel( threadId: threadId, threadVariant: threadVariant, - threadIsNoteToSelf: (self.threadId == getUserHexEncodedPublicKey()), + threadIsNoteToSelf: (initialData?.currentUserPublicKey == threadId), threadIsBlocked: initialData?.threadIsBlocked, currentUserIsClosedGroupMember: initialData?.currentUserIsClosedGroupMember, openGroupPermissions: initialData?.openGroupPermissions @@ -141,7 +144,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { // distinct stutter) self.pagedDataObserver = self.setupPagedObserver( for: threadId, - userPublicKey: getUserHexEncodedPublicKey(), + userPublicKey: (initialData?.currentUserPublicKey ?? getUserHexEncodedPublicKey()), blindedPublicKey: SessionThread.getUserHexEncodedBlindedKey( threadId: threadId, threadVariant: threadVariant diff --git a/Session/Home/HomeVC.swift b/Session/Home/HomeVC.swift index d62e359f0..5ddb4823c 100644 --- a/Session/Home/HomeVC.swift +++ b/Session/Home/HomeVC.swift @@ -228,7 +228,7 @@ final class HomeVC: BaseVC, SessionUtilRespondingViewController, UITableViewData // Preparation SessionApp.homeViewController.mutate { $0 = self } - updateNavBarButtons() + updateNavBarButtons(userProfile: self.viewModel.state.userProfile) setUpNavBarSessionHeading() // Recovery phrase reminder @@ -382,7 +382,7 @@ final class HomeVC: BaseVC, SessionUtilRespondingViewController, UITableViewData } if updatedState.userProfile != self.viewModel.state.userProfile { - updateNavBarButtons() + updateNavBarButtons(userProfile: updatedState.userProfile) } // Update the 'view seed' UI @@ -489,17 +489,17 @@ final class HomeVC: BaseVC, SessionUtilRespondingViewController, UITableViewData } } - private func updateNavBarButtons() { + private func updateNavBarButtons(userProfile: Profile) { // Profile picture view let profilePictureView = ProfilePictureView(size: .navigation) profilePictureView.accessibilityIdentifier = "User settings" profilePictureView.accessibilityLabel = "User settings" profilePictureView.isAccessibilityElement = true profilePictureView.update( - publicKey: getUserHexEncodedPublicKey(), + publicKey: userProfile.id, threadVariant: .contact, customImageData: nil, - profile: Profile.fetchOrCreateCurrentUser(), + profile: userProfile, additionalProfile: nil ) diff --git a/Session/Home/HomeViewModel.swift b/Session/Home/HomeViewModel.swift index 675317605..f081d5594 100644 --- a/Session/Home/HomeViewModel.swift +++ b/Session/Home/HomeViewModel.swift @@ -26,32 +26,39 @@ public class HomeViewModel { let showViewedSeedBanner: Bool let hasHiddenMessageRequests: Bool let unreadMessageRequestThreadCount: Int - let userProfile: Profile? - - init( - showViewedSeedBanner: Bool = !Storage.shared[.hasViewedSeed], - hasHiddenMessageRequests: Bool = Storage.shared[.hasHiddenMessageRequests], - unreadMessageRequestThreadCount: Int = 0, - userProfile: Profile? = nil - ) { - self.showViewedSeedBanner = showViewedSeedBanner - self.hasHiddenMessageRequests = hasHiddenMessageRequests - self.unreadMessageRequestThreadCount = unreadMessageRequestThreadCount - self.userProfile = userProfile - } + let userProfile: Profile } // MARK: - Initialization init() { - self.state = State() + typealias InitialData = ( + showViewedSeedBanner: Bool, + hasHiddenMessageRequests: Bool, + profile: Profile + ) + + let initialData: InitialData? = Storage.shared.read { db -> InitialData in + ( + !db[.hasViewedSeed], + db[.hasHiddenMessageRequests], + Profile.fetchOrCreateCurrentUser(db) + ) + } + + self.state = State( + showViewedSeedBanner: (initialData?.showViewedSeedBanner ?? true), + hasHiddenMessageRequests: (initialData?.hasHiddenMessageRequests ?? false), + unreadMessageRequestThreadCount: 0, + userProfile: (initialData?.profile ?? Profile.fetchOrCreateCurrentUser()) + ) self.pagedDataObserver = nil // Note: Since this references self we need to finish initializing before setting it, we // also want to skip the initial query and trigger it async so that the push animation // doesn't stutter (it should load basically immediately but without this there is a // distinct stutter) - let userPublicKey: String = getUserHexEncodedPublicKey() + let userPublicKey: String = self.state.userProfile.id let thread: TypedTableAlias = TypedTableAlias() self.pagedDataObserver = PagedDatabaseObserver( pagedTable: SessionThread.self, diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index ae7592fef..21563c96b 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -13,7 +13,7 @@ import SignalCoreKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate { - private static let maxRootViewControllerInitialQueryDuration: TimeInterval = 5 + private static let maxRootViewControllerInitialQueryDuration: TimeInterval = 10 var window: UIWindow? var backgroundSnapshotBlockerWindow: UIWindow? @@ -73,7 +73,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD }, migrationsCompletion: { [weak self] result, needsConfigSync in if case .failure(let error) = result { - self?.showFailedStartupAlert(calledFrom: .finishLaunching, error: .migrationError(error)) + DispatchQueue.main.async { + self?.showFailedStartupAlert(calledFrom: .finishLaunching, error: .databaseError(error)) + } return } @@ -150,7 +152,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD migrationsCompletion: { [weak self] result, needsConfigSync in if case .failure(let error) = result { DispatchQueue.main.async { - self?.showFailedStartupAlert(calledFrom: .enterForeground, error: .migrationError(error)) + self?.showFailedStartupAlert(calledFrom: .enterForeground, error: .databaseError(error)) } return } @@ -372,8 +374,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD }) switch error { + // Don't offer the 'Restore' option if it was a 'startupFailed' error as a restore is unlikely to + // resolve it (most likely the database is locked or the key was somehow lost - safer to get them + // to restart and manually reinstall/restore) + case .databaseError(StorageError.startupFailed): break + // Offer the 'Restore' option if it was a migration error - case .migrationError: + case .databaseError: alert.addAction(UIAlertAction(title: "vc_restore_title".localized(), style: .destructive) { _ in if SUKLegacy.hasLegacyDatabaseFile { // Remove the legacy database and any message hashes that have been migrated to the new DB @@ -402,7 +409,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD migrationsCompletion: { [weak self] result, needsConfigSync in switch result { case .failure: - self?.showFailedStartupAlert(calledFrom: lifecycleMethod, error: .failedToRestore) + DispatchQueue.main.async { + self?.showFailedStartupAlert(calledFrom: lifecycleMethod, error: .failedToRestore) + } case .success: self?.completePostMigrationSetup(calledFrom: lifecycleMethod, needsConfigSync: needsConfigSync) @@ -848,16 +857,15 @@ private enum LifecycleMethod { // MARK: - StartupError private enum StartupError: Error { - case databaseStartupError - case migrationError(Error) + case databaseError(Error) case failedToRestore case startupTimeout var message: String { switch self { - case .databaseStartupError: return "DATABASE_STARTUP_FAILED".localized() + case .databaseError(StorageError.startupFailed): return "DATABASE_STARTUP_FAILED".localized() case .failedToRestore: return "DATABASE_RESTORE_FAILED".localized() - case .migrationError: return "DATABASE_MIGRATION_FAILED".localized() + case .databaseError: return "DATABASE_MIGRATION_FAILED".localized() case .startupTimeout: return "APP_STARTUP_TIMEOUT".localized() } } diff --git a/Session/Meta/Translations/de.lproj/Localizable.strings b/Session/Meta/Translations/de.lproj/Localizable.strings index 688070b13..36e57b643 100644 --- a/Session/Meta/Translations/de.lproj/Localizable.strings +++ b/Session/Meta/Translations/de.lproj/Localizable.strings @@ -414,10 +414,10 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to share for troubleshooting or you can try to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; "APP_STARTUP_EXIT" = "Exit"; -"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to share for troubleshooting but to continue to use Session you may need to reinstall"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index 8f0e8f077..fb8c06328 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -414,10 +414,10 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to share for troubleshooting or you can try to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; "APP_STARTUP_EXIT" = "Exit"; -"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to share for troubleshooting but to continue to use Session you may need to reinstall"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/es.lproj/Localizable.strings b/Session/Meta/Translations/es.lproj/Localizable.strings index 81652e295..26cd6fcc7 100644 --- a/Session/Meta/Translations/es.lproj/Localizable.strings +++ b/Session/Meta/Translations/es.lproj/Localizable.strings @@ -414,10 +414,10 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to share for troubleshooting or you can try to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; "APP_STARTUP_EXIT" = "Exit"; -"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to share for troubleshooting but to continue to use Session you may need to reinstall"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/fa.lproj/Localizable.strings b/Session/Meta/Translations/fa.lproj/Localizable.strings index 9ca92b73c..591ed7146 100644 --- a/Session/Meta/Translations/fa.lproj/Localizable.strings +++ b/Session/Meta/Translations/fa.lproj/Localizable.strings @@ -414,10 +414,10 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "متاسفانه خطایی رخ داده است"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "لطفا بعدا دوباره تلاش کنید"; "LOADING_CONVERSATIONS" = "درحال بارگزاری پیام ها..."; -"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to share for troubleshooting or you can try to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; "APP_STARTUP_EXIT" = "Exit"; -"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to share for troubleshooting but to continue to use Session you may need to reinstall"; "DATABASE_MIGRATION_FAILED" = "هنگام بهینه‌سازی پایگاه داده خطایی روی داد\n\nشما می‌توانید گزارش‌های برنامه خود را صادر کنید تا بتوانید برای عیب‌یابی به اشتراک بگذارید یا می‌توانید دستگاه خود را بازیابی کنید\n\nهشدار: بازیابی دستگاه شما منجر به از دست رفتن داده‌های قدیمی‌تر از دو هفته می‌شود."; "RECOVERY_PHASE_ERROR_GENERIC" = "مشکلی پیش آمد. لطفاً عبارت بازیابی خود را بررسی کنید و دوباره امتحان کنید."; "RECOVERY_PHASE_ERROR_LENGTH" = "به نظر می رسد کلمات کافی وارد نکرده اید. لطفاً عبارت بازیابی خود را بررسی کنید و دوباره امتحان کنید."; diff --git a/Session/Meta/Translations/fi.lproj/Localizable.strings b/Session/Meta/Translations/fi.lproj/Localizable.strings index c63940d0a..d583693df 100644 --- a/Session/Meta/Translations/fi.lproj/Localizable.strings +++ b/Session/Meta/Translations/fi.lproj/Localizable.strings @@ -414,10 +414,10 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to share for troubleshooting or you can try to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; "APP_STARTUP_EXIT" = "Exit"; -"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to share for troubleshooting but to continue to use Session you may need to reinstall"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/fr.lproj/Localizable.strings b/Session/Meta/Translations/fr.lproj/Localizable.strings index 1b1005b26..562da051b 100644 --- a/Session/Meta/Translations/fr.lproj/Localizable.strings +++ b/Session/Meta/Translations/fr.lproj/Localizable.strings @@ -414,10 +414,10 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oups, une erreur est survenue"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Veuillez réessayer plus tard"; "LOADING_CONVERSATIONS" = "Chargement des conversations..."; -"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to share for troubleshooting or you can try to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; "APP_STARTUP_EXIT" = "Exit"; -"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to share for troubleshooting but to continue to use Session you may need to reinstall"; "DATABASE_MIGRATION_FAILED" = "Une erreur est survenue pendant l'optimisation de la base de données\n\nVous pouvez exporter votre journal d'application pour le partager et aider à régler le problème ou vous pouvez restaurer votre appareil\n\nAttention : restaurer votre appareil résultera en une perte des données des deux dernières semaines"; "RECOVERY_PHASE_ERROR_GENERIC" = "Quelque chose s'est mal passé. Vérifiez votre phrase de récupération et réessayez s'il vous plaît."; "RECOVERY_PHASE_ERROR_LENGTH" = "Il semble que vous n'avez pas saisi tous les mots. Vérifiez votre phrase de récupération et réessayez s'il vous plaît."; diff --git a/Session/Meta/Translations/hi.lproj/Localizable.strings b/Session/Meta/Translations/hi.lproj/Localizable.strings index 534ff51ef..c5837e958 100644 --- a/Session/Meta/Translations/hi.lproj/Localizable.strings +++ b/Session/Meta/Translations/hi.lproj/Localizable.strings @@ -414,10 +414,10 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to share for troubleshooting or you can try to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; "APP_STARTUP_EXIT" = "Exit"; -"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to share for troubleshooting but to continue to use Session you may need to reinstall"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/hr.lproj/Localizable.strings b/Session/Meta/Translations/hr.lproj/Localizable.strings index 39506e93f..60b5da861 100644 --- a/Session/Meta/Translations/hr.lproj/Localizable.strings +++ b/Session/Meta/Translations/hr.lproj/Localizable.strings @@ -414,10 +414,10 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to share for troubleshooting or you can try to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; "APP_STARTUP_EXIT" = "Exit"; -"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to share for troubleshooting but to continue to use Session you may need to reinstall"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/id-ID.lproj/Localizable.strings b/Session/Meta/Translations/id-ID.lproj/Localizable.strings index a19d65df2..8a45a00b0 100644 --- a/Session/Meta/Translations/id-ID.lproj/Localizable.strings +++ b/Session/Meta/Translations/id-ID.lproj/Localizable.strings @@ -414,10 +414,10 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to share for troubleshooting or you can try to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; "APP_STARTUP_EXIT" = "Exit"; -"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to share for troubleshooting but to continue to use Session you may need to reinstall"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/it.lproj/Localizable.strings b/Session/Meta/Translations/it.lproj/Localizable.strings index 101c5112a..547d49c04 100644 --- a/Session/Meta/Translations/it.lproj/Localizable.strings +++ b/Session/Meta/Translations/it.lproj/Localizable.strings @@ -414,10 +414,10 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to share for troubleshooting or you can try to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; "APP_STARTUP_EXIT" = "Exit"; -"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to share for troubleshooting but to continue to use Session you may need to reinstall"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/ja.lproj/Localizable.strings b/Session/Meta/Translations/ja.lproj/Localizable.strings index 6de3a1708..e380f3ea7 100644 --- a/Session/Meta/Translations/ja.lproj/Localizable.strings +++ b/Session/Meta/Translations/ja.lproj/Localizable.strings @@ -414,10 +414,10 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to share for troubleshooting or you can try to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; "APP_STARTUP_EXIT" = "Exit"; -"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to share for troubleshooting but to continue to use Session you may need to reinstall"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/nl.lproj/Localizable.strings b/Session/Meta/Translations/nl.lproj/Localizable.strings index 3f071ad99..8aff0e69e 100644 --- a/Session/Meta/Translations/nl.lproj/Localizable.strings +++ b/Session/Meta/Translations/nl.lproj/Localizable.strings @@ -414,10 +414,10 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to share for troubleshooting or you can try to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; "APP_STARTUP_EXIT" = "Exit"; -"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to share for troubleshooting but to continue to use Session you may need to reinstall"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/pl.lproj/Localizable.strings b/Session/Meta/Translations/pl.lproj/Localizable.strings index dd56d5cd4..bc5252c48 100644 --- a/Session/Meta/Translations/pl.lproj/Localizable.strings +++ b/Session/Meta/Translations/pl.lproj/Localizable.strings @@ -414,10 +414,10 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to share for troubleshooting or you can try to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; "APP_STARTUP_EXIT" = "Exit"; -"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to share for troubleshooting but to continue to use Session you may need to reinstall"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings index bdd6a7f9a..16a6b845d 100644 --- a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings +++ b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings @@ -414,10 +414,10 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to share for troubleshooting or you can try to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; "APP_STARTUP_EXIT" = "Exit"; -"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to share for troubleshooting but to continue to use Session you may need to reinstall"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/ru.lproj/Localizable.strings b/Session/Meta/Translations/ru.lproj/Localizable.strings index bfaf6bb8c..a5968d207 100644 --- a/Session/Meta/Translations/ru.lproj/Localizable.strings +++ b/Session/Meta/Translations/ru.lproj/Localizable.strings @@ -414,10 +414,10 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to share for troubleshooting or you can try to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; "APP_STARTUP_EXIT" = "Exit"; -"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to share for troubleshooting but to continue to use Session you may need to reinstall"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/si.lproj/Localizable.strings b/Session/Meta/Translations/si.lproj/Localizable.strings index c35173c5b..d0f8738c0 100644 --- a/Session/Meta/Translations/si.lproj/Localizable.strings +++ b/Session/Meta/Translations/si.lproj/Localizable.strings @@ -414,10 +414,10 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to share for troubleshooting or you can try to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; "APP_STARTUP_EXIT" = "Exit"; -"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to share for troubleshooting but to continue to use Session you may need to reinstall"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/sk.lproj/Localizable.strings b/Session/Meta/Translations/sk.lproj/Localizable.strings index 8e5f5db93..8df3501ac 100644 --- a/Session/Meta/Translations/sk.lproj/Localizable.strings +++ b/Session/Meta/Translations/sk.lproj/Localizable.strings @@ -414,10 +414,10 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to share for troubleshooting or you can try to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; "APP_STARTUP_EXIT" = "Exit"; -"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to share for troubleshooting but to continue to use Session you may need to reinstall"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/sv.lproj/Localizable.strings b/Session/Meta/Translations/sv.lproj/Localizable.strings index dfdb07bd1..1d3d86dff 100644 --- a/Session/Meta/Translations/sv.lproj/Localizable.strings +++ b/Session/Meta/Translations/sv.lproj/Localizable.strings @@ -414,10 +414,10 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to share for troubleshooting or you can try to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; "APP_STARTUP_EXIT" = "Exit"; -"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to share for troubleshooting but to continue to use Session you may need to reinstall"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/th.lproj/Localizable.strings b/Session/Meta/Translations/th.lproj/Localizable.strings index 0431a2c30..581fc3e1c 100644 --- a/Session/Meta/Translations/th.lproj/Localizable.strings +++ b/Session/Meta/Translations/th.lproj/Localizable.strings @@ -414,10 +414,10 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to share for troubleshooting or you can try to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; "APP_STARTUP_EXIT" = "Exit"; -"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to share for troubleshooting but to continue to use Session you may need to reinstall"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings index cbe4e9604..07311c2ab 100644 --- a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings +++ b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings @@ -414,10 +414,10 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to share for troubleshooting or you can try to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; "APP_STARTUP_EXIT" = "Exit"; -"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to share for troubleshooting but to continue to use Session you may need to reinstall"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings index 2c1c6c1d0..8fd5c3e27 100644 --- a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings @@ -414,10 +414,10 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to share for troubleshooting or you can try to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; "APP_STARTUP_EXIT" = "Exit"; -"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to share for troubleshooting but to continue to use Session you may need to reinstall"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings index d69d20a71..7f63ffbf9 100644 --- a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings @@ -414,10 +414,10 @@ "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE" = "Oops, an error occurred"; "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE" = "Please try again later"; "LOADING_CONVERSATIONS" = "Loading Conversations..."; -"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; +"DATABASE_STARTUP_FAILED" = "An error occurred when opening the database\n\nYou can export your application logs to share for troubleshooting or you can try to restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "APP_STARTUP_TIMEOUT" = "The app is taking a long time to start\n\nYou can continue to wait for the app to start, export your application logs to share for troubleshooting or you can try to open the app again"; "APP_STARTUP_EXIT" = "Exit"; -"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to be able to share for troubleshooting but to continue to use Session you may need to reinstall it"; +"DATABASE_RESTORE_FAILED" = "An error occurred when opening the restored database\n\nYou can export your application logs to share for troubleshooting but to continue to use Session you may need to reinstall"; "DATABASE_MIGRATION_FAILED" = "An error occurred when optimising the database\n\nYou can export your application logs to be able to share for troubleshooting or you can restore your device\n\nWarning: Restoring your device will result in loss of any data older than two weeks"; "RECOVERY_PHASE_ERROR_GENERIC" = "Something went wrong. Please check your recovery phrase and try again."; "RECOVERY_PHASE_ERROR_LENGTH" = "Looks like you didn't enter enough words. Please check your recovery phrase and try again."; diff --git a/Session/Open Groups/JoinOpenGroupVC.swift b/Session/Open Groups/JoinOpenGroupVC.swift index 3f25f1175..b5f214cc9 100644 --- a/Session/Open Groups/JoinOpenGroupVC.swift +++ b/Session/Open Groups/JoinOpenGroupVC.swift @@ -169,7 +169,7 @@ final class JoinOpenGroupVC: BaseVC, UIPageViewControllerDataSource, UIPageViewC ModalActivityIndicatorViewController.present(fromViewController: navigationController, canCancel: false) { [weak self] _ in Storage.shared - .writePublisherFlatMap { db in + .writePublisher { db in OpenGroupManager.shared.add( db, roomToken: roomToken, @@ -178,6 +178,15 @@ final class JoinOpenGroupVC: BaseVC, UIPageViewControllerDataSource, UIPageViewC calledFromConfigHandling: false ) } + .flatMap { successfullyAddedGroup in + OpenGroupManager.shared.performInitialRequestsAfterAdd( + successfullyAddedGroup: successfullyAddedGroup, + roomToken: roomToken, + server: server, + publicKey: publicKey, + calledFromConfigHandling: false + ) + } .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .receive(on: DispatchQueue.main) .sinkUntilComplete( diff --git a/Session/Open Groups/OpenGroupSuggestionGrid.swift b/Session/Open Groups/OpenGroupSuggestionGrid.swift index 9fb666989..f5e4718aa 100644 --- a/Session/Open Groups/OpenGroupSuggestionGrid.swift +++ b/Session/Open Groups/OpenGroupSuggestionGrid.swift @@ -9,7 +9,7 @@ import SessionUIKit final class OpenGroupSuggestionGrid: UIView, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { private let itemsPerSection: Int = (UIDevice.current.isIPad ? 4 : 2) private var maxWidth: CGFloat - private var rooms: [OpenGroupAPI.Room] = [] { didSet { update() } } + private var data: [OpenGroupManager.DefaultRoomInfo] = [] { didSet { update() } } private var heightConstraint: NSLayoutConstraint! var delegate: OpenGroupSuggestionGridDelegate? @@ -146,8 +146,13 @@ final class OpenGroupSuggestionGrid: UIView, UICollectionViewDataSource, UIColle .subscribe(on: DispatchQueue.global(qos: .default)) .receive(on: DispatchQueue.main, immediatelyIfMain: true) .sinkUntilComplete( - receiveCompletion: { [weak self] _ in self?.update() }, - receiveValue: { [weak self] rooms in self?.rooms = rooms } + receiveCompletion: { [weak self] result in + switch result { + case .finished: break + case .failure: self?.update() + } + }, + receiveValue: { [weak self] roomInfo in self?.data = roomInfo } ) } @@ -157,7 +162,7 @@ final class OpenGroupSuggestionGrid: UIView, UICollectionViewDataSource, UIColle spinner.stopAnimating() spinner.isHidden = true - let roomCount: CGFloat = CGFloat(min(rooms.count, 8)) // Cap to a maximum of 8 (4 rows of 2) + let roomCount: CGFloat = CGFloat(min(data.count, 8)) // Cap to a maximum of 8 (4 rows of 2) let numRows: CGFloat = ceil(roomCount / CGFloat(OpenGroupSuggestionGrid.numHorizontalCells)) let height: CGFloat = ((OpenGroupSuggestionGrid.cellHeight * numRows) + ((numRows - 1) * layout.minimumLineSpacing)) heightConstraint.constant = height @@ -184,7 +189,7 @@ final class OpenGroupSuggestionGrid: UIView, UICollectionViewDataSource, UIColle // If there isn't an even number of items then we want to calculate proper sizing return CGSize( - width: Cell.calculatedWith(for: rooms[indexPath.item].name), + width: Cell.calculatedWith(for: data[indexPath.item].room.name), height: OpenGroupSuggestionGrid.cellHeight ) } @@ -192,12 +197,12 @@ final class OpenGroupSuggestionGrid: UIView, UICollectionViewDataSource, UIColle // MARK: - Data Source func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return min(rooms.count, 8) // Cap to a maximum of 8 (4 rows of 2) + return min(data.count, 8) // Cap to a maximum of 8 (4 rows of 2) } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell: Cell = collectionView.dequeue(type: Cell.self, for: indexPath) - cell.room = rooms[indexPath.item] + cell.update(with: data[indexPath.item].room, existingImageData: data[indexPath.item].existingImageData) return cell } @@ -205,7 +210,7 @@ final class OpenGroupSuggestionGrid: UIView, UICollectionViewDataSource, UIColle // MARK: - Interaction func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - let room = rooms[indexPath.section * itemsPerSection + indexPath.item] + let room = data[indexPath.section * itemsPerSection + indexPath.item].room delegate?.join(room) } } @@ -232,8 +237,6 @@ extension OpenGroupSuggestionGrid { ) } - var room: OpenGroupAPI.Room? { didSet { update() } } - private lazy var snContentView: UIView = { let result: UIView = UIView() result.themeBorderColor = .borderSeparator @@ -307,9 +310,7 @@ extension OpenGroupSuggestionGrid { snContentView.pin(to: self) } - private func update() { - guard let room: OpenGroupAPI.Room = room else { return } - + fileprivate func update(with room: OpenGroupAPI.Room, existingImageData: Data?) { label.text = room.name // Only continue if we have a room image @@ -322,11 +323,13 @@ extension OpenGroupSuggestionGrid { Publishers .MergeMany( - Storage.shared - .readPublisherFlatMap { db in - OpenGroupManager - .roomImage(db, fileId: imageId, for: room.token, on: OpenGroupAPI.defaultServer) - } + OpenGroupManager + .roomImage( + fileId: imageId, + for: room.token, + on: OpenGroupAPI.defaultServer, + existingData: existingImageData + ) .map { ($0, true) } .eraseToAnyPublisher(), // If we have already received the room image then the above will emit first and diff --git a/Session/Settings/NukeDataModal.swift b/Session/Settings/NukeDataModal.swift index eafc62330..bda18bf9c 100644 --- a/Session/Settings/NukeDataModal.swift +++ b/Session/Settings/NukeDataModal.swift @@ -220,7 +220,7 @@ final class NukeDataModal: Modal { } } - private func deleteAllLocalData() { + private func deleteAllLocalData(using dependencies: Dependencies = Dependencies()) { // Unregister push notifications if needed let isUsingFullAPNs: Bool = UserDefaults.standard[.isUsingFullAPNs] let maybeDeviceToken: String? = UserDefaults.standard[.deviceToken] @@ -244,7 +244,10 @@ final class NukeDataModal: Modal { UserDefaults.removeAll() // Remove the cached key so it gets re-cached on next access - General.cache.mutate { $0.encodedPublicKey = nil } + dependencies.mutableGeneralCache.mutate { + $0.encodedPublicKey = nil + $0.recentReactionTimestamps = [] + } // Clear the Snode pool SnodeAPI.clearSnodePool() diff --git a/SessionMessagingKit/Calls/WebRTCSession.swift b/SessionMessagingKit/Calls/WebRTCSession.swift index 7d7added5..71e098ae2 100644 --- a/SessionMessagingKit/Calls/WebRTCSession.swift +++ b/SessionMessagingKit/Calls/WebRTCSession.swift @@ -146,19 +146,13 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate { } public func sendOffer( - _ db: Database, - to sessionId: String, + to thread: SessionThread, isRestartingICEConnection: Bool = false ) -> AnyPublisher { SNLog("[Calls] Sending offer message.") let uuid: String = self.uuid let mediaConstraints: RTCMediaConstraints = mediaConstraints(isRestartingICEConnection) - guard let thread: SessionThread = try? SessionThread.fetchOne(db, id: sessionId) else { - return Fail(error: WebRTCSessionError.noThread) - .eraseToAnyPublisher() - } - return Deferred { Future { [weak self] resolver in self?.peerConnection?.offer(for: mediaConstraints) { sdp, error in diff --git a/SessionMessagingKit/Database/Models/Profile.swift b/SessionMessagingKit/Database/Models/Profile.swift index 10f57c059..08761ada9 100644 --- a/SessionMessagingKit/Database/Models/Profile.swift +++ b/SessionMessagingKit/Database/Models/Profile.swift @@ -265,18 +265,6 @@ public extension Profile { ) } - /// Fetches or creates a Profile for the specified user - /// - /// **Note:** This method intentionally does **not** save the newly created Profile, - /// it will need to be explicitly saved after calling - static func fetchOrCreate(id: String) -> Profile { - let exisingProfile: Profile? = Storage.shared.read { db in - try Profile.fetchOne(db, id: id) - } - - return (exisingProfile ?? defaultFor(id)) - } - /// Fetches or creates a Profile for the specified user /// /// **Note:** This method intentionally does **not** save the newly created Profile, diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index fee25ffba..c79eecf07 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -7,30 +7,15 @@ import Sodium import SessionUtilitiesKit import SessionSnodeKit -// MARK: - OGMCacheType - -public protocol OGMCacheType { - var defaultRoomsPublisher: AnyPublisher<[OpenGroupAPI.Room], Error>? { get set } - var groupImagePublishers: [String: AnyPublisher] { get set } - - var pollers: [String: OpenGroupAPI.Poller] { get set } - var isPolling: Bool { get set } - - var hasPerformedInitialPoll: [String: Bool] { get set } - var timeSinceLastPoll: [String: TimeInterval] { get set } - - var pendingChanges: [OpenGroupAPI.PendingChange] { get set } - - func getTimeSinceLastOpen(using dependencies: Dependencies) -> TimeInterval -} - // MARK: - OpenGroupManager public final class OpenGroupManager { + public typealias DefaultRoomInfo = (room: OpenGroupAPI.Room, existingImageData: Data?) + // MARK: - Cache - public class Cache: OGMCacheType { - public var defaultRoomsPublisher: AnyPublisher<[OpenGroupAPI.Room], Error>? + public class Cache: OGMMutableCacheType { + public var defaultRoomsPublisher: AnyPublisher<[DefaultRoomInfo], Error>? public var groupImagePublishers: [String: AnyPublisher] = [:] public var pollers: [String: OpenGroupAPI.Poller] = [:] // One for each server @@ -60,10 +45,7 @@ public final class OpenGroupManager { // MARK: - Variables - public static let shared: OpenGroupManager = OpenGroupManager() - - /// Note: This should not be accessed directly but rather via the 'OGMDependencies' type - fileprivate let mutableCache: Atomic = Atomic(Cache()) + public static let shared: OpenGroupManager = OpenGroupManager() // MARK: - Polling @@ -87,6 +69,7 @@ public final class OpenGroupManager { } .defaulting(to: []) + // Update the cache state and re-create all of the pollers dependencies.mutableCache.mutate { cache in cache.isPolling = true cache.pollers = servers @@ -94,14 +77,9 @@ public final class OpenGroupManager { result[server.lowercased()]?.stop() // Should never occur result[server.lowercased()] = OpenGroupAPI.Poller(for: server.lowercased()) } - - // Note: We loop separately here because when the cache is mocked-out for tests it - // doesn't actually store the value (meaning the pollers won't be started), but if - // we do it in the 'reduce' function, the 'reduce' result will actually store the - // poller value resulting in a bunch of OpenGroup pollers running in a way that can't - // be stopped during unit tests - cache.pollers.forEach { _, poller in poller.startIfNeeded(using: dependencies) } } + // Now that the pollers have been created actually start them + dependencies.cache.pollers.forEach { _, poller in poller.startIfNeeded(using: dependencies) } } } @@ -185,7 +163,7 @@ public final class OpenGroupManager { } // First check if there is no poller for the specified server - if serverOptions.first(where: { dependencies.cache.pollers[$0] != nil }) == nil { + if Set(dependencies.cache.pollers.keys).intersection(serverOptions).isEmpty { return false } @@ -209,13 +187,11 @@ public final class OpenGroupManager { publicKey: String, calledFromConfigHandling: Bool, dependencies: OGMDependencies = OGMDependencies() - ) -> AnyPublisher { + ) -> Bool { // If we are currently polling for this server and already have a TSGroupThread for this room the do nothing if hasExistingOpenGroup(db, roomToken: roomToken, server: server, publicKey: publicKey, dependencies: dependencies) { SNLog("Ignoring join open group attempt (already joined), user initiated: \(!calledFromConfigHandling)") - return Just(()) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() + return false } // Store the open group information @@ -270,72 +246,86 @@ public final class OpenGroupManager { ) } - /// We want to avoid blocking the db write thread so we return a future which resolves once the db transaction completes - /// and dispatches the result to another queue, this means that the caller can respond to errors resulting from attepting to - /// join the community - return Future { resolver in - db.afterNextTransactionNested { _ in - OpenGroupAPI.workQueue.async { - resolver(Result.success(())) - } + return true + } + + public func performInitialRequestsAfterAdd( + successfullyAddedGroup: Bool, + roomToken: String, + server: String, + publicKey: String, + calledFromConfigHandling: Bool, + dependencies: OGMDependencies = OGMDependencies() + ) -> AnyPublisher { + guard successfullyAddedGroup else { + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + + // Store the open group information + let targetServer: String = { + guard OpenGroupManager.isSessionRunOpenGroup(server: server) else { + return server.lowercased() } - } - .flatMap { _ in - dependencies.storage - .readPublisher { db in - try OpenGroupAPI - .preparedCapabilitiesAndRoom( - db, - for: roomToken, - on: targetServer, - using: dependencies - ) - } - } - .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } - .flatMap { info, response -> Future in - Future { resolver in - dependencies.storage.write { db in - // Add the new open group to libSession - if !calledFromConfigHandling { - try SessionUtil.add( - db, - server: server, - rootToken: roomToken, - publicKey: publicKey - ) - } - - // Store the capabilities first - OpenGroupManager.handleCapabilities( + + return OpenGroupAPI.defaultServer + }() + + return dependencies.storage + .readPublisher { db in + try OpenGroupAPI + .preparedCapabilitiesAndRoom( db, - capabilities: response.capabilities.data, - on: targetServer - ) - - // Then the room - try OpenGroupManager.handlePollInfo( - db, - pollInfo: OpenGroupAPI.RoomPollInfo(room: response.room.data), - publicKey: publicKey, for: roomToken, on: targetServer, - dependencies: dependencies - ) { - resolver(Result.success(())) + using: dependencies + ) + } + .flatMap { OpenGroupAPI.send(data: $0, using: dependencies) } + .flatMap { info, response -> Future in + Future { resolver in + dependencies.storage.write { db in + // Add the new open group to libSession + if !calledFromConfigHandling { + try SessionUtil.add( + db, + server: server, + rootToken: roomToken, + publicKey: publicKey + ) + } + + // Store the capabilities first + OpenGroupManager.handleCapabilities( + db, + capabilities: response.capabilities.data, + on: targetServer + ) + + // Then the room + try OpenGroupManager.handlePollInfo( + db, + pollInfo: OpenGroupAPI.RoomPollInfo(room: response.room.data), + publicKey: publicKey, + for: roomToken, + on: targetServer, + dependencies: dependencies + ) { + resolver(Result.success(())) + } } } } - } - .handleEvents( - receiveCompletion: { result in - switch result { - case .finished: break - case .failure: SNLog("Failed to join open group.") + .handleEvents( + receiveCompletion: { result in + switch result { + case .finished: break + case .failure: SNLog("Failed to join open group.") + } } - } - ) - .eraseToAnyPublisher() + ) + .eraseToAnyPublisher() } public func delete( @@ -534,9 +524,11 @@ public final class OpenGroupManager { // Start the poller if needed if dependencies.cache.pollers[server.lowercased()] == nil { dependencies.mutableCache.mutate { + $0.pollers[server.lowercased()]?.stop() $0.pollers[server.lowercased()] = OpenGroupAPI.Poller(for: server.lowercased()) - $0.pollers[server.lowercased()]?.startIfNeeded(using: dependencies) } + + dependencies.cache.pollers[server.lowercased()]?.startIfNeeded(using: dependencies) } /// Start downloading the room image (if we don't have one or it's been updated) @@ -549,10 +541,10 @@ public final class OpenGroupManager { { OpenGroupManager .roomImage( - db, fileId: imageId, for: roomToken, on: server, + existingData: openGroup.imageData, using: dependencies ) // Note: We need to subscribe and receive on different threads to ensure the @@ -593,45 +585,26 @@ public final class OpenGroupManager { on server: String, dependencies: OGMDependencies = OGMDependencies() ) { - // Sorting the messages by server ID before importing them fixes an issue where messages - // that quote older messages can't find those older messages guard let openGroup: OpenGroup = try? OpenGroup.fetchOne(db, id: OpenGroup.idFor(roomToken: roomToken, server: server)) else { SNLog("Couldn't handle open group messages.") return } + // Sorting the messages by server ID before importing them fixes an issue where messages + // that quote older messages can't find those older messages let sortedMessages: [OpenGroupAPI.Message] = messages .filter { $0.deleted != true } .sorted { lhs, rhs in lhs.id < rhs.id } var messageServerInfoToRemove: [(id: Int64, seqNo: Int64)] = messages .filter { $0.deleted == true } .map { ($0.id, $0.seqNo) } - let updateSeqNo: (Database, String, inout Int64, Int64, OGMDependencies) -> () = { db, openGroupId, lastValidSeqNo, seqNo, dependencies in - // Only update the data if the 'seqNo' is larger than the lastValidSeqNo (only want it to increase) - guard seqNo > lastValidSeqNo else { return } - - // Update the 'openGroupSequenceNumber' value (Note: SOGS V4 uses the 'seqNo' instead of the 'serverId') - _ = try? OpenGroup - .filter(id: openGroupId) - .updateAll(db, OpenGroup.Columns.sequenceNumber.set(to: seqNo)) - - // Update pendingChange cache - dependencies.mutableCache.mutate { - $0.pendingChanges = $0.pendingChanges - .filter { $0.seqNo == nil || $0.seqNo! > seqNo } - } - - // Update the inout value - lastValidSeqNo = seqNo - } + var largestValidSeqNo: Int64 = openGroup.sequenceNumber // Process the messages - var lastValidSeqNo: Int64 = -1 sortedMessages.forEach { message in if message.base64EncodedData == nil && message.reactions == nil { messageServerInfoToRemove.append((message.id, message.seqNo)) - - return updateSeqNo(db, openGroup.id, &lastValidSeqNo, message.seqNo, dependencies) + return } // Handle messages @@ -658,7 +631,7 @@ public final class OpenGroupManager { associatedWithProto: try SNProtoContent.parseData(messageInfo.serializedProtoData), dependencies: dependencies ) - updateSeqNo(db, openGroup.id, &lastValidSeqNo, message.seqNo, dependencies) + largestValidSeqNo = max(largestValidSeqNo, message.seqNo) } } catch { @@ -703,7 +676,7 @@ public final class OpenGroupManager { openGroupMessageServerId: message.id, openGroupReactions: reactions ) - updateSeqNo(db, openGroup.id, &lastValidSeqNo, message.seqNo, dependencies) + largestValidSeqNo = max(largestValidSeqNo, message.seqNo) } catch { SNLog("Couldn't handle open group reactions due to error: \(error).") @@ -712,17 +685,27 @@ public final class OpenGroupManager { } // Handle any deletions that are needed - guard !messageServerInfoToRemove.isEmpty else { return } + if !messageServerInfoToRemove.isEmpty { + let messageServerIdsToRemove: [Int64] = messageServerInfoToRemove.map { $0.id } + _ = try? Interaction + .filter(Interaction.Columns.threadId == openGroup.threadId) + .filter(messageServerIdsToRemove.contains(Interaction.Columns.openGroupServerMessageId)) + .deleteAll(db) + + // Update the seqNo for deletions + largestValidSeqNo = max(largestValidSeqNo, (messageServerInfoToRemove.map({ $0.seqNo }).max() ?? 0)) + } - let messageServerIdsToRemove: [Int64] = messageServerInfoToRemove.map { $0.id } - _ = try? Interaction - .filter(Interaction.Columns.threadId == openGroup.threadId) - .filter(messageServerIdsToRemove.contains(Interaction.Columns.openGroupServerMessageId)) - .deleteAll(db) - - // Update the seqNo for deletions - if let lastDeletionSeqNo: Int64 = messageServerInfoToRemove.map({ $0.seqNo }).max() { - updateSeqNo(db, openGroup.id, &lastValidSeqNo, lastDeletionSeqNo, dependencies) + // Now that we've finished processing all valid message changes we can update the `sequenceNumber` to + // the `largestValidSeqNo` value + _ = try? OpenGroup + .filter(id: openGroup.id) + .updateAll(db, OpenGroup.Columns.sequenceNumber.set(to: largestValidSeqNo)) + + // Update pendingChange cache based on the `largestValidSeqNo` value + dependencies.mutableCache.mutate { + $0.pendingChanges = $0.pendingChanges + .filter { $0.seqNo == nil || $0.seqNo! > largestValidSeqNo } } } @@ -1013,14 +996,14 @@ public final class OpenGroupManager { subscribeQueue: OpenGroupAPI.workQueue, receiveQueue: OpenGroupAPI.workQueue ) - ) -> AnyPublisher<[OpenGroupAPI.Room], Error> { + ) -> AnyPublisher<[DefaultRoomInfo], Error> { // Note: If we already have a 'defaultRoomsPromise' then there is no need to get it again - if let existingPublisher: AnyPublisher<[OpenGroupAPI.Room], Error> = dependencies.cache.defaultRoomsPublisher { + if let existingPublisher: AnyPublisher<[DefaultRoomInfo], Error> = dependencies.cache.defaultRoomsPublisher { return existingPublisher } // Try to retrieve the default rooms 8 times - let publisher: AnyPublisher<[OpenGroupAPI.Room], Error> = dependencies.storage + let publisher: AnyPublisher<[DefaultRoomInfo], Error> = dependencies.storage .readPublisher { db in try OpenGroupAPI.preparedCapabilitiesAndRooms( db, @@ -1032,8 +1015,8 @@ public final class OpenGroupManager { .subscribe(on: dependencies.subscribeQueue, immediatelyIfMain: true) .receive(on: dependencies.receiveQueue, immediatelyIfMain: true) .retry(8) - .map { info, response in - dependencies.storage.writeAsync { db in + .map { info, response -> [DefaultRoomInfo]? in + dependencies.storage.write { db -> [DefaultRoomInfo] in // Store the capabilities first OpenGroupManager.handleCapabilities( db, @@ -1042,8 +1025,8 @@ public final class OpenGroupManager { ) // Then the rooms - response.rooms.data - .compactMap { room -> (String, String)? in + return response.rooms.data + .map { room -> DefaultRoomInfo in // Try to insert an inactive version of the OpenGroup (use 'insert' // rather than 'save' as we want it to fail if the room already exists) do { @@ -1066,24 +1049,32 @@ public final class OpenGroupManager { } catch {} - guard let imageId: String = room.imageId else { return nil } + // Retrieve existing image data if we have it + let existingImageData: Data? = try? OpenGroup + .select(.imageData) + .filter(id: OpenGroup.idFor(roomToken: room.token, server: OpenGroupAPI.defaultServer)) + .asRequest(of: Data.self) + .fetchOne(db) - return (imageId, room.token) - } - .forEach { imageId, roomToken in - roomImage( - db, - fileId: imageId, - for: roomToken, - on: OpenGroupAPI.defaultServer, - using: dependencies - ) + return (room, existingImageData) } } - - return response.rooms.data } + .map { ($0 ?? []) } .handleEvents( + receiveOutput: { roomInfo in + roomInfo.forEach { room, existingImageData in + guard let imageId: String = room.imageId else { return } + + roomImage( + fileId: imageId, + for: room.token, + on: OpenGroupAPI.defaultServer, + existingData: existingImageData, + using: dependencies + ) + } + }, receiveCompletion: { result in switch result { case .finished: break @@ -1108,10 +1099,10 @@ public final class OpenGroupManager { } @discardableResult public static func roomImage( - _ db: Database, fileId: String, for roomToken: String, on server: String, + existingData: Data?, using dependencies: OGMDependencies = OGMDependencies( subscribeQueue: .global(qos: .background) ) @@ -1130,16 +1121,12 @@ public final class OpenGroupManager { let now: Date = dependencies.date let timeSinceLastUpdate: TimeInterval = (lastOpenGroupImageUpdate.map { now.timeIntervalSince($0) } ?? .greatestFiniteMagnitude) let updateInterval: TimeInterval = (7 * 24 * 60 * 60) + let canUseExistingImage: Bool = ( + server.lowercased() == OpenGroupAPI.defaultServer && + timeSinceLastUpdate < updateInterval + ) - if - server.lowercased() == OpenGroupAPI.defaultServer, - timeSinceLastUpdate < updateInterval, - let data = try? OpenGroup - .select(.imageData) - .filter(id: threadId) - .asRequest(of: Data.self) - .fetchOne(db) - { + if canUseExistingImage, let data: Data = existingData { return Just(data) .setFailureType(to: Error.self) .eraseToAnyPublisher() @@ -1149,33 +1136,54 @@ public final class OpenGroupManager { return publisher } - let sendData: OpenGroupAPI.PreparedSendData - - do { - sendData = try OpenGroupAPI - .preparedDownloadFile( - db, - fileId: fileId, - from: roomToken, - on: server, - using: dependencies - ) - } - catch { - return Fail(error: error) - .eraseToAnyPublisher() - } - // Defer the actual download and run it on a separate thread to avoid blocking the calling thread let publisher: AnyPublisher = Deferred { Future { resolver in dependencies.subscribeQueue.async { // Hold on to the publisher until it has completed at least once - OpenGroupAPI - .send( - data: sendData, - using: dependencies - ) + dependencies.storage + .readPublisher { db -> (Data?, OpenGroupAPI.PreparedSendData?) in + if canUseExistingImage { + let maybeExistingData: Data? = try? OpenGroup + .select(.imageData) + .filter(id: threadId) + .asRequest(of: Data.self) + .fetchOne(db) + + if let existingData: Data = maybeExistingData { + return (existingData, nil) + } + } + + return ( + nil, + try OpenGroupAPI + .preparedDownloadFile( + db, + fileId: fileId, + from: roomToken, + on: server, + using: dependencies + ) + ) + } + .flatMap { info in + switch info { + case (.some(let existingData), _): + return Just(existingData) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + + case (_, .some(let sendData)): + return OpenGroupAPI.send(data: sendData, using: dependencies) + .map { _, imageData in imageData } + .eraseToAnyPublisher() + + default: + return Fail(error: HTTPError.generic) + .eraseToAnyPublisher() + } + } .sinkUntilComplete( receiveCompletion: { result in switch result { @@ -1183,7 +1191,7 @@ public final class OpenGroupManager { case .failure(let error): resolver(Result.failure(error)) } }, - receiveValue: { _, imageData in + receiveValue: { imageData in if server.lowercased() == OpenGroupAPI.defaultServer { dependencies.storage.write { db in _ = try OpenGroup @@ -1216,25 +1224,73 @@ public final class OpenGroupManager { } } +// MARK: - OGMCacheType + +public protocol OGMMutableCacheType: OGMCacheType { + var defaultRoomsPublisher: AnyPublisher<[OpenGroupManager.DefaultRoomInfo], Error>? { get set } + var groupImagePublishers: [String: AnyPublisher] { get set } + + var pollers: [String: OpenGroupAPI.Poller] { get set } + var isPolling: Bool { get set } + + var hasPerformedInitialPoll: [String: Bool] { get set } + var timeSinceLastPoll: [String: TimeInterval] { get set } + + var pendingChanges: [OpenGroupAPI.PendingChange] { get set } + + func getTimeSinceLastOpen(using dependencies: Dependencies) -> TimeInterval +} + +/// This is a read-only version of the `OGMMutableCacheType` designed to avoid unintentionally mutating the instance in a +/// non-thread-safe way +public protocol OGMCacheType { + var defaultRoomsPublisher: AnyPublisher<[OpenGroupManager.DefaultRoomInfo], Error>? { get } + var groupImagePublishers: [String: AnyPublisher] { get } + + var pollers: [String: OpenGroupAPI.Poller] { get } + var isPolling: Bool { get } + + var hasPerformedInitialPoll: [String: Bool] { get } + var timeSinceLastPoll: [String: TimeInterval] { get } + + var pendingChanges: [OpenGroupAPI.PendingChange] { get } +} // MARK: - OGMDependencies extension OpenGroupManager { public class OGMDependencies: SMKDependencies { - internal var _mutableCache: Atomic?> - public var mutableCache: Atomic { - get { Dependencies.getValueSettingIfNull(&_mutableCache) { OpenGroupManager.shared.mutableCache } } - set { _mutableCache.mutate { $0 = newValue } } - } + /// These should not be accessed directly but rather via an instance of this type + private static let _cacheInstance: OGMMutableCacheType = OpenGroupManager.Cache() + private static let _cacheInstanceAccessQueue = DispatchQueue(label: "OGMCacheInstanceAccess") - public var cache: OGMCacheType { return mutableCache.wrappedValue } + internal var _mutableCache: Atomic + public var mutableCache: Atomic { + get { + Dependencies.getMutableValueSettingIfNull(&_mutableCache) { + OGMDependencies._cacheInstanceAccessQueue.sync { OGMDependencies._cacheInstance } + } + } + } + public var cache: OGMCacheType { + get { + Dependencies.getValueSettingIfNull(&_mutableCache) { + OGMDependencies._cacheInstanceAccessQueue.sync { OGMDependencies._cacheInstance } + } + } + set { + guard let mutableValue: OGMMutableCacheType = newValue as? OGMMutableCacheType else { return } + + _mutableCache.mutate { $0 = mutableValue } + } + } public init( subscribeQueue: DispatchQueue? = nil, receiveQueue: DispatchQueue? = nil, - cache: Atomic? = nil, + cache: OGMMutableCacheType? = nil, onionApi: OnionRequestAPIType.Type? = nil, - generalCache: Atomic? = nil, + generalCache: MutableGeneralCacheType? = nil, storage: Storage? = nil, scheduler: ValueObservationScheduler? = nil, sodium: SodiumType? = nil, diff --git a/SessionMessagingKit/Open Groups/Types/OpenGroupAPIError.swift b/SessionMessagingKit/Open Groups/Types/OpenGroupAPIError.swift index a86383993..e1a8945da 100644 --- a/SessionMessagingKit/Open Groups/Types/OpenGroupAPIError.swift +++ b/SessionMessagingKit/Open Groups/Types/OpenGroupAPIError.swift @@ -8,6 +8,7 @@ public enum OpenGroupAPIError: LocalizedError { case noPublicKey case invalidEmoji case invalidPreparedData + case invalidPoll public var errorDescription: String? { switch self { @@ -16,6 +17,7 @@ public enum OpenGroupAPIError: LocalizedError { case .noPublicKey: return "Couldn't find server public key." case .invalidEmoji: return "The emoji is invalid." case .invalidPreparedData: return "Invalid PreparedSendData provided." + case .invalidPoll: return "Poller in invalid state." } } } diff --git a/SessionMessagingKit/SMKDependencies.swift b/SessionMessagingKit/SMKDependencies.swift index 8b763356e..34b9b9e6d 100644 --- a/SessionMessagingKit/SMKDependencies.swift +++ b/SessionMessagingKit/SMKDependencies.swift @@ -61,7 +61,7 @@ public class SMKDependencies: SSKDependencies { subscribeQueue: DispatchQueue? = nil, receiveQueue: DispatchQueue? = nil, onionApi: OnionRequestAPIType.Type? = nil, - generalCache: Atomic? = nil, + generalCache: MutableGeneralCacheType? = nil, storage: Storage? = nil, scheduler: ValueObservationScheduler? = nil, sodium: SodiumType? = nil, diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift index 92385940f..b61e3bcb4 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift @@ -200,7 +200,7 @@ extension MessageReceiver { // Open groups for openGroupURL in message.openGroups { if let (room, server, publicKey) = SessionUtil.parseCommunity(url: openGroupURL) { - OpenGroupManager.shared + let successfullyAddedGroup: Bool = OpenGroupManager.shared .add( db, roomToken: room, @@ -208,7 +208,20 @@ extension MessageReceiver { publicKey: publicKey, calledFromConfigHandling: true ) - .sinkUntilComplete() + + if successfullyAddedGroup { + db.afterNextTransactionNested { _ in + OpenGroupManager.shared.performInitialRequestsAfterAdd( + successfullyAddedGroup: successfullyAddedGroup, + roomToken: room, + server: server, + publicKey: publicKey, + calledFromConfigHandling: false + ) + .subscribe(on: OpenGroupAPI.workQueue) + .sinkUntilComplete() + } + } } } } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift index a6722a847..5cecfca71 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift @@ -12,130 +12,130 @@ extension MessageSender { public static var distributingKeyPairs: Atomic<[String: [ClosedGroupKeyPair]]> = Atomic([:]) public static func createClosedGroup( - _ db: Database, name: String, members: Set - ) throws -> AnyPublisher { - let userPublicKey: String = getUserHexEncodedPublicKey(db) - var members: Set = members - - // Generate the group's public key - let groupKeyPair: ECKeyPair = Curve25519.generateKeyPair() - let groupPublicKey: String = KeyPair( - publicKey: groupKeyPair.publicKey.bytes, - secretKey: groupKeyPair.privateKey.bytes - ).hexEncodedPublicKey // Includes the 'SessionId.Prefix.standard' prefix - // Generate the key pair that'll be used for encryption and decryption - let encryptionKeyPair: ECKeyPair = Curve25519.generateKeyPair() - - // Create the group - members.insert(userPublicKey) // Ensure the current user is included in the member list - let membersAsData: [Data] = members.map { Data(hex: $0) } - let admins: Set = [ userPublicKey ] - let adminsAsData: [Data] = admins.map { Data(hex: $0) } - let formationTimestamp: TimeInterval = (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) - - // Create the relevant objects in the database - let thread: SessionThread = try SessionThread - .fetchOrCreate(db, id: groupPublicKey, variant: .legacyGroup, shouldBeVisible: true) - try ClosedGroup( - threadId: groupPublicKey, - name: name, - formationTimestamp: formationTimestamp - ).insert(db) - - // Store the key pair - let latestKeyPairReceivedTimestamp: TimeInterval = (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) - try ClosedGroupKeyPair( - threadId: groupPublicKey, - publicKey: encryptionKeyPair.publicKey, - secretKey: encryptionKeyPair.privateKey, - receivedTimestamp: latestKeyPairReceivedTimestamp - ).insert(db) - - // Create the member objects - try admins.forEach { adminId in - try GroupMember( - groupId: groupPublicKey, - profileId: adminId, - role: .admin, - isHidden: false - ).save(db) - } - - try members.forEach { memberId in - try GroupMember( - groupId: groupPublicKey, - profileId: memberId, - role: .standard, - isHidden: false - ).save(db) - } - - // Update libSession - try SessionUtil.add( - db, - groupPublicKey: groupPublicKey, - name: name, - latestKeyPairPublicKey: encryptionKeyPair.publicKey, - latestKeyPairSecretKey: encryptionKeyPair.privateKey, - latestKeyPairReceivedTimestamp: latestKeyPairReceivedTimestamp, - disappearingConfig: DisappearingMessagesConfiguration.defaultWith(groupPublicKey), - members: members, - admins: admins - ) - - let memberSendData: [MessageSender.PreparedSendData] = try members - .map { memberId -> MessageSender.PreparedSendData in - try MessageSender.preparedSendData( + ) -> AnyPublisher { + Storage.shared + .writePublisher { db -> (String, SessionThread, [MessageSender.PreparedSendData]) in + let userPublicKey: String = getUserHexEncodedPublicKey(db) + var members: Set = members + + // Generate the group's public key + let groupKeyPair: ECKeyPair = Curve25519.generateKeyPair() + let groupPublicKey: String = KeyPair( + publicKey: groupKeyPair.publicKey.bytes, + secretKey: groupKeyPair.privateKey.bytes + ).hexEncodedPublicKey // Includes the 'SessionId.Prefix.standard' prefix + // Generate the key pair that'll be used for encryption and decryption + let encryptionKeyPair: ECKeyPair = Curve25519.generateKeyPair() + + // Create the group + members.insert(userPublicKey) // Ensure the current user is included in the member list + let membersAsData: [Data] = members.map { Data(hex: $0) } + let admins: Set = [ userPublicKey ] + let adminsAsData: [Data] = admins.map { Data(hex: $0) } + let formationTimestamp: TimeInterval = (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) + + // Create the relevant objects in the database + let thread: SessionThread = try SessionThread + .fetchOrCreate(db, id: groupPublicKey, variant: .legacyGroup, shouldBeVisible: true) + try ClosedGroup( + threadId: groupPublicKey, + name: name, + formationTimestamp: formationTimestamp + ).insert(db) + + // Store the key pair + let latestKeyPairReceivedTimestamp: TimeInterval = (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) + try ClosedGroupKeyPair( + threadId: groupPublicKey, + publicKey: encryptionKeyPair.publicKey, + secretKey: encryptionKeyPair.privateKey, + receivedTimestamp: latestKeyPairReceivedTimestamp + ).insert(db) + + // Create the member objects + try admins.forEach { adminId in + try GroupMember( + groupId: groupPublicKey, + profileId: adminId, + role: .admin, + isHidden: false + ).save(db) + } + + try members.forEach { memberId in + try GroupMember( + groupId: groupPublicKey, + profileId: memberId, + role: .standard, + isHidden: false + ).save(db) + } + + // Update libSession + try SessionUtil.add( db, - message: ClosedGroupControlMessage( - kind: .new( - publicKey: Data(hex: groupPublicKey), - name: name, - encryptionKeyPair: KeyPair( - publicKey: encryptionKeyPair.publicKey.bytes, - secretKey: encryptionKeyPair.privateKey.bytes - ), - members: membersAsData, - admins: adminsAsData, - expirationTimer: 0 - ), - // Note: We set this here to ensure the value matches - // the 'ClosedGroup' object we created - sentTimestampMs: UInt64(floor(formationTimestamp * 1000)) - ), - to: .contact(publicKey: memberId), - namespace: Message.Destination.contact(publicKey: memberId).defaultNamespace, - interactionId: nil + groupPublicKey: groupPublicKey, + name: name, + latestKeyPairPublicKey: encryptionKeyPair.publicKey, + latestKeyPairSecretKey: encryptionKeyPair.privateKey, + latestKeyPairReceivedTimestamp: latestKeyPairReceivedTimestamp, + disappearingConfig: DisappearingMessagesConfiguration.defaultWith(groupPublicKey), + members: members, + admins: admins ) - } - - return Publishers - .MergeMany( - // Send a closed group update message to all members individually - memberSendData - .map { MessageSender.sendImmediate(preparedSendData: $0) } - .appending( - // Notify the PN server - PushNotificationAPI.performOperation( - .subscribe, - for: groupPublicKey, - publicKey: userPublicKey + + let memberSendData: [MessageSender.PreparedSendData] = try members + .map { memberId -> MessageSender.PreparedSendData in + try MessageSender.preparedSendData( + db, + message: ClosedGroupControlMessage( + kind: .new( + publicKey: Data(hex: groupPublicKey), + name: name, + encryptionKeyPair: KeyPair( + publicKey: encryptionKeyPair.publicKey.bytes, + secretKey: encryptionKeyPair.privateKey.bytes + ), + members: membersAsData, + admins: adminsAsData, + expirationTimer: 0 + ), + // Note: We set this here to ensure the value matches + // the 'ClosedGroup' object we created + sentTimestampMs: UInt64(floor(formationTimestamp * 1000)) + ), + to: .contact(publicKey: memberId), + namespace: Message.Destination.contact(publicKey: memberId).defaultNamespace, + interactionId: nil ) - ) - ) - .collect() - .map { _ in thread } - .eraseToAnyPublisher() - .handleEvents( - receiveCompletion: { result in - switch result { - case .failure: break - case .finished: - // Start polling - ClosedGroupPoller.shared.startIfNeeded(for: groupPublicKey) } + + return (userPublicKey, thread, memberSendData) + } + .flatMap { userPublicKey, thread, memberSendData in + Publishers + .MergeMany( + // Send a closed group update message to all members individually + memberSendData + .map { MessageSender.sendImmediate(preparedSendData: $0) } + .appending( + // Notify the PN server + PushNotificationAPI.performOperation( + .subscribe, + for: thread.id, + publicKey: userPublicKey + ) + ) + ) + .collect() + .map { _ in thread } + } + .handleEvents( + receiveOutput: { thread in + // Start polling + ClosedGroupPoller.shared.startIfNeeded(for: thread.id) } ) .eraseToAnyPublisher() @@ -148,7 +148,6 @@ extension MessageSender { /// /// The returned promise is fulfilled when the message has been sent to the group. private static func generateAndSendNewEncryptionKeyPair( - _ db: Database, targetMembers: Set, userPublicKey: String, allGroupMembers: [GroupMember], @@ -159,65 +158,62 @@ extension MessageSender { .eraseToAnyPublisher() } - let newKeyPair: ClosedGroupKeyPair - let sendData: MessageSender.PreparedSendData - - do { - // Generate the new encryption key pair - let legacyNewKeyPair: ECKeyPair = Curve25519.generateKeyPair() - newKeyPair = ClosedGroupKeyPair( - threadId: closedGroup.threadId, - publicKey: legacyNewKeyPair.publicKey, - secretKey: legacyNewKeyPair.privateKey, - receivedTimestamp: (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) - ) - - // Distribute it - let proto = try SNProtoKeyPair.builder( - publicKey: newKeyPair.publicKey, - privateKey: newKeyPair.secretKey - ).build() - let plaintext = try proto.serializedData() - - distributingKeyPairs.mutate { - $0[closedGroup.id] = ($0[closedGroup.id] ?? []) - .appending(newKeyPair) - } - - sendData = try MessageSender - .preparedSendData( - db, - message: ClosedGroupControlMessage( - kind: .encryptionKeyPair( - publicKey: nil, - wrappers: targetMembers.map { memberPublicKey in - ClosedGroupControlMessage.KeyPairWrapper( - publicKey: memberPublicKey, - encryptedKeyPair: try MessageSender.encryptWithSessionProtocol( - db, - plaintext: plaintext, - for: memberPublicKey - ) - ) - } - ) - ), - to: try Message.Destination - .from(db, threadId: closedGroup.threadId, threadVariant: .legacyGroup), - namespace: try Message.Destination - .from(db, threadId: closedGroup.threadId, threadVariant: .legacyGroup) - .defaultNamespace, - interactionId: nil + return Storage.shared + .readPublisher { db -> (ClosedGroupKeyPair, MessageSender.PreparedSendData) in + // Generate the new encryption key pair + let legacyNewKeyPair: ECKeyPair = Curve25519.generateKeyPair() + let newKeyPair: ClosedGroupKeyPair = ClosedGroupKeyPair( + threadId: closedGroup.threadId, + publicKey: legacyNewKeyPair.publicKey, + secretKey: legacyNewKeyPair.privateKey, + receivedTimestamp: (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) ) - } - catch { - return Fail(error: error) - .eraseToAnyPublisher() - } - - return MessageSender.sendImmediate(preparedSendData: sendData) - .map { _ in newKeyPair } - .eraseToAnyPublisher() + + // Distribute it + let proto = try SNProtoKeyPair.builder( + publicKey: newKeyPair.publicKey, + privateKey: newKeyPair.secretKey + ).build() + let plaintext = try proto.serializedData() + + distributingKeyPairs.mutate { + $0[closedGroup.id] = ($0[closedGroup.id] ?? []) + .appending(newKeyPair) + } + + let sendData: MessageSender.PreparedSendData = try MessageSender + .preparedSendData( + db, + message: ClosedGroupControlMessage( + kind: .encryptionKeyPair( + publicKey: nil, + wrappers: targetMembers.map { memberPublicKey in + ClosedGroupControlMessage.KeyPairWrapper( + publicKey: memberPublicKey, + encryptedKeyPair: try MessageSender.encryptWithSessionProtocol( + db, + plaintext: plaintext, + for: memberPublicKey + ) + ) + } + ) + ), + to: try Message.Destination + .from(db, threadId: closedGroup.threadId, threadVariant: .legacyGroup), + namespace: try Message.Destination + .from(db, threadId: closedGroup.threadId, threadVariant: .legacyGroup) + .defaultNamespace, + interactionId: nil + ) + + return (newKeyPair, sendData) + } + .flatMap { newKeyPair, sendData -> AnyPublisher in + MessageSender.sendImmediate(preparedSendData: sendData) + .map { _ in newKeyPair } + .eraseToAnyPublisher() + } .handleEvents( receiveOutput: { newKeyPair in /// Store it **after** having sent out the message to the group @@ -253,116 +249,110 @@ extension MessageSender { } public static func update( - _ db: Database, groupPublicKey: String, with members: Set, name: String ) -> AnyPublisher { - // Get the group, check preconditions & prepare - guard (try? SessionThread.exists(db, id: groupPublicKey)) == true else { - SNLog("Can't update nonexistent closed group.") - return Fail(error: MessageSenderError.noThread) - .eraseToAnyPublisher() - } - guard let closedGroup: ClosedGroup = try? ClosedGroup.fetchOne(db, id: groupPublicKey) else { - return Fail(error: MessageSenderError.invalidClosedGroupUpdate) - .eraseToAnyPublisher() - } - - let userPublicKey: String = getUserHexEncodedPublicKey(db) - - do { - // Update name if needed - if name != closedGroup.name { - // Update the group - _ = try ClosedGroup - .filter(id: closedGroup.id) - .updateAll(db, ClosedGroup.Columns.name.set(to: name)) + return Storage.shared + .writePublisher { db -> (String, ClosedGroup, [GroupMember], Set) in + let userPublicKey: String = getUserHexEncodedPublicKey(db) - // Notify the user - let interaction: Interaction = try Interaction( - threadId: groupPublicKey, - authorId: userPublicKey, - variant: .infoClosedGroupUpdated, - body: ClosedGroupControlMessage.Kind - .nameChange(name: name) - .infoMessage(db, sender: userPublicKey), - timestampMs: SnodeAPI.currentOffsetTimestampMs() - ).inserted(db) + // Get the group, check preconditions & prepare + guard (try? SessionThread.exists(db, id: groupPublicKey)) == true else { + SNLog("Can't update nonexistent closed group.") + throw MessageSenderError.noThread + } + guard let closedGroup: ClosedGroup = try? ClosedGroup.fetchOne(db, id: groupPublicKey) else { + throw MessageSenderError.invalidClosedGroupUpdate + } - guard let interactionId: Int64 = interaction.id else { throw StorageError.objectNotSaved } + // Update name if needed + if name != closedGroup.name { + // Update the group + _ = try ClosedGroup + .filter(id: closedGroup.id) + .updateAll(db, ClosedGroup.Columns.name.set(to: name)) + + // Notify the user + let interaction: Interaction = try Interaction( + threadId: groupPublicKey, + authorId: userPublicKey, + variant: .infoClosedGroupUpdated, + body: ClosedGroupControlMessage.Kind + .nameChange(name: name) + .infoMessage(db, sender: userPublicKey), + timestampMs: SnodeAPI.currentOffsetTimestampMs() + ).inserted(db) + + guard let interactionId: Int64 = interaction.id else { throw StorageError.objectNotSaved } + + // Send the update to the group + try MessageSender.send( + db, + message: ClosedGroupControlMessage(kind: .nameChange(name: name)), + interactionId: interactionId, + threadId: groupPublicKey, + threadVariant: .legacyGroup + ) + + // Update libSession + try? SessionUtil.update( + db, + groupPublicKey: closedGroup.threadId, + name: name + ) + } - // Send the update to the group - try MessageSender.send( - db, - message: ClosedGroupControlMessage(kind: .nameChange(name: name)), - interactionId: interactionId, - threadId: groupPublicKey, - threadVariant: .legacyGroup - ) + // Retrieve member info + guard let allGroupMembers: [GroupMember] = try? closedGroup.allMembers.fetchAll(db) else { + throw MessageSenderError.invalidClosedGroupUpdate + } - // Update libSession - try? SessionUtil.update( - db, - groupPublicKey: closedGroup.threadId, - name: name + let standardAndZombieMemberIds: [String] = allGroupMembers + .filter { $0.role == .standard || $0.role == .zombie } + .map { $0.profileId } + let addedMembers: Set = members.subtracting(standardAndZombieMemberIds) + + // Add members if needed + if !addedMembers.isEmpty { + do { + try addMembers( + db, + addedMembers: addedMembers, + userPublicKey: userPublicKey, + allGroupMembers: allGroupMembers, + closedGroup: closedGroup + ) + } + catch { + throw MessageSenderError.invalidClosedGroupUpdate + } + } + + // Remove members if needed + return ( + userPublicKey, + closedGroup, + allGroupMembers, + Set(standardAndZombieMemberIds).subtracting(members) ) } - } - catch { - return Fail(error: error) - .eraseToAnyPublisher() - } - - // Retrieve member info - guard let allGroupMembers: [GroupMember] = try? closedGroup.allMembers.fetchAll(db) else { - return Fail(error: MessageSenderError.invalidClosedGroupUpdate) - .eraseToAnyPublisher() - } - - let standardAndZombieMemberIds: [String] = allGroupMembers - .filter { $0.role == .standard || $0.role == .zombie } - .map { $0.profileId } - let addedMembers: Set = members.subtracting(standardAndZombieMemberIds) - - // Add members if needed - if !addedMembers.isEmpty { - do { - try addMembers( - db, - addedMembers: addedMembers, - userPublicKey: userPublicKey, - allGroupMembers: allGroupMembers, - closedGroup: closedGroup - ) - } - catch { - return Fail(error: MessageSenderError.invalidClosedGroupUpdate) - .eraseToAnyPublisher() - } - } - - // Remove members if needed - let removedMembers: Set = Set(standardAndZombieMemberIds).subtracting(members) - - if !removedMembers.isEmpty { - do { - return try removeMembers( - db, + .flatMap { userPublicKey, closedGroup, allGroupMembers, removedMembers -> AnyPublisher in + guard !removedMembers.isEmpty else { + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + + return removeMembers( removedMembers: removedMembers, userPublicKey: userPublicKey, allGroupMembers: allGroupMembers, closedGroup: closedGroup ) + .catch { _ in Fail(error: MessageSenderError.invalidClosedGroupUpdate).eraseToAnyPublisher() } + .eraseToAnyPublisher() } - catch { - return Fail(error: MessageSenderError.invalidClosedGroupUpdate) - .eraseToAnyPublisher() - } - } - - return Just(()) - .setFailureType(to: Error.self) .eraseToAnyPublisher() } @@ -476,19 +466,20 @@ extension MessageSender { /// The returned promise is fulfilled when the `MEMBERS_REMOVED` message has been sent to the group AND the new encryption key pair has been /// generated and distributed. private static func removeMembers( - _ db: Database, removedMembers: Set, userPublicKey: String, allGroupMembers: [GroupMember], closedGroup: ClosedGroup - ) throws -> AnyPublisher { + ) -> AnyPublisher { guard !removedMembers.contains(userPublicKey) else { SNLog("Invalid closed group update.") - throw MessageSenderError.invalidClosedGroupUpdate + return Fail(error: MessageSenderError.invalidClosedGroupUpdate) + .eraseToAnyPublisher() } guard allGroupMembers.contains(where: { $0.role == .admin && $0.profileId == userPublicKey }) else { SNLog("Only an admin can remove members from a group.") - throw MessageSenderError.invalidClosedGroupUpdate + return Fail(error: MessageSenderError.invalidClosedGroupUpdate) + .eraseToAnyPublisher() } let groupMemberIds: [String] = allGroupMembers @@ -499,39 +490,39 @@ extension MessageSender { .map { $0.profileId } let members: Set = Set(groupMemberIds).subtracting(removedMembers) - // Update zombie & member list - try GroupMember - .filter(GroupMember.Columns.groupId == closedGroup.threadId) - .filter(removedMembers.contains(GroupMember.Columns.profileId)) - .filter([ GroupMember.Role.standard, GroupMember.Role.zombie ].contains(GroupMember.Columns.role)) - .deleteAll(db) - - let interactionId: Int64? - - // Notify the user if needed (not if only zombie members were removed) - if !removedMembers.subtracting(groupZombieIds).isEmpty { - let interaction: Interaction = try Interaction( - threadId: closedGroup.threadId, - authorId: userPublicKey, - variant: .infoClosedGroupUpdated, - body: ClosedGroupControlMessage.Kind - .membersRemoved(members: removedMembers.map { Data(hex: $0) }) - .infoMessage(db, sender: userPublicKey), - timestampMs: SnodeAPI.currentOffsetTimestampMs() - ).inserted(db) - - guard let newInteractionId: Int64 = interaction.id else { throw StorageError.objectNotSaved } - - interactionId = newInteractionId - } - else { - interactionId = nil - } - - // Send the update to the group and generate + distribute a new encryption key pair - return MessageSender - .sendImmediate( - preparedSendData: try MessageSender + return Storage.shared + .writePublisher { db in + // Update zombie & member list + try GroupMember + .filter(GroupMember.Columns.groupId == closedGroup.threadId) + .filter(removedMembers.contains(GroupMember.Columns.profileId)) + .filter([ GroupMember.Role.standard, GroupMember.Role.zombie ].contains(GroupMember.Columns.role)) + .deleteAll(db) + + let interactionId: Int64? + + // Notify the user if needed (not if only zombie members were removed) + if !removedMembers.subtracting(groupZombieIds).isEmpty { + let interaction: Interaction = try Interaction( + threadId: closedGroup.threadId, + authorId: userPublicKey, + variant: .infoClosedGroupUpdated, + body: ClosedGroupControlMessage.Kind + .membersRemoved(members: removedMembers.map { Data(hex: $0) }) + .infoMessage(db, sender: userPublicKey), + timestampMs: SnodeAPI.currentOffsetTimestampMs() + ).inserted(db) + + guard let newInteractionId: Int64 = interaction.id else { throw StorageError.objectNotSaved } + + interactionId = newInteractionId + } + else { + interactionId = nil + } + + // Send the update to the group and generate + distribute a new encryption key pair + return try MessageSender .preparedSendData( db, message: ClosedGroupControlMessage( @@ -546,18 +537,15 @@ extension MessageSender { .defaultNamespace, interactionId: interactionId ) - ) + } + .flatMap { MessageSender.sendImmediate(preparedSendData: $0) } .flatMap { _ -> AnyPublisher in - Storage.shared - .writePublisherFlatMap { db in - generateAndSendNewEncryptionKeyPair( - db, - targetMembers: members, - userPublicKey: userPublicKey, - allGroupMembers: allGroupMembers, - closedGroup: closedGroup - ) - } + MessageSender.generateAndSendNewEncryptionKeyPair( + targetMembers: members, + userPublicKey: userPublicKey, + allGroupMembers: allGroupMembers, + closedGroup: closedGroup + ) } .eraseToAnyPublisher() } diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift index 873319035..41d72b455 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift @@ -57,38 +57,47 @@ extension OpenGroupAPI { ) { guard hasStarted else { return } - let minPollFailureCount: TimeInterval = Storage.shared - .read { db in + dependencies.storage + .readPublisher { [server = server] db in try OpenGroup .filter(OpenGroup.Columns.server == server) .select(min(OpenGroup.Columns.pollFailureCount)) .asRequest(of: TimeInterval.self) .fetchOne(db) } - .defaulting(to: 0) - let lastPollStart: TimeInterval = Date().timeIntervalSince1970 - let nextPollInterval: TimeInterval = getInterval(for: minPollFailureCount, minInterval: Poller.minPollInterval, maxInterval: Poller.maxPollInterval) - - // Wait until the last poll completes before polling again ensuring we don't poll any faster than - // the 'nextPollInterval' value - poll(using: dependencies) + .tryFlatMap { [weak self] minPollFailureCount -> AnyPublisher<(TimeInterval, TimeInterval), Error> in + guard let strongSelf = self else { throw OpenGroupAPIError.invalidPoll } + + let lastPollStart: TimeInterval = Date().timeIntervalSince1970 + let nextPollInterval: TimeInterval = Poller.getInterval( + for: (minPollFailureCount ?? 0), + minInterval: Poller.minPollInterval, + maxInterval: Poller.maxPollInterval + ) + + // Wait until the last poll completes before polling again ensuring we don't poll any faster than + // the 'nextPollInterval' value + return strongSelf.poll(using: dependencies) + .map { _ in (lastPollStart, nextPollInterval) } + .eraseToAnyPublisher() + } .subscribe(on: dependencies.subscribeQueue, immediatelyIfMain: true) .receive(on: dependencies.receiveQueue, immediatelyIfMain: true) .sinkUntilComplete( - receiveCompletion: { [weak self] _ in + receiveValue: { [weak self] lastPollStart, nextPollInterval in let currentTime: TimeInterval = Date().timeIntervalSince1970 let remainingInterval: TimeInterval = max(0, nextPollInterval - (currentTime - lastPollStart)) - + guard remainingInterval > 0 else { - return Threading.pollerQueue.async { + return dependencies.subscribeQueue.async { self?.pollRecursively(using: dependencies) } } - + self?.timer = Timer.scheduledTimerOnMainThread(withTimeInterval: remainingInterval, repeats: false) { timer in timer.invalidate() - - Threading.pollerQueue.async { + + dependencies.subscribeQueue.async { self?.pollRecursively(using: dependencies) } } @@ -120,6 +129,11 @@ extension OpenGroupAPI { self.isPolling = true let server: String = self.server + let hasPerformedInitialPoll: Bool = (dependencies.cache.hasPerformedInitialPoll[server] == true) + let timeSinceLastPoll: TimeInterval = ( + dependencies.cache.timeSinceLastPoll[server] ?? + dependencies.mutableCache.mutate { $0.getTimeSinceLastOpen(using: dependencies) } + ) return dependencies.storage .readPublisher { db -> (Int64, PreparedSendData) in @@ -136,11 +150,8 @@ extension OpenGroupAPI { .preparedPoll( db, server: server, - hasPerformedInitialPoll: dependencies.cache.hasPerformedInitialPoll[server] == true, - timeSinceLastPoll: ( - dependencies.cache.timeSinceLastPoll[server] ?? - dependencies.cache.getTimeSinceLastOpen(using: dependencies) - ), + hasPerformedInitialPoll: hasPerformedInitialPoll, + timeSinceLastPoll: timeSinceLastPoll, using: dependencies ) ) @@ -591,12 +602,12 @@ extension OpenGroupAPI { } } } - } - - // MARK: - Convenience + + // MARK: - Convenience - fileprivate static func getInterval(for failureCount: TimeInterval, minInterval: TimeInterval, maxInterval: TimeInterval) -> TimeInterval { - // Arbitrary backoff factor... - return min(maxInterval, minInterval + pow(2, failureCount)) + fileprivate static func getInterval(for failureCount: TimeInterval, minInterval: TimeInterval, maxInterval: TimeInterval) -> TimeInterval { + // Arbitrary backoff factor... + return min(maxInterval, minInterval + pow(2, failureCount)) + } } } diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserGroups.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserGroups.swift index 3433ea5dd..ba9210a74 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserGroups.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserGroups.swift @@ -138,7 +138,7 @@ internal extension SessionUtil { // Add any new communities (via the OpenGroupManager) communities.forEach { community in - OpenGroupManager.shared + let successfullyAddedGroup: Bool = OpenGroupManager.shared .add( db, roomToken: community.data.roomToken, @@ -146,7 +146,20 @@ internal extension SessionUtil { publicKey: community.data.publicKey, calledFromConfigHandling: true ) - .sinkUntilComplete() + + if successfullyAddedGroup { + db.afterNextTransactionNested { _ in + OpenGroupManager.shared.performInitialRequestsAfterAdd( + successfullyAddedGroup: successfullyAddedGroup, + roomToken: community.data.roomToken, + server: community.data.server, + publicKey: community.data.publicKey, + calledFromConfigHandling: false + ) + .subscribe(on: OpenGroupAPI.workQueue) + .sinkUntilComplete() + } + } // Set the priority if it's changed (new communities will have already been inserted at // this stage) diff --git a/SessionMessagingKit/SessionUtil/SessionUtil.swift b/SessionMessagingKit/SessionUtil/SessionUtil.swift index 44eb2f20b..204068860 100644 --- a/SessionMessagingKit/SessionUtil/SessionUtil.swift +++ b/SessionMessagingKit/SessionUtil/SessionUtil.swift @@ -403,40 +403,35 @@ public enum SessionUtil { guard SessionUtil.userConfigsEnabled else { return [] } return Storage.shared - .read { db -> [String] in + .read { db -> Set in guard Identity.userExists(db) else { return [] } - let existingDumpVariants: Set = (try? ConfigDump + return try ConfigDump .select(.variant) .filter(ConfigDump.Columns.publicKey == publicKey) .asRequest(of: ConfigDump.Variant.self) - .fetchSet(db)) - .defaulting(to: []) - - /// Extract all existing hashes for any dumps associated with the given `publicKey` - return existingDumpVariants - .map { variant -> [String] in - guard - let conf = SessionUtil - .config(for: variant, publicKey: publicKey) - .wrappedValue, - let hashList: UnsafeMutablePointer = config_current_hashes(conf) - else { - return [] - } - - let result: [String] = [String]( - pointer: hashList.pointee.value, - count: hashList.pointee.len, - defaultValue: [] - ) - hashList.deallocate() - - return result - } - .reduce([], +) + .fetchSet(db) } .defaulting(to: []) + .map { variant -> [String] in + /// Extract all existing hashes for any dumps associated with the given `publicKey` + guard + let conf = SessionUtil + .config(for: variant, publicKey: publicKey) + .wrappedValue, + let hashList: UnsafeMutablePointer = config_current_hashes(conf) + else { return [] } + + let result: [String] = [String]( + pointer: hashList.pointee.value, + count: hashList.pointee.len, + defaultValue: [] + ) + hashList.deallocate() + + return result + } + .reduce([], +) } // MARK: - Receiving diff --git a/SessionMessagingKit/Utilities/ProfileManager.swift b/SessionMessagingKit/Utilities/ProfileManager.swift index ea0753690..3089f59f1 100644 --- a/SessionMessagingKit/Utilities/ProfileManager.swift +++ b/SessionMessagingKit/Utilities/ProfileManager.swift @@ -498,7 +498,7 @@ public struct ProfileManager { dependencies: Dependencies = Dependencies() ) throws { let isCurrentUser = (publicKey == getUserHexEncodedPublicKey(db, dependencies: dependencies)) - let profile: Profile = Profile.fetchOrCreate(id: publicKey) + let profile: Profile = Profile.fetchOrCreate(db, id: publicKey) var profileChanges: [ConfigColumnAssignment] = [] // Name diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift index 8beeb921b..b2c69c409 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift +++ b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift @@ -118,9 +118,9 @@ class OpenGroupManagerSpec: QuickSpec { dependencies = OpenGroupManager.OGMDependencies( subscribeQueue: DispatchQueue.main, receiveQueue: DispatchQueue.main, - cache: Atomic(mockOGMCache), + cache: mockOGMCache, onionApi: TestCapabilitiesAndRoomApi.self, - generalCache: Atomic(mockGeneralCache), + generalCache: mockGeneralCache, storage: mockStorage, sodium: mockSodium, genericHash: mockGenericHash, diff --git a/SessionMessagingKitTests/_TestUtilities/MockOGMCache.swift b/SessionMessagingKitTests/_TestUtilities/MockOGMCache.swift index cef200b39..ec2b8ac10 100644 --- a/SessionMessagingKitTests/_TestUtilities/MockOGMCache.swift +++ b/SessionMessagingKitTests/_TestUtilities/MockOGMCache.swift @@ -6,9 +6,9 @@ import SessionUtilitiesKit @testable import SessionMessagingKit -class MockOGMCache: Mock, OGMCacheType { - var defaultRoomsPublisher: AnyPublisher<[OpenGroupAPI.Room], Error>? { - get { return accept() as? AnyPublisher<[OpenGroupAPI.Room], Error> } +class MockOGMCache: Mock, OGMMutableCacheType { + var defaultRoomsPublisher: AnyPublisher<[OpenGroupManager.DefaultRoomInfo], Error>? { + get { return accept() as? AnyPublisher<[OpenGroupManager.DefaultRoomInfo], Error> } set { accept(args: [newValue]) } } diff --git a/SessionSnodeKit/SSKDependencies.swift b/SessionSnodeKit/SSKDependencies.swift index 01faa2289..875762f75 100644 --- a/SessionSnodeKit/SSKDependencies.swift +++ b/SessionSnodeKit/SSKDependencies.swift @@ -17,7 +17,7 @@ open class SSKDependencies: Dependencies { subscribeQueue: DispatchQueue? = nil, receiveQueue: DispatchQueue? = nil, onionApi: OnionRequestAPIType.Type? = nil, - generalCache: Atomic? = nil, + generalCache: MutableGeneralCacheType? = nil, storage: Storage? = nil, scheduler: ValueObservationScheduler? = nil, standardUserDefaults: UserDefaultsType? = nil, diff --git a/SessionUtilitiesKit/Combine/ReplaySubject.swift b/SessionUtilitiesKit/Combine/ReplaySubject.swift index 7648fa989..428e58d9c 100644 --- a/SessionUtilitiesKit/Combine/ReplaySubject.swift +++ b/SessionUtilitiesKit/Combine/ReplaySubject.swift @@ -49,10 +49,23 @@ public final class ReplaySubject: Subject { public func receive(subscriber: S) where S: Subscriber, Failure == S.Failure, Output == S.Input { lock.lock(); defer { lock.unlock() } - let subscription = ReplaySubjectSubscription(downstream: AnySubscriber(subscriber)) + /// According to the below comment the `subscriber.receive(subscription: subscription)` code runs asynchronously + /// which aligns with testing (resulting in the `request(_ newDemand: Subscribers.Demand)` function getting called after this + /// function returns + /// + /// Later in the thread it's mentioned that as of `iOS 13.3` this behaviour changed to be synchronous but as of writing the minimum + /// deployment version is set to `iOS 13.0` which I assume is why we are seeing the async behaviour which results in `receiveValue` + /// not being called in some cases + /// + /// When the project is eventually updated to have a minimum version higher than `iOS 13.3` we should re-test this behaviour to see if + /// we can revert this change + /// + /// https://forums.swift.org/t/combine-receive-on-runloop-main-loses-sent-value-how-can-i-make-it-work/28631/20 + let subscription: ReplaySubjectSubscription = ReplaySubjectSubscription(downstream: AnySubscriber(subscriber)) { [buffer = buffer, completion = completion] subscription in + subscription.replay(buffer, completion: completion) + } subscriber.receive(subscription: subscription) subscriptions.append(subscription) - subscription.replay(buffer, completion: completion) } } @@ -62,17 +75,21 @@ public final class ReplaySubjectSubscription: Subscripti private let downstream: AnySubscriber private var isCompleted: Bool = false private var demand: Subscribers.Demand = .none + private var onInitialDemand: ((ReplaySubjectSubscription) -> ())? // MARK: - Initialization - init(downstream: AnySubscriber) { + init(downstream: AnySubscriber, onInitialDemand: @escaping (ReplaySubjectSubscription) -> ()) { self.downstream = downstream + self.onInitialDemand = onInitialDemand } // MARK: - Subscription public func request(_ newDemand: Subscribers.Demand) { demand += newDemand + onInitialDemand?(self) + onInitialDemand = nil } public func cancel() { diff --git a/SessionUtilitiesKit/Database/Storage.swift b/SessionUtilitiesKit/Database/Storage.swift index 4cd35ccce..1feb26663 100644 --- a/SessionUtilitiesKit/Database/Storage.swift +++ b/SessionUtilitiesKit/Database/Storage.swift @@ -516,24 +516,6 @@ open class Storage { // MARK: - Combine Extensions -public extension Storage { - func readPublisherFlatMap( - value: @escaping (Database) throws -> AnyPublisher - ) -> AnyPublisher { - return readPublisher(value: value) - .flatMap { resultPublisher -> AnyPublisher in resultPublisher } - .eraseToAnyPublisher() - } - - func writePublisherFlatMap( - updates: @escaping (Database) throws -> AnyPublisher - ) -> AnyPublisher { - return writePublisher(updates: updates) - .flatMap { resultPublisher -> AnyPublisher in resultPublisher } - .eraseToAnyPublisher() - } -} - public extension ValueObservation { func publisher( in storage: Storage, diff --git a/SessionUtilitiesKit/General/Dependencies.swift b/SessionUtilitiesKit/General/Dependencies.swift index 64e2d5f3e..61da1dc05 100644 --- a/SessionUtilitiesKit/General/Dependencies.swift +++ b/SessionUtilitiesKit/General/Dependencies.swift @@ -4,6 +4,10 @@ import Foundation import GRDB open class Dependencies { + /// These should not be accessed directly but rather via an instance of this type + private static let _generalCacheInstance: MutableGeneralCacheType = General.Cache() + private static let _generalCacheInstanceAccessQueue = DispatchQueue(label: "GeneralCacheInstanceAccess") + public var _subscribeQueue: Atomic public var subscribeQueue: DispatchQueue { get { Dependencies.getValueSettingIfNull(&_subscribeQueue) { DispatchQueue.global(qos: .default) } } @@ -16,10 +20,25 @@ open class Dependencies { set { _receiveQueue.mutate { $0 = newValue } } } - public var _generalCache: Atomic?> - public var generalCache: Atomic { - get { Dependencies.getValueSettingIfNull(&_generalCache) { General.cache } } - set { _generalCache.mutate { $0 = newValue } } + public var _mutableGeneralCache: Atomic + public var mutableGeneralCache: Atomic { + get { + Dependencies.getMutableValueSettingIfNull(&_mutableGeneralCache) { + Dependencies._generalCacheInstanceAccessQueue.sync { Dependencies._generalCacheInstance } + } + } + } + public var generalCache: GeneralCacheType { + get { + Dependencies.getValueSettingIfNull(&_mutableGeneralCache) { + Dependencies._generalCacheInstanceAccessQueue.sync { Dependencies._generalCacheInstance } + } + } + set { + guard let mutableValue: MutableGeneralCacheType = newValue as? MutableGeneralCacheType else { return } + + _mutableGeneralCache.mutate { $0 = mutableValue } + } } public var _storage: Atomic @@ -51,7 +70,7 @@ open class Dependencies { public init( subscribeQueue: DispatchQueue? = nil, receiveQueue: DispatchQueue? = nil, - generalCache: Atomic? = nil, + generalCache: MutableGeneralCacheType? = nil, storage: Storage? = nil, scheduler: ValueObservationScheduler? = nil, standardUserDefaults: UserDefaultsType? = nil, @@ -59,7 +78,7 @@ open class Dependencies { ) { _subscribeQueue = Atomic(subscribeQueue) _receiveQueue = Atomic(receiveQueue) - _generalCache = Atomic(generalCache) + _mutableGeneralCache = Atomic(generalCache) _storage = Atomic(storage) _scheduler = Atomic(scheduler) _standardUserDefaults = Atomic(standardUserDefaults) @@ -77,4 +96,14 @@ open class Dependencies { return value } + + public static func getMutableValueSettingIfNull(_ maybeValue: inout Atomic, _ valueGenerator: () -> T) -> Atomic { + guard let value: T = maybeValue.wrappedValue else { + let value: T = valueGenerator() + maybeValue.mutate { $0 = value } + return Atomic(value) + } + + return Atomic(value) + } } diff --git a/SessionUtilitiesKit/General/General.swift b/SessionUtilitiesKit/General/General.swift index 9c7e9f0f4..5f59f2b13 100644 --- a/SessionUtilitiesKit/General/General.swift +++ b/SessionUtilitiesKit/General/General.swift @@ -3,35 +3,48 @@ import Foundation import GRDB -public protocol GeneralCacheType { - var encodedPublicKey: String? { get set } - var recentReactionTimestamps: [Int64] { get set } -} +// MARK: - General.Cache public enum General { - public class Cache: GeneralCacheType { + public class Cache: MutableGeneralCacheType { public var encodedPublicKey: String? = nil public var recentReactionTimestamps: [Int64] = [] } - - public static var cache: Atomic = Atomic(Cache()) } +// MARK: - GeneralError + public enum GeneralError: Error { case invalidSeed case keyGenerationFailed case randomGenerationFailed } +// MARK: - Convenience + public func getUserHexEncodedPublicKey(_ db: Database? = nil, dependencies: Dependencies = Dependencies()) -> String { - if let cachedKey: String = dependencies.generalCache.wrappedValue.encodedPublicKey { return cachedKey } + if let cachedKey: String = dependencies.generalCache.encodedPublicKey { return cachedKey } if let publicKey: Data = Identity.fetchUserPublicKey(db) { // Can be nil under some circumstances let sessionId: SessionId = SessionId(.standard, publicKey: publicKey.bytes) - dependencies.generalCache.mutate { $0.encodedPublicKey = sessionId.hexString } + dependencies.mutableGeneralCache.mutate { $0.encodedPublicKey = sessionId.hexString } return sessionId.hexString } return "" } + +// MARK: - GeneralCacheType + +public protocol MutableGeneralCacheType: GeneralCacheType { + var encodedPublicKey: String? { get set } + var recentReactionTimestamps: [Int64] { get set } +} + +/// This is a read-only version of the `OGMMutableCacheType` designed to avoid unintentionally mutating the instance in a +/// non-thread-safe way +public protocol GeneralCacheType { + var encodedPublicKey: String? { get } + var recentReactionTimestamps: [Int64] { get } +} From 5e2e103ee162a2ceff6d47b66438b53d7aedbcbc Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 29 Jun 2023 12:11:54 +1000 Subject: [PATCH 099/135] Resolved the remaining known internal testing issues Removed the 'immediatelyOnMain' extensions as they would break in some cases (eg. upstream errors with multiple 'receive(on:)' calls) resulting in logic running on unexpected threads Updated the ReplaySubject to add subscribers in an Atomic just to be safe Updated the code to remove the invalid open group when the user receives an error after joining Fixed a bug with editing closed group members Fixed broken unit tests --- Session/Calls/CallVC.swift | 1 - Session/Calls/VideoPreviewVC.swift | 1 - .../Views & Modals/IncomingCallBanner.swift | 1 - Session/Closed Groups/EditClosedGroupVC.swift | 3 +- .../ConversationVC+Interaction.swift | 12 + Session/Meta/AppDelegate.swift | 1 - Session/Open Groups/JoinOpenGroupVC.swift | 22 +- .../Open Groups/OpenGroupSuggestionGrid.swift | 4 +- .../Shared/SessionTableViewController.swift | 13 +- Session/Utilities/BackgroundPoller.swift | 4 +- .../Control Messages/CallMessage.swift | 1 - .../Open Groups/OpenGroupManager.swift | 5 +- .../Pollers/OpenGroupPoller.swift | 4 +- .../Sending & Receiving/Pollers/Poller.swift | 8 +- .../Open Groups/OpenGroupManagerSpec.swift | 240 ++++++++++-------- .../_TestUtilities/DependencyExtensions.swift | 4 +- .../OGMDependencyExtensions.swift | 6 +- SessionShareExtension/ThreadPickerVC.swift | 2 +- ...eadDisappearingMessagesViewModelSpec.swift | 10 +- .../ThreadSettingsViewModelSpec.swift | 10 +- .../NotificationContentViewModelSpec.swift | 6 +- .../Combine/Publisher+Utilities.swift | 58 ----- .../Combine/ReplaySubject.swift | 11 +- _SharedTestUtilities/CombineExtensions.swift | 6 +- _SharedTestUtilities/MockGeneralCache.swift | 2 +- 25 files changed, 204 insertions(+), 231 deletions(-) diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index 88a665d0c..c7657d8a5 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -3,7 +3,6 @@ import UIKit import YYImage import MediaPlayer -import WebRTC import SessionUIKit import SessionMessagingKit import SessionUtilitiesKit diff --git a/Session/Calls/VideoPreviewVC.swift b/Session/Calls/VideoPreviewVC.swift index 4311d961b..b75b05b16 100644 --- a/Session/Calls/VideoPreviewVC.swift +++ b/Session/Calls/VideoPreviewVC.swift @@ -1,7 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit -import WebRTC import SessionUIKit public protocol VideoPreviewDelegate: AnyObject { diff --git a/Session/Calls/Views & Modals/IncomingCallBanner.swift b/Session/Calls/Views & Modals/IncomingCallBanner.swift index 98fdbf424..7646903a2 100644 --- a/Session/Calls/Views & Modals/IncomingCallBanner.swift +++ b/Session/Calls/Views & Modals/IncomingCallBanner.swift @@ -1,7 +1,6 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit -import WebRTC import SessionUIKit import SessionMessagingKit import SignalUtilitiesKit diff --git a/Session/Closed Groups/EditClosedGroupVC.swift b/Session/Closed Groups/EditClosedGroupVC.swift index 11161e534..07475227c 100644 --- a/Session/Closed Groups/EditClosedGroupVC.swift +++ b/Session/Closed Groups/EditClosedGroupVC.swift @@ -465,7 +465,8 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat ModalActivityIndicatorViewController.present(fromViewController: navigationController) { _ in Storage.shared .writePublisher { db in - guard updatedMemberIds.contains(userPublicKey) else { return } + // If the user is no longer a member then leave the group + guard !updatedMemberIds.contains(userPublicKey) else { return } try MessageSender.leave( db, diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index c3073d9ce..13704aadb 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -1552,6 +1552,18 @@ extension ConversationVC: switch result { case .finished: break case .failure(let error): + // If there was a failure then the group will be in invalid state until + // the next launch so remove it (the user will be left on the previous + // screen so can re-trigger the join) + Storage.shared.writeAsync { db in + OpenGroupManager.shared.delete( + db, + openGroupId: OpenGroup.idFor(roomToken: room, server: server), + calledFromConfigHandling: false + ) + } + + // Show the user an error indicating they failed to properly join the group let errorModal: ConfirmationModal = ConfirmationModal( info: ConfirmationModal.Info( title: "COMMUNITY_ERROR_GENERIC".localized(), diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 21563c96b..82f373b48 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -4,7 +4,6 @@ import UIKit import Combine import UserNotifications import GRDB -import WebRTC import SessionUIKit import SessionMessagingKit import SessionUtilitiesKit diff --git a/Session/Open Groups/JoinOpenGroupVC.swift b/Session/Open Groups/JoinOpenGroupVC.swift index b5f214cc9..5c7e19df5 100644 --- a/Session/Open Groups/JoinOpenGroupVC.swift +++ b/Session/Open Groups/JoinOpenGroupVC.swift @@ -193,11 +193,25 @@ final class JoinOpenGroupVC: BaseVC, UIPageViewControllerDataSource, UIPageViewC receiveCompletion: { result in switch result { case .failure(let error): - self?.dismiss(animated: true, completion: nil) // Dismiss the loader - let title = "COMMUNITY_ERROR_GENERIC".localized() - let message = error.localizedDescription + // If there was a failure then the group will be in invalid state until + // the next launch so remove it (the user will be left on the previous + // screen so can re-trigger the join) + Storage.shared.writeAsync { db in + OpenGroupManager.shared.delete( + db, + openGroupId: OpenGroup.idFor(roomToken: roomToken, server: server), + calledFromConfigHandling: false + ) + } + + // Show the user an error indicating they failed to properly join the group self?.isJoining = false - self?.showError(title: title, message: message) + self?.dismiss(animated: true) { // Dismiss the loader + self?.showError( + title: "COMMUNITY_ERROR_GENERIC".localized(), + message: error.localizedDescription + ) + } case .finished: self?.presentingViewController?.dismiss(animated: true, completion: nil) diff --git a/Session/Open Groups/OpenGroupSuggestionGrid.swift b/Session/Open Groups/OpenGroupSuggestionGrid.swift index f5e4718aa..f7037bc55 100644 --- a/Session/Open Groups/OpenGroupSuggestionGrid.swift +++ b/Session/Open Groups/OpenGroupSuggestionGrid.swift @@ -144,7 +144,7 @@ final class OpenGroupSuggestionGrid: UIView, UICollectionViewDataSource, UIColle OpenGroupManager.getDefaultRoomsIfNeeded() .subscribe(on: DispatchQueue.global(qos: .default)) - .receive(on: DispatchQueue.main, immediatelyIfMain: true) + .receive(on: DispatchQueue.main) .sinkUntilComplete( receiveCompletion: { [weak self] result in switch result { @@ -340,7 +340,7 @@ extension OpenGroupSuggestionGrid { .eraseToAnyPublisher() ) .subscribe(on: DispatchQueue.global(qos: .userInitiated)) - .receive(on: DispatchQueue.main, immediatelyIfMain: true) + .receive(on: DispatchQueue.main) .sinkUntilComplete( receiveValue: { [weak self] imageData, hasData in guard hasData else { diff --git a/Session/Shared/SessionTableViewController.swift b/Session/Shared/SessionTableViewController.swift index 1be684f0b..f1a01d854 100644 --- a/Session/Shared/SessionTableViewController.swift +++ b/Session/Shared/SessionTableViewController.swift @@ -187,13 +187,7 @@ class SessionTableViewController [DefaultRoomInfo]? in dependencies.storage.write { db -> [DefaultRoomInfo] in diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift index 41d72b455..a4cce3c35 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift @@ -81,8 +81,8 @@ extension OpenGroupAPI { .map { _ in (lastPollStart, nextPollInterval) } .eraseToAnyPublisher() } - .subscribe(on: dependencies.subscribeQueue, immediatelyIfMain: true) - .receive(on: dependencies.receiveQueue, immediatelyIfMain: true) + .subscribe(on: dependencies.subscribeQueue) + .receive(on: dependencies.receiveQueue) .sinkUntilComplete( receiveValue: { [weak self] lastPollStart, nextPollInterval in let currentTime: TimeInterval = Date().timeIntervalSince1970 diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift index e69afe618..277196448 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift @@ -93,7 +93,6 @@ public class Poller { let namespaces: [SnodeAPI.Namespace] = self.namespaces getSnodeForPolling(for: publicKey) - .subscribe(on: dependencies.subscribeQueue, immediatelyIfMain: true) .flatMap { snode -> AnyPublisher<[Message], Error> in Poller.poll( namespaces: namespaces, @@ -103,7 +102,8 @@ public class Poller { using: dependencies ) } - .receive(on: dependencies.receiveQueue, immediatelyIfMain: true) + .subscribe(on: dependencies.subscribeQueue) + .receive(on: dependencies.receiveQueue) .sinkUntilComplete( receiveCompletion: { [weak self] result in switch result { @@ -134,7 +134,6 @@ public class Poller { timer.invalidate() self?.getSnodeForPolling(for: publicKey) - .subscribe(on: dependencies.subscribeQueue, immediatelyIfMain: true) .flatMap { snode -> AnyPublisher<[Message], Error> in Poller.poll( namespaces: namespaces, @@ -144,7 +143,8 @@ public class Poller { using: dependencies ) } - .receive(on: dependencies.receiveQueue, immediatelyIfMain: true) + .subscribe(on: dependencies.subscribeQueue) + .receive(on: dependencies.receiveQueue) .sinkUntilComplete( receiveCompletion: { result in switch result { diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift index b2c69c409..4adf3f940 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift +++ b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift @@ -367,7 +367,11 @@ class OpenGroupManagerSpec: QuickSpec { mockOGMCache.when { $0.hasPerformedInitialPoll }.thenReturn([:]) mockOGMCache.when { $0.timeSinceLastPoll }.thenReturn([:]) - mockOGMCache.when { $0.getTimeSinceLastOpen(using: dependencies) }.thenReturn(0) + mockOGMCache + .when { [dependencies = dependencies!] cache in + cache.getTimeSinceLastOpen(using: dependencies) + } + .thenReturn(0) mockOGMCache.when { $0.isPolling }.thenReturn(false) mockOGMCache.when { $0.pollers }.thenReturn([:]) @@ -816,7 +820,7 @@ class OpenGroupManagerSpec: QuickSpec { var didComplete: Bool = false // Prevent multi-threading test bugs mockStorage - .writePublisherFlatMap { (db: Database) -> AnyPublisher in + .writePublisher { (db: Database) -> Bool in openGroupManager .add( db, @@ -827,6 +831,16 @@ class OpenGroupManagerSpec: QuickSpec { dependencies: dependencies ) } + .flatMap { successfullyAddedGroup in + openGroupManager.performInitialRequestsAfterAdd( + successfullyAddedGroup: successfullyAddedGroup, + roomToken: "testRoom", + server: "testServer", + publicKey: TestConstants.serverPublicKey, + calledFromConfigHandling: true, // Don't trigger SessionUtil logic + dependencies: dependencies + ) + } .handleEvents(receiveCompletion: { _ in didComplete = true }) .sinkAndStore(in: &disposables) @@ -847,7 +861,7 @@ class OpenGroupManagerSpec: QuickSpec { var didComplete: Bool = false // Prevent multi-threading test bugs mockStorage - .writePublisherFlatMap { (db: Database) -> AnyPublisher in + .writePublisher { (db: Database) -> Bool in openGroupManager .add( db, @@ -858,6 +872,16 @@ class OpenGroupManagerSpec: QuickSpec { dependencies: dependencies ) } + .flatMap { successfullyAddedGroup in + openGroupManager.performInitialRequestsAfterAdd( + successfullyAddedGroup: successfullyAddedGroup, + roomToken: "testRoom", + server: "testServer", + publicKey: TestConstants.serverPublicKey, + calledFromConfigHandling: true, // Don't trigger SessionUtil logic + dependencies: dependencies + ) + } .handleEvents(receiveCompletion: { _ in didComplete = true }) .sinkAndStore(in: &disposables) @@ -884,7 +908,7 @@ class OpenGroupManagerSpec: QuickSpec { var didComplete: Bool = false // Prevent multi-threading test bugs mockStorage - .writePublisherFlatMap { (db: Database) -> AnyPublisher in + .writePublisher { (db: Database) -> Bool in openGroupManager .add( db, @@ -897,6 +921,18 @@ class OpenGroupManagerSpec: QuickSpec { dependencies: dependencies ) } + .flatMap { successfullyAddedGroup in + openGroupManager.performInitialRequestsAfterAdd( + successfullyAddedGroup: successfullyAddedGroup, + roomToken: "testRoom", + server: "testServer", + publicKey: TestConstants.serverPublicKey + .replacingOccurrences(of: "c3", with: "00") + .replacingOccurrences(of: "b3", with: "00"), + calledFromConfigHandling: true, // Don't trigger SessionUtil logic + dependencies: dependencies + ) + } .handleEvents(receiveCompletion: { _ in didComplete = true }) .sinkAndStore(in: &disposables) @@ -940,7 +976,7 @@ class OpenGroupManagerSpec: QuickSpec { var error: Error? mockStorage - .writePublisherFlatMap { (db: Database) -> AnyPublisher in + .writePublisher { (db: Database) -> Bool in openGroupManager .add( db, @@ -951,6 +987,16 @@ class OpenGroupManagerSpec: QuickSpec { dependencies: dependencies ) } + .flatMap { successfullyAddedGroup in + openGroupManager.performInitialRequestsAfterAdd( + successfullyAddedGroup: successfullyAddedGroup, + roomToken: "testRoom", + server: "testServer", + publicKey: TestConstants.serverPublicKey, + calledFromConfigHandling: true, // Don't trigger SessionUtil logic + dependencies: dependencies + ) + } .mapError { result -> Error in error.setting(to: result) } .sinkAndStore(in: &disposables) @@ -3334,15 +3380,16 @@ class OpenGroupManagerSpec: QuickSpec { upload: false, defaultUpload: nil ) - let publisher = Future<[OpenGroupAPI.Room], Error> { resolver in - resolver(Result.success([uniqueRoomInstance])) + let publisher = Future<[OpenGroupManager.DefaultRoomInfo], Error> { resolver in + resolver(Result.success([(uniqueRoomInstance, nil)])) } .shareReplay(1) .eraseToAnyPublisher() mockOGMCache.when { $0.defaultRoomsPublisher }.thenReturn(publisher) let publisher2 = OpenGroupManager.getDefaultRoomsIfNeeded(using: dependencies) - expect(publisher2.firstValue()).to(equal(publisher.firstValue())) + expect(publisher2.firstValue()?.map { $0.room }) + .to(equal(publisher.firstValue()?.map { $0.room })) } it("stores the open group information") { @@ -3376,13 +3423,13 @@ class OpenGroupManagerSpec: QuickSpec { } it("fetches rooms for the server") { - var response: [OpenGroupAPI.Room]? + var response: [OpenGroupManager.DefaultRoomInfo]? OpenGroupManager.getDefaultRoomsIfNeeded(using: dependencies) - .handleEvents(receiveOutput: { (data: [OpenGroupAPI.Room]) in response = data }) + .handleEvents(receiveOutput: { response = $0 }) .sinkAndStore(in: &disposables) - expect(response) + expect(response?.map { $0.room }) .toEventually( equal( [ @@ -3598,17 +3645,14 @@ class OpenGroupManagerSpec: QuickSpec { .thenReturn([OpenGroup.idFor(roomToken: "testRoom", server: "testServer"): publisher]) var result: Data? - mockStorage - .readPublisherFlatMap { (db: Database) -> AnyPublisher in - OpenGroupManager - .roomImage( - db, - fileId: "1", - for: "testRoom", - on: "testServer", - using: dependencies - ) - } + OpenGroupManager + .roomImage( + fileId: "1", + for: "testRoom", + on: "testServer", + existingData: nil, + using: dependencies + ) .handleEvents(receiveOutput: { result = $0 }) .sinkAndStore(in: &disposables) @@ -3617,17 +3661,14 @@ class OpenGroupManagerSpec: QuickSpec { it("does not save the fetched image to storage") { var didComplete: Bool = false - mockStorage - .readPublisherFlatMap { (db: Database) -> AnyPublisher in - OpenGroupManager - .roomImage( - db, - fileId: "1", - for: "testRoom", - on: "testServer", - using: dependencies - ) - } + OpenGroupManager + .roomImage( + fileId: "1", + for: "testRoom", + on: "testServer", + existingData: nil, + using: dependencies + ) .handleEvents(receiveCompletion: { _ in didComplete = true }) .sinkAndStore(in: &disposables) @@ -3648,17 +3689,14 @@ class OpenGroupManagerSpec: QuickSpec { it("does not update the image update timestamp") { var didComplete: Bool = false - mockStorage - .readPublisherFlatMap { (db: Database) -> AnyPublisher in - OpenGroupManager - .roomImage( - db, - fileId: "1", - for: "testRoom", - on: "testServer", - using: dependencies - ) - } + OpenGroupManager + .roomImage( + fileId: "1", + for: "testRoom", + on: "testServer", + existingData: nil, + using: dependencies + ) .handleEvents(receiveCompletion: { _ in didComplete = true }) .sinkAndStore(in: &disposables) @@ -3690,17 +3728,14 @@ class OpenGroupManagerSpec: QuickSpec { } dependencies = dependencies.with(onionApi: TestNeverReturningApi.self) - let publisher = mockStorage - .readPublisherFlatMap { (db: Database) -> AnyPublisher in - OpenGroupManager - .roomImage( - db, - fileId: "1", - for: "testRoom", - on: "testServer", - using: dependencies - ) - } + let publisher = OpenGroupManager + .roomImage( + fileId: "1", + for: "testRoom", + on: "testServer", + existingData: nil, + using: dependencies + ) publisher.sinkAndStore(in: &disposables) expect(mockOGMCache) @@ -3716,17 +3751,14 @@ class OpenGroupManagerSpec: QuickSpec { it("fetches a new image if there is no cached one") { var result: Data? - mockStorage - .readPublisherFlatMap { (db: Database) -> AnyPublisher in - OpenGroupManager - .roomImage( - db, - fileId: "1", - for: "testRoom", - on: OpenGroupAPI.defaultServer, - using: dependencies - ) - } + OpenGroupManager + .roomImage( + fileId: "1", + for: "testRoom", + on: OpenGroupAPI.defaultServer, + existingData: nil, + using: dependencies + ) .handleEvents(receiveOutput: { (data: Data) in result = data }) .sinkAndStore(in: &disposables) @@ -3736,17 +3768,14 @@ class OpenGroupManagerSpec: QuickSpec { it("saves the fetched image to storage") { var didComplete: Bool = false - mockStorage - .readPublisherFlatMap { (db: Database) -> AnyPublisher in - OpenGroupManager - .roomImage( - db, - fileId: "1", - for: "testRoom", - on: OpenGroupAPI.defaultServer, - using: dependencies - ) - } + OpenGroupManager + .roomImage( + fileId: "1", + for: "testRoom", + on: OpenGroupAPI.defaultServer, + existingData: nil, + using: dependencies + ) .handleEvents(receiveCompletion: { _ in didComplete = true }) .sinkAndStore(in: &disposables) @@ -3768,17 +3797,14 @@ class OpenGroupManagerSpec: QuickSpec { it("updates the image update timestamp") { var didComplete: Bool = false - mockStorage - .readPublisherFlatMap { (db: Database) -> AnyPublisher in - OpenGroupManager - .roomImage( - db, - fileId: "1", - for: "testRoom", - on: OpenGroupAPI.defaultServer, - using: dependencies - ) - } + OpenGroupManager + .roomImage( + fileId: "1", + for: "testRoom", + on: OpenGroupAPI.defaultServer, + existingData: nil, + using: dependencies + ) .handleEvents(receiveCompletion: { _ in didComplete = true }) .sinkAndStore(in: &disposables) @@ -3816,17 +3842,14 @@ class OpenGroupManagerSpec: QuickSpec { it("retrieves the cached image") { var result: Data? - mockStorage - .readPublisherFlatMap { (db: Database) -> AnyPublisher in - OpenGroupManager - .roomImage( - db, - fileId: "1", - for: "testRoom", - on: OpenGroupAPI.defaultServer, - using: dependencies - ) - } + OpenGroupManager + .roomImage( + fileId: "1", + for: "testRoom", + on: OpenGroupAPI.defaultServer, + existingData: Data([2, 3, 4]), + using: dependencies + ) .handleEvents(receiveOutput: { (data: Data) in result = data }) .sinkAndStore(in: &disposables) @@ -3846,17 +3869,14 @@ class OpenGroupManagerSpec: QuickSpec { var result: Data? - mockStorage - .readPublisherFlatMap { (db: Database) -> AnyPublisher in - OpenGroupManager - .roomImage( - db, - fileId: "1", - for: "testRoom", - on: OpenGroupAPI.defaultServer, - using: dependencies - ) - } + OpenGroupManager + .roomImage( + fileId: "1", + for: "testRoom", + on: OpenGroupAPI.defaultServer, + existingData: Data([2, 3, 4]), + using: dependencies + ) .handleEvents(receiveOutput: { (data: Data) in result = data }) .sinkAndStore(in: &disposables) diff --git a/SessionMessagingKitTests/_TestUtilities/DependencyExtensions.swift b/SessionMessagingKitTests/_TestUtilities/DependencyExtensions.swift index 38f83c578..83e6af787 100644 --- a/SessionMessagingKitTests/_TestUtilities/DependencyExtensions.swift +++ b/SessionMessagingKitTests/_TestUtilities/DependencyExtensions.swift @@ -10,7 +10,7 @@ import SessionUtilitiesKit extension SMKDependencies { public func with( onionApi: OnionRequestAPIType.Type? = nil, - generalCache: Atomic? = nil, + generalCache: MutableGeneralCacheType? = nil, storage: Storage? = nil, scheduler: ValueObservationScheduler? = nil, sodium: SodiumType? = nil, @@ -26,7 +26,7 @@ extension SMKDependencies { ) -> SMKDependencies { return SMKDependencies( onionApi: (onionApi ?? self._onionApi.wrappedValue), - generalCache: (generalCache ?? self._generalCache.wrappedValue), + generalCache: (generalCache ?? self._mutableGeneralCache.wrappedValue), storage: (storage ?? self._storage.wrappedValue), scheduler: (scheduler ?? self._scheduler.wrappedValue), sodium: (sodium ?? self._sodium.wrappedValue), diff --git a/SessionMessagingKitTests/_TestUtilities/OGMDependencyExtensions.swift b/SessionMessagingKitTests/_TestUtilities/OGMDependencyExtensions.swift index b297e62a8..a2be81109 100644 --- a/SessionMessagingKitTests/_TestUtilities/OGMDependencyExtensions.swift +++ b/SessionMessagingKitTests/_TestUtilities/OGMDependencyExtensions.swift @@ -9,9 +9,9 @@ import SessionUtilitiesKit extension OpenGroupManager.OGMDependencies { public func with( - cache: Atomic? = nil, + cache: OGMMutableCacheType? = nil, onionApi: OnionRequestAPIType.Type? = nil, - generalCache: Atomic? = nil, + generalCache: MutableGeneralCacheType? = nil, storage: Storage? = nil, scheduler: ValueObservationScheduler? = nil, sodium: SodiumType? = nil, @@ -28,7 +28,7 @@ extension OpenGroupManager.OGMDependencies { return OpenGroupManager.OGMDependencies( cache: (cache ?? self._mutableCache.wrappedValue), onionApi: (onionApi ?? self._onionApi.wrappedValue), - generalCache: (generalCache ?? self._generalCache.wrappedValue), + generalCache: (generalCache ?? self._mutableGeneralCache.wrappedValue), storage: (storage ?? self._storage.wrappedValue), scheduler: (scheduler ?? self._scheduler.wrappedValue), sodium: (sodium ?? self._sodium.wrappedValue), diff --git a/SessionShareExtension/ThreadPickerVC.swift b/SessionShareExtension/ThreadPickerVC.swift index 2facdea62..4728da4d3 100644 --- a/SessionShareExtension/ThreadPickerVC.swift +++ b/SessionShareExtension/ThreadPickerVC.swift @@ -160,7 +160,7 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView ShareNavController.attachmentPrepPublisher? .subscribe(on: DispatchQueue.global(qos: .userInitiated)) - .receive(on: DispatchQueue.main, immediatelyIfMain: true) + .receive(on: DispatchQueue.main) .sinkUntilComplete( receiveValue: { [weak self] attachments in guard let strongSelf = self else { return } diff --git a/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift b/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift index 862a650e8..f583b8b3c 100644 --- a/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift +++ b/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift @@ -49,7 +49,7 @@ class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec { ) cancellables.append( viewModel.observableTableData - .receive(on: DispatchQueue.main, immediatelyIfMain: true) + .receive(on: ImmediateScheduler.shared) .sink( receiveCompletion: { _ in }, receiveValue: { viewModel.updateTableData($0.0) } @@ -132,7 +132,7 @@ class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec { ) cancellables.append( viewModel.observableTableData - .receive(on: DispatchQueue.main, immediatelyIfMain: true) + .receive(on: ImmediateScheduler.shared) .sink( receiveCompletion: { _ in }, receiveValue: { viewModel.updateTableData($0.0) } @@ -178,7 +178,7 @@ class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec { cancellables.append( viewModel.rightNavItems - .receive(on: DispatchQueue.main, immediatelyIfMain: true) + .receive(on: ImmediateScheduler.shared) .sink( receiveCompletion: { _ in }, receiveValue: { navItems in items = navItems } @@ -194,7 +194,7 @@ class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec { beforeEach { cancellables.append( viewModel.rightNavItems - .receive(on: DispatchQueue.main, immediatelyIfMain: true) + .receive(on: ImmediateScheduler.shared) .sink( receiveCompletion: { _ in }, receiveValue: { navItems in items = navItems } @@ -221,7 +221,7 @@ class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec { cancellables.append( viewModel.dismissScreen - .receive(on: DispatchQueue.main, immediatelyIfMain: true) + .receive(on: ImmediateScheduler.shared) .sink( receiveCompletion: { _ in }, receiveValue: { _ in didDismissScreen = true } diff --git a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift index e05de6c9a..2750a3803 100644 --- a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift +++ b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift @@ -35,7 +35,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { ) mockGeneralCache = MockGeneralCache() dependencies = Dependencies( - generalCache: Atomic(mockGeneralCache), + generalCache: mockGeneralCache, storage: mockStorage, scheduler: .immediate ) @@ -75,7 +75,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { ) disposables.append( viewModel.observableTableData - .receive(on: DispatchQueue.main, immediatelyIfMain: true) + .receive(on: ImmediateScheduler.shared) .sink( receiveCompletion: { _ in }, receiveValue: { viewModel.updateTableData($0.0) } @@ -173,7 +173,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { ) disposables.append( viewModel.observableTableData - .receive(on: DispatchQueue.main, immediatelyIfMain: true) + .receive(on: ImmediateScheduler.shared) .sink( receiveCompletion: { _ in }, receiveValue: { viewModel.updateTableData($0.0) } @@ -447,7 +447,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { ) disposables.append( viewModel.observableTableData - .receive(on: DispatchQueue.main, immediatelyIfMain: true) + .receive(on: ImmediateScheduler.shared) .sink( receiveCompletion: { _ in }, receiveValue: { viewModel.updateTableData($0.0) } @@ -489,7 +489,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { ) disposables.append( viewModel.observableTableData - .receive(on: DispatchQueue.main, immediatelyIfMain: true) + .receive(on: ImmediateScheduler.shared) .sink( receiveCompletion: { _ in }, receiveValue: { viewModel.updateTableData($0.0) } diff --git a/SessionTests/Settings/NotificationContentViewModelSpec.swift b/SessionTests/Settings/NotificationContentViewModelSpec.swift index 6538c95b0..e4214f4da 100644 --- a/SessionTests/Settings/NotificationContentViewModelSpec.swift +++ b/SessionTests/Settings/NotificationContentViewModelSpec.swift @@ -31,7 +31,7 @@ class NotificationContentViewModelSpec: QuickSpec { ) viewModel = NotificationContentViewModel(storage: mockStorage, scheduling: .immediate) dataChangeCancellable = viewModel.observableTableData - .receive(on: DispatchQueue.main, immediatelyIfMain: true) + .receive(on: ImmediateScheduler.shared) .sink( receiveCompletion: { _ in }, receiveValue: { viewModel.updateTableData($0.0) } @@ -99,7 +99,7 @@ class NotificationContentViewModelSpec: QuickSpec { } viewModel = NotificationContentViewModel(storage: mockStorage, scheduling: .immediate) dataChangeCancellable = viewModel.observableTableData - .receive(on: DispatchQueue.main, immediatelyIfMain: true) + .receive(on: ImmediateScheduler.shared) .sink( receiveCompletion: { _ in }, receiveValue: { viewModel.updateTableData($0.0) } @@ -148,7 +148,7 @@ class NotificationContentViewModelSpec: QuickSpec { var didDismissScreen: Bool = false dismissCancellable = viewModel.dismissScreen - .receive(on: DispatchQueue.main, immediatelyIfMain: true) + .receive(on: ImmediateScheduler.shared) .sink( receiveCompletion: { _ in }, receiveValue: { _ in didDismissScreen = true } diff --git a/SessionUtilitiesKit/Combine/Publisher+Utilities.swift b/SessionUtilitiesKit/Combine/Publisher+Utilities.swift index ddc059a85..796e9b29d 100644 --- a/SessionUtilitiesKit/Combine/Publisher+Utilities.swift +++ b/SessionUtilitiesKit/Combine/Publisher+Utilities.swift @@ -25,64 +25,6 @@ public extension Publisher { ) } - /// The standard `.subscribe(on: DispatchQueue.main)` seems to ocassionally dispatch to the - /// next run loop before actually subscribing, this method checks if it's running on the main thread already and - /// if so just subscribes directly rather than routing via `.receive(on:)` - func subscribe( - on scheduler: S, - immediatelyIfMain: Bool, - options: S.SchedulerOptions? = nil - ) -> AnyPublisher where S: Scheduler { - guard immediatelyIfMain && ((scheduler as? DispatchQueue) == DispatchQueue.main) else { - return self.subscribe(on: scheduler, options: options) - .eraseToAnyPublisher() - } - - return self - .flatMap { value -> AnyPublisher in - guard Thread.isMainThread else { - return Just(value) - .setFailureType(to: Failure.self) - .subscribe(on: scheduler, options: options) - .eraseToAnyPublisher() - } - - return Just(value) - .setFailureType(to: Failure.self) - .eraseToAnyPublisher() - } - .eraseToAnyPublisher() - } - - /// The standard `.receive(on: DispatchQueue.main)` seems to ocassionally dispatch to the - /// next run loop before emitting data, this method checks if it's running on the main thread already and - /// if so just emits directly rather than routing via `.receive(on:)` - func receive( - on scheduler: S, - immediatelyIfMain: Bool, - options: S.SchedulerOptions? = nil - ) -> AnyPublisher where S: Scheduler { - guard immediatelyIfMain && ((scheduler as? DispatchQueue) == DispatchQueue.main) else { - return self.receive(on: scheduler, options: options) - .eraseToAnyPublisher() - } - - return self - .flatMap { value -> AnyPublisher in - guard Thread.isMainThread else { - return Just(value) - .setFailureType(to: Failure.self) - .receive(on: scheduler, options: options) - .eraseToAnyPublisher() - } - - return Just(value) - .setFailureType(to: Failure.self) - .eraseToAnyPublisher() - } - .eraseToAnyPublisher() - } - func tryFlatMap( maxPublishers: Subscribers.Demand = .unlimited, _ transform: @escaping (Self.Output) throws -> P diff --git a/SessionUtilitiesKit/Combine/ReplaySubject.swift b/SessionUtilitiesKit/Combine/ReplaySubject.swift index 428e58d9c..ad5f1efea 100644 --- a/SessionUtilitiesKit/Combine/ReplaySubject.swift +++ b/SessionUtilitiesKit/Combine/ReplaySubject.swift @@ -9,8 +9,7 @@ public final class ReplaySubject: Subject { private var buffer: [Output] = [Output]() private let bufferSize: Int private let lock: NSRecursiveLock = NSRecursiveLock() - - private var subscriptions = [ReplaySubjectSubscription]() + private var subscriptions: Atomic<[ReplaySubjectSubscription]> = Atomic([]) private var completion: Subscribers.Completion? // MARK: - Initialization @@ -27,7 +26,7 @@ public final class ReplaySubject: Subject { buffer.append(value) buffer = buffer.suffix(bufferSize) - subscriptions.forEach { $0.receive(value) } + subscriptions.wrappedValue.forEach { $0.receive(value) } } /// Sends a completion signal to the subscriber @@ -35,7 +34,7 @@ public final class ReplaySubject: Subject { lock.lock(); defer { lock.unlock() } self.completion = completion - subscriptions.forEach { subscription in subscription.receive(completion: completion) } + subscriptions.wrappedValue.forEach { $0.receive(completion: completion) } } /// Provides this Subject an opportunity to establish demand for any new upstream subscriptions @@ -61,11 +60,11 @@ public final class ReplaySubject: Subject { /// we can revert this change /// /// https://forums.swift.org/t/combine-receive-on-runloop-main-loses-sent-value-how-can-i-make-it-work/28631/20 - let subscription: ReplaySubjectSubscription = ReplaySubjectSubscription(downstream: AnySubscriber(subscriber)) { [buffer = buffer, completion = completion] subscription in + let subscription: ReplaySubjectSubscription = ReplaySubjectSubscription(downstream: AnySubscriber(subscriber)) { [weak self, buffer = buffer, completion = completion] subscription in + self?.subscriptions.mutate { $0.append(subscription) } subscription.replay(buffer, completion: completion) } subscriber.receive(subscription: subscription) - subscriptions.append(subscription) } } diff --git a/_SharedTestUtilities/CombineExtensions.swift b/_SharedTestUtilities/CombineExtensions.swift index f040a6008..057a2ddb3 100644 --- a/_SharedTestUtilities/CombineExtensions.swift +++ b/_SharedTestUtilities/CombineExtensions.swift @@ -7,8 +7,8 @@ import SessionUtilitiesKit public extension Publisher { func sinkAndStore(in storage: inout C) where C: RangeReplaceableCollection, C.Element == AnyCancellable { self - .subscribe(on: DispatchQueue.main, immediatelyIfMain: true) - .receive(on: DispatchQueue.main, immediatelyIfMain: true) + .subscribe(on: ImmediateScheduler.shared) + .receive(on: ImmediateScheduler.shared) .sink( receiveCompletion: { _ in }, receiveValue: { _ in } @@ -22,7 +22,7 @@ public extension AnyPublisher { var value: Output? _ = self - .receive(on: DispatchQueue.main, immediatelyIfMain: true) + .receive(on: ImmediateScheduler.shared) .sink( receiveCompletion: { _ in }, receiveValue: { result in value = result } diff --git a/_SharedTestUtilities/MockGeneralCache.swift b/_SharedTestUtilities/MockGeneralCache.swift index 0d3c55b78..b847a01dd 100644 --- a/_SharedTestUtilities/MockGeneralCache.swift +++ b/_SharedTestUtilities/MockGeneralCache.swift @@ -5,7 +5,7 @@ import SessionUtilitiesKit @testable import SessionMessagingKit -class MockGeneralCache: Mock, GeneralCacheType { +class MockGeneralCache: Mock, MutableGeneralCacheType { var encodedPublicKey: String? { get { return accept() as? String } set { accept(args: [newValue]) } From d0be7f786c10844e5b5d7a4998791549b14fd655 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 29 Jun 2023 16:58:23 +1000 Subject: [PATCH 100/135] Fixed a hang, removed redundant libs, files and code Fixed an issue where returning the app from the background would result in the app staying permanently on the splash screen Included a CRC32 implementation so we can drop the CryptoSwift dependenciy (using CryptoKit everywhere else) Removed 'SocketRocket' (unused) Removed the `xcode_14_3_workaround` post-install hook (fixed with CocoaPods 1.12.1) Removed the `enable_fts5_support` post-install hook (enabled by default since GRDB 6.7.0 so redundant) Removed the `enable_whole_module_optimization_for_crypto_swift` post-install hook (dropped CryptoSwift support) Cleared out a bunch of headers from the Signal-Bridging-header file (direct imports instead to reduce incremental build sizes) Deleted some unused code --- Podfile | 39 +-- Podfile.lock | 12 +- Session.xcodeproj/project.pbxproj | 52 +--- .../Call Management/SessionCallManager.swift | 1 + .../Calls/Views & Modals/CallVideoView.swift | 1 + .../Conversations/ConversationSearch.swift | 1 + .../Emoji Picker/EmojiSkinTonePicker.swift | 1 + .../Content Views/MediaView.swift | 1 + .../Message Cells/DateHeaderCell.swift | 1 + .../Message Cells/UnreadMarkerCell.swift | 1 + ...isappearingMessagesSettingsViewModel.swift | 1 + .../ConversationTitleView.swift | 2 +- .../New Conversation/NewConversationVC.swift | 1 + .../GIFs/GifPickerCell.swift | 1 + .../GIFs/GifPickerViewController.swift | 2 +- .../GIFs/GiphyAPI.swift | 1 + .../OWSImagePickerController.swift | 1 + .../PhotoCapture.swift | 1 + .../PhotoCaptureViewController.swift | 2 +- .../SendMediaNavigationController.swift | 6 +- .../Transitions/MediaInteractiveDismiss.swift | 1 + Session/Meta/AppDelegate.swift | 25 +- Session/Meta/SessionApp.swift | 1 + Session/Meta/Signal-Bridging-Header.h | 23 -- .../PushRegistrationManager.swift | 1 + Session/Onboarding/Onboarding.swift | 1 + Session/Shared/CaptionView.swift | 2 +- Session/Shared/FullConversationCell.swift | 6 +- Session/Shared/OWSBezierPathView.h | 2 + .../Utilities/Sodium+Utilities.swift | 7 +- .../SignalShareExtension-Bridging-Header.h | 1 - SessionUtilitiesKit/Crypto/Hex.swift | 10 +- SessionUtilitiesKit/Crypto/Mnemonic.swift | 20 +- .../General/NSNotificationCenter+OWS.h | 24 -- .../General/NSNotificationCenter+OWS.m | 29 --- SessionUtilitiesKit/General/NSString+SSK.h | 15 -- SessionUtilitiesKit/General/NSString+SSK.m | 23 -- SessionUtilitiesKit/General/String+SSK.swift | 4 - .../Meta/SessionUtilitiesKit.h | 2 - .../AttachmentCaptionToolbar.swift | 2 +- .../MediaMessageView.swift | 16 +- SignalUtilitiesKit/Meta/SignalUtilitiesKit.h | 3 - .../Utilities/NSAttributedString+OWS.h | 16 -- .../Utilities/NSAttributedString+OWS.m | 39 --- SignalUtilitiesKit/Utilities/OWSDispatch.h | 23 -- SignalUtilitiesKit/Utilities/OWSDispatch.m | 34 --- SignalUtilitiesKit/Utilities/UIFont+OWS.h | 63 ----- SignalUtilitiesKit/Utilities/UIFont+OWS.m | 234 ------------------ 48 files changed, 97 insertions(+), 658 deletions(-) delete mode 100644 SessionUtilitiesKit/General/NSNotificationCenter+OWS.h delete mode 100644 SessionUtilitiesKit/General/NSNotificationCenter+OWS.m delete mode 100644 SessionUtilitiesKit/General/NSString+SSK.h delete mode 100644 SessionUtilitiesKit/General/NSString+SSK.m delete mode 100644 SignalUtilitiesKit/Utilities/NSAttributedString+OWS.h delete mode 100644 SignalUtilitiesKit/Utilities/NSAttributedString+OWS.m delete mode 100644 SignalUtilitiesKit/Utilities/OWSDispatch.h delete mode 100644 SignalUtilitiesKit/Utilities/OWSDispatch.m delete mode 100644 SignalUtilitiesKit/Utilities/UIFont+OWS.h delete mode 100644 SignalUtilitiesKit/Utilities/UIFont+OWS.m diff --git a/Podfile b/Podfile index d9f7d9613..5747d4620 100644 --- a/Podfile +++ b/Podfile @@ -6,16 +6,16 @@ inhibit_all_warnings! # Dependencies to be included in the app and all extensions/frameworks abstract_target 'GlobalDependencies' do - pod 'CryptoSwift' # FIXME: If https://github.com/jedisct1/swift-sodium/pull/249 gets resolved then revert this back to the standard pod pod 'Sodium', :git => 'https://github.com/oxen-io/session-ios-swift-sodium.git', branch: 'session-build' pod 'GRDB.swift/SQLCipher' + + # FIXME: Would be nice to migrate from CocoaPods to SwiftPackageManager (should allow us to speed up build time), haven't gone through all of the dependencies but currently unfortunately SQLCipher doesn't support SPM (for more info see: https://github.com/sqlcipher/sqlcipher/issues/371) pod 'SQLCipher', '~> 4.5.3' # FIXME: We want to remove this once it's been long enough since the migration to GRDB pod 'YapDatabase/SQLCipher', :git => 'https://github.com/oxen-io/session-ios-yap-database.git', branch: 'signal-release' pod 'WebRTC-lib' - pod 'SocketRocket', '~> 0.5.1' target 'Session' do pod 'Reachability' @@ -100,23 +100,7 @@ end # Actions to perform post-install post_install do |installer| - enable_whole_module_optimization_for_crypto_swift(installer) set_minimum_deployment_target(installer) - enable_fts5_support(installer) - - #FIXME: Remove this workaround once an official fix is released (hopefully Cocoapods 1.12.1) - xcode_14_3_workaround(installer) -end - -def enable_whole_module_optimization_for_crypto_swift(installer) - installer.pods_project.targets.each do |target| - if target.name.end_with? "CryptoSwift" - target.build_configurations.each do |config| - config.build_settings['GCC_OPTIMIZATION_LEVEL'] = 'fast' - config.build_settings['SWIFT_OPTIMIZATION_LEVEL'] = '-O' - end - end - end end def set_minimum_deployment_target(installer) @@ -126,22 +110,3 @@ def set_minimum_deployment_target(installer) end end end - -# This is to ensure we enable support for FastTextSearch5 (might not be enabled by default) -# For more info see https://github.com/groue/GRDB.swift/blob/master/Documentation/FullTextSearch.md#enabling-fts5-support -def enable_fts5_support(installer) - installer.pods_project.targets.select { |target| target.name == "GRDB.swift" }.each do |target| - target.build_configurations.each do |config| - config.build_settings['OTHER_SWIFT_FLAGS'] = "$(inherited) -D SQLITE_ENABLE_FTS5" - end - end -end - -# Workaround for Xcode 14.3: -# Sourced from https://github.com/flutter/flutter/issues/123852#issuecomment-1493232105 -def xcode_14_3_workaround(installer) - system('sed -i \'\' \'44s/readlink/readlink -f/\' \'Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests-frameworks.sh\'') - system('sed -i \'\' \'44s/readlink/readlink -f/\' \'Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests-frameworks.sh\'') - system('sed -i \'\' \'44s/readlink/readlink -f/\' \'Pods/Target Support Files/Pods-GlobalDependencies-Session/Pods-GlobalDependencies-Session-frameworks.sh\'') - system('sed -i \'\' \'44s/readlink/readlink -f/\' \'Pods/Target Support Files/Pods-GlobalDependencies-Session-SessionTests/Pods-GlobalDependencies-Session-SessionTests-frameworks.sh\'') -end diff --git a/Podfile.lock b/Podfile.lock index 051db4dda..970a62e71 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -2,7 +2,6 @@ PODS: - CocoaLumberjack (3.8.0): - CocoaLumberjack/Core (= 3.8.0) - CocoaLumberjack/Core (3.8.0) - - CryptoSwift (1.4.2) - Curve25519Kit (2.1.0): - CocoaLumberjack - SignalCoreKit @@ -35,7 +34,6 @@ PODS: - SignalCoreKit (1.0.0): - CocoaLumberjack - OpenSSL-Universal - - SocketRocket (0.5.1) - Sodium (0.9.1) - SQLCipher (4.5.3): - SQLCipher/standard (= 4.5.3) @@ -115,7 +113,6 @@ PODS: - ZXingObjC/All (3.6.5) DEPENDENCIES: - - CryptoSwift - Curve25519Kit (from `https://github.com/oxen-io/session-ios-curve-25519-kit.git`, branch `session-version`) - DifferenceKit - GRDB.swift/SQLCipher @@ -126,7 +123,6 @@ DEPENDENCIES: - Reachability - SAMKeychain - SignalCoreKit (from `https://github.com/oxen-io/session-ios-core-kit`, branch `session-version`) - - SocketRocket (~> 0.5.1) - Sodium (from `https://github.com/oxen-io/session-ios-swift-sodium.git`, branch `session-build`) - SQLCipher (~> 4.5.3) - SwiftProtobuf (~> 1.5.0) @@ -138,7 +134,6 @@ DEPENDENCIES: SPEC REPOS: https://github.com/CocoaPods/Specs.git: - CocoaLumberjack - - CryptoSwift - DifferenceKit - GRDB.swift - libwebp @@ -149,7 +144,6 @@ SPEC REPOS: - Quick - Reachability - SAMKeychain - - SocketRocket - SQLCipher - SwiftProtobuf - WebRTC-lib @@ -190,7 +184,6 @@ CHECKOUT OPTIONS: SPEC CHECKSUMS: CocoaLumberjack: 78abfb691154e2a9df8ded4350d504ee19d90732 - CryptoSwift: a532e74ed010f8c95f611d00b8bbae42e9fe7c17 Curve25519Kit: e63f9859ede02438ae3defc5e1a87e09d1ec7ee6 DifferenceKit: ab185c4d7f9cef8af3fcf593e5b387fb81e999ca GRDB.swift: fe420b1af49ec519c7e96e07887ee44f5dfa2b78 @@ -203,7 +196,6 @@ SPEC CHECKSUMS: Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c SignalCoreKit: 1fbd8732163ef76de16cd1107d1fa3684b607e5d - SocketRocket: d57c7159b83c3c6655745cd15302aa24b6bae531 Sodium: a7d42cb46e789d2630fa552d35870b416ed055ae SQLCipher: 57fa9f863fa4a3ed9dd3c90ace52315db8c0fdca SwiftProtobuf: 241400280f912735c1e1b9fe675fdd2c6c4d42e2 @@ -212,6 +204,6 @@ SPEC CHECKSUMS: YYImage: f1ddd15ac032a58b78bbed1e012b50302d318331 ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: 5a4993725a7d48be883663a52df2a5de45d2d099 +PODFILE CHECKSUM: 68799237a4dc046f5ac25c573af03b559f5b10c4 -COCOAPODS: 1.11.3 +COCOAPODS: 1.12.1 diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 16cef78c5..458f0c863 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -219,8 +219,6 @@ B8856D7B256F14F4001CE70E /* UIView+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF23E255B6D66007E1867 /* UIView+OWS.m */; }; B8856D8D256F1502001CE70E /* UIView+OWS.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF23D255B6D66007E1867 /* UIView+OWS.h */; settings = {ATTRIBUTES = (Public, ); }; }; B8856DE6256F15F2001CE70E /* String+SSK.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB3F255A580C00E217F9 /* String+SSK.swift */; }; - B8856DEF256F161F001CE70E /* NSString+SSK.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB45255A580C00E217F9 /* NSString+SSK.m */; }; - B8856DF8256F1633001CE70E /* NSString+SSK.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDB12255A580800E217F9 /* NSString+SSK.h */; settings = {ATTRIBUTES = (Public, ); }; }; B8856E09256F1676001CE70E /* UIDevice+featureSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF237255B6D65007E1867 /* UIDevice+featureSupport.swift */; }; B8856E1A256F1700001CE70E /* OWSMath.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDB14255A580800E217F9 /* OWSMath.h */; settings = {ATTRIBUTES = (Public, ); }; }; B886B4A72398B23E00211ABE /* QRCodeVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B886B4A62398B23E00211ABE /* QRCodeVC.swift */; }; @@ -284,8 +282,6 @@ C32C5A24256DB7DB003C73A2 /* SNUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB6B255A580F00E217F9 /* SNUserDefaults.swift */; }; C32C5A48256DB8F0003C73A2 /* BuildConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDAA8255A57FF00E217F9 /* BuildConfiguration.swift */; }; C32C5A88256DBCF9003C73A2 /* MessageReceiver+ClosedGroups.swift in Sources */ = {isa = PBXBuildFile; fileRef = C32C5A87256DBCF9003C73A2 /* MessageReceiver+ClosedGroups.swift */; }; - C32C5B48256DC211003C73A2 /* NSNotificationCenter+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB6C255A580F00E217F9 /* NSNotificationCenter+OWS.m */; }; - C32C5B51256DC219003C73A2 /* NSNotificationCenter+OWS.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDB3B255A580B00E217F9 /* NSNotificationCenter+OWS.h */; settings = {ATTRIBUTES = (Public, ); }; }; C32C5C3D256DCBAF003C73A2 /* AppReadiness.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB75255A581000E217F9 /* AppReadiness.m */; }; C32C5C46256DCBB2003C73A2 /* AppReadiness.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDB01255A580700E217F9 /* AppReadiness.h */; settings = {ATTRIBUTES = (Public, ); }; }; C32C5D83256DD5B6003C73A2 /* SSKKeychainStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBBC255A581600E217F9 /* SSKKeychainStorage.swift */; }; @@ -320,10 +316,8 @@ C33FD9C5255A54EF00E217F9 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; }; C33FDC29255A581F00E217F9 /* ReachabilityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA6F255A57FA00E217F9 /* ReachabilityManager.swift */; }; C33FDC45255A581F00E217F9 /* AppVersion.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA8B255A57FD00E217F9 /* AppVersion.m */; }; - C33FDC50255A582000E217F9 /* OWSDispatch.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDA96255A57FE00E217F9 /* OWSDispatch.h */; settings = {ATTRIBUTES = (Public, ); }; }; C33FDC58255A582000E217F9 /* ReverseDispatchQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA9E255A57FF00E217F9 /* ReverseDispatchQueue.swift */; }; C33FDC78255A582000E217F9 /* TSConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDABE255A580100E217F9 /* TSConstants.m */; }; - C33FDC7D255A582000E217F9 /* OWSDispatch.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDAC3255A580200E217F9 /* OWSDispatch.m */; }; C33FDC98255A582000E217F9 /* SwiftSingletons.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDADE255A580400E217F9 /* SwiftSingletons.swift */; }; C33FDC9A255A582000E217F9 /* ByteParser.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDAE0255A580400E217F9 /* ByteParser.m */; }; C33FDCD1255A582000E217F9 /* FunctionalUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB17255A580800E217F9 /* FunctionalUtil.m */; }; @@ -367,10 +361,6 @@ C38EF00C255B61CC007E1867 /* SignalUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C33FD9AB255A548A00E217F9 /* SignalUtilitiesKit.framework */; }; C38EF22B255B6D5D007E1867 /* ShareViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF226255B6D5D007E1867 /* ShareViewDelegate.swift */; }; C38EF22C255B6D5D007E1867 /* OWSVideoPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF227255B6D5D007E1867 /* OWSVideoPlayer.swift */; }; - C38EF245255B6D67007E1867 /* UIFont+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF238255B6D66007E1867 /* UIFont+OWS.m */; }; - C38EF246255B6D67007E1867 /* UIFont+OWS.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF239255B6D66007E1867 /* UIFont+OWS.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C38EF247255B6D67007E1867 /* NSAttributedString+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF23A255B6D66007E1867 /* NSAttributedString+OWS.m */; }; - C38EF24C255B6D67007E1867 /* NSAttributedString+OWS.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF23F255B6D67007E1867 /* NSAttributedString+OWS.h */; settings = {ATTRIBUTES = (Public, ); }; }; 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 */; }; C38EF2B3255B6D9C007E1867 /* UIViewController+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2B1255B6D9C007E1867 /* UIViewController+Utilities.swift */; }; @@ -1435,11 +1425,9 @@ C33FDA87255A57FC00E217F9 /* TypingIndicators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypingIndicators.swift; sourceTree = ""; }; C33FDA8B255A57FD00E217F9 /* AppVersion.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppVersion.m; sourceTree = ""; }; C33FDA8E255A57FD00E217F9 /* OWSFileSystem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSFileSystem.m; sourceTree = ""; }; - C33FDA96255A57FE00E217F9 /* OWSDispatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSDispatch.h; sourceTree = ""; }; C33FDA9E255A57FF00E217F9 /* ReverseDispatchQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReverseDispatchQueue.swift; sourceTree = ""; }; C33FDAA8255A57FF00E217F9 /* BuildConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BuildConfiguration.swift; sourceTree = ""; }; C33FDABE255A580100E217F9 /* TSConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSConstants.m; sourceTree = ""; }; - C33FDAC3255A580200E217F9 /* OWSDispatch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSDispatch.m; sourceTree = ""; }; C33FDADE255A580400E217F9 /* SwiftSingletons.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftSingletons.swift; sourceTree = ""; }; C33FDAE0255A580400E217F9 /* ByteParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ByteParser.m; sourceTree = ""; }; C33FDAEF255A580500E217F9 /* NSData+Image.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+Image.m"; sourceTree = ""; }; @@ -1448,7 +1436,6 @@ C33FDAFC255A580600E217F9 /* MIMETypeUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIMETypeUtil.h; sourceTree = ""; }; C33FDAFD255A580600E217F9 /* LRUCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LRUCache.swift; sourceTree = ""; }; C33FDB01255A580700E217F9 /* AppReadiness.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppReadiness.h; sourceTree = ""; }; - C33FDB12255A580800E217F9 /* NSString+SSK.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+SSK.h"; sourceTree = ""; }; C33FDB14255A580800E217F9 /* OWSMath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMath.h; sourceTree = ""; }; C33FDB17255A580800E217F9 /* FunctionalUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FunctionalUtil.m; sourceTree = ""; }; C33FDB1C255A580900E217F9 /* UIImage+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+OWS.h"; sourceTree = ""; }; @@ -1457,11 +1444,9 @@ C33FDB34255A580B00E217F9 /* ClosedGroupPoller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClosedGroupPoller.swift; sourceTree = ""; }; C33FDB38255A580B00E217F9 /* OWSBackgroundTask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBackgroundTask.h; sourceTree = ""; }; C33FDB3A255A580B00E217F9 /* Poller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Poller.swift; sourceTree = ""; }; - C33FDB3B255A580B00E217F9 /* NSNotificationCenter+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSNotificationCenter+OWS.h"; sourceTree = ""; }; C33FDB3F255A580C00E217F9 /* String+SSK.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+SSK.swift"; sourceTree = ""; }; C33FDB40255A580C00E217F9 /* SignalIOSProto.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalIOSProto.swift; sourceTree = ""; }; C33FDB41255A580C00E217F9 /* MIMETypeUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIMETypeUtil.m; sourceTree = ""; }; - C33FDB45255A580C00E217F9 /* NSString+SSK.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+SSK.m"; sourceTree = ""; }; C33FDB49255A580C00E217F9 /* WeakTimer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakTimer.swift; sourceTree = ""; }; C33FDB4C255A580D00E217F9 /* AppVersion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppVersion.h; sourceTree = ""; }; C33FDB51255A580D00E217F9 /* NSUserDefaults+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSUserDefaults+OWS.h"; sourceTree = ""; }; @@ -1469,7 +1454,6 @@ C33FDB68255A580F00E217F9 /* ContentProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentProxy.swift; sourceTree = ""; }; C33FDB69255A580F00E217F9 /* FeatureFlags.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeatureFlags.swift; sourceTree = ""; }; C33FDB6B255A580F00E217F9 /* SNUserDefaults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SNUserDefaults.swift; sourceTree = ""; }; - C33FDB6C255A580F00E217F9 /* NSNotificationCenter+OWS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSNotificationCenter+OWS.m"; sourceTree = ""; }; C33FDB75255A581000E217F9 /* AppReadiness.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppReadiness.m; sourceTree = ""; }; C33FDB77255A581000E217F9 /* NSUserDefaults+OWS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSUserDefaults+OWS.m"; sourceTree = ""; }; C33FDB78255A581000E217F9 /* OWSOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSOperation.m; sourceTree = ""; }; @@ -1523,12 +1507,8 @@ C38EF226255B6D5D007E1867 /* ShareViewDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ShareViewDelegate.swift; path = SignalUtilitiesKit/Utilities/ShareViewDelegate.swift; sourceTree = SOURCE_ROOT; }; C38EF227255B6D5D007E1867 /* OWSVideoPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OWSVideoPlayer.swift; path = "SignalUtilitiesKit/Media Viewing & Editing/OWSVideoPlayer.swift"; sourceTree = SOURCE_ROOT; }; C38EF237255B6D65007E1867 /* UIDevice+featureSupport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIDevice+featureSupport.swift"; path = "SessionUtilitiesKit/General/UIDevice+featureSupport.swift"; sourceTree = SOURCE_ROOT; }; - C38EF238255B6D66007E1867 /* UIFont+OWS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIFont+OWS.m"; path = "SignalUtilitiesKit/Utilities/UIFont+OWS.m"; sourceTree = SOURCE_ROOT; }; - C38EF239255B6D66007E1867 /* UIFont+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIFont+OWS.h"; path = "SignalUtilitiesKit/Utilities/UIFont+OWS.h"; sourceTree = SOURCE_ROOT; }; - C38EF23A255B6D66007E1867 /* NSAttributedString+OWS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSAttributedString+OWS.m"; path = "SignalUtilitiesKit/Utilities/NSAttributedString+OWS.m"; sourceTree = SOURCE_ROOT; }; C38EF23D255B6D66007E1867 /* UIView+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIView+OWS.h"; path = "SessionUtilitiesKit/General/UIView+OWS.h"; sourceTree = SOURCE_ROOT; }; C38EF23E255B6D66007E1867 /* UIView+OWS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIView+OWS.m"; path = "SessionUtilitiesKit/General/UIView+OWS.m"; sourceTree = SOURCE_ROOT; }; - C38EF23F255B6D67007E1867 /* NSAttributedString+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSAttributedString+OWS.h"; path = "SignalUtilitiesKit/Utilities/NSAttributedString+OWS.h"; sourceTree = SOURCE_ROOT; }; 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; }; C38EF281255B6D84007E1867 /* OWSAudioSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OWSAudioSession.swift; path = SessionMessagingKit/Utilities/OWSAudioSession.swift; sourceTree = SOURCE_ROOT; }; @@ -2635,11 +2615,7 @@ B8BC00BF257D90E30032E807 /* General.swift */, C3C2A5CE2553860700C340D1 /* Logging.swift */, C33FDAFD255A580600E217F9 /* LRUCache.swift */, - C33FDB3B255A580B00E217F9 /* NSNotificationCenter+OWS.h */, - C33FDB6C255A580F00E217F9 /* NSNotificationCenter+OWS.m */, C33FDA7A255A57FB00E217F9 /* NSRegularExpression+SSK.swift */, - C33FDB12255A580800E217F9 /* NSString+SSK.h */, - C33FDB45255A580C00E217F9 /* NSString+SSK.m */, C352A3762557859C00338F3E /* NSTimer+Proxying.h */, C352A36C2557858D00338F3E /* NSTimer+Proxying.m */, 7B1D74AF27C365960030B423 /* Timer+MainThread.swift */, @@ -3404,10 +3380,6 @@ FD71161D28D9772700B47552 /* UIViewController+OWS.swift */, C38EF2B1255B6D9C007E1867 /* UIViewController+Utilities.swift */, C38EF307255B6DBE007E1867 /* UIGestureRecognizer+OWS.swift */, - C38EF239255B6D66007E1867 /* UIFont+OWS.h */, - C38EF238255B6D66007E1867 /* UIFont+OWS.m */, - C33FDA96255A57FE00E217F9 /* OWSDispatch.h */, - C33FDAC3255A580200E217F9 /* OWSDispatch.m */, C33FDBF9255A581C00E217F9 /* OWSError.h */, C33FDC0B255A581D00E217F9 /* OWSError.m */, C33FDBA1255A581400E217F9 /* OWSOperation.h */, @@ -3442,8 +3414,6 @@ C38EF3AE255B6DE5007E1867 /* OrderedDictionary.swift */, C38EF226255B6D5D007E1867 /* ShareViewDelegate.swift */, C38EF2FA255B6DBD007E1867 /* Bench.swift */, - C38EF23F255B6D67007E1867 /* NSAttributedString+OWS.h */, - C38EF23A255B6D66007E1867 /* NSAttributedString+OWS.m */, ); path = Utilities; sourceTree = ""; @@ -4447,14 +4417,11 @@ C33FDDD0255A582000E217F9 /* FunctionalUtil.h in Headers */, C33FDD5B255A582000E217F9 /* OWSOperation.h in Headers */, C33FDD7C255A582000E217F9 /* SSKAsserts.h in Headers */, - C38EF24C255B6D67007E1867 /* NSAttributedString+OWS.h in Headers */, C33FDDCC255A582000E217F9 /* TSConstants.h in Headers */, C33FDDBD255A582000E217F9 /* ByteParser.h in Headers */, C33FDDB3255A582000E217F9 /* OWSError.h in Headers */, C38EF35E255B6DCC007E1867 /* OWSViewController.h in Headers */, - C38EF246255B6D67007E1867 /* UIFont+OWS.h in Headers */, C33FD9AF255A548A00E217F9 /* SignalUtilitiesKit.h in Headers */, - C33FDC50255A582000E217F9 /* OWSDispatch.h in Headers */, C33FDD06255A582000E217F9 /* AppVersion.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -4475,12 +4442,10 @@ C3D9E38A256760390040E4F3 /* OWSFileSystem.h in Headers */, C3D9E379256760340040E4F3 /* MIMETypeUtil.h in Headers */, C3D9E50E25677A510040E4F3 /* DataSource.h in Headers */, - B8856DF8256F1633001CE70E /* NSString+SSK.h in Headers */, C3D9E4FD256778E30040E4F3 /* NSData+Image.h in Headers */, C3D9E4E3256778720040E4F3 /* UIImage+OWS.h in Headers */, B8856E1A256F1700001CE70E /* OWSMath.h in Headers */, C352A3772557864000338F3E /* NSTimer+Proxying.h in Headers */, - C32C5B51256DC219003C73A2 /* NSNotificationCenter+OWS.h in Headers */, FD30036A2A3ADEC100B5A5FB /* CExceptionHelper.h in Headers */, C3C2A67D255388CC00C340D1 /* SessionUtilitiesKit.h in Headers */, C32C6018256E07F9003C73A2 /* NSUserDefaults+OWS.h in Headers */, @@ -5486,8 +5451,6 @@ files = ( C38EF3C6255B6DE7007E1867 /* ImageEditorModel.swift in Sources */, C38EF3C3255B6DE7007E1867 /* ImageEditorTextItem.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 */, C38EF385255B6DD2007E1867 /* AttachmentTextToolbar.swift in Sources */, @@ -5496,7 +5459,6 @@ C38EF389255B6DD2007E1867 /* AttachmentTextView.swift in Sources */, C38EF3FF255B6DF7007E1867 /* TappableView.swift in Sources */, C38EF3C2255B6DE7007E1867 /* ImageEditorPaletteView.swift in Sources */, - C38EF245255B6D67007E1867 /* UIFont+OWS.m in Sources */, C38EF36F255B6DCC007E1867 /* OWSViewController.m in Sources */, C38EF3FB255B6DF7007E1867 /* UIAlertController+OWS.swift in Sources */, C38EF30C255B6DBF007E1867 /* ScreenLock.swift in Sources */, @@ -5648,7 +5610,6 @@ 7B7CB192271508AD0079FF93 /* CallRingTonePlayer.swift in Sources */, FD848B8B283DC509000E298B /* PagedDatabaseObserver.swift in Sources */, B8856E09256F1676001CE70E /* UIDevice+featureSupport.swift in Sources */, - B8856DEF256F161F001CE70E /* NSString+SSK.m in Sources */, FD09C5E2282212B3000CE219 /* JobDependencies.swift in Sources */, FDED2E3C282E1B5D00B2CD2A /* UICollectionView+ReusableView.swift in Sources */, FDF8487B29405906007DCAE5 /* HTTPHeader.swift in Sources */, @@ -5676,7 +5637,6 @@ FD09797D27FBDB2000936362 /* Notification+Utilities.swift in Sources */, FDC6D7602862B3F600B04575 /* Dependencies.swift in Sources */, C3C2AC2E2553CBEB00C340D1 /* String+Trimming.swift in Sources */, - C32C5B48256DC211003C73A2 /* NSNotificationCenter+OWS.m in Sources */, FD17D7C727F5207C00122BE0 /* DatabaseMigrator+Utilities.swift in Sources */, FD848B9328420164000E298B /* UnicodeScalar+Utilities.swift in Sources */, FD09796B27F6C67500936362 /* Failable.swift in Sources */, @@ -6417,7 +6377,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 411; + CURRENT_PROJECT_VERSION = 412; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6489,7 +6449,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 411; + CURRENT_PROJECT_VERSION = 412; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6554,7 +6514,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 411; + CURRENT_PROJECT_VERSION = 412; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6628,7 +6588,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 411; + CURRENT_PROJECT_VERSION = 412; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -7536,7 +7496,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 411; + CURRENT_PROJECT_VERSION = 412; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -7607,7 +7567,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 411; + CURRENT_PROJECT_VERSION = 412; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/Session/Calls/Call Management/SessionCallManager.swift b/Session/Calls/Call Management/SessionCallManager.swift index a453a1218..18fa498da 100644 --- a/Session/Calls/Call Management/SessionCallManager.swift +++ b/Session/Calls/Call Management/SessionCallManager.swift @@ -5,6 +5,7 @@ import CallKit import GRDB import SessionMessagingKit import SignalCoreKit +import SignalUtilitiesKit public final class SessionCallManager: NSObject, CallManagerProtocol { let provider: CXProvider? diff --git a/Session/Calls/Views & Modals/CallVideoView.swift b/Session/Calls/Views & Modals/CallVideoView.swift index 65f76812c..e0b73e5e7 100644 --- a/Session/Calls/Views & Modals/CallVideoView.swift +++ b/Session/Calls/Views & Modals/CallVideoView.swift @@ -3,6 +3,7 @@ import WebRTC import Foundation import SessionUtilitiesKit +import SignalCoreKit #if targetEnvironment(simulator) // Note: 'RTCMTLVideoView' doesn't seem to work on the simulator so use 'RTCEAGLVideoView' instead diff --git a/Session/Conversations/ConversationSearch.swift b/Session/Conversations/ConversationSearch.swift index e62f2d2f2..785578104 100644 --- a/Session/Conversations/ConversationSearch.swift +++ b/Session/Conversations/ConversationSearch.swift @@ -4,6 +4,7 @@ import UIKit import GRDB import SignalUtilitiesKit import SignalCoreKit +import SessionUIKit public class StyledSearchController: UISearchController { public override var preferredStatusBarStyle: UIStatusBarStyle { diff --git a/Session/Conversations/Emoji Picker/EmojiSkinTonePicker.swift b/Session/Conversations/Emoji Picker/EmojiSkinTonePicker.swift index b771ec7d6..5c6da4152 100644 --- a/Session/Conversations/Emoji Picker/EmojiSkinTonePicker.swift +++ b/Session/Conversations/Emoji Picker/EmojiSkinTonePicker.swift @@ -3,6 +3,7 @@ import UIKit import SessionUIKit import SignalCoreKit +import SignalUtilitiesKit class EmojiSkinTonePicker: UIView { let emoji: Emoji diff --git a/Session/Conversations/Message Cells/Content Views/MediaView.swift b/Session/Conversations/Message Cells/Content Views/MediaView.swift index 8d7e498de..0af198314 100644 --- a/Session/Conversations/Message Cells/Content Views/MediaView.swift +++ b/Session/Conversations/Message Cells/Content Views/MediaView.swift @@ -5,6 +5,7 @@ import YYImage import SessionUIKit import SessionMessagingKit import SignalCoreKit +import SignalUtilitiesKit public class MediaView: UIView { static let contentMode: UIView.ContentMode = .scaleAspectFill diff --git a/Session/Conversations/Message Cells/DateHeaderCell.swift b/Session/Conversations/Message Cells/DateHeaderCell.swift index f6a99ca3f..0f37ed4e4 100644 --- a/Session/Conversations/Message Cells/DateHeaderCell.swift +++ b/Session/Conversations/Message Cells/DateHeaderCell.swift @@ -4,6 +4,7 @@ import UIKit import SignalUtilitiesKit import SessionUtilitiesKit import SessionMessagingKit +import SessionUIKit final class DateHeaderCell: MessageCell { // MARK: - UI diff --git a/Session/Conversations/Message Cells/UnreadMarkerCell.swift b/Session/Conversations/Message Cells/UnreadMarkerCell.swift index 76410c050..2ab5f89cf 100644 --- a/Session/Conversations/Message Cells/UnreadMarkerCell.swift +++ b/Session/Conversations/Message Cells/UnreadMarkerCell.swift @@ -4,6 +4,7 @@ import UIKit import SignalUtilitiesKit import SessionUtilitiesKit import SessionMessagingKit +import SessionUIKit final class UnreadMarkerCell: MessageCell { public static let height: CGFloat = 32 diff --git a/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift b/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift index 981c05624..86a5ac164 100644 --- a/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift +++ b/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift @@ -7,6 +7,7 @@ import DifferenceKit import SessionUIKit import SessionMessagingKit import SessionUtilitiesKit +import SessionSnodeKit class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel { // MARK: - Config diff --git a/Session/Conversations/Views & Modals/ConversationTitleView.swift b/Session/Conversations/Views & Modals/ConversationTitleView.swift index d4a0c78f0..03c9d5534 100644 --- a/Session/Conversations/Views & Modals/ConversationTitleView.swift +++ b/Session/Conversations/Views & Modals/ConversationTitleView.swift @@ -144,7 +144,7 @@ final class ConversationTitleView: UIView { subtitleLabel?.attributedText = NSAttributedString( string: FullConversationCell.mutePrefix, attributes: [ - .font: UIFont.ows_elegantIconsFont(10), + .font: UIFont(name: "ElegantIcons", size: 10) as Any, .foregroundColor: textPrimary ] ) diff --git a/Session/Home/New Conversation/NewConversationVC.swift b/Session/Home/New Conversation/NewConversationVC.swift index 5e8c96eae..0b40a49c1 100644 --- a/Session/Home/New Conversation/NewConversationVC.swift +++ b/Session/Home/New Conversation/NewConversationVC.swift @@ -4,6 +4,7 @@ import UIKit import GRDB import SessionUIKit import SessionMessagingKit +import SignalUtilitiesKit final class NewConversationVC: BaseVC, ThemedNavigation, UITableViewDelegate, UITableViewDataSource { private let newConversationViewModel = NewConversationViewModel() diff --git a/Session/Media Viewing & Editing/GIFs/GifPickerCell.swift b/Session/Media Viewing & Editing/GIFs/GifPickerCell.swift index b748a9521..0967e8020 100644 --- a/Session/Media Viewing & Editing/GIFs/GifPickerCell.swift +++ b/Session/Media Viewing & Editing/GIFs/GifPickerCell.swift @@ -4,6 +4,7 @@ import Foundation import Combine import YYImage import SignalUtilitiesKit +import SignalCoreKit class GifPickerCell: UICollectionViewCell { diff --git a/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift b/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift index c5e92d27d..4b2436258 100644 --- a/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift +++ b/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift @@ -217,7 +217,7 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect private func createErrorLabel(text: String) -> UILabel { let label: UILabel = UILabel() - label.font = .ows_mediumFont(withSize: 20) + label.font = UIFont.systemFont(ofSize: 20, weight: .medium) label.text = text label.themeTextColor = .textPrimary label.textAlignment = .center diff --git a/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift b/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift index ec2ccddbb..1a3f6eef5 100644 --- a/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift +++ b/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift @@ -5,6 +5,7 @@ import Combine import CoreServices import SignalUtilitiesKit import SessionUtilitiesKit +import SignalCoreKit // There's no UTI type for webp! enum GiphyFormat { diff --git a/Session/Media Viewing & Editing/OWSImagePickerController.swift b/Session/Media Viewing & Editing/OWSImagePickerController.swift index 904a239df..d88e3a60a 100644 --- a/Session/Media Viewing & Editing/OWSImagePickerController.swift +++ b/Session/Media Viewing & Editing/OWSImagePickerController.swift @@ -3,6 +3,7 @@ // import Foundation +import SignalUtilitiesKit @objc class OWSImagePickerController: UIImagePickerController { diff --git a/Session/Media Viewing & Editing/PhotoCapture.swift b/Session/Media Viewing & Editing/PhotoCapture.swift index 37fa99901..6f4687e85 100644 --- a/Session/Media Viewing & Editing/PhotoCapture.swift +++ b/Session/Media Viewing & Editing/PhotoCapture.swift @@ -6,6 +6,7 @@ import AVFoundation import CoreServices import SessionMessagingKit import SessionUtilitiesKit +import SignalCoreKit protocol PhotoCaptureDelegate: AnyObject { func photoCapture(_ photoCapture: PhotoCapture, didFinishProcessingAttachment attachment: SignalAttachment) diff --git a/Session/Media Viewing & Editing/PhotoCaptureViewController.swift b/Session/Media Viewing & Editing/PhotoCaptureViewController.swift index 32b08929e..91e43eb61 100644 --- a/Session/Media Viewing & Editing/PhotoCaptureViewController.swift +++ b/Session/Media Viewing & Editing/PhotoCaptureViewController.swift @@ -601,7 +601,7 @@ class RecordingTimerView: UIView { private lazy var label: UILabel = { let label: UILabel = UILabel() - label.font = .ows_monospacedDigitFont(withSize: 20) + label.font = UIFont.monospacedDigitSystemFont(ofSize: 20, weight: .regular) label.themeTextColor = .textPrimary label.textAlignment = .center label.layer.shadowOffset = CGSize.zero diff --git a/Session/Media Viewing & Editing/SendMediaNavigationController.swift b/Session/Media Viewing & Editing/SendMediaNavigationController.swift index a452a3cbf..63bf1fde4 100644 --- a/Session/Media Viewing & Editing/SendMediaNavigationController.swift +++ b/Session/Media Viewing & Editing/SendMediaNavigationController.swift @@ -5,6 +5,7 @@ import Combine import Photos import SignalUtilitiesKit import SignalCoreKit +import SessionUIKit class SendMediaNavigationController: UINavigationController { public override var preferredStatusBarStyle: UIStatusBarStyle { @@ -596,7 +597,10 @@ private class DoneButton: UIView { private lazy var badgeLabel: UILabel = { let result: UILabel = UILabel() - result.font = .ows_dynamicTypeSubheadline.ows_monospaced() + result.font = UIFont.monospacedDigitSystemFont( + ofSize: UIFont.preferredFont(forTextStyle: .subheadline).pointSize, + weight: .regular + ) result.themeTextColor = .black // Will render on the primary color so should always be black result.textAlignment = .center diff --git a/Session/Media Viewing & Editing/Transitions/MediaInteractiveDismiss.swift b/Session/Media Viewing & Editing/Transitions/MediaInteractiveDismiss.swift index bd213e0b9..ad1cb6fcd 100644 --- a/Session/Media Viewing & Editing/Transitions/MediaInteractiveDismiss.swift +++ b/Session/Media Viewing & Editing/Transitions/MediaInteractiveDismiss.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit +import SignalUtilitiesKit // MARK: - InteractivelyDismissableViewController diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 82f373b48..254679d33 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -27,8 +27,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD // MARK: - Lifecycle func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - // Log something immediately to make it easier to track app launches (and crashes during launch) - SNLog("Launching \(SessionApp.versionInfo)") startTime = CACurrentMediaTime() // These should be the first things we do (the startup process can fail without them) @@ -288,8 +286,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD Configuration.performMainSetup() JobRunner.add(executor: SyncPushTokensJob.self, for: .syncPushTokens) - // Setup the UI and trigger any post-UI setup actions - self.ensureRootViewController(calledFrom: lifecycleMethod) { [weak self] in + // Setup the UI if needed, then trigger any post-UI setup actions + self.ensureRootViewController(calledFrom: lifecycleMethod) { [weak self] success in + // If we didn't successfully ensure the rootViewController then don't continue as + // the user is in an invalid state (and should have already been shown a modal) + guard success else { return } + /// Trigger any launch-specific jobs and start the JobRunner with `JobRunner.appDidFinishLaunching()` some /// of these jobs (eg. DisappearingMessages job) can impact the interactions which get fetched to display on the home /// screen, if the PagedDatabaseObserver hasn't been setup yet then the home screen can show stale (ie. deleted) @@ -483,11 +485,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD private func ensureRootViewController( calledFrom lifecycleMethod: LifecycleMethod, - onComplete: (() -> ())? = nil + onComplete: @escaping ((Bool) -> ()) = { _ in } ) { - guard (AppReadiness.isAppReady() || lifecycleMethod == .finishLaunching) && Storage.shared.isValid && !hasInitialRootViewController else { - return - } + let hasInitialRootViewController: Bool = self.hasInitialRootViewController + + // Always call the completion block and indicate whether we successfully created the UI + guard + Storage.shared.isValid && + (AppReadiness.isAppReady() || lifecycleMethod == .finishLaunching) && + !hasInitialRootViewController + else { return DispatchQueue.main.async { onComplete(hasInitialRootViewController) } } /// Start a timeout for the creation of the rootViewController setup process (if it takes too long then we want to give the user /// the option to export their logs) @@ -541,7 +548,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD } // Setup is completed so run any post-setup tasks - onComplete?() + onComplete(true) } // Navigate to the approriate screen depending on the onboarding state diff --git a/Session/Meta/SessionApp.swift b/Session/Meta/SessionApp.swift index 5dfc97351..319c82f7e 100644 --- a/Session/Meta/SessionApp.swift +++ b/Session/Meta/SessionApp.swift @@ -4,6 +4,7 @@ import Foundation import SessionUtilitiesKit import SessionMessagingKit import SignalCoreKit +import SessionUIKit public struct SessionApp { // FIXME: Refactor this to be protocol based for unit testing (or even dynamic based on view hierarchy - do want to avoid needing to use the main thread to access them though) diff --git a/Session/Meta/Signal-Bridging-Header.h b/Session/Meta/Signal-Bridging-Header.h index e30005afb..a745cd256 100644 --- a/Session/Meta/Signal-Bridging-Header.h +++ b/Session/Meta/Signal-Bridging-Header.h @@ -2,32 +2,9 @@ // Copyright (c) 2019 Open Whisper Systems. All rights reserved. // -#import -#import -#import - -// Separate iOS Frameworks from other imports. #import "AVAudioSession+OWS.h" #import "OWSAudioPlayer.h" #import "OWSBezierPathView.h" #import "OWSMessageTimerView.h" #import "OWSWindowManager.h" #import "MainAppContext.h" -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import diff --git a/Session/Notifications/PushRegistrationManager.swift b/Session/Notifications/PushRegistrationManager.swift index 50fe02510..392b7c542 100644 --- a/Session/Notifications/PushRegistrationManager.swift +++ b/Session/Notifications/PushRegistrationManager.swift @@ -5,6 +5,7 @@ import Combine import PushKit import GRDB import SignalUtilitiesKit +import SignalCoreKit public enum PushRegistrationError: Error { case assertionError(description: String) diff --git a/Session/Onboarding/Onboarding.swift b/Session/Onboarding/Onboarding.swift index b09fae7ef..97607724d 100644 --- a/Session/Onboarding/Onboarding.swift +++ b/Session/Onboarding/Onboarding.swift @@ -6,6 +6,7 @@ import Sodium import GRDB import SessionUtilitiesKit import SessionMessagingKit +import SessionSnodeKit enum Onboarding { private static let profileNameRetrievalIdentifier: Atomic = Atomic(nil) diff --git a/Session/Shared/CaptionView.swift b/Session/Shared/CaptionView.swift index a861a5a7e..1b66232ca 100644 --- a/Session/Shared/CaptionView.swift +++ b/Session/Shared/CaptionView.swift @@ -100,7 +100,7 @@ private class CaptionView: UIView { let textView: CaptionTextView = { let textView = CaptionTextView() - textView.font = UIFont.ows_dynamicTypeBody + textView.font = UIFont.preferredFont(forTextStyle: .body) textView.themeTextColor = .textPrimary textView.themeBackgroundColor = .clear textView.isEditable = false diff --git a/Session/Shared/FullConversationCell.swift b/Session/Shared/FullConversationCell.swift index 9a8d7fa83..feba6c06d 100644 --- a/Session/Shared/FullConversationCell.swift +++ b/Session/Shared/FullConversationCell.swift @@ -492,7 +492,7 @@ public final class FullConversationCell: UITableViewCell, SwipeActionOptimisticC case (true, false): self.snippetLabel.attributedText = NSAttributedString( string: FullConversationCell.mutePrefix, - attributes: [ .font: UIFont.ows_elegantIconsFont(10) ] + attributes: [ .font: UIFont(name: "ElegantIcons", size: 10) as Any ] ) .appending(attrString) @@ -547,7 +547,7 @@ public final class FullConversationCell: UITableViewCell, SwipeActionOptimisticC result.append(NSAttributedString( string: FullConversationCell.mutePrefix, attributes: [ - .font: UIFont.ows_elegantIconsFont(10), + .font: UIFont(name: "ElegantIcons", size: 10) as Any, .foregroundColor: textColor ] )) @@ -562,7 +562,7 @@ public final class FullConversationCell: UITableViewCell, SwipeActionOptimisticC result.append(NSAttributedString( string: " ", attributes: [ - .font: UIFont.ows_elegantIconsFont(10), + .font: UIFont(name: "ElegantIcons", size: 10) as Any, .foregroundColor: textColor ] )) diff --git a/Session/Shared/OWSBezierPathView.h b/Session/Shared/OWSBezierPathView.h index 6cb0201a8..c3aea7400 100644 --- a/Session/Shared/OWSBezierPathView.h +++ b/Session/Shared/OWSBezierPathView.h @@ -2,6 +2,8 @@ // Copyright (c) 2017 Open Whisper Systems. All rights reserved. // +#import + typedef void (^ConfigureShapeLayerBlock)(CAShapeLayer *_Nonnull layer, CGRect bounds); NS_ASSUME_NONNULL_BEGIN diff --git a/SessionMessagingKit/Utilities/Sodium+Utilities.swift b/SessionMessagingKit/Utilities/Sodium+Utilities.swift index 9166c392d..8e5210a73 100644 --- a/SessionMessagingKit/Utilities/Sodium+Utilities.swift +++ b/SessionMessagingKit/Utilities/Sodium+Utilities.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import CryptoKit import Clibsodium import Sodium import Curve25519Kit @@ -108,10 +109,10 @@ extension Sodium { /// pubkeys (this doesn't affect verification at all) public func sogsSignature(message: Bytes, secretKey: Bytes, blindedSecretKey ka: Bytes, blindedPublicKey kA: Bytes) -> Bytes? { /// H_rh = sha512(s.encode()).digest()[32:] - let H_rh: Bytes = Bytes(secretKey.sha512().suffix(32)) + let H_rh: Bytes = Bytes(SHA512.hash(data: secretKey).suffix(32)) /// r = salt.crypto_core_ed25519_scalar_reduce(sha512_multipart(H_rh, kA, message_parts)) - let combinedHashBytes: Bytes = (H_rh + kA + message).sha512() + let combinedHashBytes: Bytes = SHA512.hash(data: H_rh + kA + message).bytes let rPtr: UnsafeMutablePointer = UnsafeMutablePointer.allocate(capacity: Sodium.scalarLength) _ = combinedHashBytes.withUnsafeBytes { (combinedHashPtr: UnsafeRawBufferPointer) -> Int32 in @@ -129,7 +130,7 @@ extension Sodium { /// HRAM = salt.crypto_core_ed25519_scalar_reduce(sha512_multipart(sig_R, kA, message_parts)) let sig_RBytes: Bytes = Data(bytes: sig_RPtr, count: Sodium.noClampLength).bytes - let HRAMHashBytes: Bytes = (sig_RBytes + kA + message).sha512() + let HRAMHashBytes: Bytes = SHA512.hash(data: sig_RBytes + kA + message).bytes let HRAMPtr: UnsafeMutablePointer = UnsafeMutablePointer.allocate(capacity: Sodium.scalarLength) _ = HRAMHashBytes.withUnsafeBytes { (HRAMHashPtr: UnsafeRawBufferPointer) -> Int32 in diff --git a/SessionShareExtension/Meta/SignalShareExtension-Bridging-Header.h b/SessionShareExtension/Meta/SignalShareExtension-Bridging-Header.h index 9334672ce..216371943 100644 --- a/SessionShareExtension/Meta/SignalShareExtension-Bridging-Header.h +++ b/SessionShareExtension/Meta/SignalShareExtension-Bridging-Header.h @@ -6,7 +6,6 @@ #import // Separate iOS Frameworks from other imports. -#import #import #import #import diff --git a/SessionUtilitiesKit/Crypto/Hex.swift b/SessionUtilitiesKit/Crypto/Hex.swift index f9b01f406..ade2e838b 100644 --- a/SessionUtilitiesKit/Crypto/Hex.swift +++ b/SessionUtilitiesKit/Crypto/Hex.swift @@ -16,7 +16,7 @@ public extension Data { var bytes: [UInt8] { return Array(self) } func toHexString() -> String { - return bytes.map { String(format: "%02x", $0) }.joined() + return bytes.toHexString() } init(hex: String) { @@ -71,4 +71,12 @@ public extension Array where Element == UInt8 { append(b) } } + + func toHexString() -> String { + return map { String(format: "%02x", $0) }.joined() + } + + func toBase64(options: Data.Base64EncodingOptions = []) -> String { + Data(self).base64EncodedString(options: options) + } } diff --git a/SessionUtilitiesKit/Crypto/Mnemonic.swift b/SessionUtilitiesKit/Crypto/Mnemonic.swift index ac8f46af5..a02c31940 100644 --- a/SessionUtilitiesKit/Crypto/Mnemonic.swift +++ b/SessionUtilitiesKit/Crypto/Mnemonic.swift @@ -1,10 +1,26 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation -import CryptoSwift /// Based on [mnemonic.js](https://github.com/loki-project/loki-messenger/blob/development/libloki/modules/mnemonic.js) . public enum Mnemonic { + /// This implementation was sourced from https://gist.github.com/antfarm/695fa78e0730b67eb094c77d53942216 + enum CRC32 { + static let table: [UInt32] = { + (0...255).map { i -> UInt32 in + (0..<8).reduce(UInt32(i), { c, _ in + ((0xEDB88320 * (c % 2)) ^ (c >> 1)) + }) + } + }() + + static func checksum(bytes: [UInt8]) -> UInt32 { + return ~(bytes.reduce(~UInt32(0), { crc, byte in + (crc >> 8) ^ table[(Int(crc) ^ Int(byte)) & 0xFF] + })) + } + } + public struct Language: Hashable { fileprivate let filename: String fileprivate let prefixLength: UInt @@ -147,7 +163,7 @@ public enum Mnemonic { } private static func determineChecksumIndex(for x: [String], prefixLength: UInt) -> Int { - let checksum = Array(x.map { $0.prefix(length: prefixLength) }.joined().utf8).crc32() + let checksum = CRC32.checksum(bytes: Array(x.map { $0.prefix(length: prefixLength) }.joined().utf8)) return Int(checksum) % x.count } diff --git a/SessionUtilitiesKit/General/NSNotificationCenter+OWS.h b/SessionUtilitiesKit/General/NSNotificationCenter+OWS.h deleted file mode 100644 index 2a97bda43..000000000 --- a/SessionUtilitiesKit/General/NSNotificationCenter+OWS.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -// We often use notifications as way to publish events. -// -// We never need these events to be received synchronously, -// so we should always send them asynchronously to avoid any -// possible risk of deadlock. These methods also ensure that -// the notifications are always fired on the main thread. -@interface NSNotificationCenter (OWS) - -- (void)postNotificationNameAsync:(NSNotificationName)name object:(nullable id)object; -- (void)postNotificationNameAsync:(NSNotificationName)name - object:(nullable id)object - userInfo:(nullable NSDictionary *)userInfo; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SessionUtilitiesKit/General/NSNotificationCenter+OWS.m b/SessionUtilitiesKit/General/NSNotificationCenter+OWS.m deleted file mode 100644 index c406ff6b7..000000000 --- a/SessionUtilitiesKit/General/NSNotificationCenter+OWS.m +++ /dev/null @@ -1,29 +0,0 @@ -// -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. -// - -#import "NSNotificationCenter+OWS.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation NSNotificationCenter (OWS) - -- (void)postNotificationNameAsync:(NSNotificationName)name object:(nullable id)object -{ - dispatch_async(dispatch_get_main_queue(), ^{ - [self postNotificationName:name object:object]; - }); -} - -- (void)postNotificationNameAsync:(NSNotificationName)name - object:(nullable id)object - userInfo:(nullable NSDictionary *)userInfo -{ - dispatch_async(dispatch_get_main_queue(), ^{ - [self postNotificationName:name object:object userInfo:userInfo]; - }); -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SessionUtilitiesKit/General/NSString+SSK.h b/SessionUtilitiesKit/General/NSString+SSK.h deleted file mode 100644 index 48c43f466..000000000 --- a/SessionUtilitiesKit/General/NSString+SSK.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface NSString (SSK) - -- (NSString *)rtlSafeAppend:(NSString *)string; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SessionUtilitiesKit/General/NSString+SSK.m b/SessionUtilitiesKit/General/NSString+SSK.m deleted file mode 100644 index c69067838..000000000 --- a/SessionUtilitiesKit/General/NSString+SSK.m +++ /dev/null @@ -1,23 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "NSString+SSK.h" -#import "AppContext.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation NSString (SSK) - -- (NSString *)rtlSafeAppend:(NSString *)string -{ - if (CurrentAppContext().isRTL) { - return [string stringByAppendingString:self]; - } else { - return [self stringByAppendingString:string]; - } -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SessionUtilitiesKit/General/String+SSK.swift b/SessionUtilitiesKit/General/String+SSK.swift index ae50abb28..f888c654a 100644 --- a/SessionUtilitiesKit/General/String+SSK.swift +++ b/SessionUtilitiesKit/General/String+SSK.swift @@ -10,10 +10,6 @@ public extension String { return (self as NSString).digitsOnly() } - func rtlSafeAppend(_ string: String) -> String { - return (self as NSString).rtlSafeAppend(string) - } - func substring(from index: Int) -> String { return String(self[self.index(self.startIndex, offsetBy: index)...]) } diff --git a/SessionUtilitiesKit/Meta/SessionUtilitiesKit.h b/SessionUtilitiesKit/Meta/SessionUtilitiesKit.h index e4124f453..7bd86403d 100644 --- a/SessionUtilitiesKit/Meta/SessionUtilitiesKit.h +++ b/SessionUtilitiesKit/Meta/SessionUtilitiesKit.h @@ -7,8 +7,6 @@ FOUNDATION_EXPORT const unsigned char SessionUtilitiesKitVersionString[]; #import #import #import -#import -#import #import #import #import diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentCaptionToolbar.swift b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentCaptionToolbar.swift index 534c7321c..4c3ad9f57 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentCaptionToolbar.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentCaptionToolbar.swift @@ -128,7 +128,7 @@ class AttachmentCaptionToolbar: UIView, UITextViewDelegate { textView.themeBackgroundColor = .clear textView.themeTintColor = .textPrimary - textView.font = UIFont.ows_dynamicTypeBody + textView.font = UIFont.preferredFont(forTextStyle: .body) textView.themeTextColor = .textPrimary textView.textContainerInset = UIEdgeInsets(top: 7, left: 7, bottom: 7, right: 7) diff --git a/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift b/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift index 5884bb82d..6089077cc 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift @@ -260,15 +260,15 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate { // Styling switch mode { case .attachmentApproval: - label.font = UIFont.ows_boldFont(withSize: ScaleFromIPhone5To7Plus(16, 22)) + label.font = UIFont.boldSystemFont(ofSize: ScaleFromIPhone5To7Plus(16, 22)) label.themeTextColor = .textPrimary case .large: - label.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5To7Plus(18, 24)) + label.font = UIFont.systemFont(ofSize: ScaleFromIPhone5To7Plus(18, 24)) label.themeTextColor = .primary case .small: - label.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5To7Plus(14, 14)) + label.font = UIFont.systemFont(ofSize: ScaleFromIPhone5To7Plus(14, 14)) label.themeTextColor = .primary } @@ -315,15 +315,15 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate { // Styling switch mode { case .attachmentApproval: - label.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5To7Plus(12, 18)) + label.font = UIFont.systemFont(ofSize: ScaleFromIPhone5To7Plus(12, 18)) label.themeTextColor = .textSecondary case .large: - label.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5To7Plus(18, 24)) + label.font = UIFont.systemFont(ofSize: ScaleFromIPhone5To7Plus(18, 24)) label.themeTextColor = .primary case .small: - label.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5To7Plus(14, 14)) + label.font = UIFont.systemFont(ofSize: ScaleFromIPhone5To7Plus(14, 14)) label.themeTextColor = .primary } @@ -332,7 +332,7 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate { // We only load Link Previews for HTTPS urls so append an explanation for not if let linkPreviewURL: String = linkPreviewInfo?.url { if let targetUrl: URL = URL(string: linkPreviewURL), targetUrl.scheme?.lowercased() != "https" { - label.font = UIFont.ows_regularFont(withSize: Values.verySmallFontSize) + label.font = UIFont.systemFont(ofSize: Values.verySmallFontSize) label.text = "vc_share_link_previews_unsecure".localized() label.themeTextColor = (mode == .attachmentApproval ? .textSecondary : @@ -585,7 +585,7 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate { // This error case is handled already in the 'subtitleLabel' creation } else { - self?.subtitleLabel.font = UIFont.ows_regularFont(withSize: Values.verySmallFontSize) + self?.subtitleLabel.font = UIFont.systemFont(ofSize: Values.verySmallFontSize) self?.subtitleLabel.text = "vc_share_link_previews_error".localized() self?.subtitleLabel.themeTextColor = (self?.mode == .attachmentApproval ? .textSecondary : diff --git a/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h b/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h index aceb514c8..f90e1721d 100644 --- a/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h +++ b/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h @@ -10,12 +10,9 @@ FOUNDATION_EXPORT const unsigned char SignalUtilitiesKitVersionString[]; #import #import #import -#import #import -#import #import #import #import #import #import -#import diff --git a/SignalUtilitiesKit/Utilities/NSAttributedString+OWS.h b/SignalUtilitiesKit/Utilities/NSAttributedString+OWS.h deleted file mode 100644 index d78b1744f..000000000 --- a/SignalUtilitiesKit/Utilities/NSAttributedString+OWS.h +++ /dev/null @@ -1,16 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface NSAttributedString (OWS) - -- (NSAttributedString *)rtlSafeAppend:(NSString *)text attributes:(NSDictionary *)attributes; -- (NSAttributedString *)rtlSafeAppend:(NSAttributedString *)string; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalUtilitiesKit/Utilities/NSAttributedString+OWS.m b/SignalUtilitiesKit/Utilities/NSAttributedString+OWS.m deleted file mode 100644 index 3762543d2..000000000 --- a/SignalUtilitiesKit/Utilities/NSAttributedString+OWS.m +++ /dev/null @@ -1,39 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "NSAttributedString+OWS.h" -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@implementation NSAttributedString (OWS) - -- (NSAttributedString *)rtlSafeAppend:(NSString *)text attributes:(NSDictionary *)attributes -{ - OWSAssertDebug(text); - OWSAssertDebug(attributes); - - NSAttributedString *substring = [[NSAttributedString alloc] initWithString:text attributes:attributes]; - return [self rtlSafeAppend:substring]; -} - -- (NSAttributedString *)rtlSafeAppend:(NSAttributedString *)string -{ - OWSAssertDebug(string); - - NSMutableAttributedString *result = [NSMutableAttributedString new]; - if (CurrentAppContext().isRTL) { - [result appendAttributedString:string]; - [result appendAttributedString:self]; - } else { - [result appendAttributedString:self]; - [result appendAttributedString:string]; - } - return [result copy]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalUtilitiesKit/Utilities/OWSDispatch.h b/SignalUtilitiesKit/Utilities/OWSDispatch.h deleted file mode 100644 index ef0dccd93..000000000 --- a/SignalUtilitiesKit/Utilities/OWSDispatch.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSDispatch : NSObject - -/** - * Attachment downloading - */ -+ (dispatch_queue_t)attachmentsQueue; - -/** - * Serial message sending queue - */ -+ (dispatch_queue_t)sendingQueue; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalUtilitiesKit/Utilities/OWSDispatch.m b/SignalUtilitiesKit/Utilities/OWSDispatch.m deleted file mode 100644 index 5f300a2f9..000000000 --- a/SignalUtilitiesKit/Utilities/OWSDispatch.m +++ /dev/null @@ -1,34 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSDispatch.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation OWSDispatch - -+ (dispatch_queue_t)attachmentsQueue -{ - static dispatch_once_t onceToken; - static dispatch_queue_t queue; - dispatch_once(&onceToken, ^{ - queue = dispatch_queue_create("org.whispersystems.signal.attachments", NULL); - }); - return queue; -} - -+ (dispatch_queue_t)sendingQueue -{ - static dispatch_once_t onceToken; - static dispatch_queue_t queue; - dispatch_once(&onceToken, ^{ - dispatch_queue_attr_t attributes = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0); - queue = dispatch_queue_create("org.whispersystems.signal.sendQueue", attributes); - }); - return queue; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalUtilitiesKit/Utilities/UIFont+OWS.h b/SignalUtilitiesKit/Utilities/UIFont+OWS.h deleted file mode 100644 index 741ed989b..000000000 --- a/SignalUtilitiesKit/Utilities/UIFont+OWS.h +++ /dev/null @@ -1,63 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface UIFont (OWS) - -+ (UIFont *)ows_thinFontWithSize:(CGFloat)size; - -+ (UIFont *)ows_lightFontWithSize:(CGFloat)size; - -+ (UIFont *)ows_regularFontWithSize:(CGFloat)size; - -+ (UIFont *)ows_mediumFontWithSize:(CGFloat)size; - -+ (UIFont *)ows_boldFontWithSize:(CGFloat)size; - -+ (UIFont *)ows_monospacedDigitFontWithSize:(CGFloat)size; - -#pragma mark - Icon Fonts - -+ (UIFont *)ows_fontAwesomeFont:(CGFloat)size; -+ (UIFont *)ows_dripIconsFont:(CGFloat)size; -+ (UIFont *)ows_elegantIconsFont:(CGFloat)size; - -#pragma mark - Dynamic Type - -@property (class, readonly, nonatomic) UIFont *ows_dynamicTypeTitle1Font; -@property (class, readonly, nonatomic) UIFont *ows_dynamicTypeTitle2Font; -@property (class, readonly, nonatomic) UIFont *ows_dynamicTypeTitle3Font; -@property (class, readonly, nonatomic) UIFont *ows_dynamicTypeHeadlineFont; -@property (class, readonly, nonatomic) UIFont *ows_dynamicTypeBodyFont; -@property (class, readonly, nonatomic) UIFont *ows_dynamicTypeSubheadlineFont; -@property (class, readonly, nonatomic) UIFont *ows_dynamicTypeFootnoteFont; -@property (class, readonly, nonatomic) UIFont *ows_dynamicTypeCaption1Font; -@property (class, readonly, nonatomic) UIFont *ows_dynamicTypeCaption2Font; - -#pragma mark - Dynamic Type Clamped - -@property (class, readonly, nonatomic) UIFont *ows_dynamicTypeLargeTitle1ClampedFont; -@property (class, readonly, nonatomic) UIFont *ows_dynamicTypeTitle1ClampedFont; -@property (class, readonly, nonatomic) UIFont *ows_dynamicTypeTitle2ClampedFont; -@property (class, readonly, nonatomic) UIFont *ows_dynamicTypeTitle3ClampedFont; -@property (class, readonly, nonatomic) UIFont *ows_dynamicTypeHeadlineClampedFont; -@property (class, readonly, nonatomic) UIFont *ows_dynamicTypeBodyClampedFont; -@property (class, readonly, nonatomic) UIFont *ows_dynamicTypeSubheadlineClampedFont; -@property (class, readonly, nonatomic) UIFont *ows_dynamicTypeFootnoteClampedFont; -@property (class, readonly, nonatomic) UIFont *ows_dynamicTypeCaption1ClampedFont; -@property (class, readonly, nonatomic) UIFont *ows_dynamicTypeCaption2ClampedFont; - -#pragma mark - Styles - -- (UIFont *)ows_italic; -- (UIFont *)ows_bold; -- (UIFont *)ows_mediumWeight; -- (UIFont *)ows_monospaced; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalUtilitiesKit/Utilities/UIFont+OWS.m b/SignalUtilitiesKit/Utilities/UIFont+OWS.m deleted file mode 100644 index 78497bd44..000000000 --- a/SignalUtilitiesKit/Utilities/UIFont+OWS.m +++ /dev/null @@ -1,234 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "UIFont+OWS.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@implementation UIFont (OWS) - -+ (UIFont *)ows_thinFontWithSize:(CGFloat)size -{ - return [UIFont systemFontOfSize:size weight:UIFontWeightThin]; -} - -+ (UIFont *)ows_lightFontWithSize:(CGFloat)size -{ - return [UIFont systemFontOfSize:size weight:UIFontWeightLight]; -} - -+ (UIFont *)ows_regularFontWithSize:(CGFloat)size -{ - return [UIFont systemFontOfSize:size weight:UIFontWeightRegular]; -} - -+ (UIFont *)ows_mediumFontWithSize:(CGFloat)size -{ - return [UIFont systemFontOfSize:size weight:UIFontWeightMedium]; -} - -+ (UIFont *)ows_boldFontWithSize:(CGFloat)size -{ - return [UIFont boldSystemFontOfSize:size]; -} - -+ (UIFont *)ows_monospacedDigitFontWithSize:(CGFloat)size; -{ - return [self monospacedDigitSystemFontOfSize:size weight:UIFontWeightRegular]; -} - -#pragma mark - Icon Fonts - -+ (UIFont *)ows_fontAwesomeFont:(CGFloat)size -{ - return [UIFont fontWithName:@"FontAwesome" size:size]; -} - -+ (UIFont *)ows_dripIconsFont:(CGFloat)size -{ - return [UIFont fontWithName:@"dripicons-v2" size:size]; -} - -+ (UIFont *)ows_elegantIconsFont:(CGFloat)size -{ - return [UIFont fontWithName:@"ElegantIcons" size:size]; -} - -#pragma mark - Dynamic Type - -+ (UIFont *)ows_dynamicTypeTitle1Font -{ - return [UIFont preferredFontForTextStyle:UIFontTextStyleTitle1]; -} - -+ (UIFont *)ows_dynamicTypeTitle2Font -{ - return [UIFont preferredFontForTextStyle:UIFontTextStyleTitle2]; -} - -+ (UIFont *)ows_dynamicTypeTitle3Font -{ - return [UIFont preferredFontForTextStyle:UIFontTextStyleTitle3]; -} - -+ (UIFont *)ows_dynamicTypeHeadlineFont -{ - return [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline]; -} - -+ (UIFont *)ows_dynamicTypeBodyFont -{ - return [UIFont preferredFontForTextStyle:UIFontTextStyleBody]; -} - -+ (UIFont *)ows_dynamicTypeSubheadlineFont -{ - return [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline]; -} - -+ (UIFont *)ows_dynamicTypeFootnoteFont -{ - return [UIFont preferredFontForTextStyle:UIFontTextStyleFootnote]; -} - -+ (UIFont *)ows_dynamicTypeCaption1Font -{ - return [UIFont preferredFontForTextStyle:UIFontTextStyleCaption1]; -} - -+ (UIFont *)ows_dynamicTypeCaption2Font -{ - return [UIFont preferredFontForTextStyle:UIFontTextStyleCaption2]; -} - -#pragma mark - Dynamic Type Clamped - -+ (UIFont *)preferredFontForTextStyleClamped:(UIFontTextStyle)fontTextStyle -{ - // We clamp the dynamic type sizes at the max size available - // without "larger accessibility sizes" enabled. - static NSDictionary *maxPointSizeMap = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSMutableDictionary *map = [@{ - UIFontTextStyleTitle1 : @(34.0), - UIFontTextStyleTitle2 : @(28.0), - UIFontTextStyleTitle3 : @(26.0), - UIFontTextStyleHeadline : @(23.0), - UIFontTextStyleBody : @(23.0), - UIFontTextStyleSubheadline : @(21.0), - UIFontTextStyleFootnote : @(19.0), - UIFontTextStyleCaption1 : @(18.0), - UIFontTextStyleCaption2 : @(17.0), - } mutableCopy]; - map[UIFontTextStyleLargeTitle] = @(40.0); - maxPointSizeMap = map; - }); - - UIFont *font = [UIFont preferredFontForTextStyle:fontTextStyle]; - NSNumber *_Nullable maxPointSize = maxPointSizeMap[fontTextStyle]; - if (maxPointSize) { - if (maxPointSize.floatValue < font.pointSize) { - return [font fontWithSize:maxPointSize.floatValue]; - } - } else { - OWSFailDebug(@"Missing max point size for style: %@", fontTextStyle); - } - - return font; -} - -+ (UIFont *)ows_dynamicTypeLargeTitle1ClampedFont -{ - return [UIFont preferredFontForTextStyleClamped:UIFontTextStyleLargeTitle]; -} - -+ (UIFont *)ows_dynamicTypeTitle1ClampedFont -{ - return [UIFont preferredFontForTextStyleClamped:UIFontTextStyleTitle1]; -} - -+ (UIFont *)ows_dynamicTypeTitle2ClampedFont -{ - return [UIFont preferredFontForTextStyleClamped:UIFontTextStyleTitle2]; -} - -+ (UIFont *)ows_dynamicTypeTitle3ClampedFont -{ - return [UIFont preferredFontForTextStyleClamped:UIFontTextStyleTitle3]; -} - -+ (UIFont *)ows_dynamicTypeHeadlineClampedFont -{ - return [UIFont preferredFontForTextStyleClamped:UIFontTextStyleHeadline]; -} - -+ (UIFont *)ows_dynamicTypeBodyClampedFont -{ - return [UIFont preferredFontForTextStyleClamped:UIFontTextStyleBody]; -} - -+ (UIFont *)ows_dynamicTypeSubheadlineClampedFont -{ - return [UIFont preferredFontForTextStyleClamped:UIFontTextStyleSubheadline]; -} - -+ (UIFont *)ows_dynamicTypeFootnoteClampedFont -{ - return [UIFont preferredFontForTextStyleClamped:UIFontTextStyleFootnote]; -} - -+ (UIFont *)ows_dynamicTypeCaption1ClampedFont -{ - return [UIFont preferredFontForTextStyleClamped:UIFontTextStyleCaption1]; -} - -+ (UIFont *)ows_dynamicTypeCaption2ClampedFont -{ - return [UIFont preferredFontForTextStyleClamped:UIFontTextStyleCaption2]; -} - -#pragma mark - Styles - -- (UIFont *)ows_italic -{ - return [self styleWithSymbolicTraits:UIFontDescriptorTraitItalic]; -} - -- (UIFont *)ows_bold -{ - return [self styleWithSymbolicTraits:UIFontDescriptorTraitBold]; -} - -- (UIFont *)styleWithSymbolicTraits:(UIFontDescriptorSymbolicTraits)symbolicTraits -{ - UIFontDescriptor *fontDescriptor = [self.fontDescriptor fontDescriptorWithSymbolicTraits:symbolicTraits]; - UIFont *font = [UIFont fontWithDescriptor:fontDescriptor size:0]; - OWSAssertDebug(font); - return font ?: self; -} - -- (UIFont *)ows_mediumWeight -{ - // The recommended approach of deriving "medium" weight fonts for dynamic - // type fonts is: - // - // [UIFontDescriptor fontDescriptorByAddingAttributes:...] - // - // But this doesn't seem to work in practice on iOS 11 using UIFontWeightMedium. - - UIFont *derivedFont = [UIFont systemFontOfSize:self.pointSize weight:UIFontWeightMedium]; - return derivedFont; -} - -- (UIFont *)ows_monospaced -{ - return [self.class ows_monospacedDigitFontWithSize:self.pointSize]; -} - - -@end - -NS_ASSUME_NONNULL_END From ec81236615f84a7314f0e187fc93a4733c99e9a9 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 30 Jun 2023 17:59:37 +1000 Subject: [PATCH 101/135] Fixed a few more threading-related bugs Updated the libSession build script to reset and checkout submodules based on a flag (simplify the process) Updated the share and notification extensions to generate the DSYM on debug builds to allow for better debugging Added additional startup logging for debugging purposes Tweaked the SNLog function to indicate when the log happens on the main thread (slightly less efficient but should help track down logic incorrectly running on the main thread) Fixed a bug where we weren't recording the last PN server registration (so would re-register every time) Fixed a bug where if the user sent the app to the background too quickly after launching the app wouldn't successfully startup when re-opening Fixed a bug where the Notification and Share extensions would assert because they were dispatching the post-migration logic to the main thread (due to the threading changes in a previous release) Fixed a bug where the Notification extension would incorrectly poll open groups on the main thread --- Scripts/build_libSession_util.sh | 79 +++++++++++++++-- Session.xcodeproj/project.pbxproj | 16 ++-- .../xcshareddata/xcschemes/Session.xcscheme | 2 +- .../xcschemes/SessionMessagingKit.xcscheme | 2 +- ...ssionNotificationServiceExtension.xcscheme | 2 +- .../xcschemes/SessionShareExtension.xcscheme | 2 +- .../xcschemes/SessionUtilitiesKit.xcscheme | 2 +- .../xcschemes/SignalUtilitiesKit.xcscheme | 2 +- Session/Meta/AppDelegate.swift | 84 +++++++++++++++++-- Session/Meta/MainAppContext.h | 2 + Session/Notifications/SyncPushTokensJob.swift | 31 +++---- .../NotificationServiceExtension.swift | 24 +++--- .../ShareNavController.swift | 27 +++--- SessionUtilitiesKit/Database/Storage.swift | 10 ++- SessionUtilitiesKit/General/Logging.swift | 6 +- 15 files changed, 225 insertions(+), 66 deletions(-) diff --git a/Scripts/build_libSession_util.sh b/Scripts/build_libSession_util.sh index bb3cb671b..562c9871a 100755 --- a/Scripts/build_libSession_util.sh +++ b/Scripts/build_libSession_util.sh @@ -21,9 +21,13 @@ # path = "{FRAMEWORK NAME GOES HERE}"; # sourceTree = BUILD_DIR; # }; +# +# Note: We might one day be able to replace this with a local podspec if this GitHub feature +# request ever gets implemented: https://github.com/CocoaPods/CocoaPods/issues/8464 # Need to set the path or we won't find cmake PATH=${PATH}:/usr/local/bin:/opt/homebrew/bin:/sbin/md5 +SHOULD_AUTO_INIT_SUBMODULES=${1:-false} # Ensure the build directory exists (in case we need it before XCode creates it) mkdir -p "${TARGET_BUILD_DIR}" @@ -41,11 +45,76 @@ if ! which cmake > /dev/null; then exit 0 fi -if [ ! -d "${SRCROOT}/LibSession-Util" ] || [ ! -d "${SRCROOT}/LibSession-Util/src" ]; then - touch "${TARGET_BUILD_DIR}/libsession_util_error.log" - echo "error: Need to fetch LibSession-Util submodule." - echo "error: Need to fetch LibSession-Util submodule." > "${TARGET_BUILD_DIR}/libsession_util_error.log" - exit 1 +# Check if we have the `LibSession-Util` submodule checked out and if not (depending on the 'SHOULD_AUTO_INIT_SUBMODULES' argument) perform the checkout +if [ ! -d "${SRCROOT}/LibSession-Util" ] || [ ! -d "${SRCROOT}/LibSession-Util/src" ] || [ ! "$(ls -A "${SRCROOT}/LibSession-Util")" ]; then + if [ "${SHOULD_AUTO_INIT_SUBMODULES}" != "false" ] & command -v git >/dev/null 2>&1; then + echo "info: LibSession-Util submodule doesn't exist, resetting and checking out recusively now" + git submodule foreach --recursive git reset --hard + git submodule update --init --recursive + echo "info: Checkout complete" + else + touch "${TARGET_BUILD_DIR}/libsession_util_error.log" + echo "error: Need to fetch LibSession-Util submodule (git submodule update --init --recursive)." + echo "error: Need to fetch LibSession-Util submodule (git submodule update --init --recursive)." > "${TARGET_BUILD_DIR}/libsession_util_error.log" + exit 1 + fi +else + are_submodules_valid() { + local PARENT_PATH=$1 + + # Change into the path to check for it's submodules + cd "${PARENT_PATH}" + local SUB_MODULE_PATHS=($(git config --file .gitmodules --get-regexp path | awk '{ print $2 }')) + + # If there are no submodules then return success based on whether the folder has any content + if [ ${#SUB_MODULE_PATHS[@]} -eq 0 ]; then + if [ "$(ls -A "${SRCROOT}/LibSession-Util")" ]; then + return 1 + else + return 0 + fi + fi + + # Loop through the child submodules and check if they are valid + for i in "${!SUB_MODULE_PATHS[@]}"; do + local CHILD_PATH="${SUB_MODULE_PATHS[$i]}" + + # If the child path doesn't exist then it's invalid + if [ ! -d "${CHILD_PATH}" ]; then + return 1 + fi + + are_submodules_valid "${PARENT_PATH}/${CHILD_PATH}" + local RESULT=$? + + if [ "${RESULT}" -eq 1 ]; then + echo "info: Submodule ${CHILD_PATH} is in an invalid state." + return 1 + fi + done + + return 0 + } + + # Validate the state of the submodules + are_submodules_valid "${SRCROOT}/LibSession-Util" + + HAS_INVALID_SUBMODULE=$? + + if [ "${HAS_INVALID_SUBMODULE}" -eq 1 ]; then + if [ "${SHOULD_AUTO_INIT_SUBMODULES}" != "false" ] && command -v git >/dev/null 2>&1; then + echo "info: Submodules are in an invalid state, resetting and checking out recusively now" + cd "${SRCROOT}/LibSession-Util" + git submodule foreach --recursive git reset --hard + git submodule update --init --recursive + echo "info: Checkout complete" + else + touch "${TARGET_BUILD_DIR}/libsession_util_error.log" + echo "error: Submodules are in an invalid state, please delete 'LibSession-Util' and run 'git submodule update --init --recursive'." + echo "error: Submodules are in an invalid state, please delete 'LibSession-Util' and run 'git submodule update --init --recursive'." > "${TARGET_BUILD_DIR}/libsession_util_error.log" + exit 1 + fi + fi fi # Generate a hash of the libSession-util source files and check if they differ from the last hash diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 458f0c863..771ff32af 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -6377,8 +6377,8 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 412; - DEBUG_INFORMATION_FORMAT = dwarf; + CURRENT_PROJECT_VERSION = 413; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -6449,7 +6449,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 412; + CURRENT_PROJECT_VERSION = 413; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6514,8 +6514,8 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 412; - DEBUG_INFORMATION_FORMAT = dwarf; + CURRENT_PROJECT_VERSION = 413; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -6588,7 +6588,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 412; + CURRENT_PROJECT_VERSION = 413; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -7496,7 +7496,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 412; + CURRENT_PROJECT_VERSION = 413; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -7567,7 +7567,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 412; + CURRENT_PROJECT_VERSION = 413; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme b/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme index f2e5c8744..58585f0bb 100644 --- a/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme +++ b/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme @@ -10,7 +10,7 @@ ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction"> + scriptText = ""${SRCROOT}/Scripts/build_libSession_util.sh" true "> + scriptText = ""${SRCROOT}/Scripts/build_libSession_util.sh" true "> + scriptText = ""${SRCROOT}/Scripts/build_libSession_util.sh" true "> + scriptText = ""${SRCROOT}/Scripts/build_libSession_util.sh" true "> + scriptText = ""${SRCROOT}/Scripts/build_libSession_util.sh" true "> + scriptText = ""${SRCROOT}/Scripts/build_libSession_util.sh" true "> Bool { + switch (lhs, rhs) { + case (.finishLaunching, .finishLaunching): return true + case (.enterForeground(let lhsFailed), .enterForeground(let rhsFailed)): return (lhsFailed == rhsFailed) + case (.didBecomeActive, .didBecomeActive): return true + default: return false + } + } } // MARK: - StartupError @@ -867,6 +928,15 @@ private enum StartupError: Error { case failedToRestore case startupTimeout + var name: String { + switch self { + case .databaseError(StorageError.startupFailed): return "Database startup failed" + case .failedToRestore: return "Failed to restore" + case .databaseError: return "Database error" + case .startupTimeout: return "Startup timeout" + } + } + var message: String { switch self { case .databaseError(StorageError.startupFailed): return "DATABASE_STARTUP_FAILED".localized() diff --git a/Session/Meta/MainAppContext.h b/Session/Meta/MainAppContext.h index a5c0ac97e..6fab6a1ad 100644 --- a/Session/Meta/MainAppContext.h +++ b/Session/Meta/MainAppContext.h @@ -10,6 +10,8 @@ extern NSString *const ReportedApplicationStateDidChangeNotification; @interface MainAppContext : NSObject +- (instancetype)init; + @end NS_ASSUME_NONNULL_END diff --git a/Session/Notifications/SyncPushTokensJob.swift b/Session/Notifications/SyncPushTokensJob.swift index 36b32e56a..2a93618fb 100644 --- a/Session/Notifications/SyncPushTokensJob.swift +++ b/Session/Notifications/SyncPushTokensJob.swift @@ -22,23 +22,25 @@ public enum SyncPushTokensJob: JobExecutor { deferred: @escaping (Job) -> () ) { // Don't run when inactive or not in main app or if the user doesn't exist yet - guard - (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false), - Identity.userCompletedRequiredOnboarding() - else { + guard (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) else { + return deferred(job) // Don't need to do anything if it's not the main app + } + guard Identity.userCompletedRequiredOnboarding() else { SNLog("[SyncPushTokensJob] Deferred due to incomplete registration") - deferred(job) // Don't need to do anything if it's not the main app - return + return deferred(job) } - // We need to check a UIApplication setting which needs to run on the main thread so if we aren't on - // the main thread then swap to it - guard Thread.isMainThread else { - DispatchQueue.main.async { - run(job, queue: queue, success: success, failure: failure, deferred: deferred) + // We need to check a UIApplication setting which needs to run on the main thread so synchronously + // retrieve the value so we can continue + let isRegisteredForRemoteNotifications: Bool = { + guard !Thread.isMainThread else { + return UIApplication.shared.isRegisteredForRemoteNotifications } - return - } + + return DispatchQueue.main.sync { + return UIApplication.shared.isRegisteredForRemoteNotifications + } + }() // Push tokens don't normally change while the app is launched, so you would assume checking once // during launch is sufficient, but e.g. on iOS11, users who have disabled "Allow Notifications" @@ -60,7 +62,7 @@ public enum SyncPushTokensJob: JobExecutor { guard job.behaviour == .runOnce || - !UIApplication.shared.isRegisteredForRemoteNotifications || + !isRegisteredForRemoteNotifications || Date().timeIntervalSince(lastPushNotificationSync) >= SyncPushTokensJob.maxFrequency else { SNLog("[SyncPushTokensJob] Deferred due to Fast Mode disabled or recent-enough registration") @@ -94,6 +96,7 @@ public enum SyncPushTokensJob: JobExecutor { case .finished: Logger.warn("Recording push tokens locally. pushToken: \(redact(pushToken)), voipToken: \(redact(voipToken))") SNLog("[SyncPushTokensJob] Completed") + UserDefaults.standard[.lastPushNotificationSync] = Date() Storage.shared.write { db in db[.lastRecordedPushToken] = pushToken diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index 59b31366b..0526360a4 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -12,7 +12,6 @@ import SignalCoreKit public final class NotificationServiceExtension: UNNotificationServiceExtension { private var didPerformSetup = false - private var areVersionMigrationsComplete = false private var contentHandler: ((UNNotificationContent) -> Void)? private var request: UNNotificationRequest? @@ -50,6 +49,8 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension defer { Publishers .MergeMany(openGroupPollingPublishers) + .subscribe(on: DispatchQueue.global(qos: .background)) + .subscribe(on: DispatchQueue.main) .sinkUntilComplete( receiveCompletion: { _ in self.completeSilenty() @@ -234,19 +235,23 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension $0 = NSENotificationPresenter() } }, - migrationsCompletion: { [weak self] _, needsConfigSync in - self?.versionMigrationsDidComplete(needsConfigSync: needsConfigSync) + migrationsCompletion: { [weak self] result, needsConfigSync in + switch result { + case .failure: SNLog("[NotificationServiceExtension] Failed to complete migrations") + case .success: + DispatchQueue.main.async { + self?.versionMigrationsDidComplete(needsConfigSync: needsConfigSync) + } + } + completion() } ) } - @objc private func versionMigrationsDidComplete(needsConfigSync: Bool) { AssertIsOnMainThread() - areVersionMigrationsComplete = true - // If we need a config sync then trigger it now if needsConfigSync { Storage.shared.write { db in @@ -254,18 +259,17 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension } } - checkIsAppReady() + checkIsAppReady(migrationsCompleted: true) } - @objc - private func checkIsAppReady() { + private func checkIsAppReady(migrationsCompleted: Bool) { AssertIsOnMainThread() // Only mark the app as ready once. guard !AppReadiness.isAppReady() else { return } // App isn't ready until storage is ready AND all version migrations are complete. - guard Storage.shared.isValid && areVersionMigrationsComplete else { return } + guard Storage.shared.isValid && migrationsCompleted else { return } SignalUtilitiesKit.Configuration.performMainSetup() diff --git a/SessionShareExtension/ShareNavController.swift b/SessionShareExtension/ShareNavController.swift index d8a1cfde4..496674eae 100644 --- a/SessionShareExtension/ShareNavController.swift +++ b/SessionShareExtension/ShareNavController.swift @@ -8,7 +8,6 @@ import SessionUIKit import SignalCoreKit final class ShareNavController: UINavigationController, ShareViewDelegate { - private var areVersionMigrationsComplete = false public static var attachmentPrepPublisher: AnyPublisher<[SignalAttachment], Error>? // MARK: - Error @@ -58,10 +57,16 @@ final class ShareNavController: UINavigationController, ShareViewDelegate { $0 = NoopNotificationsManager() } }, - migrationsCompletion: { [weak self] _, needsConfigSync in - // performUpdateCheck must be invoked after Environment has been initialized because - // upgrade process may depend on Environment. - self?.versionMigrationsDidComplete(needsConfigSync: needsConfigSync) + migrationsCompletion: { [weak self] result, needsConfigSync in + switch result { + case .failure: SNLog("[SessionShareExtension] Failed to complete migrations") + case .success: + DispatchQueue.main.async { + // performUpdateCheck must be invoked after Environment has been initialized because + // upgrade process may depend on Environment. + self?.versionMigrationsDidComplete(needsConfigSync: needsConfigSync) + } + } } ) @@ -82,14 +87,11 @@ final class ShareNavController: UINavigationController, ShareViewDelegate { ThemeManager.traitCollectionDidChange(previousTraitCollection) } - @objc func versionMigrationsDidComplete(needsConfigSync: Bool) { AssertIsOnMainThread() Logger.debug("") - areVersionMigrationsComplete = true - // If we need a config sync then trigger it now if needsConfigSync { Storage.shared.write { db in @@ -97,15 +99,14 @@ final class ShareNavController: UINavigationController, ShareViewDelegate { } } - checkIsAppReady() + checkIsAppReady(migrationsCompleted: true) } - @objc - func checkIsAppReady() { + func checkIsAppReady(migrationsCompleted: Bool) { AssertIsOnMainThread() // App isn't ready until storage is ready AND all version migrations are complete. - guard areVersionMigrationsComplete else { return } + guard migrationsCompleted else { return } guard Storage.shared.isValid else { return } guard !AppReadiness.isAppReady() else { // Only mark the app as ready once. @@ -195,6 +196,8 @@ final class ShareNavController: UINavigationController, ShareViewDelegate { message: "vc_share_loading_message".localized() ) { activityIndicator in publisher + .subscribe(on: DispatchQueue.global(qos: .userInitiated)) + .receive(on: DispatchQueue.main) .sinkUntilComplete( receiveCompletion: { _ in activityIndicator.dismiss { } } ) diff --git a/SessionUtilitiesKit/Database/Storage.swift b/SessionUtilitiesKit/Database/Storage.swift index 1feb26663..4d8f9fe39 100644 --- a/SessionUtilitiesKit/Database/Storage.swift +++ b/SessionUtilitiesKit/Database/Storage.swift @@ -214,8 +214,14 @@ open class Storage { self?.migrationProgressUpdater = nil SUKLegacy.clearLegacyDatabaseInstance() - if case .failure(let error) = result { - SNLog("[Migration Error] Migration failed with error: \(error)") + // Don't log anything in the case of a 'success' or if the database is suspended (the + // latter will happen if the user happens to return to the background too quickly on + // launch so is unnecessarily alarming, it also gets caught and logged separately by + // the 'write' functions anyway) + switch result { + case .success: break + case .failure(DatabaseError.SQLITE_ABORT): break + case .failure(let error): SNLog("[Migration Error] Migration failed with error: \(error)") } onComplete(result, needsConfigSync) diff --git a/SessionUtilitiesKit/General/Logging.swift b/SessionUtilitiesKit/General/Logging.swift index 7bd18d8f7..b3f64899e 100644 --- a/SessionUtilitiesKit/General/Logging.swift +++ b/SessionUtilitiesKit/General/Logging.swift @@ -4,10 +4,12 @@ import Foundation import SignalCoreKit public func SNLog(_ message: String) { + let threadString: String = (Thread.isMainThread ? " Main" : "") + #if DEBUG - print("[Session] \(message)") + print("[Session\(threadString)] \(message)") #endif - OWSLogger.info("[Session] \(message)") + OWSLogger.info("[Session\(threadString)] \(message)") } public func SNLogNotTests(_ message: String) { From 1ed86d483e417a203fe50af406d4c721f15409d8 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Mon, 3 Jul 2023 09:54:04 +1000 Subject: [PATCH 102/135] Tweaks to the Build pre-action Removed the flag to automatically reset and check out submodules (seemed too destructive for a default behaviour) Updated all targets to run the 'Validate pre-build actions' script Updated the build script to return a success instead of an error (forgot that this was the point of the 'Validate pre-build actions' script) --- Scripts/build_libSession_util.sh | 8 +- Session.xcodeproj/project.pbxproj | 210 ++++++++++++++++++ .../xcshareddata/xcschemes/Session.xcscheme | 2 +- .../xcschemes/SessionMessagingKit.xcscheme | 2 +- ...ssionNotificationServiceExtension.xcscheme | 2 +- .../xcschemes/SessionShareExtension.xcscheme | 2 +- .../xcschemes/SessionUtilitiesKit.xcscheme | 2 +- .../xcschemes/SignalUtilitiesKit.xcscheme | 2 +- 8 files changed, 220 insertions(+), 10 deletions(-) diff --git a/Scripts/build_libSession_util.sh b/Scripts/build_libSession_util.sh index 562c9871a..6e680a86c 100755 --- a/Scripts/build_libSession_util.sh +++ b/Scripts/build_libSession_util.sh @@ -7,8 +7,8 @@ # build stage so XCode is able to build the dependency graph # # XCode's Pre-action scripts don't output anything into XCode so the only way to emit a useful -# error is to return a success status and have the project detect and log the error itself then -# log it, stopping the build at that point +# error is to **return a success status** and have the project detect and log the error itself +# then log it, stopping the build at that point # # The other step to get this to work properly is to ensure the framework in "Link Binary with # Libraries" isn't using a relative directory, unfortunately there doesn't seem to be a good @@ -56,7 +56,7 @@ if [ ! -d "${SRCROOT}/LibSession-Util" ] || [ ! -d "${SRCROOT}/LibSession-Util/s touch "${TARGET_BUILD_DIR}/libsession_util_error.log" echo "error: Need to fetch LibSession-Util submodule (git submodule update --init --recursive)." echo "error: Need to fetch LibSession-Util submodule (git submodule update --init --recursive)." > "${TARGET_BUILD_DIR}/libsession_util_error.log" - exit 1 + exit 0 fi else are_submodules_valid() { @@ -112,7 +112,7 @@ else touch "${TARGET_BUILD_DIR}/libsession_util_error.log" echo "error: Submodules are in an invalid state, please delete 'LibSession-Util' and run 'git submodule update --init --recursive'." echo "error: Submodules are in an invalid state, please delete 'LibSession-Util' and run 'git submodule update --init --recursive'." > "${TARGET_BUILD_DIR}/libsession_util_error.log" - exit 1 + exit 0 fi fi fi diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 771ff32af..e9eb21bf9 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -4473,6 +4473,7 @@ buildConfigurationList = 453518761FC635DD00210559 /* Build configuration list for PBXNativeTarget "SessionShareExtension" */; buildPhases = ( 55CE11E14880742A24ADC127 /* [CP] Check Pods Manifest.lock */, + FD7692EC2A524320000E4B70 /* Validate pre-build actions */, 453518641FC635DD00210559 /* Sources */, 453518651FC635DD00210559 /* Frameworks */, 453518661FC635DD00210559 /* Resources */, @@ -4496,6 +4497,7 @@ buildConfigurationList = 7BC01A45241F40AB00BC7C55 /* Build configuration list for PBXNativeTarget "SessionNotificationServiceExtension" */; buildPhases = ( 18CDA58AE057F8C9AE71F46E /* [CP] Check Pods Manifest.lock */, + FD7692ED2A52433E000E4B70 /* Validate pre-build actions */, 7BC01A37241F40AB00BC7C55 /* Sources */, 7BC01A38241F40AB00BC7C55 /* Frameworks */, 7BC01A39241F40AB00BC7C55 /* Resources */, @@ -4518,6 +4520,7 @@ buildConfigurationList = C331FF262558F9D400070591 /* Build configuration list for PBXNativeTarget "SessionUIKit" */; buildPhases = ( D5AFDC09857840D2D2631E2D /* [CP] Check Pods Manifest.lock */, + FD7692EF2A52436A000E4B70 /* Validate pre-build actions */, C331FF162558F9D300070591 /* Headers */, C331FF172558F9D300070591 /* Sources */, C331FF182558F9D300070591 /* Frameworks */, @@ -4538,6 +4541,7 @@ buildConfigurationList = C33FD9B6255A548A00E217F9 /* Build configuration list for PBXNativeTarget "SignalUtilitiesKit" */; buildPhases = ( 5CE8055024B876590AED6DEA /* [CP] Check Pods Manifest.lock */, + FD7692EE2A524357000E4B70 /* Validate pre-build actions */, C33FD9A6255A548A00E217F9 /* Headers */, C33FD9A7255A548A00E217F9 /* Sources */, C33FD9A8255A548A00E217F9 /* Frameworks */, @@ -4557,6 +4561,7 @@ buildConfigurationList = C3C2A5AA255385C100C340D1 /* Build configuration list for PBXNativeTarget "SessionSnodeKit" */; buildPhases = ( 77F55C879DAF28750120D343 /* [CP] Check Pods Manifest.lock */, + FD7692F02A524393000E4B70 /* Validate pre-build actions */, C3C2A59A255385C100C340D1 /* Headers */, C3C2A59B255385C100C340D1 /* Sources */, C3C2A59C255385C100C340D1 /* Frameworks */, @@ -4576,6 +4581,7 @@ buildConfigurationList = C3C2A684255388CC00C340D1 /* Build configuration list for PBXNativeTarget "SessionUtilitiesKit" */; buildPhases = ( 446B0E16474DF9F15509BC64 /* [CP] Check Pods Manifest.lock */, + FD7692F12A5243AE000E4B70 /* Validate pre-build actions */, C3C2A674255388CC00C340D1 /* Headers */, C3C2A675255388CC00C340D1 /* Sources */, C3C2A676255388CC00C340D1 /* Frameworks */, @@ -4615,6 +4621,7 @@ buildConfigurationList = D221A0BC169C9E5F00537ABF /* Build configuration list for PBXNativeTarget "Session" */; buildPhases = ( 351E727E03A8F141EA25FBF4 /* [CP] Check Pods Manifest.lock */, + FD7692EA2A524303000E4B70 /* Validate pre-build actions */, FDE7214D287E50820093DF33 /* Lint Localizable.strings */, D221A085169C9E5E00537ABF /* Sources */, D221A086169C9E5E00537ABF /* Frameworks */, @@ -4645,6 +4652,7 @@ buildConfigurationList = FD71160F28D00BAE00B47552 /* Build configuration list for PBXNativeTarget "SessionTests" */; buildPhases = ( 19CD7B4EDC153293FB61CBA1 /* [CP] Check Pods Manifest.lock */, + FD7692F22A5243C3000E4B70 /* Validate pre-build actions */, FD71160528D00BAE00B47552 /* Sources */, FD71160628D00BAE00B47552 /* Frameworks */, FD71160728D00BAE00B47552 /* Resources */, @@ -4665,6 +4673,7 @@ buildConfigurationList = FD83B9B627CF200A005E1583 /* Build configuration list for PBXNativeTarget "SessionUtilitiesKitTests" */; buildPhases = ( EDDFB3BFBD5E1378BD03AAAB /* [CP] Check Pods Manifest.lock */, + FD7692F42A5243EC000E4B70 /* Validate pre-build actions */, FD83B9AB27CF200A005E1583 /* Sources */, FD83B9AC27CF200A005E1583 /* Frameworks */, FD83B9AD27CF200A005E1583 /* Resources */, @@ -4686,6 +4695,7 @@ buildConfigurationList = FDC4389527B9FFC700C60D73 /* Build configuration list for PBXNativeTarget "SessionMessagingKitTests" */; buildPhases = ( 0E6C1748F41E48ED59563D96 /* [CP] Check Pods Manifest.lock */, + FD7692F32A5243DA000E4B70 /* Validate pre-build actions */, FDC4388A27B9FFC700C60D73 /* Sources */, FDC4388B27B9FFC700C60D73 /* Frameworks */, FDC4388C27B9FFC700C60D73 /* Resources */, @@ -5313,6 +5323,206 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + FD7692EA2A524303000E4B70 /* Validate pre-build actions */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Validate pre-build actions"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [ -f \"${TARGET_BUILD_DIR}/libsession_util_error.log\" ]; then\n read -r line < \"${TARGET_BUILD_DIR}/libsession_util_error.log\"\n echo \"${line}\"\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + FD7692EC2A524320000E4B70 /* Validate pre-build actions */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Validate pre-build actions"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [ -f \"${TARGET_BUILD_DIR}/libsession_util_error.log\" ]; then\n read -r line < \"${TARGET_BUILD_DIR}/libsession_util_error.log\"\n echo \"${line}\"\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + FD7692ED2A52433E000E4B70 /* Validate pre-build actions */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Validate pre-build actions"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [ -f \"${TARGET_BUILD_DIR}/libsession_util_error.log\" ]; then\n read -r line < \"${TARGET_BUILD_DIR}/libsession_util_error.log\"\n echo \"${line}\"\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + FD7692EE2A524357000E4B70 /* Validate pre-build actions */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Validate pre-build actions"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [ -f \"${TARGET_BUILD_DIR}/libsession_util_error.log\" ]; then\n read -r line < \"${TARGET_BUILD_DIR}/libsession_util_error.log\"\n echo \"${line}\"\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + FD7692EF2A52436A000E4B70 /* Validate pre-build actions */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Validate pre-build actions"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [ -f \"${TARGET_BUILD_DIR}/libsession_util_error.log\" ]; then\n read -r line < \"${TARGET_BUILD_DIR}/libsession_util_error.log\"\n echo \"${line}\"\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + FD7692F02A524393000E4B70 /* Validate pre-build actions */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Validate pre-build actions"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [ -f \"${TARGET_BUILD_DIR}/libsession_util_error.log\" ]; then\n read -r line < \"${TARGET_BUILD_DIR}/libsession_util_error.log\"\n echo \"${line}\"\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + FD7692F12A5243AE000E4B70 /* Validate pre-build actions */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Validate pre-build actions"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [ -f \"${TARGET_BUILD_DIR}/libsession_util_error.log\" ]; then\n read -r line < \"${TARGET_BUILD_DIR}/libsession_util_error.log\"\n echo \"${line}\"\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + FD7692F22A5243C3000E4B70 /* Validate pre-build actions */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Validate pre-build actions"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [ -f \"${TARGET_BUILD_DIR}/libsession_util_error.log\" ]; then\n read -r line < \"${TARGET_BUILD_DIR}/libsession_util_error.log\"\n echo \"${line}\"\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + FD7692F32A5243DA000E4B70 /* Validate pre-build actions */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Validate pre-build actions"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [ -f \"${TARGET_BUILD_DIR}/libsession_util_error.log\" ]; then\n read -r line < \"${TARGET_BUILD_DIR}/libsession_util_error.log\"\n echo \"${line}\"\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + FD7692F42A5243EC000E4B70 /* Validate pre-build actions */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Validate pre-build actions"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [ -f \"${TARGET_BUILD_DIR}/libsession_util_error.log\" ]; then\n read -r line < \"${TARGET_BUILD_DIR}/libsession_util_error.log\"\n echo \"${line}\"\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; FDD82C422A2085B900425F05 /* Add Commit Hash To Build Info Plist */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme b/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme index 58585f0bb..f2e5c8744 100644 --- a/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme +++ b/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme @@ -10,7 +10,7 @@ ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction"> + scriptText = ""${SRCROOT}/Scripts/build_libSession_util.sh" "> + scriptText = ""${SRCROOT}/Scripts/build_libSession_util.sh" "> + scriptText = ""${SRCROOT}/Scripts/build_libSession_util.sh" "> + scriptText = ""${SRCROOT}/Scripts/build_libSession_util.sh" "> + scriptText = ""${SRCROOT}/Scripts/build_libSession_util.sh" "> + scriptText = ""${SRCROOT}/Scripts/build_libSession_util.sh" "> Date: Mon, 3 Jul 2023 10:12:04 +1000 Subject: [PATCH 103/135] Fixed a few path issues in the build script submodule validation logic --- Scripts/build_libSession_util.sh | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Scripts/build_libSession_util.sh b/Scripts/build_libSession_util.sh index 6e680a86c..998c8f462 100755 --- a/Scripts/build_libSession_util.sh +++ b/Scripts/build_libSession_util.sh @@ -61,6 +61,7 @@ if [ ! -d "${SRCROOT}/LibSession-Util" ] || [ ! -d "${SRCROOT}/LibSession-Util/s else are_submodules_valid() { local PARENT_PATH=$1 + local RELATIVE_PATH=$2 # Change into the path to check for it's submodules cd "${PARENT_PATH}" @@ -68,10 +69,10 @@ else # If there are no submodules then return success based on whether the folder has any content if [ ${#SUB_MODULE_PATHS[@]} -eq 0 ]; then - if [ "$(ls -A "${SRCROOT}/LibSession-Util")" ]; then - return 1 - else + if [[ ! -z "$(ls -A "${PARENT_PATH}")" ]]; then return 0 + else + return 1 fi fi @@ -80,15 +81,16 @@ else local CHILD_PATH="${SUB_MODULE_PATHS[$i]}" # If the child path doesn't exist then it's invalid - if [ ! -d "${CHILD_PATH}" ]; then + if [ ! -d "${PARENT_PATH}/${CHILD_PATH}" ]; then + echo "info: Submodule '${RELATIVE_PATH}/${CHILD_PATH}' doesn't exist." return 1 fi - are_submodules_valid "${PARENT_PATH}/${CHILD_PATH}" + are_submodules_valid "${PARENT_PATH}/${CHILD_PATH}" "${RELATIVE_PATH}/${CHILD_PATH}" local RESULT=$? if [ "${RESULT}" -eq 1 ]; then - echo "info: Submodule ${CHILD_PATH} is in an invalid state." + echo "info: Submodule '${RELATIVE_PATH}/${CHILD_PATH}' is in an invalid state." return 1 fi done @@ -97,7 +99,7 @@ else } # Validate the state of the submodules - are_submodules_valid "${SRCROOT}/LibSession-Util" + are_submodules_valid "${SRCROOT}/LibSession-Util" "LibSession-Util" HAS_INVALID_SUBMODULE=$? From 30779bdf5be6cf83164a7d6a19cd12f8c60f241a Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Mon, 3 Jul 2023 10:40:54 +1000 Subject: [PATCH 104/135] Removed an incorrect `AssertIsOnMainThread` --- Session/Notifications/PushRegistrationManager.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Session/Notifications/PushRegistrationManager.swift b/Session/Notifications/PushRegistrationManager.swift index 392b7c542..d6eb260be 100644 --- a/Session/Notifications/PushRegistrationManager.swift +++ b/Session/Notifications/PushRegistrationManager.swift @@ -100,7 +100,6 @@ public enum PushRegistrationError: Error { // User notification settings must be registered *before* AppDelegate will // return any requested push tokens. public func registerUserNotificationSettings() -> AnyPublisher { - AssertIsOnMainThread() return notificationPresenter.registerNotificationSettings() } @@ -129,6 +128,7 @@ public enum PushRegistrationError: Error { return true } + // FIXME: Might be nice to try to avoid having this required to run on the main thread (follow a similar approach to the 'SyncPushTokensJob' & `Atomic`?) private func registerForVanillaPushToken() -> AnyPublisher { AssertIsOnMainThread() From 7b70f8d535f71a5f81a8fbfc1ef7a81ca0ca87dc Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Mon, 3 Jul 2023 10:45:13 +1000 Subject: [PATCH 105/135] Increased build number --- Session.xcodeproj/project.pbxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index e9eb21bf9..176b6cdcf 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -6587,7 +6587,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 413; + CURRENT_PROJECT_VERSION = 414; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6659,7 +6659,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 413; + CURRENT_PROJECT_VERSION = 414; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6724,7 +6724,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 413; + CURRENT_PROJECT_VERSION = 414; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6798,7 +6798,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 413; + CURRENT_PROJECT_VERSION = 414; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -7706,7 +7706,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 413; + CURRENT_PROJECT_VERSION = 414; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -7777,7 +7777,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 413; + CURRENT_PROJECT_VERSION = 414; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", From f45568644e75d95874997a8c0746cd8e5c638e86 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Mon, 3 Jul 2023 12:05:43 +1000 Subject: [PATCH 106/135] Fixed a somehow reproducable crash Fixed a crash when autoLoadNextPageIfNeeded was run after the table data was updated but before the tableView was reloaded resulting in a index out of bounds exception --- Session/Conversations/ConversationVC.swift | 8 ++++++-- Session/Home/HomeVC.swift | 10 +++++++--- .../MessageRequestsViewController.swift | 9 +++++++-- .../DocumentTitleViewController.swift | 4 ++-- .../MediaTileViewController.swift | 4 ++-- Session/Shared/SessionTableViewController.swift | 9 +++++++-- .../Settings/NotificationContentViewModelSpec.swift | 3 +++ 7 files changed, 34 insertions(+), 13 deletions(-) diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index 6e95bce32..d25342c47 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -875,7 +875,6 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers guard self.hasLoadedInitialInteractionData else { // Need to dispatch async to prevent this from causing glitches in the push animation DispatchQueue.main.async { - self.hasLoadedInitialInteractionData = true self.viewModel.updateInteractionData(updatedData) // Update the empty state @@ -883,6 +882,7 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers UIView.performWithoutAnimation { self.tableView.reloadData() + self.hasLoadedInitialInteractionData = true self.performInitialScrollIfNeeded() } } @@ -1191,7 +1191,11 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers } private func autoLoadNextPageIfNeeded() { - guard !self.isAutoLoadingNextPage && !self.isLoadingMore else { return } + guard + self.hasLoadedInitialInteractionData && + !self.isAutoLoadingNextPage && + !self.isLoadingMore + else { return } self.isAutoLoadingNextPage = true diff --git a/Session/Home/HomeVC.swift b/Session/Home/HomeVC.swift index 5ddb4823c..bc524a220 100644 --- a/Session/Home/HomeVC.swift +++ b/Session/Home/HomeVC.swift @@ -409,8 +409,6 @@ final class HomeVC: BaseVC, SessionUtilRespondingViewController, UITableViewData // Ensure the first load runs without animations (if we don't do this the cells will animate // in from a frame of CGRect.zero) guard hasLoadedInitialThreadData else { - hasLoadedInitialThreadData = true - UIView.performWithoutAnimation { [weak self] in // Hide the 'loading conversations' label (now that we have received conversation data) self?.loadingConversationsLabel.isHidden = true @@ -422,6 +420,8 @@ final class HomeVC: BaseVC, SessionUtilRespondingViewController, UITableViewData ) self?.viewModel.updateThreadData(updatedData) + self?.tableView.reloadData() + self?.hasLoadedInitialThreadData = true } return } @@ -460,7 +460,11 @@ final class HomeVC: BaseVC, SessionUtilRespondingViewController, UITableViewData } private func autoLoadNextPageIfNeeded() { - guard !self.isAutoLoadingNextPage && !self.isLoadingMore else { return } + guard + self.hasLoadedInitialThreadData && + !self.isAutoLoadingNextPage && + !self.isLoadingMore + else { return } self.isAutoLoadingNextPage = true diff --git a/Session/Home/Message Requests/MessageRequestsViewController.swift b/Session/Home/Message Requests/MessageRequestsViewController.swift index b7c63b57d..efc5f2e99 100644 --- a/Session/Home/Message Requests/MessageRequestsViewController.swift +++ b/Session/Home/Message Requests/MessageRequestsViewController.swift @@ -232,9 +232,10 @@ class MessageRequestsViewController: BaseVC, SessionUtilRespondingViewController // Ensure the first load runs without animations (if we don't do this the cells will animate // in from a frame of CGRect.zero) guard hasLoadedInitialThreadData else { - hasLoadedInitialThreadData = true UIView.performWithoutAnimation { handleThreadUpdates(updatedData, changeset: changeset, initialLoad: true) + tableView.reloadData() + hasLoadedInitialThreadData = true } return } @@ -271,7 +272,11 @@ class MessageRequestsViewController: BaseVC, SessionUtilRespondingViewController } private func autoLoadNextPageIfNeeded() { - guard !self.isAutoLoadingNextPage && !self.isLoadingMore else { return } + guard + self.hasLoadedInitialThreadData && + !self.isAutoLoadingNextPage && + !self.isLoadingMore + else { return } self.isAutoLoadingNextPage = true diff --git a/Session/Media Viewing & Editing/DocumentTitleViewController.swift b/Session/Media Viewing & Editing/DocumentTitleViewController.swift index f95682a55..d7a84d87e 100644 --- a/Session/Media Viewing & Editing/DocumentTitleViewController.swift +++ b/Session/Media Viewing & Editing/DocumentTitleViewController.swift @@ -153,7 +153,7 @@ public class DocumentTileViewController: UIViewController, UITableViewDelegate, } private func autoLoadNextPageIfNeeded() { - guard !self.isAutoLoadingNextPage else { return } + guard self.hasLoadedInitialData && !self.isAutoLoadingNextPage else { return } self.isAutoLoadingNextPage = true @@ -204,11 +204,11 @@ public class DocumentTileViewController: UIViewController, UITableViewDelegate, // Ensure the first load runs without animations (if we don't do this the cells will animate // in from a frame of CGRect.zero) guard hasLoadedInitialData else { - self.hasLoadedInitialData = true self.viewModel.updateGalleryData(updatedGalleryData) UIView.performWithoutAnimation { self.tableView.reloadData() + self.hasLoadedInitialData = true self.performInitialScrollIfNeeded() } return diff --git a/Session/Media Viewing & Editing/MediaTileViewController.swift b/Session/Media Viewing & Editing/MediaTileViewController.swift index 649ed132a..5d24475b9 100644 --- a/Session/Media Viewing & Editing/MediaTileViewController.swift +++ b/Session/Media Viewing & Editing/MediaTileViewController.swift @@ -246,7 +246,7 @@ public class MediaTileViewController: UIViewController, UICollectionViewDataSour } private func autoLoadNextPageIfNeeded() { - guard !self.isAutoLoadingNextPage else { return } + guard self.hasLoadedInitialData && !self.isAutoLoadingNextPage else { return } self.isAutoLoadingNextPage = true @@ -307,12 +307,12 @@ public class MediaTileViewController: UIViewController, UICollectionViewDataSour // Ensure the first load runs without animations (if we don't do this the cells will animate // in from a frame of CGRect.zero) guard hasLoadedInitialData else { - self.hasLoadedInitialData = true self.viewModel.updateGalleryData(updatedGalleryData) self.updateSelectButton(updatedData: updatedGalleryData, inBatchSelectMode: isInBatchSelectMode) UIView.performWithoutAnimation { self.collectionView.reloadData() + self.hasLoadedInitialData = true self.performInitialScrollIfNeeded() } return diff --git a/Session/Shared/SessionTableViewController.swift b/Session/Shared/SessionTableViewController.swift index f1a01d854..3f37f079c 100644 --- a/Session/Shared/SessionTableViewController.swift +++ b/Session/Shared/SessionTableViewController.swift @@ -227,9 +227,10 @@ class SessionTableViewController Date: Mon, 3 Jul 2023 12:33:26 +1000 Subject: [PATCH 107/135] Fixed infinite loops introduced by the last change >_> --- .../MessageRequestsViewController.swift | 10 +++++++++- .../Shared/SessionTableViewController.swift | 18 ++++++++++++------ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/Session/Home/Message Requests/MessageRequestsViewController.swift b/Session/Home/Message Requests/MessageRequestsViewController.swift index efc5f2e99..42cb93344 100644 --- a/Session/Home/Message Requests/MessageRequestsViewController.swift +++ b/Session/Home/Message Requests/MessageRequestsViewController.swift @@ -233,7 +233,15 @@ class MessageRequestsViewController: BaseVC, SessionUtilRespondingViewController // in from a frame of CGRect.zero) guard hasLoadedInitialThreadData else { UIView.performWithoutAnimation { - handleThreadUpdates(updatedData, changeset: changeset, initialLoad: true) + // Hide the 'loading conversations' label (now that we have received conversation data) + loadingConversationsLabel.isHidden = true + + // Show the empty state if there is no data + clearAllButton.isHidden = !(updatedData.first?.elements.isEmpty == false) + emptyStateLabel.isHidden = !clearAllButton.isHidden + + // Update the content + viewModel.updateThreadData(updatedData) tableView.reloadData() hasLoadedInitialThreadData = true } diff --git a/Session/Shared/SessionTableViewController.swift b/Session/Shared/SessionTableViewController.swift index 3f37f079c..5a825f5fc 100644 --- a/Session/Shared/SessionTableViewController.swift +++ b/Session/Shared/SessionTableViewController.swift @@ -224,22 +224,28 @@ class SessionTableViewController, initialLoad: Bool = false ) { + // Determine if we have any items for the empty state + let itemCount: Int = updatedData + .map { $0.elements.count } + .reduce(0, +) + // Ensure the first load runs without animations (if we don't do this the cells will animate // in from a frame of CGRect.zero) guard hasLoadedInitialTableData else { UIView.performWithoutAnimation { - handleDataUpdates(updatedData, changeset: changeset, initialLoad: true) + // Update the empty state + emptyStateLabel.isHidden = (itemCount > 0) + + // Update the content + viewModel.updateTableData(updatedData) tableView.reloadData() hasLoadedInitialTableData = true } return } - // Show the empty state if there is no data - let itemCount: Int = updatedData - .map { $0.elements.count } - .reduce(0, +) - emptyStateLabel.isHidden = (itemCount > 0) + // Update the empty state + self.emptyStateLabel.isHidden = (itemCount > 0) CATransaction.begin() CATransaction.setCompletionBlock { [weak self] in From 3151aa8901abd80ba265f4cbe6aa6a2b5a47091c Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Mon, 3 Jul 2023 17:13:15 +1000 Subject: [PATCH 108/135] Fixed an issue where the users push token might never get unregistered --- Session.xcodeproj/project.pbxproj | 12 +- Session/Notifications/SyncPushTokensJob.swift | 195 +++++++----------- .../NotificationServiceExtension.swift | 3 +- 3 files changed, 84 insertions(+), 126 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 176b6cdcf..169bf35ab 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -6587,7 +6587,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 414; + CURRENT_PROJECT_VERSION = 415; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6659,7 +6659,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 414; + CURRENT_PROJECT_VERSION = 415; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6724,7 +6724,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 414; + CURRENT_PROJECT_VERSION = 415; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6798,7 +6798,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 414; + CURRENT_PROJECT_VERSION = 415; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -7706,7 +7706,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 414; + CURRENT_PROJECT_VERSION = 415; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -7777,7 +7777,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 414; + CURRENT_PROJECT_VERSION = 415; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/Session/Notifications/SyncPushTokensJob.swift b/Session/Notifications/SyncPushTokensJob.swift index 2a93618fb..2df9657b3 100644 --- a/Session/Notifications/SyncPushTokensJob.swift +++ b/Session/Notifications/SyncPushTokensJob.swift @@ -42,75 +42,97 @@ public enum SyncPushTokensJob: JobExecutor { } }() - // Push tokens don't normally change while the app is launched, so you would assume checking once - // during launch is sufficient, but e.g. on iOS11, users who have disabled "Allow Notifications" - // and disabled "Background App Refresh" will not be able to obtain an APN token. Enabling those - // settings does not restart the app, so we check every activation for users who haven't yet - // registered. - // - // It's also possible for a device to successfully register for push notifications but fail to - // register with Session - // - // Due to the above we want to re-register at least once every ~12 hours to ensure users will - // continue to receive push notifications - // - // In addition to this if we are custom running the job (eg. by toggling the push notification - // setting) then we should run regardless of the other settings so users have a mechanism to force - // the registration to run - let lastPushNotificationSync: Date = UserDefaults.standard[.lastPushNotificationSync] - .defaulting(to: Date.distantPast) - - guard - job.behaviour == .runOnce || - !isRegisteredForRemoteNotifications || - Date().timeIntervalSince(lastPushNotificationSync) >= SyncPushTokensJob.maxFrequency - else { - SNLog("[SyncPushTokensJob] Deferred due to Fast Mode disabled or recent-enough registration") + // Apple's documentation states that we should re-register for notifications on every launch: + // https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/HandlingRemoteNotifications.html#//apple_ref/doc/uid/TP40008194-CH6-SW1 + guard job.behaviour == .runOnce || !isRegisteredForRemoteNotifications else { + SNLog("[SyncPushTokensJob] Deferred due to Fast Mode disabled") deferred(job) // Don't need to do anything if push notifications are already registered return } - Logger.info("Re-registering for remote notifications.") + // Determine if the device has 'Fast Mode' (APNS) enabled + let isUsingFullAPNs: Bool = UserDefaults.standard[.isUsingFullAPNs] - // Perform device registration - PushRegistrationManager.shared.requestPushTokens() - .subscribe(on: queue) - .flatMap { (pushToken: String, voipToken: String) -> AnyPublisher in - Deferred { - Future { resolver in - SyncPushTokensJob.registerForPushNotifications( - pushToken: pushToken, - voipToken: voipToken, - isForcedUpdate: true, - success: { resolver(Result.success(())) }, - failure: { resolver(Result.failure($0)) } - ) + // If the job is running and 'Fast Mode' is disabled then we should try to unregister the existing + // token + guard isUsingFullAPNs else { + Just(Storage.shared[.lastRecordedPushToken]) + .setFailureType(to: Error.self) + .flatMap { lastRecordedPushToken in + if let existingToken: String = lastRecordedPushToken { + SNLog("[SyncPushTokensJob] Unregister using last recorded push token: \(redact(existingToken))") + return Just(existingToken) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } + + SNLog("[SyncPushTokensJob] Unregister using live token provided from device") + return PushRegistrationManager.shared.requestPushTokens() + .map { token, _ in token } + .eraseToAnyPublisher() } - .handleEvents( + .flatMap { pushToken in PushNotificationAPI.unregister(Data(hex: pushToken)) } + .map { + // Tell the device to unregister for remote notifications (essentially try to invalidate + // the token if needed + DispatchQueue.main.sync { UIApplication.shared.unregisterForRemoteNotifications() } + + Storage.shared.write { db in + db[.lastRecordedPushToken] = nil + } + return () + } + .subscribe(on: queue) + .sinkUntilComplete( receiveCompletion: { result in switch result { - case .failure(let error): - SNLog("[SyncPushTokensJob] Failed to register due to error: \(error)") - - case .finished: - Logger.warn("Recording push tokens locally. pushToken: \(redact(pushToken)), voipToken: \(redact(voipToken))") - SNLog("[SyncPushTokensJob] Completed") - UserDefaults.standard[.lastPushNotificationSync] = Date() - - Storage.shared.write { db in - db[.lastRecordedPushToken] = pushToken - db[.lastRecordedVoipToken] = voipToken - } + case .finished: SNLog("[SyncPushTokensJob] Unregister Completed") + case .failure: SNLog("[SyncPushTokensJob] Unregister Failed") } + + // We want to complete this job regardless of success or failure + success(job, false) } ) - .eraseToAnyPublisher() + return + } + + // Perform device registration + Logger.info("Re-registering for remote notifications.") + PushRegistrationManager.shared.requestPushTokens() + .flatMap { (pushToken: String, voipToken: String) -> AnyPublisher in + PushNotificationAPI + .register( + with: Data(hex: pushToken), + publicKey: getUserHexEncodedPublicKey(), + isForcedUpdate: true + ) + .retry(3) + .handleEvents( + receiveCompletion: { result in + switch result { + case .failure(let error): + SNLog("[SyncPushTokensJob] Failed to register due to error: \(error)") + + case .finished: + Logger.warn("Recording push tokens locally. pushToken: \(redact(pushToken)), voipToken: \(redact(voipToken))") + SNLog("[SyncPushTokensJob] Completed") + UserDefaults.standard[.lastPushNotificationSync] = Date() + + Storage.shared.write { db in + db[.lastRecordedPushToken] = pushToken + db[.lastRecordedVoipToken] = voipToken + } + } + } + ) + .map { _ in () } + .eraseToAnyPublisher() } + .subscribe(on: queue) .sinkUntilComplete( // We want to complete this job regardless of success or failure - receiveCompletion: { _ in success(job, false) }, - receiveValue: { _ in } + receiveCompletion: { _ in success(job, false) } ) } @@ -147,68 +169,3 @@ extension SyncPushTokensJob { private func redact(_ string: String) -> String { return OWSIsDebugBuild() ? string : "[ READACTED \(string.prefix(2))...\(string.suffix(2)) ]" } - -extension SyncPushTokensJob { - fileprivate static func registerForPushNotifications( - pushToken: String, - voipToken: String, - isForcedUpdate: Bool, - success: @escaping () -> (), - failure: @escaping (Error) -> (), - remainingRetries: Int = 3 - ) { - let isUsingFullAPNs: Bool = UserDefaults.standard[.isUsingFullAPNs] - - Just(Data(hex: pushToken)) - .setFailureType(to: Error.self) - .flatMap { pushTokenAsData -> AnyPublisher in - guard isUsingFullAPNs else { - return PushNotificationAPI.unregister(pushTokenAsData) - .map { _ in true } - .eraseToAnyPublisher() - } - - return PushNotificationAPI - .register( - with: pushTokenAsData, - publicKey: getUserHexEncodedPublicKey(), - isForcedUpdate: isForcedUpdate - ) - .map { _ in true } - .eraseToAnyPublisher() - } - .catch { error -> AnyPublisher in - guard remainingRetries == 0 else { - SyncPushTokensJob.registerForPushNotifications( - pushToken: pushToken, - voipToken: voipToken, - isForcedUpdate: isForcedUpdate, - success: success, - failure: failure, - remainingRetries: (remainingRetries - 1) - ) - - return Just(false) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - } - - return Fail(error: error) - .eraseToAnyPublisher() - } - .subscribe(on: DispatchQueue.global(qos: .userInitiated)) - .sinkUntilComplete( - receiveCompletion: { result in - switch result { - case .finished: break - case .failure(let error): failure(error) - } - }, - receiveValue: { didComplete in - guard didComplete else { return } - - success() - } - ) - } -} diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index 0526360a4..2db3e27ba 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -237,7 +237,8 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension }, migrationsCompletion: { [weak self] result, needsConfigSync in switch result { - case .failure: SNLog("[NotificationServiceExtension] Failed to complete migrations") + // Only 'NSLog' works in the extension - viewable via Console.app + case .failure: NSLog("[NotificationServiceExtension] Failed to complete migrations") case .success: DispatchQueue.main.async { self?.versionMigrationsDidComplete(needsConfigSync: needsConfigSync) From 0225f436bd0ae3cfac488eb3dcc2d83523382d60 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 4 Jul 2023 17:09:50 +1000 Subject: [PATCH 109/135] Podfile tweaks to speed up sim builds, unit tests & minor bug fix Added a patch to the Podfile to avoid rsync'ing and signing WebRTC-lib for simulator builds shaving off 10+ seconds of build time per target due to the sheer size of the WebRTC debug framework Added some basic unit tests to validate the current search behaviour Fixed some buggy search behaviours --- Podfile | 10 + Podfile.lock | 2 +- Scripts/skip_web_rtc_re_rsync.patch | 12 + Session.xcodeproj/project.pbxproj | 14 +- .../ConversationVC+Interaction.swift | 3 +- .../MessageRequestsViewController.swift | 1 + .../SessionThreadViewModel.swift | 49 +-- .../SessionThreadViewModelSpec.swift | 334 ++++++++++++++++++ ...eadDisappearingMessagesViewModelSpec.swift | 2 + .../ThreadSettingsViewModelSpec.swift | 2 + 10 files changed, 403 insertions(+), 26 deletions(-) create mode 100644 Scripts/skip_web_rtc_re_rsync.patch create mode 100644 SessionMessagingKitTests/Shared Models/SessionThreadViewModelSpec.swift diff --git a/Podfile b/Podfile index 5747d4620..25f76e8e9 100644 --- a/Podfile +++ b/Podfile @@ -101,6 +101,7 @@ end # Actions to perform post-install post_install do |installer| set_minimum_deployment_target(installer) + avoid_rsync_webrtc_if_unchanged(installer) end def set_minimum_deployment_target(installer) @@ -110,3 +111,12 @@ def set_minimum_deployment_target(installer) end end end + +# This function patches the Cocoapods 'Embed Frameworks' script to avoid running rsync +# for the WebRTC-lib framework in simulator builds if it has already been copied over +# because due to the size it can take over 10 seconds to embed, and gets embeded in +# each target on every build regardless of whether there were changes, drastically +# increasing the length of the build +def avoid_rsync_webrtc_if_unchanged(installer) + system('find "./Pods/Target Support Files" -name "*-frameworks.sh" -exec patch -p0 -i ./Scripts/skip_web_rtc_re_rsync.patch {} \;') +end diff --git a/Podfile.lock b/Podfile.lock index 970a62e71..d44b761b9 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -204,6 +204,6 @@ SPEC CHECKSUMS: YYImage: f1ddd15ac032a58b78bbed1e012b50302d318331 ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: 68799237a4dc046f5ac25c573af03b559f5b10c4 +PODFILE CHECKSUM: dcca0c4ad69b14cbc2d6ba49f9d690b239828e6d COCOAPODS: 1.12.1 diff --git a/Scripts/skip_web_rtc_re_rsync.patch b/Scripts/skip_web_rtc_re_rsync.patch new file mode 100644 index 000000000..6b8232e50 --- /dev/null +++ b/Scripts/skip_web_rtc_re_rsync.patch @@ -0,0 +1,12 @@ +@@ -41,0 +41,11 @@ ++ # Skip the rsync step for the WebRTC-lib in simulator builds ++ if [[ "$PLATFORM_NAME" == "iphonesimulator" ]] && [[ "$source" == *WebRTC.framework* ]]; then ++ if [[ -f "${source}/../already_rsynced.nonce" ]]; then ++ echo "Already rsynced WebRTC, skipping" ++ return 0 ++ fi ++ ++ echo "About to rsync a simulator WebRTC, creating nonce to prevent future rsyncing" ++ touch "${source}/../already_rsynced.nonce" ++ fi ++ diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 169bf35ab..7e18bd930 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -696,6 +696,7 @@ FD716E6C28505E1C00C96BF4 /* MessageRequestsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD716E6B28505E1C00C96BF4 /* MessageRequestsViewModel.swift */; }; FD716E7128505E5200C96BF4 /* MessageRequestsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD716E7028505E5100C96BF4 /* MessageRequestsCell.swift */; }; FD716E722850647600C96BF4 /* Data+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EF127BF6BA200510D0C /* Data+Utilities.swift */; }; + FD7692F72A53A2ED000E4B70 /* SessionThreadViewModelSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7692F62A53A2ED000E4B70 /* SessionThreadViewModelSpec.swift */; }; FD7728962849E7E90018502F /* String+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7728952849E7E90018502F /* String+Utilities.swift */; }; FD7728982849E8110018502F /* UITableView+ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7728972849E8110018502F /* UITableView+ReusableView.swift */; }; FD77289A284AF1BD0018502F /* Sodium+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD772899284AF1BD0018502F /* Sodium+Utilities.swift */; }; @@ -1828,6 +1829,7 @@ FD716E692850327900C96BF4 /* EndCallMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndCallMode.swift; sourceTree = ""; }; FD716E6B28505E1C00C96BF4 /* MessageRequestsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageRequestsViewModel.swift; sourceTree = ""; }; FD716E7028505E5100C96BF4 /* MessageRequestsCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageRequestsCell.swift; sourceTree = ""; }; + FD7692F62A53A2ED000E4B70 /* SessionThreadViewModelSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionThreadViewModelSpec.swift; sourceTree = ""; }; FD7728952849E7E90018502F /* String+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Utilities.swift"; sourceTree = ""; }; FD7728972849E8110018502F /* UITableView+ReusableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITableView+ReusableView.swift"; sourceTree = ""; }; FD772899284AF1BD0018502F /* Sodium+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Sodium+Utilities.swift"; sourceTree = ""; }; @@ -3316,10 +3318,10 @@ B8DE1FB226C22F1F0079C9CE /* Calls */, C32C5BCB256DC818003C73A2 /* Database */, C300A5BB2554AFFB00555489 /* Messages */, - C300A5F02554B08500555489 /* Sending & Receiving */, C352A2F325574B3300338F3E /* Jobs */, C3A7215C2558C0AC0043A11F /* File Server */, C3A721332558BDDF0043A11F /* Open Groups */, + C300A5F02554B08500555489 /* Sending & Receiving */, FD8ECF7529340F4800C0D1BB /* SessionUtil */, FD3E0C82283B581F002A425C /* Shared Models */, C3BBE0B32554F0D30050F1E3 /* Utilities */, @@ -3994,6 +3996,14 @@ path = Views; sourceTree = ""; }; + FD7692F52A53A2C7000E4B70 /* Shared Models */ = { + isa = PBXGroup; + children = ( + FD7692F62A53A2ED000E4B70 /* SessionThreadViewModelSpec.swift */, + ); + path = "Shared Models"; + sourceTree = ""; + }; FD7728A1284F0DF50018502F /* Message Handling */ = { isa = PBXGroup; children = ( @@ -4224,6 +4234,7 @@ FD3C906527E416A200CD579F /* Contacts */, FDC4389827BA001800C60D73 /* Open Groups */, FD3C906B27E43C2400CD579F /* Sending & Receiving */, + FD7692F52A53A2C7000E4B70 /* Shared Models */, FD3C906827E417B100CD579F /* Utilities */, FD8ECF802934385900C0D1BB /* LibSessionUtil */, ); @@ -6391,6 +6402,7 @@ FDC2908B27D707F3005DAE71 /* SendMessageRequestSpec.swift in Sources */, FD8ECF822934387A00C0D1BB /* ConfigUserProfileSpec.swift in Sources */, FDC290A827D9B46D005DAE71 /* NimbleExtensions.swift in Sources */, + FD7692F72A53A2ED000E4B70 /* SessionThreadViewModelSpec.swift in Sources */, FDC2908F27D70938005DAE71 /* SendDirectMessageRequestSpec.swift in Sources */, FDC438BD27BB2AB400C60D73 /* Mockable.swift in Sources */, FDA1E83929A5771A00C5C3BD /* LibSessionSpec.swift in Sources */, diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 13704aadb..db534d40b 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -1979,8 +1979,7 @@ extension ConversationVC: self?.showInputAccessoryView() }) - self.inputAccessoryView?.isHidden = true - self.inputAccessoryView?.alpha = 0 + self.hideInputAccessoryView() Modal.setupForIPadIfNeeded(actionSheet, targetView: self.view) self.present(actionSheet, animated: true) } diff --git a/Session/Home/Message Requests/MessageRequestsViewController.swift b/Session/Home/Message Requests/MessageRequestsViewController.swift index 42cb93344..6b91630da 100644 --- a/Session/Home/Message Requests/MessageRequestsViewController.swift +++ b/Session/Home/Message Requests/MessageRequestsViewController.swift @@ -106,6 +106,7 @@ class MessageRequestsViewController: BaseVC, SessionUtilRespondingViewController result.translatesAutoresizingMaskIntoConstraints = false result.setTitle("MESSAGE_REQUESTS_CLEAR_ALL".localized(), for: .normal) result.addTarget(self, action: #selector(clearAllTapped), for: .touchUpInside) + result.accessibilityIdentifier = "Clear all" return result }() diff --git a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift index 5fd4f3857..bee72d0d0 100644 --- a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift +++ b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift @@ -1112,19 +1112,22 @@ public extension SessionThreadViewModel { /// Step 2 - Separate any words outside of quotes /// Step 3 - Join the different search term parts with 'OR" (include results for each individual term) /// Step 4 - Append a wild-card character to the final word (as long as the last word doesn't end in a quote) - return standardQuotes(searchTerm) - .split(separator: "\"") - .enumerated() - .flatMap { index, value -> [String] in - guard index % 2 == 1 else { - return String(value) - .split(separator: " ") - .map { "\"\(String($0))\"" } - } - - return ["\"\(value)\""] - } - .filter { !$0.isEmpty } + let normalisedTerm: String = standardQuotes(searchTerm) + + guard let regex = try? NSRegularExpression(pattern: "[^\\s\"']+|\"([^\"]*)\"") else { + // Fallback to removing the quotes and just splitting on spaces + return normalisedTerm + .replacingOccurrences(of: "\"", with: "") + .split(separator: " ") + .map { "\"\($0)\"" } + .filter { !$0.isEmpty } + } + + return regex + .matches(in: normalisedTerm, range: NSRange(location: 0, length: normalisedTerm.count)) + .compactMap { Range($0.range, in: normalisedTerm) } + .map { normalisedTerm[$0].trimmingCharacters(in: CharacterSet(charactersIn: "\"")) } + .map { "\"\($0)\"" } } static func standardQuotes(_ term: String) -> String { @@ -1155,15 +1158,17 @@ public extension SessionThreadViewModel { /// There are cases where creating a pattern can fail, we want to try and recover from those cases /// by failling back to simpler patterns if needed - let maybePattern: FTS5Pattern? = (try? db.makeFTS5Pattern(rawPattern: rawPattern, forTable: table)) - .defaulting( - to: (try? db.makeFTS5Pattern(rawPattern: fallbackTerm, forTable: table)) - .defaulting(to: FTS5Pattern(matchingAnyTokenIn: fallbackTerm)) - ) - - guard let pattern: FTS5Pattern = maybePattern else { throw StorageError.invalidSearchPattern } - - return pattern + return try { + if let pattern: FTS5Pattern = try? db.makeFTS5Pattern(rawPattern: rawPattern, forTable: table) { + return pattern + } + + if let pattern: FTS5Pattern = try? db.makeFTS5Pattern(rawPattern: fallbackTerm, forTable: table) { + return pattern + } + + return try FTS5Pattern(matchingAnyTokenIn: fallbackTerm) ?? { throw StorageError.invalidSearchPattern }() + }() } static func messagesQuery(userPublicKey: String, pattern: FTS5Pattern) -> AdaptedFetchRequest> { diff --git a/SessionMessagingKitTests/Shared Models/SessionThreadViewModelSpec.swift b/SessionMessagingKitTests/Shared Models/SessionThreadViewModelSpec.swift new file mode 100644 index 000000000..df58e26c3 --- /dev/null +++ b/SessionMessagingKitTests/Shared Models/SessionThreadViewModelSpec.swift @@ -0,0 +1,334 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import GRDB +import Quick +import Nimble +import SessionUtilitiesKit + +@testable import SessionMessagingKit + +class SessionThreadViewModelSpec: QuickSpec { + public struct TestMessage: Codable, Equatable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { + public static var databaseTableName: String { "testMessage" } + + public typealias Columns = CodingKeys + public enum CodingKeys: String, CodingKey, ColumnExpression { + case body + } + + public let body: String + } + + // MARK: - Spec + + override func spec() { + describe("a SessionThreadViewModel") { + var mockStorage: Storage! + + beforeEach { + mockStorage = SynchronousStorage( + customWriter: try! DatabaseQueue() + ) + + mockStorage.write { db in + try db.create(table: TestMessage.self) { t in + t.column(.body, .text).notNull() + } + + try db.create(virtualTable: TestMessage.fullTextSearchTableName, using: FTS5()) { t in + t.synchronize(withTable: TestMessage.databaseTableName) + t.tokenizer = .porter(wrapping: .unicode61()) + + t.column(TestMessage.Columns.body.name) + } + } + } + + // MARK: - when processing a search term + context("when processing a search term") { + // MARK: -- correctly generates a safe search term + it("correctly generates a safe search term") { + expect(SessionThreadViewModel.searchSafeTerm("Test")).to(equal("\"Test\"")) + } + + // MARK: -- standardises odd quote characters + it("standardises odd quote characters") { + expect(SessionThreadViewModel.standardQuotes("\"")).to(equal("\"")) + expect(SessionThreadViewModel.standardQuotes("”")).to(equal("\"")) + expect(SessionThreadViewModel.standardQuotes("“")).to(equal("\"")) + } + + // MARK: -- splits on the space character + it("splits on the space character") { + expect(SessionThreadViewModel.searchTermParts("Test Message")) + .to(equal([ + "\"Test\"", + "\"Message\"" + ])) + } + + // MARK: -- surrounds each split term with quotes + it("surrounds each split term with quotes") { + expect(SessionThreadViewModel.searchTermParts("Test Message")) + .to(equal([ + "\"Test\"", + "\"Message\"" + ])) + } + + // MARK: -- keeps words within quotes together + it("keeps words within quotes together") { + expect(SessionThreadViewModel.searchTermParts("This \"is a Test\" Message")) + .to(equal([ + "\"This\"", + "\"is a Test\"", + "\"Message\"" + ])) + expect(SessionThreadViewModel.searchTermParts("\"This is\" a Test Message")) + .to(equal([ + "\"This is\"", + "\"a\"", + "\"Test\"", + "\"Message\"" + ])) + expect(SessionThreadViewModel.searchTermParts("\"This is\" \"a Test\" Message")) + .to(equal([ + "\"This is\"", + "\"a Test\"", + "\"Message\"" + ])) + expect(SessionThreadViewModel.searchTermParts("\"This is\" a \"Test Message\"")) + .to(equal([ + "\"This is\"", + "\"a\"", + "\"Test Message\"" + ])) + expect(SessionThreadViewModel.searchTermParts("\"This is\"\" a \"Test Message")) + .to(equal([ + "\"This is\"", + "\" a \"", + "\"Test\"", + "\"Message\"" + ])) + } + + // MARK: -- keeps words within weird quotes together + it("keeps words within weird quotes together") { + expect(SessionThreadViewModel.searchTermParts("This ”is a Test“ Message")) + .to(equal([ + "\"This\"", + "\"is a Test\"", + "\"Message\"" + ])) + } + + // MARK: -- removes extra whitespace + it("removes extra whitespace") { + expect(SessionThreadViewModel.searchTermParts(" Test Message ")) + .to(equal([ + "\"Test\"", + "\"Message\"" + ])) + } + } + + // MARK: - when searching + context("when searching") { + beforeEach { + mockStorage.write { db in + try TestMessage(body: "Test").insert(db) + try TestMessage(body: "Test123").insert(db) + try TestMessage(body: "Test234").insert(db) + try TestMessage(body: "Test Test123").insert(db) + try TestMessage(body: "Test Test123 Test234").insert(db) + try TestMessage(body: "Test Test234").insert(db) + try TestMessage(body: "Test Test234 Test123").insert(db) + try TestMessage(body: "This is a Test Message").insert(db) + try TestMessage(body: "is a Message This Test").insert(db) + try TestMessage(body: "this message is a test").insert(db) + try TestMessage( + body: "This content is something which includes a combination of test words found in another message" + ) + .insert(db) + try TestMessage(body: "Do test messages contain content?").insert(db) + try TestMessage(body: "Is messaging awesome?").insert(db) + } + } + + // MARK: -- returns results + it("returns results") { + let results = mockStorage.read { db in + let pattern: FTS5Pattern = try SessionThreadViewModel.pattern( + db, + searchTerm: "Message", + forTable: TestMessage.self + ) + + return try SQLRequest(literal: """ + SELECT * + FROM testMessage + JOIN testMessage_fts ON ( + testMessage_fts.rowId = testMessage.rowId AND + testMessage_fts.body MATCH \(pattern) + ) + """).fetchAll(db) + } + + expect(results) + .to(equal([ + TestMessage(body: "This is a Test Message"), + TestMessage(body: "is a Message This Test"), + TestMessage(body: "this message is a test"), + TestMessage(body: "This content is something which includes a combination of test words found in another message"), + TestMessage(body: "Do test messages contain content?"), + TestMessage(body: "Is messaging awesome?") + ])) + } + + // MARK: -- adds a wildcard to the final part + it("adds a wildcard to the final part") { + let results = mockStorage.read { db in + let pattern: FTS5Pattern = try SessionThreadViewModel.pattern( + db, + searchTerm: "This mes", + forTable: TestMessage.self + ) + + return try SQLRequest(literal: """ + SELECT * + FROM testMessage + JOIN testMessage_fts ON ( + testMessage_fts.rowId = testMessage.rowId AND + testMessage_fts.body MATCH \(pattern) + ) + """).fetchAll(db) + } + + expect(results) + .to(equal([ + TestMessage(body: "This is a Test Message"), + TestMessage(body: "is a Message This Test"), + TestMessage(body: "this message is a test"), + TestMessage(body: "This content is something which includes a combination of test words found in another message"), + TestMessage(body: "Do test messages contain content?"), + TestMessage(body: "Is messaging awesome?") + ])) + } + + // MARK: -- does not add a wildcard to other parts + it("does not add a wildcard to other parts") { + let results = mockStorage.read { db in + let pattern: FTS5Pattern = try SessionThreadViewModel.pattern( + db, + searchTerm: "mes Random", + forTable: TestMessage.self + ) + + return try SQLRequest(literal: """ + SELECT * + FROM testMessage + JOIN testMessage_fts ON ( + testMessage_fts.rowId = testMessage.rowId AND + testMessage_fts.body MATCH \(pattern) + ) + """).fetchAll(db) + } + + expect(results) + .to(beEmpty()) + } + + // MARK: -- finds similar words without the wildcard due to the porter tokenizer + it("finds similar words without the wildcard due to the porter tokenizer") { + let results = mockStorage.read { db in + let pattern: FTS5Pattern = try SessionThreadViewModel.pattern( + db, + searchTerm: "message z", + forTable: TestMessage.self + ) + + return try SQLRequest(literal: """ + SELECT * + FROM testMessage + JOIN testMessage_fts ON ( + testMessage_fts.rowId = testMessage.rowId AND + testMessage_fts.body MATCH \(pattern) + ) + """).fetchAll(db) + } + + expect(results) + .to(equal([ + TestMessage(body: "This is a Test Message"), + TestMessage(body: "is a Message This Test"), + TestMessage(body: "this message is a test"), + TestMessage( + body: "This content is something which includes a combination of test words found in another message" + ), + TestMessage(body: "Do test messages contain content?"), + TestMessage(body: "Is messaging awesome?") + ])) + } + + // MARK: -- finds results containing the words regardless of the order + it("finds results containing the words regardless of the order") { + let results = mockStorage.read { db in + let pattern: FTS5Pattern = try SessionThreadViewModel.pattern( + db, + searchTerm: "is a message", + forTable: TestMessage.self + ) + + return try SQLRequest(literal: """ + SELECT * + FROM testMessage + JOIN testMessage_fts ON ( + testMessage_fts.rowId = testMessage.rowId AND + testMessage_fts.body MATCH \(pattern) + ) + """).fetchAll(db) + } + + expect(results) + .to(equal([ + TestMessage(body: "This is a Test Message"), + TestMessage(body: "is a Message This Test"), + TestMessage(body: "this message is a test"), + TestMessage( + body: "This content is something which includes a combination of test words found in another message" + ), + TestMessage(body: "Do test messages contain content?"), + TestMessage(body: "Is messaging awesome?") + ])) + } + + // MARK: -- does not find quoted parts out of order + it("does not find quoted parts out of order") { + let results = mockStorage.read { db in + let pattern: FTS5Pattern = try SessionThreadViewModel.pattern( + db, + searchTerm: "\"this is a\" \"test message\"", + forTable: TestMessage.self + ) + + return try SQLRequest(literal: """ + SELECT * + FROM testMessage + JOIN testMessage_fts ON ( + testMessage_fts.rowId = testMessage.rowId AND + testMessage_fts.body MATCH \(pattern) + ) + """).fetchAll(db) + } + + expect(results) + .to(equal([ + TestMessage(body: "This is a Test Message"), + TestMessage(body: "Do test messages contain content?") + ])) + } + } + } + } +} diff --git a/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift b/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift index f583b8b3c..8c51c3c49 100644 --- a/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift +++ b/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift @@ -4,6 +4,8 @@ import Combine import GRDB import Quick import Nimble +import SessionUIKit +import SessionSnodeKit @testable import Session diff --git a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift index 2750a3803..eaf4b915b 100644 --- a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift +++ b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift @@ -4,6 +4,8 @@ import Combine import GRDB import Quick import Nimble +import SessionUIKit +import SessionSnodeKit @testable import Session From a7761697a9578bfef1f12ea774dbdc881dda55c2 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 6 Jul 2023 15:39:41 +1000 Subject: [PATCH 110/135] More performance logging and some minor optimisations Added code to throttle the 'markAsRead' logic when scrolling to 100ms Added a launch counter so we will always get error logs on launch if the database is suspended Updated the logging to also indicate whether it's the DBWrite queue Moved a couple of Atomic mutations outside of DB write closure on the off chance they are blocking --- Session.xcodeproj/project.pbxproj | 12 +-- .../Conversations/ConversationViewModel.swift | 59 ++++++++++----- Session/Meta/AppDelegate.swift | 4 + .../Sending & Receiving/Pollers/Poller.swift | 16 ++-- .../Utilities/Preferences.swift | 6 ++ SessionSnodeKit/Networking/SnodeAPI.swift | 1 - SessionUtilitiesKit/Database/Storage.swift | 74 +++++++++++++++++-- SessionUtilitiesKit/General/Logging.swift | 29 +++++++- .../General/Timer+MainThread.swift | 6 +- SessionUtilitiesKit/JobRunner/JobRunner.swift | 33 +++++---- 10 files changed, 181 insertions(+), 59 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 7e18bd930..a37a16a23 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -6599,7 +6599,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 415; + CURRENT_PROJECT_VERSION = 416; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6671,7 +6671,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 415; + CURRENT_PROJECT_VERSION = 416; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6736,7 +6736,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 415; + CURRENT_PROJECT_VERSION = 416; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6810,7 +6810,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 415; + CURRENT_PROJECT_VERSION = 416; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -7718,7 +7718,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 415; + CURRENT_PROJECT_VERSION = 416; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -7789,7 +7789,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 415; + CURRENT_PROJECT_VERSION = 416; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/Session/Conversations/ConversationViewModel.swift b/Session/Conversations/ConversationViewModel.swift index cd9c8b8a5..1373c0bd5 100644 --- a/Session/Conversations/ConversationViewModel.swift +++ b/Session/Conversations/ConversationViewModel.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import Combine import GRDB import DifferenceKit import SessionMessagingKit @@ -44,6 +45,8 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { public let focusedInteractionInfo: Interaction.TimestampInfo? // Note: This is used for global search public let focusBehaviour: FocusBehaviour private let initialUnreadInteractionId: Int64? + private let markAsReadTrigger: PassthroughSubject<(SessionThreadViewModel.ReadTarget, Int64?), Never> = PassthroughSubject() + private var markAsReadPublisher: AnyPublisher? public lazy var blockedBannerMessage: String = { switch self.threadData.threadVariant { @@ -646,29 +649,45 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { timestampMs: Int64? ) { /// Since this method now gets triggered when scrolling we want to try to optimise it and avoid busying the database - /// write queue when it isn't needed, in order to do this we don't bother marking anything as read if this was called with - /// the same `interactionId` that we previously marked as read (ie. when scrolling and the last message hasn't changed) + /// write queue when it isn't needed, in order to do this we: + /// - Throttle the updates to 100ms (quick enough that users shouldn't notice, but will help the DB when the user flings the list) + /// - Don't bother marking anything as read if this was called with the same `interactionId` that we previously marked as + /// read (ie. when scrolling and the last message hasn't changed) /// /// The `ThreadViewModel.markAsRead` method also tries to avoid marking as read if a conversation is already fully read - switch target { - case .thread: self.threadData.markAsRead(target: target) - case .threadAndInteractions: - guard - timestampMs == nil || - self.lastInteractionTimestampMsMarkedAsRead < (timestampMs ?? 0) - else { - self.threadData.markAsRead(target: .thread) - return - } - - // If we were given a timestamp then update the 'lastInteractionTimestampMsMarkedAsRead' - // to avoid needless updates - if let timestampMs: Int64 = timestampMs { - self.lastInteractionTimestampMsMarkedAsRead = timestampMs - } - - self.threadData.markAsRead(target: target) + if markAsReadPublisher == nil { + markAsReadPublisher = markAsReadTrigger + .throttle(for: .milliseconds(100), scheduler: DispatchQueue.global(qos: .userInitiated), latest: true) + .handleEvents( + receiveOutput: { [weak self] target, timestampMs in + switch target { + case .thread: self?.threadData.markAsRead(target: target) + case .threadAndInteractions: + guard + timestampMs == nil || + (self?.lastInteractionTimestampMsMarkedAsRead ?? 0) < (timestampMs ?? 0) + else { + self?.threadData.markAsRead(target: .thread) + return + } + + // If we were given a timestamp then update the 'lastInteractionTimestampMsMarkedAsRead' + // to avoid needless updates + if let timestampMs: Int64 = timestampMs { + self?.lastInteractionTimestampMsMarkedAsRead = timestampMs + } + + self?.threadData.markAsRead(target: target) + } + } + ) + .map { _ in () } + .eraseToAnyPublisher() + + markAsReadPublisher?.sinkUntilComplete() } + + markAsReadTrigger.send((target, timestampMs)) } public func swapToThread(updatedThreadId: String) { diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 5495583c3..1f193601c 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -352,6 +352,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD /// App won't be ready for extensions and no need to enqueue a config sync unless we successfully completed startup Storage.shared.writeAsync { db in + // Increment the launch count (guaranteed to change which results in the write actually + // doing something and outputting and error if the DB is suspended) + db[.activeCounter] = ((db[.activeCounter] ?? 0) + 1) + // Disable the SAE until the main app has successfully completed launch process // at least once in the post-SAE world. db[.isReadyForAppExtensions] = true diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift index 277196448..480f40a07 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift @@ -262,6 +262,7 @@ public class Poller { var hadValidHashUpdate: Bool = false var configMessageJobsToRun: [Job] = [] var standardMessageJobsToRun: [Job] = [] + var pollerLogOutput: String = "\(pollerName) failed to process any messages" Storage.shared.write { db in let allProcessedMessages: [ProcessedMessage] = allMessages @@ -369,11 +370,12 @@ public class Poller { } } + // Set the output for logging + pollerLogOutput = "Received \(messageCount) new message\(messageCount == 1 ? "" : "s") in \(pollerName) (duplicates: \(allMessages.count - messageCount))" + // Clean up message hashes and add some logs about the poll results if allMessages.isEmpty && !hadValidHashUpdate { - if !calledFromBackgroundPoller { - SNLog("Received \(allMessages.count) new message\(allMessages.count == 1 ? "" : "s"), all duplicates - marking the hash we polled with as invalid") - } + pollerLogOutput = "Received \(allMessages.count) new message\(allMessages.count == 1 ? "" : "s") in \(pollerName), all duplicates - marking the hash we polled with as invalid" // Update the cached validity of the messages try SnodeReceivedMessageInfo.handlePotentialDeletedOrInvalidHash( @@ -382,9 +384,11 @@ public class Poller { otherKnownValidHashes: otherKnownHashes ) } - else if !calledFromBackgroundPoller { - SNLog("Received \(messageCount) new message\(messageCount == 1 ? "" : "s") in \(pollerName) (duplicates: \(allMessages.count - messageCount))") - } + } + + // Only output logs if it isn't the background poller + if !calledFromBackgroundPoller { + SNLog(pollerLogOutput) } // If we aren't runing in a background poller then just finish immediately diff --git a/SessionMessagingKit/Utilities/Preferences.swift b/SessionMessagingKit/Utilities/Preferences.swift index 0b3c91209..34a00860e 100644 --- a/SessionMessagingKit/Utilities/Preferences.swift +++ b/SessionMessagingKit/Utilities/Preferences.swift @@ -89,6 +89,12 @@ public extension Setting.DoubleKey { static let screenLockTimeoutSeconds: Setting.DoubleKey = "screenLockTimeoutSeconds" } +public extension Setting.IntKey { + /// This is the number of times the app has successfully become active, it's not actually used for anything but allows us to make + /// a database change on launch so the database will output an error if it fails to write + static let activeCounter: Setting.IntKey = "activeCounter" +} + public enum Preferences { public enum NotificationPreviewType: Int, CaseIterable, EnumIntSetting, Differentiable { public static var defaultPreviewType: NotificationPreviewType = .nameAndPreview diff --git a/SessionSnodeKit/Networking/SnodeAPI.swift b/SessionSnodeKit/Networking/SnodeAPI.swift index de81c675c..04e1836c2 100644 --- a/SessionSnodeKit/Networking/SnodeAPI.swift +++ b/SessionSnodeKit/Networking/SnodeAPI.swift @@ -1253,7 +1253,6 @@ public final class SnodeAPI { // MARK: - Error Handling - /// - Note: Should only be invoked from `Threading.workQueue` to avoid race conditions. @discardableResult internal static func handleError( withStatusCode statusCode: UInt, diff --git a/SessionUtilitiesKit/Database/Storage.swift b/SessionUtilitiesKit/Database/Storage.swift index 4d8f9fe39..edc9618e0 100644 --- a/SessionUtilitiesKit/Database/Storage.swift +++ b/SessionUtilitiesKit/Database/Storage.swift @@ -7,10 +7,12 @@ import GRDB import SignalCoreKit open class Storage { + public static let queuePrefix: String = "SessionDatabase" private static let dbFileName: String = "Session.sqlite" private static let keychainService: String = "TSKeyChainService" private static let dbCipherKeySpecKey: String = "GRDBDatabaseCipherKeySpec" private static let kSQLCipherKeySpecLength: Int = 48 + private static let writeWarningThreadshold: TimeInterval = 3 private static var sharedDatabaseDirectoryPath: String { "\(OWSFileSystem.appSharedDataDirectoryPath())/database" } private static var databasePath: String { "\(Storage.sharedDatabaseDirectoryPath)/\(Storage.dbFileName)" } @@ -78,6 +80,7 @@ open class Storage { // Configure the database and create the DatabasePool for interacting with the database var config = Configuration() + config.label = Storage.queuePrefix config.maximumReaderCount = 10 // Increase the max read connection limit - Default is 5 config.observesSuspensionNotifications = true // Minimise `0xDEAD10CC` exceptions config.prepareDatabase { db in @@ -365,7 +368,29 @@ open class Storage { try SSKDefaultKeychainStorage.shared.remove(service: keychainService, key: dbCipherKeySpecKey) } - // MARK: - Functions + // MARK: - Logging Functions + + typealias CallInfo = (file: String, function: String, line: Int) + + private static func logSlowWrites( + info: CallInfo, + updates: @escaping (Database) throws -> T + ) -> (Database) throws -> T { + return { db in + let timeout: Timer = Timer.scheduledTimerOnMainThread(withTimeInterval: writeWarningThreadshold) { + $0.invalidate() + + // Don't want to log on the main thread as to avoid confusion when debugging issues + DispatchQueue.global(qos: .default).async { + let fileName: String = (info.file.components(separatedBy: "/").last.map { " \($0):\(info.line)" } ?? "") + SNLog("[Storage\(fileName)] Slow write taking longer than \(writeWarningThreadshold)s - \(info.function)") + } + } + defer { timeout.invalidate() } + + return try updates(db) + } + } private static func logIfNeeded(_ error: Error, isWrite: Bool) { switch error { @@ -382,22 +407,50 @@ open class Storage { return nil } - @discardableResult public final func write(updates: (Database) throws -> T?) -> T? { + // MARK: - Functions + + @discardableResult public final func write( + fileName: String = #file, + functionName: String = #function, + lineNumber: Int = #line, + updates: @escaping (Database) throws -> T? + ) -> T? { guard isValid, let dbWriter: DatabaseWriter = dbWriter else { return nil } - do { return try dbWriter.write(updates) } + let info: CallInfo = (fileName, functionName, lineNumber) + + do { return try dbWriter.write(Storage.logSlowWrites(info: info, updates: updates)) } catch { return Storage.logIfNeeded(error, isWrite: true) } } - open func writeAsync(updates: @escaping (Database) throws -> T) { - writeAsync(updates: updates, completion: { _, _ in }) + open func writeAsync( + fileName: String = #file, + functionName: String = #function, + lineNumber: Int = #line, + updates: @escaping (Database) throws -> T + ) { + writeAsync( + fileName: fileName, + functionName: functionName, + lineNumber: lineNumber, + updates: updates, + completion: { _, _ in } + ) } - open func writeAsync(updates: @escaping (Database) throws -> T, completion: @escaping (Database, Swift.Result) throws -> Void) { + open func writeAsync( + fileName: String = #file, + functionName: String = #function, + lineNumber: Int = #line, + updates: @escaping (Database) throws -> T, + completion: @escaping (Database, Swift.Result) throws -> Void + ) { guard isValid, let dbWriter: DatabaseWriter = dbWriter else { return } + let info: CallInfo = (fileName, functionName, lineNumber) + dbWriter.asyncWrite( - updates, + Storage.logSlowWrites(info: info, updates: updates), completion: { db, result in switch result { case .failure(let error): Storage.logIfNeeded(error, isWrite: true) @@ -410,6 +463,9 @@ open class Storage { } open func writePublisher( + fileName: String = #file, + functionName: String = #function, + lineNumber: Int = #line, updates: @escaping (Database) throws -> T ) -> AnyPublisher { guard isValid, let dbWriter: DatabaseWriter = dbWriter else { @@ -417,6 +473,8 @@ open class Storage { .eraseToAnyPublisher() } + let info: CallInfo = (fileName, functionName, lineNumber) + /// **Note:** GRDB does have a `writePublisher` method but it appears to asynchronously trigger /// both the `output` and `complete` closures at the same time which causes a lot of unexpected /// behaviours (this behaviour is apparently expected but still causes a number of odd behaviours in our code @@ -426,7 +484,7 @@ open class Storage { /// which behaves in a much more expected way than the GRDB `writePublisher` does return Deferred { Future { resolver in - do { resolver(Result.success(try dbWriter.write(updates))) } + do { resolver(Result.success(try dbWriter.write(Storage.logSlowWrites(info: info, updates: updates)))) } catch { Storage.logIfNeeded(error, isWrite: true) resolver(Result.failure(error)) diff --git a/SessionUtilitiesKit/General/Logging.swift b/SessionUtilitiesKit/General/Logging.swift index b3f64899e..e759e83fd 100644 --- a/SessionUtilitiesKit/General/Logging.swift +++ b/SessionUtilitiesKit/General/Logging.swift @@ -3,13 +3,36 @@ import Foundation import SignalCoreKit +private extension DispatchQueue { + static var isDBWriteQueue: Bool { + /// The `dispatch_queue_get_label` function is used to get the label for a given DispatchQueue, in Swift this + /// was replaced with the `label` property on a queue instance but you used to be able to just pass `nil` in order + /// to get the name of the current queue - it seems that there might be a hole in the current design where there isn't + /// a built-in way to get the label of the current queue natively in Swift + /// + /// On a positive note it seems that we can safely call `__dispatch_queue_get_label(nil)` in order to do this, + /// it won't appear in auto-completed code but works properly + /// + /// For more information see + /// https://developer.apple.com/forums/thread/701313?answerId=705773022#705773022 + /// https://forums.swift.org/t/gcd-getting-current-dispatch-queue-name-with-swift-3/3039/2 + return (String(cString: __dispatch_queue_get_label(nil)) == "\(Storage.queuePrefix).writer") + } +} + public func SNLog(_ message: String) { - let threadString: String = (Thread.isMainThread ? " Main" : "") + let logPrefixes: String = [ + "Session", + (Thread.isMainThread ? "Main" : nil), + (DispatchQueue.isDBWriteQueue ? "DBWrite" : nil) + ] + .compactMap { $0 } + .joined(separator: ", ") #if DEBUG - print("[Session\(threadString)] \(message)") + print("[\(logPrefixes)] \(message)") #endif - OWSLogger.info("[Session\(threadString)] \(message)") + OWSLogger.info("[\(logPrefixes)] \(message)") } public func SNLogNotTests(_ message: String) { diff --git a/SessionUtilitiesKit/General/Timer+MainThread.swift b/SessionUtilitiesKit/General/Timer+MainThread.swift index b8a5ce314..7cea385a2 100644 --- a/SessionUtilitiesKit/General/Timer+MainThread.swift +++ b/SessionUtilitiesKit/General/Timer+MainThread.swift @@ -5,7 +5,11 @@ import Foundation extension Timer { @discardableResult - public static func scheduledTimerOnMainThread(withTimeInterval timeInterval: TimeInterval, repeats: Bool, block: @escaping (Timer) -> Void) -> Timer { + public static func scheduledTimerOnMainThread( + withTimeInterval timeInterval: TimeInterval, + repeats: Bool = false, + block: @escaping (Timer) -> Void + ) -> Timer { let timer = Timer(timeInterval: timeInterval, repeats: repeats, block: block) RunLoop.main.add(timer, forMode: .common) return timer diff --git a/SessionUtilitiesKit/JobRunner/JobRunner.swift b/SessionUtilitiesKit/JobRunner/JobRunner.swift index a562cc961..d87731118 100644 --- a/SessionUtilitiesKit/JobRunner/JobRunner.swift +++ b/SessionUtilitiesKit/JobRunner/JobRunner.swift @@ -1199,21 +1199,16 @@ public final class JobQueue { // Get the max failure count for the job (a value of '-1' means it will retry indefinitely) let maxFailureCount: Int = (JobRunner.executorMap.wrappedValue[job.variant]?.maxFailureCount ?? 0) let nextRunTimestamp: TimeInterval = (Date().timeIntervalSince1970 + JobRunner.getRetryInterval(for: job)) + var dependantJobIds: [Int64] = [] + var failureText: String = "failed" Storage.shared.write { db in - /// Remove any dependant jobs from the queue (shouldn't be in there but filter the queue just in case so we don't try - /// to run a deleted job or get stuck in a loop of trying to run dependencies indefinitely) - let dependantJobIds: [Int64] = try job.dependantJobs + /// Retrieve a list of dependant jobs so we can clear them from the queue + dependantJobIds = try job.dependantJobs .select(.id) .asRequest(of: Int64.self) .fetchAll(db) - - if !dependantJobIds.isEmpty { - queue.mutate { queue in - queue = queue.filter { !dependantJobIds.contains($0.id ?? -1) } - } - } - + /// Delete/update the failed jobs and any dependencies let updatedFailureCount: UInt = (job.failureCount + 1) @@ -1223,7 +1218,10 @@ public final class JobQueue { updatedFailureCount <= maxFailureCount ) else { - SNLog("[JobRunner] \(queueContext) \(job.variant) failed permanently\(maxFailureCount >= 0 && updatedFailureCount > maxFailureCount ? "; too many retries" : "")") + failureText = (maxFailureCount >= 0 && updatedFailureCount > maxFailureCount ? + "failed permanently; too many retries" : + "failed permanently" + ) // If the job permanently failed or we have performed all of our retry attempts // then delete the job and all of it's dependant jobs (it'll probably never succeed) @@ -1231,12 +1229,10 @@ public final class JobQueue { .deleteAll(db) _ = try job.delete(db) - - performCleanUp(for: job, result: .failed) return } - SNLog("[JobRunner] \(queueContext) \(job.variant) job failed; scheduling retry (failure count is \(job.failureCount + 1))") + failureText = "failed; scheduling retry (failure count is \(updatedFailureCount))" _ = try job .with( @@ -1256,6 +1252,15 @@ public final class JobQueue { ) } + /// Remove any dependant jobs from the queue (shouldn't be in there but filter the queue just in case so we don't try + /// to run a deleted job or get stuck in a loop of trying to run dependencies indefinitely) + if !dependantJobIds.isEmpty { + queue.mutate { queue in + queue = queue.filter { !dependantJobIds.contains($0.id ?? -1) } + } + } + + SNLog("[JobRunner] \(queueContext) \(job.variant) job \(failureText)") performCleanUp(for: job, result: .failed) internalQueue.async { [weak self] in self?.runNextJob() From 6f4bdcdccbed1080810de0764a2673564986a539 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 7 Jul 2023 13:20:32 +1000 Subject: [PATCH 111/135] Moved some logic outside of DBWrite closures to prevent hangs Updated the SessionApp.presentConversation function from using the DBWrite thread if it didn't need to Updated the PagedDatabaseObserver to process database commits async on a serial queue to avoid holding up the DBWrite thread Moved another Atomic mutation outside of a DBWrite closure Refactored the PagedDatabaseObserver 'databaseDidCommit' logic to be much more straightforward Tweaked a couple of flaky unit tests --- Session.xcodeproj/project.pbxproj | 12 +- Session/Closed Groups/NewClosedGroupVC.swift | 8 +- .../New Conversation/NewConversationVC.swift | 15 +- Session/Home/New Conversation/NewDMVC.swift | 16 +- Session/Meta/SessionApp.swift | 111 ++-- Session/Notifications/AppNotifications.swift | 38 +- Session/Open Groups/JoinOpenGroupVC.swift | 8 +- Session/Settings/QRCodeVC.swift | 16 +- .../Database/Models/SessionThread.swift | 46 +- .../Utilities/ProfileManager.swift | 5 +- .../Open Groups/OpenGroupManagerSpec.swift | 8 - ...eadDisappearingMessagesViewModelSpec.swift | 11 +- .../Types/PagedDatabaseObserver.swift | 515 +++++++++--------- _SharedTestUtilities/SynchronousStorage.swift | 5 +- 14 files changed, 434 insertions(+), 380 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index a37a16a23..e929c5a0f 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -6599,7 +6599,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 416; + CURRENT_PROJECT_VERSION = 417; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6671,7 +6671,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 416; + CURRENT_PROJECT_VERSION = 417; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6736,7 +6736,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 416; + CURRENT_PROJECT_VERSION = 417; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6810,7 +6810,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 416; + CURRENT_PROJECT_VERSION = 417; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -7718,7 +7718,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 416; + CURRENT_PROJECT_VERSION = 417; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -7789,7 +7789,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 416; + CURRENT_PROJECT_VERSION = 417; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/Session/Closed Groups/NewClosedGroupVC.swift b/Session/Closed Groups/NewClosedGroupVC.swift index deb2165f7..7e4a1eb51 100644 --- a/Session/Closed Groups/NewClosedGroupVC.swift +++ b/Session/Closed Groups/NewClosedGroupVC.swift @@ -356,8 +356,12 @@ final class NewClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegate } }, receiveValue: { thread in - self?.presentingViewController?.dismiss(animated: true, completion: nil) - SessionApp.presentConversation(for: thread.id, action: .compose, animated: false) + SessionApp.presentConversationCreatingIfNeeded( + for: thread.id, + variant: thread.variant, + dismissing: self?.presentingViewController, + animated: false + ) } ) } diff --git a/Session/Home/New Conversation/NewConversationVC.swift b/Session/Home/New Conversation/NewConversationVC.swift index 0b40a49c1..34b78e82f 100644 --- a/Session/Home/New Conversation/NewConversationVC.swift +++ b/Session/Home/New Conversation/NewConversationVC.swift @@ -179,16 +179,13 @@ final class NewConversationVC: BaseVC, ThemedNavigation, UITableViewDelegate, UI tableView.deselectRow(at: indexPath, animated: true) let sessionId = newConversationViewModel.sectionData[indexPath.section].contacts[indexPath.row].id - let maybeThread: SessionThread? = Storage.shared.write { db in - try SessionThread - .fetchOrCreate(db, id: sessionId, variant: .contact, shouldBeVisible: nil) - } - guard maybeThread != nil else { return } - - self.navigationController?.dismiss(animated: true, completion: nil) - - SessionApp.presentConversation(for: sessionId, action: .compose, animated: false) + SessionApp.presentConversationCreatingIfNeeded( + for: sessionId, + variant: .contact, + dismissing: navigationController, + animated: false + ) } func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) { diff --git a/Session/Home/New Conversation/NewDMVC.swift b/Session/Home/New Conversation/NewDMVC.swift index 2ce3b92a1..2d900083d 100644 --- a/Session/Home/New Conversation/NewDMVC.swift +++ b/Session/Home/New Conversation/NewDMVC.swift @@ -260,16 +260,12 @@ final class NewDMVC: BaseVC, UIPageViewControllerDataSource, UIPageViewControlle } private func startNewDM(with sessionId: String) { - let maybeThread: SessionThread? = Storage.shared.write { db in - try SessionThread - .fetchOrCreate(db, id: sessionId, variant: .contact, shouldBeVisible: nil) - } - - guard maybeThread != nil else { return } - - presentingViewController?.dismiss(animated: true, completion: nil) - - SessionApp.presentConversation(for: sessionId, action: .compose, animated: false) + SessionApp.presentConversationCreatingIfNeeded( + for: sessionId, + variant: .contact, + dismissing: presentingViewController, + animated: false + ) } } diff --git a/Session/Meta/SessionApp.swift b/Session/Meta/SessionApp.swift index 319c82f7e..7ffa9292c 100644 --- a/Session/Meta/SessionApp.swift +++ b/Session/Meta/SessionApp.swift @@ -35,59 +35,78 @@ public struct SessionApp { // MARK: - View Convenience Methods - public static func presentConversation(for threadId: String, action: ConversationViewModel.Action = .none, animated: Bool) { - let maybeThreadInfo: (thread: SessionThread, isMessageRequest: Bool)? = Storage.shared.write { db in - let thread: SessionThread = try SessionThread - .fetchOrCreate(db, id: threadId, variant: .contact, shouldBeVisible: nil) - - return (thread, thread.isMessageRequest(db)) - } - - guard - let variant: SessionThread.Variant = maybeThreadInfo?.thread.variant, - let isMessageRequest: Bool = maybeThreadInfo?.isMessageRequest - else { return } - - self.presentConversation( - for: threadId, - threadVariant: variant, - isMessageRequest: isMessageRequest, - action: action, - focusInteractionInfo: nil, - animated: animated - ) - } - - public static func presentConversation( + public static func presentConversationCreatingIfNeeded( for threadId: String, - threadVariant: SessionThread.Variant, - isMessageRequest: Bool, - action: ConversationViewModel.Action, - focusInteractionInfo: Interaction.TimestampInfo?, + variant: SessionThread.Variant, + action: ConversationViewModel.Action = .none, + dismissing presentingViewController: UIViewController?, animated: Bool ) { - guard Thread.isMainThread else { - DispatchQueue.main.async { - self.presentConversation( - for: threadId, - threadVariant: threadVariant, - isMessageRequest: isMessageRequest, - action: action, - focusInteractionInfo: focusInteractionInfo, - animated: animated - ) + let threadInfo: (threadExists: Bool, isMessageRequest: Bool)? = Storage.shared.read { db in + let isMessageRequest: Bool = { + switch variant { + case .contact: + return SessionThread + .isMessageRequest( + id: threadId, + variant: .contact, + currentUserPublicKey: getUserHexEncodedPublicKey(db), + shouldBeVisible: nil, + contactIsApproved: (try? Contact + .filter(id: threadId) + .select(.isApproved) + .asRequest(of: Bool.self) + .fetchOne(db)) + .defaulting(to: false), + includeNonVisible: true + ) + + default: return false + } + }() + + return (SessionThread.filter(id: threadId).isNotEmpty(db), isMessageRequest) + } + + // Store the post-creation logic in a closure to avoid duplication + let afterThreadCreated: () -> () = { + presentingViewController?.dismiss(animated: true, completion: nil) + + homeViewController.wrappedValue?.show( + threadId, + variant: variant, + isMessageRequest: (threadInfo?.isMessageRequest == true), + with: action, + focusedInteractionInfo: nil, + animated: animated + ) + } + + /// The thread should generally exist at the time of calling this method, but on the off change it doesn't then we need to `fetchOrCreate` it and + /// should do it on a background thread just in case something is keeping the DBWrite thread busy as in the past this could cause the app to hang + guard threadInfo?.threadExists == true else { + DispatchQueue.global(qos: .userInitiated).async { + Storage.shared.write { db in + try SessionThread.fetchOrCreate(db, id: threadId, variant: variant, shouldBeVisible: nil) + } + + // Send back to main thread for UI transitions + DispatchQueue.main.async { + afterThreadCreated() + } } return } - homeViewController.wrappedValue?.show( - threadId, - variant: threadVariant, - isMessageRequest: isMessageRequest, - with: action, - focusedInteractionInfo: focusInteractionInfo, - animated: animated - ) + // Send to main thread if needed + guard Thread.isMainThread else { + DispatchQueue.main.async { + afterThreadCreated() + } + return + } + + afterThreadCreated() } // MARK: - Functions diff --git a/Session/Notifications/AppNotifications.swift b/Session/Notifications/AppNotifications.swift index 314fee3b5..31ea7d6b0 100644 --- a/Session/Notifications/AppNotifications.swift +++ b/Session/Notifications/AppNotifications.swift @@ -37,6 +37,7 @@ enum AppNotificationAction: CaseIterable { struct AppNotificationUserInfoKey { static let threadId = "Signal.AppNotificationsUserInfoKey.threadId" + static let threadVariantRaw = "Signal.AppNotificationsUserInfoKey.threadVariantRaw" static let callBackNumber = "Signal.AppNotificationsUserInfoKey.callBackNumber" static let localCallId = "Signal.AppNotificationsUserInfoKey.localCallId" static let threadNotificationCounter = "Session.AppNotificationsUserInfoKey.threadNotificationCounter" @@ -232,8 +233,9 @@ public class NotificationPresenter: NotificationsProtocol { // "no longer verified". let category = AppNotificationCategory.incomingMessage - let userInfo = [ - AppNotificationUserInfoKey.threadId: thread.id + let userInfo: [AnyHashable: Any] = [ + AppNotificationUserInfoKey.threadId: thread.id, + AppNotificationUserInfoKey.threadVariantRaw: thread.variant.rawValue ] let userPublicKey: String = getUserHexEncodedPublicKey(db) @@ -301,8 +303,9 @@ public class NotificationPresenter: NotificationsProtocol { let previewType: Preferences.NotificationPreviewType = db[.preferencesNotificationPreviewType] .defaulting(to: .nameAndPreview) - let userInfo = [ - AppNotificationUserInfoKey.threadId: thread.id + let userInfo: [AnyHashable: Any] = [ + AppNotificationUserInfoKey.threadId: thread.id, + AppNotificationUserInfoKey.threadVariantRaw: thread.variant.rawValue ] let notificationTitle: String = "Session" @@ -378,8 +381,9 @@ public class NotificationPresenter: NotificationsProtocol { let category = AppNotificationCategory.incomingMessage - let userInfo = [ - AppNotificationUserInfoKey.threadId: thread.id + let userInfo: [AnyHashable: Any] = [ + AppNotificationUserInfoKey.threadId: thread.id, + AppNotificationUserInfoKey.threadVariantRaw: thread.variant.rawValue ] let threadName: String = SessionThread.displayName( @@ -440,8 +444,9 @@ public class NotificationPresenter: NotificationsProtocol { let notificationBody = NotificationStrings.failedToSendBody - let userInfo = [ - AppNotificationUserInfoKey.threadId: thread.id + let userInfo: [AnyHashable: Any] = [ + AppNotificationUserInfoKey.threadId: thread.id, + AppNotificationUserInfoKey.threadVariantRaw: thread.variant.rawValue ] let fallbackSound: Preferences.Sound = db[.defaultNotificationSound] .defaulting(to: Preferences.Sound.defaultNotificationSound) @@ -609,15 +614,22 @@ class NotificationActionHandler { } func showThread(userInfo: [AnyHashable: Any]) -> AnyPublisher { - guard let threadId = userInfo[AppNotificationUserInfoKey.threadId] as? String else { - return showHomeVC() - } + guard + let threadId = userInfo[AppNotificationUserInfoKey.threadId] as? String, + let threadVariantRaw = userInfo[AppNotificationUserInfoKey.threadVariantRaw] as? Int, + let threadVariant: SessionThread.Variant = SessionThread.Variant(rawValue: threadVariantRaw) + else { return showHomeVC() } // If this happens when the the app is not, visible we skip the animation so the thread // can be visible to the user immediately upon opening the app, rather than having to watch // it animate in from the homescreen. - let shouldAnimate: Bool = (UIApplication.shared.applicationState == .active) - SessionApp.presentConversation(for: threadId, animated: shouldAnimate) + SessionApp.presentConversationCreatingIfNeeded( + for: threadId, + variant: threadVariant, + dismissing: nil, + animated: (UIApplication.shared.applicationState == .active) + ) + return Just(()) .eraseToAnyPublisher() } diff --git a/Session/Open Groups/JoinOpenGroupVC.swift b/Session/Open Groups/JoinOpenGroupVC.swift index 5c7e19df5..71d39ec73 100644 --- a/Session/Open Groups/JoinOpenGroupVC.swift +++ b/Session/Open Groups/JoinOpenGroupVC.swift @@ -217,12 +217,10 @@ final class JoinOpenGroupVC: BaseVC, UIPageViewControllerDataSource, UIPageViewC self?.presentingViewController?.dismiss(animated: true, completion: nil) if shouldOpenCommunity { - SessionApp.presentConversation( + SessionApp.presentConversationCreatingIfNeeded( for: OpenGroup.idFor(roomToken: roomToken, server: server), - threadVariant: .community, - isMessageRequest: false, - action: .compose, - focusInteractionInfo: nil, + variant: .community, + dismissing: nil, animated: false ) } diff --git a/Session/Settings/QRCodeVC.swift b/Session/Settings/QRCodeVC.swift index 18abb9f46..7f16b4bc7 100644 --- a/Session/Settings/QRCodeVC.swift +++ b/Session/Settings/QRCodeVC.swift @@ -138,16 +138,12 @@ final class QRCodeVC : BaseVC, UIPageViewControllerDataSource, UIPageViewControl self.present(modal, animated: true) } else { - let maybeThread: SessionThread? = Storage.shared.write { db in - try SessionThread - .fetchOrCreate(db, id: hexEncodedPublicKey, variant: .contact, shouldBeVisible: nil) - } - - guard maybeThread != nil else { return } - - presentingViewController?.dismiss(animated: true, completion: nil) - - SessionApp.presentConversation(for: hexEncodedPublicKey, action: .compose, animated: false) + SessionApp.presentConversationCreatingIfNeeded( + for: hexEncodedPublicKey, + variant: .contact, + dismissing: presentingViewController, + animated: false + ) } } } diff --git a/SessionMessagingKit/Database/Models/SessionThread.swift b/SessionMessagingKit/Database/Models/SessionThread.swift index 0e5df42e5..baefc44d1 100644 --- a/SessionMessagingKit/Database/Models/SessionThread.swift +++ b/SessionMessagingKit/Database/Models/SessionThread.swift @@ -192,20 +192,6 @@ public extension SessionThread { ) } - func isMessageRequest(_ db: Database, includeNonVisible: Bool = false) -> Bool { - return ( - (includeNonVisible || shouldBeVisible) && - variant == .contact && - id != getUserHexEncodedPublicKey(db) && // Note to self - (try? Contact - .filter(id: id) - .select(.isApproved) - .asRequest(of: Bool.self) - .fetchOne(db)) - .defaulting(to: false) == false - ) - } - static func canSendReadReceipt( _ db: Database, threadId: String, @@ -431,6 +417,38 @@ public extension SessionThread { ).sqlExpression } + func isMessageRequest(_ db: Database, includeNonVisible: Bool = false) -> Bool { + return SessionThread.isMessageRequest( + id: id, + variant: variant, + currentUserPublicKey: getUserHexEncodedPublicKey(db), + shouldBeVisible: shouldBeVisible, + contactIsApproved: (try? Contact + .filter(id: id) + .select(.isApproved) + .asRequest(of: Bool.self) + .fetchOne(db)) + .defaulting(to: false), + includeNonVisible: includeNonVisible + ) + } + + static func isMessageRequest( + id: String, + variant: SessionThread.Variant?, + currentUserPublicKey: String, + shouldBeVisible: Bool?, + contactIsApproved: Bool?, + includeNonVisible: Bool = false + ) -> Bool { + return ( + (includeNonVisible || shouldBeVisible == true) && + variant == .contact && + id != currentUserPublicKey && // Note to self + ((contactIsApproved ?? false) == false) + ) + } + func isNoteToSelf(_ db: Database? = nil) -> Bool { return ( variant == .contact && diff --git a/SessionMessagingKit/Utilities/ProfileManager.swift b/SessionMessagingKit/Utilities/ProfileManager.swift index 3089f59f1..5ffc3d937 100644 --- a/SessionMessagingKit/Utilities/ProfileManager.swift +++ b/SessionMessagingKit/Utilities/ProfileManager.swift @@ -265,12 +265,15 @@ public struct ProfileManager { return } + // Update the cache first (in case the DBWrite thread is blocked, this way other threads + // can retrieve from the cache and avoid triggering a download) + profileAvatarCache.mutate { $0[fileName] = decryptedData } + // Store the updated 'profilePictureFileName' Storage.shared.write { db in _ = try? Profile .filter(id: profile.id) .updateAll(db, Profile.Columns.profilePictureFileName.set(to: fileName)) - profileAvatarCache.mutate { $0[fileName] = decryptedData } } } ) diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift index 4adf3f940..eb93e936c 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift +++ b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift @@ -438,14 +438,6 @@ class OpenGroupManagerSpec: QuickSpec { mockOGMCache.when { $0.isPolling }.thenReturn(true) mockOGMCache.when { $0.pollers }.thenReturn(["testserver": OpenGroupAPI.Poller(for: "testserver")]) - - mockUserDefaults - .when { (defaults: inout any UserDefaultsType) -> Any? in - defaults.object(forKey: SNUserDefaults.Date.lastOpen.rawValue) - } - .thenReturn(Date(timeIntervalSince1970: 1234567890)) - - openGroupManager.startPolling(using: dependencies) } it("removes all pollers") { diff --git a/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift b/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift index 8c51c3c49..89d8453f7 100644 --- a/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift +++ b/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift @@ -246,16 +246,9 @@ class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec { try DisappearingMessagesConfiguration.fetchOne(db, id: "TestId") } - expect(updatedConfig?.isEnabled) - .toEventually( - beTrue(), - timeout: .milliseconds(100) - ) + expect(updatedConfig?.isEnabled).to(beTrue()) expect(updatedConfig?.durationSeconds) - .toEventually( - equal(DisappearingMessagesConfiguration.validDurationsSeconds.last ?? -1), - timeout: .milliseconds(100) - ) + .to(equal(DisappearingMessagesConfiguration.validDurationsSeconds.last ?? -1)) } } } diff --git a/SessionUtilitiesKit/Database/Types/PagedDatabaseObserver.swift b/SessionUtilitiesKit/Database/Types/PagedDatabaseObserver.swift index 26920940d..a2640835c 100644 --- a/SessionUtilitiesKit/Database/Types/PagedDatabaseObserver.swift +++ b/SessionUtilitiesKit/Database/Types/PagedDatabaseObserver.swift @@ -10,6 +10,12 @@ import DifferenceKit /// /// **Note:** We **MUST** have accurate `filterSQL` and `orderSQL` values otherwise the indexing won't work public class PagedDatabaseObserver: TransactionObserver where ObservedTable: TableRecord & ColumnExpressible & Identifiable, T: FetchableRecordWithRowId & Identifiable { + private let commitProcessingQueue: DispatchQueue = DispatchQueue( + label: "PagedDatabaseObserver.commitProcessingQueue", + qos: .userInitiated, + attributes: [] // Must be serial in order to avoid updates getting processed in the wrong order + ) + // MARK: - Variables private let pagedTableName: String @@ -145,74 +151,58 @@ public class PagedDatabaseObserver: TransactionObserver where changesInCommit.mutate { $0.insert(trackedChange) } } - // Note: We will process all updates which come through this method even if - // 'onChange' is null because if the UI stops observing and then starts again - // later we don't want to have missed any changes which happened while the UI - // wasn't subscribed (and doing a full re-query seems painful...) + /// We will process all updates which come through this method even if 'onChange' is null because if the UI stops observing and then starts + /// again later we don't want to have missed any changes which happened while the UI wasn't subscribed (and doing a full re-query seems painful...) + /// + /// **Note:** This function is generally called within the DBWrite thread but we don't actually need write access to process the commit, in order + /// to avoid blocking the DBWrite thread we dispatch to a serial `commitProcessingQueue` to process the incoming changes (in the past not doing + /// so was resulting in hanging when there was a lot of activity happening) public func databaseDidCommit(_ db: Database) { + // Since we can't be sure the behaviours of 'databaseDidChange' and 'databaseDidCommit' won't change in + // the future we extract and clear the values in 'changesInCommit' since it's 'Atomic' so will different + // threads modifying the data resulting in us missing a change var committedChanges: Set = [] + self.changesInCommit.mutate { cachedChanges in committedChanges = cachedChanges cachedChanges.removeAll() } - // Note: This method will be called regardless of whether there were actually changes - // in the areas we are observing so we want to early-out if there aren't any relevant - // updated rows + commitProcessingQueue.async { [weak self] in + self?.processDatabaseCommit(committedChanges: committedChanges) + } + } + + private func processDatabaseCommit(committedChanges: Set) { + // Do nothing when there are no changes guard !committedChanges.isEmpty else { return } + typealias AssociatedDataInfo = [(hasChanges: Bool, data: ErasedAssociatedRecord)] + typealias UpdatedData = (cache: DataCache, pageInfo: PagedData.PageInfo, hasChanges: Bool, associatedData: AssociatedDataInfo) + + // Store the instance variables locally to avoid unwrapping + let dataCache: DataCache = self.dataCache.wrappedValue + let pageInfo: PagedData.PageInfo = self.pageInfo.wrappedValue let joinSQL: SQL? = self.joinSQL let orderSQL: SQL = self.orderSQL let filterSQL: SQL = self.filterSQL let associatedRecords: [ErasedAssociatedRecord] = self.associatedRecords - - let updateDataAndCallbackIfNeeded: (DataCache, PagedData.PageInfo, Bool) -> () = { [weak self] updatedDataCache, updatedPageInfo, cacheHasChanges in - let associatedDataInfo: [(hasChanges: Bool, data: ErasedAssociatedRecord)] = associatedRecords - .map { associatedRecord in - let hasChanges: Bool = associatedRecord.tryUpdateForDatabaseCommit( - db, - changes: committedChanges, - joinSQL: joinSQL, - orderSQL: orderSQL, - filterSQL: filterSQL, - pageInfo: updatedPageInfo - ) - - return (hasChanges, associatedRecord) - } - - // Check if we need to trigger a change callback - guard cacheHasChanges || associatedDataInfo.contains(where: { hasChanges, _ in hasChanges }) else { - return - } - - // If the associated data changed then update the updatedCachedData with the - // updated associated data - var finalUpdatedDataCache: DataCache = updatedDataCache - - associatedDataInfo.forEach { hasChanges, associatedData in - guard cacheHasChanges || hasChanges else { return } + let getAssociatedDataInfo: (Database, PagedData.PageInfo) -> AssociatedDataInfo = { db, updatedPageInfo in + associatedRecords.map { associatedRecord in + let hasChanges: Bool = associatedRecord.tryUpdateForDatabaseCommit( + db, + changes: committedChanges, + joinSQL: joinSQL, + orderSQL: orderSQL, + filterSQL: filterSQL, + pageInfo: updatedPageInfo + ) - finalUpdatedDataCache = associatedData.updateAssociatedData(to: finalUpdatedDataCache) + return (hasChanges, associatedRecord) } - - // Update the cache, pageInfo and the change callback - self?.dataCache.mutate { $0 = finalUpdatedDataCache } - self?.pageInfo.mutate { $0 = updatedPageInfo } - - - // Make sure the updates run on the main thread - guard Thread.isMainThread else { - DispatchQueue.main.async { [weak self] in - self?.onChangeUnsorted(finalUpdatedDataCache.values, updatedPageInfo) - } - return - } - - self?.onChangeUnsorted(finalUpdatedDataCache.values, updatedPageInfo) } - // Determing if there were any direct or related data changes + // Determine if there were any direct or related data changes let directChanges: Set = committedChanges .filter { $0.tableName == pagedTableName } let relatedChanges: [String: [PagedData.TrackedChange]] = committedChanges @@ -227,215 +217,248 @@ public class PagedDatabaseObserver: TransactionObserver where .filter { $0.tableName != pagedTableName } .filter { $0.kind == .delete } - guard !directChanges.isEmpty || !relatedChanges.isEmpty || !relatedDeletions.isEmpty else { - updateDataAndCallbackIfNeeded(self.dataCache.wrappedValue, self.pageInfo.wrappedValue, false) - return - } - - var updatedPageInfo: PagedData.PageInfo = self.pageInfo.wrappedValue - var updatedDataCache: DataCache = self.dataCache.wrappedValue - let deletionChanges: [Int64] = directChanges - .filter { $0.kind == .delete } - .map { $0.rowId } - let oldDataCount: Int = dataCache.wrappedValue.count - - // First remove any items which have been deleted - if !deletionChanges.isEmpty { - updatedDataCache = updatedDataCache.deleting(rowIds: deletionChanges) - - // Make sure there were actually changes - if updatedDataCache.count != oldDataCount { - let dataSizeDiff: Int = (updatedDataCache.count - oldDataCount) - - updatedPageInfo = PagedData.PageInfo( - pageSize: updatedPageInfo.pageSize, - pageOffset: updatedPageInfo.pageOffset, - currentCount: (updatedPageInfo.currentCount + dataSizeDiff), - totalCount: (updatedPageInfo.totalCount + dataSizeDiff) - ) - } - } - - // If there are no inserted/updated rows then trigger the update callback and stop here - let changesToQuery: [PagedData.TrackedChange] = directChanges - .filter { $0.kind != .delete } - - guard !changesToQuery.isEmpty || !relatedChanges.isEmpty || !relatedDeletions.isEmpty else { - updateDataAndCallbackIfNeeded(updatedDataCache, updatedPageInfo, !deletionChanges.isEmpty) - return - } - - // First we need to get the rowIds for the paged data connected to any of the related changes - let pagedRowIdsForRelatedChanges: Set = { - guard !relatedChanges.isEmpty else { return [] } - - return relatedChanges - .reduce(into: []) { result, next in - guard - let observedChange: PagedData.ObservedChanges = observedTableChangeTypes[next.key], - let joinToPagedType: SQL = observedChange.joinToPagedType - else { return } - - let pagedRowIds: [Int64] = PagedData.pagedRowIdsForRelatedRowIds( - db, - tableName: next.key, - pagedTableName: pagedTableName, - relatedRowIds: Array(next.value.map { $0.rowId }.asSet()), - joinToPagedType: joinToPagedType - ) - - result.append(contentsOf: pagedRowIds) + // Process and retrieve the updated data + let updatedData: UpdatedData = Storage.shared + .read { db -> UpdatedData in + // If there aren't any direct or related changes then early-out + guard !directChanges.isEmpty || !relatedChanges.isEmpty || !relatedDeletions.isEmpty else { + return (dataCache, pageInfo, false, getAssociatedDataInfo(db, pageInfo)) } - .asSet() - }() - - guard !changesToQuery.isEmpty || !pagedRowIdsForRelatedChanges.isEmpty || !relatedDeletions.isEmpty else { - updateDataAndCallbackIfNeeded(updatedDataCache, updatedPageInfo, !deletionChanges.isEmpty) - return - } - - // Fetch the indexes of the rowIds so we can determine whether they should be added to the screen - let directRowIds: Set = changesToQuery.map { $0.rowId }.asSet() - let pagedRowIdsForRelatedDeletions: Set = relatedDeletions - .compactMap { $0.pagedRowIdsForRelatedDeletion } - .flatMap { $0 } - .asSet() - let itemIndexes: [PagedData.RowIndexInfo] = PagedData.indexes( - db, - rowIds: Array(directRowIds), - tableName: pagedTableName, - requiredJoinSQL: joinSQL, - orderSQL: orderSQL, - filterSQL: filterSQL - ) - let relatedChangeIndexes: [PagedData.RowIndexInfo] = PagedData.indexes( - db, - rowIds: Array(pagedRowIdsForRelatedChanges), - tableName: pagedTableName, - requiredJoinSQL: joinSQL, - orderSQL: orderSQL, - filterSQL: filterSQL - ) - let relatedDeletionIndexes: [PagedData.RowIndexInfo] = PagedData.indexes( - db, - rowIds: Array(pagedRowIdsForRelatedDeletions), - tableName: pagedTableName, - requiredJoinSQL: joinSQL, - orderSQL: orderSQL, - filterSQL: filterSQL - ) - - // Determine if the indexes for the row ids should be displayed on the screen and remove any - // which shouldn't - values less than 'currentCount' or if there is at least one value less than - // 'currentCount' and the indexes are sequential (ie. more than the current loaded content was - // added at once) - func determineValidChanges(for indexInfo: [PagedData.RowIndexInfo]) -> [Int64] { - let indexes: [Int64] = Array(indexInfo - .map { $0.rowIndex } - .sorted() - .asSet()) - let indexesAreSequential: Bool = (indexes.map { $0 - 1 }.dropFirst() == indexes.dropLast()) - let hasOneValidIndex: Bool = indexInfo.contains(where: { info -> Bool in - info.rowIndex >= updatedPageInfo.pageOffset && ( - info.rowIndex < updatedPageInfo.currentCount || ( - updatedPageInfo.currentCount < updatedPageInfo.pageSize && - info.rowIndex <= (updatedPageInfo.pageOffset + updatedPageInfo.pageSize) - ) + + // Store a mutable copies of the dataCache and pageInfo for updating + var updatedDataCache: DataCache = dataCache + var updatedPageInfo: PagedData.PageInfo = pageInfo + let deletionChanges: [Int64] = directChanges + .filter { $0.kind == .delete } + .map { $0.rowId } + let oldDataCount: Int = dataCache.count + + // First remove any items which have been deleted + if !deletionChanges.isEmpty { + updatedDataCache = updatedDataCache.deleting(rowIds: deletionChanges) + + // Make sure there were actually changes + if updatedDataCache.count != oldDataCount { + let dataSizeDiff: Int = (updatedDataCache.count - oldDataCount) + + updatedPageInfo = PagedData.PageInfo( + pageSize: updatedPageInfo.pageSize, + pageOffset: updatedPageInfo.pageOffset, + currentCount: (updatedPageInfo.currentCount + dataSizeDiff), + totalCount: (updatedPageInfo.totalCount + dataSizeDiff) + ) + } + } + + // If there are no inserted/updated rows then trigger then early-out + let changesToQuery: [PagedData.TrackedChange] = directChanges + .filter { $0.kind != .delete } + + guard !changesToQuery.isEmpty || !relatedChanges.isEmpty || !relatedDeletions.isEmpty else { + let associatedData: AssociatedDataInfo = getAssociatedDataInfo(db, updatedPageInfo) + return (updatedDataCache, updatedPageInfo, !deletionChanges.isEmpty, associatedData) + } + + // Next we need to determine if any related changes were associated to the pagedData we are + // observing, if they aren't (and there were no other direct changes) we can early-out + let pagedRowIdsForRelatedChanges: Set = { + guard !relatedChanges.isEmpty else { return [] } + + return relatedChanges + .reduce(into: []) { result, next in + guard + let observedChange: PagedData.ObservedChanges = observedTableChangeTypes[next.key], + let joinToPagedType: SQL = observedChange.joinToPagedType + else { return } + + let pagedRowIds: [Int64] = PagedData.pagedRowIdsForRelatedRowIds( + db, + tableName: next.key, + pagedTableName: pagedTableName, + relatedRowIds: Array(next.value.map { $0.rowId }.asSet()), + joinToPagedType: joinToPagedType + ) + + result.append(contentsOf: pagedRowIds) + } + .asSet() + }() + + guard !changesToQuery.isEmpty || !pagedRowIdsForRelatedChanges.isEmpty || !relatedDeletions.isEmpty else { + let associatedData: AssociatedDataInfo = getAssociatedDataInfo(db, updatedPageInfo) + return (updatedDataCache, updatedPageInfo, !deletionChanges.isEmpty, associatedData) + } + + // Fetch the indexes of the rowIds so we can determine whether they should be added to the screen + let directRowIds: Set = changesToQuery.map { $0.rowId }.asSet() + let pagedRowIdsForRelatedDeletions: Set = relatedDeletions + .compactMap { $0.pagedRowIdsForRelatedDeletion } + .flatMap { $0 } + .asSet() + let itemIndexes: [PagedData.RowIndexInfo] = PagedData.indexes( + db, + rowIds: Array(directRowIds), + tableName: pagedTableName, + requiredJoinSQL: joinSQL, + orderSQL: orderSQL, + filterSQL: filterSQL ) - }) - - return (indexesAreSequential && hasOneValidIndex ? - indexInfo.map { $0.rowId } : - indexInfo - .filter { info -> Bool in + let relatedChangeIndexes: [PagedData.RowIndexInfo] = PagedData.indexes( + db, + rowIds: Array(pagedRowIdsForRelatedChanges), + tableName: pagedTableName, + requiredJoinSQL: joinSQL, + orderSQL: orderSQL, + filterSQL: filterSQL + ) + let relatedDeletionIndexes: [PagedData.RowIndexInfo] = PagedData.indexes( + db, + rowIds: Array(pagedRowIdsForRelatedDeletions), + tableName: pagedTableName, + requiredJoinSQL: joinSQL, + orderSQL: orderSQL, + filterSQL: filterSQL + ) + + // Determine if the indexes for the row ids should be displayed on the screen and remove any + // which shouldn't - values less than 'currentCount' or if there is at least one value less than + // 'currentCount' and the indexes are sequential (ie. more than the current loaded content was + // added at once) + func determineValidChanges(for indexInfo: [PagedData.RowIndexInfo]) -> [Int64] { + let indexes: [Int64] = Array(indexInfo + .map { $0.rowIndex } + .sorted() + .asSet()) + let indexesAreSequential: Bool = (indexes.map { $0 - 1 }.dropFirst() == indexes.dropLast()) + let hasOneValidIndex: Bool = indexInfo.contains(where: { info -> Bool in info.rowIndex >= updatedPageInfo.pageOffset && ( info.rowIndex < updatedPageInfo.currentCount || ( updatedPageInfo.currentCount < updatedPageInfo.pageSize && info.rowIndex <= (updatedPageInfo.pageOffset + updatedPageInfo.pageSize) ) ) + }) + + return (indexesAreSequential && hasOneValidIndex ? + indexInfo.map { $0.rowId } : + indexInfo + .filter { info -> Bool in + info.rowIndex >= updatedPageInfo.pageOffset && ( + info.rowIndex < updatedPageInfo.currentCount || ( + updatedPageInfo.currentCount < updatedPageInfo.pageSize && + info.rowIndex <= (updatedPageInfo.pageOffset + updatedPageInfo.pageSize) + ) + ) + } + .map { info -> Int64 in info.rowId } + ) + } + let validChangeRowIds: [Int64] = determineValidChanges(for: itemIndexes) + let validRelatedChangeRowIds: [Int64] = determineValidChanges(for: relatedChangeIndexes) + let validRelatedDeletionRowIds: [Int64] = determineValidChanges(for: relatedDeletionIndexes) + let countBefore: Int = itemIndexes.filter { $0.rowIndex < updatedPageInfo.pageOffset }.count + + // If the number of indexes doesn't match the number of rowIds then it means something changed + // resulting in an item being filtered out + func performRemovalsIfNeeded(for rowIds: Set, indexes: [PagedData.RowIndexInfo]) { + let uniqueIndexes: Set = indexes.map { $0.rowId }.asSet() + + // If they have the same count then nothin was filtered out so do nothing + guard rowIds.count != uniqueIndexes.count else { return } + + // Otherwise something was probably removed so try to remove it from the cache + let rowIdsRemoved: Set = rowIds.subtracting(uniqueIndexes) + let preDeletionCount: Int = updatedDataCache.count + updatedDataCache = updatedDataCache.deleting(rowIds: Array(rowIdsRemoved)) + + // Lastly make sure there were actually changes before updating the page info + guard updatedDataCache.count != preDeletionCount else { return } + + let dataSizeDiff: Int = (updatedDataCache.count - preDeletionCount) + + updatedPageInfo = PagedData.PageInfo( + pageSize: updatedPageInfo.pageSize, + pageOffset: updatedPageInfo.pageOffset, + currentCount: (updatedPageInfo.currentCount + dataSizeDiff), + totalCount: (updatedPageInfo.totalCount + dataSizeDiff) + ) + } + + // Actually perform any required removals + performRemovalsIfNeeded(for: directRowIds, indexes: itemIndexes) + performRemovalsIfNeeded(for: pagedRowIdsForRelatedChanges, indexes: relatedChangeIndexes) + performRemovalsIfNeeded(for: pagedRowIdsForRelatedDeletions, indexes: relatedDeletionIndexes) + + // Update the offset and totalCount even if the rows are outside of the current page (need to + // in order to ensure the 'load more' sections are accurate) + updatedPageInfo = PagedData.PageInfo( + pageSize: updatedPageInfo.pageSize, + pageOffset: (updatedPageInfo.pageOffset + countBefore), + currentCount: updatedPageInfo.currentCount, + totalCount: ( + updatedPageInfo.totalCount + + changesToQuery + .filter { $0.kind == .insert } + .filter { validChangeRowIds.contains($0.rowId) } + .count + ) + ) + + // If there are no valid row ids then early-out (at this point the pageInfo would have changed + // so we want to flat 'hasChanges' as true) + guard !validChangeRowIds.isEmpty || !validRelatedChangeRowIds.isEmpty || !validRelatedDeletionRowIds.isEmpty else { + let associatedData: AssociatedDataInfo = getAssociatedDataInfo(db, updatedPageInfo) + return (updatedDataCache, updatedPageInfo, true, associatedData) + } + + // Fetch the inserted/updated rows + let targetRowIds: [Int64] = Array((validChangeRowIds + validRelatedChangeRowIds + validRelatedDeletionRowIds).asSet()) + let updatedItems: [T] = { + do { return try dataQuery(targetRowIds).fetchAll(db) } + catch { + SNLog("[PagedDatabaseObserver] Error fetching data during change: \(error)") + return [] } - .map { info -> Int64 in info.rowId } - ) - } - let validChangeRowIds: [Int64] = determineValidChanges(for: itemIndexes) - let validRelatedChangeRowIds: [Int64] = determineValidChanges(for: relatedChangeIndexes) - let validRelatedDeletionRowIds: [Int64] = determineValidChanges(for: relatedDeletionIndexes) - let countBefore: Int = itemIndexes.filter { $0.rowIndex < updatedPageInfo.pageOffset }.count + }() + + updatedDataCache = updatedDataCache.upserting(items: updatedItems) + + // Update the currentCount for the upserted data + let dataSizeDiff: Int = (updatedDataCache.count - oldDataCount) + updatedPageInfo = PagedData.PageInfo( + pageSize: updatedPageInfo.pageSize, + pageOffset: updatedPageInfo.pageOffset, + currentCount: (updatedPageInfo.currentCount + dataSizeDiff), + totalCount: updatedPageInfo.totalCount + ) + + // Return the final updated data + let associatedData: AssociatedDataInfo = getAssociatedDataInfo(db, updatedPageInfo) + return (updatedDataCache, updatedPageInfo, true, associatedData) + } + .defaulting(to: (cache: dataCache, pageInfo: pageInfo, hasChanges: false, associatedData: [])) - // If the number of indexes doesn't match the number of rowIds then it means something changed - // resulting in an item being filtered out - func performRemovalsIfNeeded(for rowIds: Set, indexes: [PagedData.RowIndexInfo]) { - let uniqueIndexes: Set = indexes.map { $0.rowId }.asSet() - - // If they have the same count then nothin was filtered out so do nothing - guard rowIds.count != uniqueIndexes.count else { return } - - // Otherwise something was probably removed so try to remove it from the cache - let rowIdsRemoved: Set = rowIds.subtracting(uniqueIndexes) - let preDeletionCount: Int = updatedDataCache.count - updatedDataCache = updatedDataCache.deleting(rowIds: Array(rowIdsRemoved)) - - // Lastly make sure there were actually changes before updating the page info - guard updatedDataCache.count != preDeletionCount else { return } - - let dataSizeDiff: Int = (updatedDataCache.count - preDeletionCount) - - updatedPageInfo = PagedData.PageInfo( - pageSize: updatedPageInfo.pageSize, - pageOffset: updatedPageInfo.pageOffset, - currentCount: (updatedPageInfo.currentCount + dataSizeDiff), - totalCount: (updatedPageInfo.totalCount + dataSizeDiff) - ) - } - - // Actually perform any required removals - performRemovalsIfNeeded(for: directRowIds, indexes: itemIndexes) - performRemovalsIfNeeded(for: pagedRowIdsForRelatedChanges, indexes: relatedChangeIndexes) - performRemovalsIfNeeded(for: pagedRowIdsForRelatedDeletions, indexes: relatedDeletionIndexes) - - // Update the offset and totalCount even if the rows are outside of the current page (need to - // in order to ensure the 'load more' sections are accurate) - updatedPageInfo = PagedData.PageInfo( - pageSize: updatedPageInfo.pageSize, - pageOffset: (updatedPageInfo.pageOffset + countBefore), - currentCount: updatedPageInfo.currentCount, - totalCount: ( - updatedPageInfo.totalCount + - changesToQuery - .filter { $0.kind == .insert } - .filter { validChangeRowIds.contains($0.rowId) } - .count - ) - ) - - // If there are no valid row ids then stop here (trigger updates though since the page info - // has changes) - guard !validChangeRowIds.isEmpty || !validRelatedChangeRowIds.isEmpty || !validRelatedDeletionRowIds.isEmpty else { - updateDataAndCallbackIfNeeded(updatedDataCache, updatedPageInfo, true) + // Now that we have all of the changes, check if there were actually any changes + guard updatedData.hasChanges || updatedData.associatedData.contains(where: { hasChanges, _ in hasChanges }) else { return } - // Fetch the inserted/updated rows - let targetRowIds: [Int64] = Array((validChangeRowIds + validRelatedChangeRowIds + validRelatedDeletionRowIds).asSet()) - let updatedItems: [T] = (try? dataQuery(targetRowIds) - .fetchAll(db)) - .defaulting(to: []) + // If the associated data changed then update the updatedCachedData with the updated associated data + var finalUpdatedDataCache: DataCache = updatedData.cache - // Process the upserted data - updatedDataCache = updatedDataCache.upserting(items: updatedItems) - - // Update the currentCount for the upserted data - let dataSizeDiff: Int = (updatedDataCache.count - oldDataCount) - - updatedPageInfo = PagedData.PageInfo( - pageSize: updatedPageInfo.pageSize, - pageOffset: updatedPageInfo.pageOffset, - currentCount: (updatedPageInfo.currentCount + dataSizeDiff), - totalCount: updatedPageInfo.totalCount - ) - - updateDataAndCallbackIfNeeded(updatedDataCache, updatedPageInfo, true) + updatedData.associatedData.forEach { hasChanges, associatedData in + guard updatedData.hasChanges || hasChanges else { return } + + finalUpdatedDataCache = associatedData.updateAssociatedData(to: finalUpdatedDataCache) + } + + // Update the cache, pageInfo and the change callback + self.dataCache.mutate { $0 = finalUpdatedDataCache } + self.pageInfo.mutate { $0 = updatedData.pageInfo } + + // Trigger the unsorted change callback (the actual UI update triggering should eventually be run on + // the main thread via the `PagedData.processAndTriggerUpdates` function) + self.onChangeUnsorted(finalUpdatedDataCache.values, updatedData.pageInfo) } public func databaseDidRollback(_ db: Database) {} diff --git a/_SharedTestUtilities/SynchronousStorage.swift b/_SharedTestUtilities/SynchronousStorage.swift index c7feaf137..0e9e87655 100644 --- a/_SharedTestUtilities/SynchronousStorage.swift +++ b/_SharedTestUtilities/SynchronousStorage.swift @@ -19,9 +19,12 @@ class SynchronousStorage: Storage { } override func writePublisher( + fileName: String = #file, + functionName: String = #function, + lineNumber: Int = #line, updates: @escaping (Database) throws -> T ) -> AnyPublisher { - guard let result: T = super.write(updates: updates) else { + guard let result: T = super.write(fileName: fileName, functionName: functionName, lineNumber: lineNumber, updates: updates) else { return Fail(error: StorageError.generic) .eraseToAnyPublisher() } From 0f52d358d4996188b94156738db85dec82e6fd81 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 7 Jul 2023 14:55:03 +1000 Subject: [PATCH 112/135] Fixed an issue where return from background could hang Stopped trying to run migrations if there aren't any to run (remove unneeded DBWrite thread use) Shifted return from background migration running to a background thread to prevent hanging Updated the slow write log to also output once the write completes --- Session/Meta/AppDelegate.swift | 50 ++++++++++--------- SessionUtilitiesKit/Database/Storage.swift | 38 +++++++++----- .../General/String+Utilities.swift | 17 +++++++ 3 files changed, 70 insertions(+), 35 deletions(-) diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 1f193601c..e2afa3890 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -143,6 +143,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD // If we've already completed migrations at least once this launch then check // to see if any "delayed" migrations now need to run if Storage.shared.hasCompletedMigrations { + SNLog("Checking for pending migrations") let initialLaunchFailed: Bool = self.initialLaunchFailed AppReadiness.invalidate() @@ -154,30 +155,33 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD self.window?.rootViewController?.dismiss(animated: false) } - AppSetup.runPostSetupMigrations( - migrationProgressChanged: { [weak self] progress, minEstimatedTotalTime in - self?.loadingViewController?.updateProgress( - progress: progress, - minEstimatedTotalTime: minEstimatedTotalTime - ) - }, - migrationsCompletion: { [weak self] result, needsConfigSync in - if case .failure(let error) = result { - DispatchQueue.main.async { - self?.showFailedStartupAlert( - calledFrom: .enterForeground(initialLaunchFailed: initialLaunchFailed), - error: .databaseError(error) - ) + // Dispatch async so things can continue to be progressed if a migration does need to run + DispatchQueue.global(qos: .userInitiated).async { [weak self] in + AppSetup.runPostSetupMigrations( + migrationProgressChanged: { progress, minEstimatedTotalTime in + self?.loadingViewController?.updateProgress( + progress: progress, + minEstimatedTotalTime: minEstimatedTotalTime + ) + }, + migrationsCompletion: { result, needsConfigSync in + if case .failure(let error) = result { + DispatchQueue.main.async { + self?.showFailedStartupAlert( + calledFrom: .enterForeground(initialLaunchFailed: initialLaunchFailed), + error: .databaseError(error) + ) + } + return } - return + + self?.completePostMigrationSetup( + calledFrom: .enterForeground(initialLaunchFailed: initialLaunchFailed), + needsConfigSync: needsConfigSync + ) } - - self?.completePostMigrationSetup( - calledFrom: .enterForeground(initialLaunchFailed: initialLaunchFailed), - needsConfigSync: needsConfigSync - ) - } - ) + ) + } } } @@ -322,8 +326,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD // the user is in an invalid state (and should have already been shown a modal) guard success else { return } + SNLog("RootViewController ready, readying remaining processes") self?.initialLaunchFailed = false - SNLog("Migrations completed, performing setup and ensuring rootViewController") /// Trigger any launch-specific jobs and start the JobRunner with `JobRunner.appDidFinishLaunching()` some /// of these jobs (eg. DisappearingMessages job) can impact the interactions which get fetched to display on the home diff --git a/SessionUtilitiesKit/Database/Storage.swift b/SessionUtilitiesKit/Database/Storage.swift index edc9618e0..1667f1384 100644 --- a/SessionUtilitiesKit/Database/Storage.swift +++ b/SessionUtilitiesKit/Database/Storage.swift @@ -206,11 +206,6 @@ open class Storage { } }) - // If we have an unperformed migration then trigger the progress updater immediately - if let firstMigrationKey: String = unperformedMigrations.first?.key { - self.migrationProgressUpdater?.wrappedValue(firstMigrationKey, 0) - } - // Store the logic to run when the migration completes let migrationCompleted: (Swift.Result) -> () = { [weak self] result in self?.migrationsCompleted.mutate { $0 = true } @@ -230,10 +225,17 @@ open class Storage { onComplete(result, needsConfigSync) } - // Update the 'migrationsCompleted' state (since we not support running migrations when - // returning from the background it's possible for this flag to transition back to false) - if unperformedMigrations.isEmpty { - self.migrationsCompleted.mutate { $0 = false } + // if there aren't any migrations to run then just complete immediately (this way the migrator + // doesn't try to execute on the DBWrite thread so returning from the background can't get blocked + // due to some weird endless process running) + guard !unperformedMigrations.isEmpty else { + migrationCompleted(.success(())) + return + } + + // If we have an unperformed migration then trigger the progress updater immediately + if let firstMigrationKey: String = unperformedMigrations.first?.key { + self.migrationProgressUpdater?.wrappedValue(firstMigrationKey, 0) } // Note: The non-async migration should only be used for unit tests @@ -377,16 +379,28 @@ open class Storage { updates: @escaping (Database) throws -> T ) -> (Database) throws -> T { return { db in + let start: CFTimeInterval = CACurrentMediaTime() + let fileName: String = (info.file.components(separatedBy: "/").last.map { " \($0):\(info.line)" } ?? "") let timeout: Timer = Timer.scheduledTimerOnMainThread(withTimeInterval: writeWarningThreadshold) { $0.invalidate() // Don't want to log on the main thread as to avoid confusion when debugging issues DispatchQueue.global(qos: .default).async { - let fileName: String = (info.file.components(separatedBy: "/").last.map { " \($0):\(info.line)" } ?? "") - SNLog("[Storage\(fileName)] Slow write taking longer than \(writeWarningThreadshold)s - \(info.function)") + SNLog("[Storage\(fileName)] Slow write taking longer than \(writeWarningThreadshold, format: ".2", omitZeroDecimal: true)s - \(info.function)") } } - defer { timeout.invalidate() } + defer { + // If we timed out then log the actual duration to help us prioritise performance issues + if !timeout.isValid { + let end: CFTimeInterval = CACurrentMediaTime() + + DispatchQueue.global(qos: .default).async { + SNLog("[Storage\(fileName)] Slow write completed after \(end - start, format: ".2", omitZeroDecimal: true)s") + } + } + + timeout.invalidate() + } return try updates(db) } diff --git a/SessionUtilitiesKit/General/String+Utilities.swift b/SessionUtilitiesKit/General/String+Utilities.swift index 8bb735ceb..42cd4c5a9 100644 --- a/SessionUtilitiesKit/General/String+Utilities.swift +++ b/SessionUtilitiesKit/General/String+Utilities.swift @@ -77,6 +77,23 @@ public extension String { // MARK: - Formatting +extension String.StringInterpolation { + mutating func appendInterpolation(_ value: Int, format: String) { + let result: String = String(format: "%\(format)d", value) + appendLiteral(result) + } + + mutating func appendInterpolation(_ value: Double, format: String, omitZeroDecimal: Bool = false) { + guard !omitZeroDecimal || Int(exactly: value) == nil else { + appendLiteral("\(Int(exactly: value)!)") + return + } + + let result: String = String(format: "%\(format)f", value) + appendLiteral(result) + } +} + public extension String { static func formattedDuration(_ duration: TimeInterval, format: TimeInterval.DurationFormat = .short) -> String { let secondsPerMinute: TimeInterval = 60 From a5306f85b7b7094ebc1f61362c6661d1ce81cc4e Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 7 Jul 2023 15:19:13 +1000 Subject: [PATCH 113/135] Added in a little defensive coding for config message processing Updated the config 'pendingChanges' to use the readonly version of the conf (no use blocking access) Added code to throw and log when the config processing exceeds 50000 loops (ie. infinite loop protection) --- .../Config Handling/SessionUtil+Contacts.swift | 7 +++++-- .../SessionUtil+ConvoInfoVolatile.swift | 12 ++++++------ .../Config Handling/SessionUtil+Shared.swift | 14 ++++++++++---- .../Config Handling/SessionUtil+UserGroups.swift | 3 +++ SessionMessagingKit/SessionUtil/SessionUtil.swift | 10 ++++------ .../SessionUtil/SessionUtilError.swift | 1 + 6 files changed, 29 insertions(+), 18 deletions(-) diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift index 9487b7b68..1d142893d 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift @@ -40,7 +40,7 @@ internal extension SessionUtil { // The current users contact data is handled separately so exclude it if it's present (as that's // actually a bug) let userPublicKey: String = getUserHexEncodedPublicKey(db) - let targetContactData: [String: ContactData] = extractContacts( + let targetContactData: [String: ContactData] = try extractContacts( from: conf, latestConfigSentTimestampMs: latestConfigSentTimestampMs ).filter { $0.key != userPublicKey } @@ -540,12 +540,15 @@ private extension SessionUtil { static func extractContacts( from conf: UnsafeMutablePointer?, latestConfigSentTimestampMs: Int64 - ) -> [String: ContactData] { + ) throws -> [String: ContactData] { + var infiniteLoopGuard: Int = 0 var result: [String: ContactData] = [:] var contact: contacts_contact = contacts_contact() let contactIterator: UnsafeMutablePointer = contacts_iterator_new(conf) while !contacts_iterator_done(contactIterator, &contact) { + try SessionUtil.checkLoopLimitReached(&infiniteLoopGuard, for: .contacts) + let contactId: String = String(cString: withUnsafeBytes(of: contact.session_id) { [UInt8]($0) } .map { CChar($0) } .nullTerminated() diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift index 677f5bd81..c504a0c35 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift @@ -23,7 +23,7 @@ internal extension SessionUtil { guard conf != nil else { throw SessionUtilError.nilConfigObject } // Get the volatile thread info from the conf and local conversations - let volatileThreadInfo: [VolatileThreadInfo] = extractConvoVolatileInfo(from: conf) + let volatileThreadInfo: [VolatileThreadInfo] = try extractConvoVolatileInfo(from: conf) let localVolatileThreadInfo: [String: VolatileThreadInfo] = VolatileThreadInfo.fetchAll(db) .reduce(into: [:]) { result, next in result[next.threadId] = next } @@ -314,10 +314,7 @@ public extension SessionUtil { openGroup: OpenGroup? ) -> Bool { return SessionUtil - .config( - for: .convoInfoVolatile, - publicKey: userPublicKey - ) + .config(for: .convoInfoVolatile, publicKey: userPublicKey) .wrappedValue .map { conf in switch threadVariant { @@ -512,7 +509,8 @@ public extension SessionUtil { internal static func extractConvoVolatileInfo( from conf: UnsafeMutablePointer? - ) -> [VolatileThreadInfo] { + ) throws -> [VolatileThreadInfo] { + var infiniteLoopGuard: Int = 0 var result: [VolatileThreadInfo] = [] var oneToOne: convo_info_volatile_1to1 = convo_info_volatile_1to1() var community: convo_info_volatile_community = convo_info_volatile_community() @@ -520,6 +518,8 @@ public extension SessionUtil { let convoIterator: OpaquePointer = convo_info_volatile_iterator_new(conf) while !convo_info_volatile_iterator_done(convoIterator) { + try SessionUtil.checkLoopLimitReached(&infiniteLoopGuard, for: .convoInfoVolatile) + if convo_info_volatile_it_is_1to1(convoIterator, &oneToOne) { result.append( VolatileThreadInfo( diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift index 6c23a7b7d..909ea9ce7 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Shared.swift @@ -59,10 +59,7 @@ internal extension SessionUtil { do { needsPush = try SessionUtil - .config( - for: variant, - publicKey: publicKey - ) + .config(for: variant, publicKey: publicKey) .mutate { conf in guard conf != nil else { throw SessionUtilError.nilConfigObject } @@ -332,6 +329,15 @@ internal extension SessionUtil { // Ensure the change occurred after the last config message was handled (minus the buffer period) return (changeTimestampMs >= (configDumpTimestampMs - Int64(SessionUtil.configChangeBufferPeriod * 1000))) } + + static func checkLoopLimitReached(_ loopCounter: inout Int, for variant: ConfigDump.Variant, maxLoopCount: Int = 50000) throws { + loopCounter += 1 + + guard loopCounter < maxLoopCount else { + SNLog("[libSession] Got stuck in infinite loop processing '\(variant.configMessageKind.description)' data") + throw SessionUtilError.processingLoopLimitReached + } + } } // MARK: - External Outgoing Changes diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserGroups.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserGroups.swift index ba9210a74..a10e617be 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserGroups.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+UserGroups.swift @@ -34,6 +34,7 @@ internal extension SessionUtil { guard mergeNeedsDump else { return } guard conf != nil else { throw SessionUtilError.nilConfigObject } + var infiniteLoopGuard: Int = 0 var communities: [PrioritisedData] = [] var legacyGroups: [LegacyGroupInfo] = [] var community: ugroups_community_info = ugroups_community_info() @@ -41,6 +42,8 @@ internal extension SessionUtil { let groupsIterator: OpaquePointer = user_groups_iterator_new(conf) while !user_groups_iterator_done(groupsIterator) { + try SessionUtil.checkLoopLimitReached(&infiniteLoopGuard, for: .userGroups) + if user_groups_it_is_community(groupsIterator, &community) { let server: String = String(libSessionVal: community.base_url) let roomToken: String = String(libSessionVal: community.room) diff --git a/SessionMessagingKit/SessionUtil/SessionUtil.swift b/SessionMessagingKit/SessionUtil/SessionUtil.swift index 204068860..62b77c9fe 100644 --- a/SessionMessagingKit/SessionUtil/SessionUtil.swift +++ b/SessionMessagingKit/SessionUtil/SessionUtil.swift @@ -314,9 +314,10 @@ public enum SessionUtil { .compactMap { variant -> OutgoingConfResult? in try SessionUtil .config(for: variant, publicKey: publicKey) - .mutate { conf in + .wrappedValue + .map { conf in // Check if the config needs to be pushed - guard conf != nil && config_needs_push(conf) else { return nil } + guard config_needs_push(conf) else { return nil } var cPushData: UnsafeMutablePointer! let configCountInfo: String = { @@ -375,10 +376,7 @@ public enum SessionUtil { publicKey: String ) -> ConfigDump? { return SessionUtil - .config( - for: message.kind.configDumpVariant, - publicKey: publicKey - ) + .config(for: message.kind.configDumpVariant, publicKey: publicKey) .mutate { conf in guard conf != nil else { return nil } diff --git a/SessionMessagingKit/SessionUtil/SessionUtilError.swift b/SessionMessagingKit/SessionUtil/SessionUtilError.swift index 1c3cd4d9e..42da99da5 100644 --- a/SessionMessagingKit/SessionUtil/SessionUtilError.swift +++ b/SessionMessagingKit/SessionUtil/SessionUtilError.swift @@ -7,4 +7,5 @@ public enum SessionUtilError: Error { case nilConfigObject case userDoesNotExist case getOrConstructFailedUnexpectedly + case processingLoopLimitReached } From 38420997b0585efcd7e46a840cc2af8457af4efa Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Mon, 10 Jul 2023 17:56:58 +1000 Subject: [PATCH 114/135] Fixed a couple of bugs and made some performance tweaks Cleaned up some duplicate poller logic (avoid going back to the main queue) Updated the code to remove a profile image if a user sends a message which doesn't have a profile image (ie. they've explicitly removed it) Fixed an issue where some more logic could incorrectly run in the DBWrite queue Fixed a bug where the OpenGroupPoller could stop polling when getting an error Fixed a bug where messages which had the same timestamp wouldn't get correctly marked as read when scrolling under the right circumstances --- Session.xcodeproj/project.pbxproj | 12 +- .../Conversations/ConversationViewModel.swift | 11 +- .../Open Groups/OpenGroupManager.swift | 102 +++++---- .../MessageReceiver+VisibleMessages.swift | 2 +- .../Pollers/ClosedGroupPoller.swift | 24 +- .../Pollers/CurrentUserPoller.swift | 49 +--- .../Pollers/OpenGroupPoller.swift | 61 +++-- .../Sending & Receiving/Pollers/Poller.swift | 214 +++++++++--------- .../SessionUtil+ConvoInfoVolatile.swift | 3 +- .../Types/PagedDatabaseObserver.swift | 6 +- 10 files changed, 218 insertions(+), 266 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index e929c5a0f..11e85bdf7 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -6599,7 +6599,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 417; + CURRENT_PROJECT_VERSION = 418; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6671,7 +6671,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 417; + CURRENT_PROJECT_VERSION = 418; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6736,7 +6736,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 417; + CURRENT_PROJECT_VERSION = 418; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6810,7 +6810,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 417; + CURRENT_PROJECT_VERSION = 418; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -7718,7 +7718,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 417; + CURRENT_PROJECT_VERSION = 418; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -7789,7 +7789,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 417; + CURRENT_PROJECT_VERSION = 418; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/Session/Conversations/ConversationViewModel.swift b/Session/Conversations/ConversationViewModel.swift index 1373c0bd5..7c30437dc 100644 --- a/Session/Conversations/ConversationViewModel.swift +++ b/Session/Conversations/ConversationViewModel.swift @@ -213,6 +213,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { // MARK: - Interaction Data + private var lastInteractionIdMarkedAsRead: Int64? = nil private var lastInteractionTimestampMsMarkedAsRead: Int64 = 0 public private(set) var unobservedInteractionDataChanges: ([SectionModel], StagedChangeset<[SectionModel]>)? public private(set) var interactionData: [SectionModel] = [] @@ -651,8 +652,8 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { /// Since this method now gets triggered when scrolling we want to try to optimise it and avoid busying the database /// write queue when it isn't needed, in order to do this we: /// - Throttle the updates to 100ms (quick enough that users shouldn't notice, but will help the DB when the user flings the list) - /// - Don't bother marking anything as read if this was called with the same `interactionId` that we previously marked as - /// read (ie. when scrolling and the last message hasn't changed) + /// - Only mark interactions as read if they have newer `timestampMs` or `id` values (ie. were sent later or were more-recent + /// entries in the database), **Note:** Old messages will be marked as read upon insertion so shouldn't be an issue /// /// The `ThreadViewModel.markAsRead` method also tries to avoid marking as read if a conversation is already fully read if markAsReadPublisher == nil { @@ -662,10 +663,11 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { receiveOutput: { [weak self] target, timestampMs in switch target { case .thread: self?.threadData.markAsRead(target: target) - case .threadAndInteractions: + case .threadAndInteractions(let interactionId): guard timestampMs == nil || - (self?.lastInteractionTimestampMsMarkedAsRead ?? 0) < (timestampMs ?? 0) + (self?.lastInteractionTimestampMsMarkedAsRead ?? 0) < (timestampMs ?? 0) || + (self?.lastInteractionIdMarkedAsRead ?? 0) < (interactionId ?? 0) else { self?.threadData.markAsRead(target: .thread) return @@ -677,6 +679,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { self?.lastInteractionTimestampMsMarkedAsRead = timestampMs } + self?.lastInteractionIdMarkedAsRead = (interactionId ?? self?.threadData.interactionId) self?.threadData.markAsRead(target: target) } } diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index 211f2ed92..5c351ec76 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -521,61 +521,65 @@ public final class OpenGroupManager { } } - db.afterNextTransactionNested { db in - // Start the poller if needed - if dependencies.cache.pollers[server.lowercased()] == nil { - dependencies.mutableCache.mutate { - $0.pollers[server.lowercased()]?.stop() - $0.pollers[server.lowercased()] = OpenGroupAPI.Poller(for: server.lowercased()) + db.afterNextTransactionNested { _ in + // Dispatch async to the workQueue to prevent holding up the DBWrite thread from the + // above transaction + OpenGroupAPI.workQueue.async { + // Start the poller if needed + if dependencies.cache.pollers[server.lowercased()] == nil { + dependencies.mutableCache.mutate { + $0.pollers[server.lowercased()]?.stop() + $0.pollers[server.lowercased()] = OpenGroupAPI.Poller(for: server.lowercased()) + } + + dependencies.cache.pollers[server.lowercased()]?.startIfNeeded(using: dependencies) } - dependencies.cache.pollers[server.lowercased()]?.startIfNeeded(using: dependencies) - } - - /// Start downloading the room image (if we don't have one or it's been updated) - if - let imageId: String = (pollInfo.details?.imageId ?? openGroup.imageId), - ( - openGroup.imageData == nil || - openGroup.imageId != imageId - ) - { - OpenGroupManager - .roomImage( - fileId: imageId, - for: roomToken, - on: server, - existingData: openGroup.imageData, - using: dependencies + /// Start downloading the room image (if we don't have one or it's been updated) + if + let imageId: String = (pollInfo.details?.imageId ?? openGroup.imageId), + ( + openGroup.imageData == nil || + openGroup.imageId != imageId ) - // Note: We need to subscribe and receive on different threads to ensure the - // logic in 'receiveValue' doesn't result in a reentrancy database issue - .subscribe(on: OpenGroupAPI.workQueue) - .receive(on: DispatchQueue.global(qos: .default)) - .sinkUntilComplete( - receiveCompletion: { _ in - if waitForImageToComplete { - completion?() + { + OpenGroupManager + .roomImage( + fileId: imageId, + for: roomToken, + on: server, + existingData: openGroup.imageData, + using: dependencies + ) + // Note: We need to subscribe and receive on different threads to ensure the + // logic in 'receiveValue' doesn't result in a reentrancy database issue + .subscribe(on: OpenGroupAPI.workQueue) + .receive(on: DispatchQueue.global(qos: .default)) + .sinkUntilComplete( + receiveCompletion: { _ in + if waitForImageToComplete { + completion?() + } + }, + receiveValue: { data in + dependencies.storage.write { db in + _ = try OpenGroup + .filter(id: threadId) + .updateAll(db, OpenGroup.Columns.imageData.set(to: data)) + } } - }, - receiveValue: { data in - dependencies.storage.write { db in - _ = try OpenGroup - .filter(id: threadId) - .updateAll(db, OpenGroup.Columns.imageData.set(to: data)) - } - } - ) - } - else if waitForImageToComplete { + ) + } + else if waitForImageToComplete { + completion?() + } + + // If we want to wait for the image to complete then don't call the completion here + guard !waitForImageToComplete else { return } + + // Finish completion?() } - - // If we want to wait for the image to complete then don't call the completion here - guard !waitForImageToComplete else { return } - - // Finish - completion?() } } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift index 34ce81e73..693f84da7 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift @@ -35,7 +35,7 @@ extension MessageReceiver { guard let profilePictureUrl: String = profile.profilePictureUrl, let profileKey: Data = profile.profileKey - else { return .none } + else { return .remove } return .updateTo( url: profilePictureUrl, diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift index b752b6464..35174c7fa 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift @@ -14,7 +14,7 @@ public final class ClosedGroupPoller: Poller { override var namespaces: [SnodeAPI.Namespace] { ClosedGroupPoller.namespaces } override var maxNodePollCount: UInt { 0 } - private static let minPollInterval: Double = 2 + private static let minPollInterval: Double = 3 private static let maxPollInterval: Double = 30 // MARK: - Initialization @@ -78,30 +78,12 @@ public final class ClosedGroupPoller: Poller { return nextPollInterval } - override func getSnodeForPolling( - for publicKey: String - ) -> AnyPublisher { - return SnodeAPI.getSwarm(for: publicKey) - .tryMap { swarm -> Snode in - guard let snode: Snode = swarm.randomElement() else { - throw OnionRequestAPIError.insufficientSnodes - } - - return snode - } - .eraseToAnyPublisher() - } - override func handlePollError( _ error: Error, for publicKey: String, using dependencies: SMKDependencies = SMKDependencies() - ) { + ) -> Bool { SNLog("Polling failed for closed group with public key: \(publicKey) due to error: \(error).") - - // Try to restart the poller from scratch - Threading.pollerQueue.async { [weak self] in - self?.setUpPolling(for: publicKey, using: dependencies) - } + return true } } diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift index b144f9479..02b793160 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift @@ -11,9 +11,6 @@ public final class CurrentUserPoller: Poller { public static var namespaces: [SnodeAPI.Namespace] = [ .default, .configUserProfile, .configContacts, .configConvoInfoVolatile, .configUserGroups ] - - private var targetSnode: Atomic = Atomic(nil) - private var usedSnodes: Atomic> = Atomic([]) // MARK: - Settings @@ -63,53 +60,16 @@ public final class CurrentUserPoller: Poller { return min(maxRetryInterval, nextDelay) } - override func getSnodeForPolling( - for publicKey: String - ) -> AnyPublisher { - if let targetSnode: Snode = self.targetSnode.wrappedValue { - return Just(targetSnode) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - } - - // Used the cached swarm for the given key and update the list of unusedSnodes - let swarm: Set = (SnodeAPI.swarmCache.wrappedValue[publicKey] ?? []) - let unusedSnodes: Set = swarm.subtracting(usedSnodes.wrappedValue) - - // randomElement() uses the system's default random generator, which is cryptographically secure - if let nextSnode: Snode = unusedSnodes.randomElement() { - self.targetSnode.mutate { $0 = nextSnode } - self.usedSnodes.mutate { $0.insert(nextSnode) } - - return Just(nextSnode) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - } - - // If we haven't retrieved a target snode at this point then either the cache - // is empty or we have used all of the snodes and need to start from scratch - return SnodeAPI.getSwarm(for: publicKey) - .tryFlatMap { [weak self] _ -> AnyPublisher in - guard let strongSelf = self else { throw SnodeAPIError.generic } - - self?.targetSnode.mutate { $0 = nil } - self?.usedSnodes.mutate { $0.removeAll() } - - return strongSelf.getSnodeForPolling(for: publicKey) - } - .eraseToAnyPublisher() - } - override func handlePollError( _ error: Error, for publicKey: String, using dependencies: SMKDependencies = SMKDependencies() - ) { + ) -> Bool { if UserDefaults.sharedLokiProject?[.isMainAppActive] != true { // Do nothing when an error gets throws right after returning from the background (happens frequently) } else if let targetSnode: Snode = targetSnode.wrappedValue { - SNLog("Polling \(targetSnode) failed; dropping it and switching to next snode.") + SNLog("Main Poller polling \(targetSnode) failed; dropping it and switching to next snode.") self.targetSnode.mutate { $0 = nil } SnodeAPI.dropSnodeFromSwarmIfNeeded(targetSnode, publicKey: publicKey) } @@ -117,9 +77,6 @@ public final class CurrentUserPoller: Poller { SNLog("Polling failed due to having no target service node.") } - // Try to restart the poller from scratch - Threading.pollerQueue.async { [weak self] in - self?.setUpPolling(for: publicKey, using: dependencies) - } + return true } } diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift index a4cce3c35..48cb16f64 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift @@ -57,49 +57,42 @@ extension OpenGroupAPI { ) { guard hasStarted else { return } - dependencies.storage - .readPublisher { [server = server] db in - try OpenGroup - .filter(OpenGroup.Columns.server == server) - .select(min(OpenGroup.Columns.pollFailureCount)) - .asRequest(of: TimeInterval.self) - .fetchOne(db) - } - .tryFlatMap { [weak self] minPollFailureCount -> AnyPublisher<(TimeInterval, TimeInterval), Error> in - guard let strongSelf = self else { throw OpenGroupAPIError.invalidPoll } - - let lastPollStart: TimeInterval = Date().timeIntervalSince1970 - let nextPollInterval: TimeInterval = Poller.getInterval( - for: (minPollFailureCount ?? 0), - minInterval: Poller.minPollInterval, - maxInterval: Poller.maxPollInterval - ) - - // Wait until the last poll completes before polling again ensuring we don't poll any faster than - // the 'nextPollInterval' value - return strongSelf.poll(using: dependencies) - .map { _ in (lastPollStart, nextPollInterval) } - .eraseToAnyPublisher() - } + let server: String = self.server + let lastPollStart: TimeInterval = Date().timeIntervalSince1970 + + poll(using: dependencies) .subscribe(on: dependencies.subscribeQueue) .receive(on: dependencies.receiveQueue) .sinkUntilComplete( - receiveValue: { [weak self] lastPollStart, nextPollInterval in + receiveCompletion: { [weak self] _ in + let minPollFailureCount: Int64 = dependencies.storage + .read { db in + try OpenGroup + .filter(OpenGroup.Columns.server == server) + .select(min(OpenGroup.Columns.pollFailureCount)) + .asRequest(of: Int64.self) + .fetchOne(db) + } + .defaulting(to: 0) + + // Calculate the remaining poll delay let currentTime: TimeInterval = Date().timeIntervalSince1970 + let nextPollInterval: TimeInterval = Poller.getInterval( + for: TimeInterval(minPollFailureCount), + minInterval: Poller.minPollInterval, + maxInterval: Poller.maxPollInterval + ) let remainingInterval: TimeInterval = max(0, nextPollInterval - (currentTime - lastPollStart)) - + + // Schedule the next poll guard remainingInterval > 0 else { return dependencies.subscribeQueue.async { self?.pollRecursively(using: dependencies) } } - - self?.timer = Timer.scheduledTimerOnMainThread(withTimeInterval: remainingInterval, repeats: false) { timer in - timer.invalidate() - - dependencies.subscribeQueue.async { - self?.pollRecursively(using: dependencies) - } + + dependencies.subscribeQueue.asyncAfter(deadline: .now() + .milliseconds(Int(remainingInterval * 1000)), qos: .default) { + self?.pollRecursively(using: dependencies) } } ) @@ -227,7 +220,7 @@ extension OpenGroupAPI { .defaulting(to: 0) var prunedIds: [String] = [] - Storage.shared.writeAsync { db in + dependencies.storage.writeAsync { db in struct Info: Decodable, FetchableRecord { let id: String let shouldBeVisible: Bool diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift index 480f40a07..60ae24320 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift @@ -8,11 +8,14 @@ import SessionSnodeKit import SessionUtilitiesKit public class Poller { - private var timers: Atomic<[String: Timer]> = Atomic([:]) + private var cancellables: Atomic<[String: AnyCancellable]> = Atomic([:]) internal var isPolling: Atomic<[String: Bool]> = Atomic([:]) internal var pollCount: Atomic<[String: Int]> = Atomic([:]) internal var failureCount: Atomic<[String: Int]> = Atomic([:]) + internal var targetSnode: Atomic = Atomic(nil) + private var usedSnodes: Atomic> = Atomic([]) + // MARK: - Settings /// The namespaces which this poller queries @@ -20,7 +23,7 @@ public class Poller { preconditionFailure("abstract class - override in subclass") } - /// The number of times the poller can poll before swapping to a new snode + /// The number of times the poller can poll a single snode before swapping to a new snode internal var maxNodePollCount: UInt { preconditionFailure("abstract class - override in subclass") } @@ -39,7 +42,7 @@ public class Poller { public func stopPolling(for publicKey: String) { isPolling.mutate { $0[publicKey] = false } - timers.mutate { $0[publicKey]?.invalidate() } + cancellables.mutate { $0[publicKey]?.cancel() } } // MARK: - Abstract Methods @@ -49,17 +52,13 @@ public class Poller { preconditionFailure("abstract class - override in subclass") } + /// Calculate the delay which should occur before the next poll internal func nextPollDelay(for publicKey: String) -> TimeInterval { preconditionFailure("abstract class - override in subclass") } - internal func getSnodeForPolling( - for publicKey: String - ) -> AnyPublisher { - preconditionFailure("abstract class - override in subclass") - } - - internal func handlePollError(_ error: Error, for publicKey: String, using dependencies: SMKDependencies) { + /// Perform and logic which should occur when the poll errors, will stop polling if `false` is returned + internal func handlePollError(_ error: Error, for publicKey: String, using dependencies: SMKDependencies) -> Bool { preconditionFailure("abstract class - override in subclass") } @@ -75,48 +74,65 @@ public class Poller { // and the timer is not created, if we mark the group as is polling // after setUpPolling. So the poller may not work, thus misses messages self?.isPolling.mutate { $0[publicKey] = true } - self?.setUpPolling(for: publicKey) + self?.pollRecursively(for: publicKey) } } - /// We want to initially trigger a poll against the target service node and then run the recursive polling, - /// if an error is thrown during the poll then this should automatically restart the polling - internal func setUpPolling( + internal func getSnodeForPolling( for publicKey: String, - using dependencies: SMKDependencies = SMKDependencies( - subscribeQueue: Threading.pollerQueue, - receiveQueue: Threading.pollerQueue - ) - ) { - guard isPolling.wrappedValue[publicKey] == true else { return } - - let namespaces: [SnodeAPI.Namespace] = self.namespaces - - getSnodeForPolling(for: publicKey) - .flatMap { snode -> AnyPublisher<[Message], Error> in - Poller.poll( - namespaces: namespaces, - from: snode, - for: publicKey, - poller: self, - using: dependencies - ) - } - .subscribe(on: dependencies.subscribeQueue) - .receive(on: dependencies.receiveQueue) - .sinkUntilComplete( - receiveCompletion: { [weak self] result in - switch result { - case .finished: self?.pollRecursively(for: publicKey, using: dependencies) - case .failure(let error): - guard self?.isPolling.wrappedValue[publicKey] == true else { return } - - self?.handlePollError(error, for: publicKey, using: dependencies) - } + using dependencies: SMKDependencies = SMKDependencies() + ) -> AnyPublisher { + // If we don't want to poll a snode multiple times then just grab a random one from the swarm + guard maxNodePollCount > 0 else { + return SnodeAPI.getSwarm(for: publicKey, using: dependencies) + .tryMap { swarm -> Snode in + try swarm.randomElement() ?? { throw OnionRequestAPIError.insufficientSnodes }() } - ) + .eraseToAnyPublisher() + } + + // If we already have a target snode then use that + if let targetSnode: Snode = self.targetSnode.wrappedValue { + return Just(targetSnode) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + + // Select the next unused snode from the swarm (if we've used them all then clear the used list and + // start cycling through them again) + return SnodeAPI.getSwarm(for: publicKey, using: dependencies) + .tryMap { [usedSnodes = self.usedSnodes, targetSnode = self.targetSnode] swarm -> Snode in + let unusedSnodes: Set = swarm.subtracting(usedSnodes.wrappedValue) + + // If we've used all of the SNodes then clear out the used list + if unusedSnodes.isEmpty { + usedSnodes.mutate { $0.removeAll() } + } + + // Select the next SNode + let nextSnode: Snode = try swarm.randomElement() ?? { throw OnionRequestAPIError.insufficientSnodes }() + targetSnode.mutate { $0 = nextSnode } + usedSnodes.mutate { $0.insert(nextSnode) } + + return nextSnode + } + .eraseToAnyPublisher() } - + + internal func incrementPollCount(publicKey: String) { + guard maxNodePollCount > 0 else { return } + + let pollCount: Int = (self.pollCount.wrappedValue[publicKey] ?? 0) + self.pollCount.mutate { $0[publicKey] = (pollCount + 1) } + + // Check if we've polled the serice node too many times + guard pollCount > maxNodePollCount else { return } + + // If we have polled this service node more than the maximum allowed then clear out + // the 'targetServiceNode' value + self.targetSnode.mutate { $0 = nil } + } + private func pollRecursively( for publicKey: String, using dependencies: SMKDependencies = SMKDependencies() @@ -124,65 +140,60 @@ public class Poller { guard isPolling.wrappedValue[publicKey] == true else { return } let namespaces: [SnodeAPI.Namespace] = self.namespaces - let nextPollInterval: TimeInterval = nextPollDelay(for: publicKey) + let lastPollStart: TimeInterval = Date().timeIntervalSince1970 + let lastPollInterval: TimeInterval = nextPollDelay(for: publicKey) + let getSnodePublisher: AnyPublisher = getSnodeForPolling(for: publicKey) - timers.mutate { - $0[publicKey] = Timer.scheduledTimerOnMainThread( - withTimeInterval: nextPollInterval, - repeats: false - ) { [weak self] timer in - timer.invalidate() - - self?.getSnodeForPolling(for: publicKey) - .flatMap { snode -> AnyPublisher<[Message], Error> in - Poller.poll( - namespaces: namespaces, - from: snode, - for: publicKey, - poller: self, - using: dependencies + // Store the publisher intp the cancellables dictionary + cancellables.mutate { [weak self] cancellables in + cancellables[publicKey] = getSnodePublisher + .flatMap { snode -> AnyPublisher<[Message], Error> in + Poller.poll( + namespaces: namespaces, + from: snode, + for: publicKey, + poller: self, + using: dependencies + ) + } + .subscribe(on: dependencies.subscribeQueue) + .receive(on: dependencies.receiveQueue) + .sink( + receiveCompletion: { result in + switch result { + case .failure(let error): + // Determine if the error should stop us from polling anymore + guard self?.handlePollError(error, for: publicKey, using: dependencies) == true else { + return + } + + case .finished: break + } + + // Increment the poll count + self?.incrementPollCount(publicKey: publicKey) + + // Calculate the remaining poll delay + let currentTime: TimeInterval = Date().timeIntervalSince1970 + let nextPollInterval: TimeInterval = ( + self?.nextPollDelay(for: publicKey) ?? + lastPollInterval ) - } - .subscribe(on: dependencies.subscribeQueue) - .receive(on: dependencies.receiveQueue) - .sinkUntilComplete( - receiveCompletion: { result in - switch result { - case .failure(let error): self?.handlePollError(error, for: publicKey, using: dependencies) - case .finished: - let maxNodePollCount: UInt = (self?.maxNodePollCount ?? 0) - - // If we have polled this service node more than the - // maximum allowed then throw an error so the parent - // loop can restart the polling - if maxNodePollCount > 0 { - let pollCount: Int = (self?.pollCount.wrappedValue[publicKey] ?? 0) - self?.pollCount.mutate { $0[publicKey] = (pollCount + 1) } - - guard pollCount < maxNodePollCount else { - let newSnodeNextPollInterval: TimeInterval = (self?.nextPollDelay(for: publicKey) ?? nextPollInterval) - - self?.timers.mutate { - $0[publicKey] = Timer.scheduledTimerOnMainThread( - withTimeInterval: newSnodeNextPollInterval, - repeats: false - ) { [weak self] timer in - timer.invalidate() - - self?.pollCount.mutate { $0[publicKey] = 0 } - self?.setUpPolling(for: publicKey, using: dependencies) - } - } - return - } - } - - // Otherwise just loop - self?.pollRecursively(for: publicKey, using: dependencies) + let remainingInterval: TimeInterval = max(0, nextPollInterval - (currentTime - lastPollStart)) + + // Schedule the next poll + guard remainingInterval > 0 else { + return dependencies.subscribeQueue.async { + self?.pollRecursively(for: publicKey, using: dependencies) } } - ) - } + + dependencies.subscribeQueue.asyncAfter(deadline: .now() + .milliseconds(Int(remainingInterval * 1000)), qos: .default) { + self?.pollRecursively(for: publicKey, using: dependencies) + } + }, + receiveValue: { _ in } + ) } } @@ -199,6 +210,7 @@ public class Poller { isBackgroundPollValid: @escaping (() -> Bool) = { true }, poller: Poller? = nil, using dependencies: SMKDependencies = SMKDependencies( + subscribeQueue: Threading.pollerQueue, receiveQueue: Threading.pollerQueue ) ) -> AnyPublisher<[Message], Error> { diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift index c504a0c35..7e83611db 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift @@ -80,7 +80,8 @@ internal extension SessionUtil { try Interaction .filter( Interaction.Columns.threadId == threadId && - Interaction.Columns.timestampMs <= lastReadTimestampMs + Interaction.Columns.timestampMs <= lastReadTimestampMs && + Interaction.Columns.wasRead == false ) .updateAll( // Handling a config update so don't use `updateAllAndConfig` db, diff --git a/SessionUtilitiesKit/Database/Types/PagedDatabaseObserver.swift b/SessionUtilitiesKit/Database/Types/PagedDatabaseObserver.swift index a2640835c..ab6ae915f 100644 --- a/SessionUtilitiesKit/Database/Types/PagedDatabaseObserver.swift +++ b/SessionUtilitiesKit/Database/Types/PagedDatabaseObserver.swift @@ -158,6 +158,9 @@ public class PagedDatabaseObserver: TransactionObserver where /// to avoid blocking the DBWrite thread we dispatch to a serial `commitProcessingQueue` to process the incoming changes (in the past not doing /// so was resulting in hanging when there was a lot of activity happening) public func databaseDidCommit(_ db: Database) { + // If there were no pending changes in the commit then do nothing + guard !self.changesInCommit.wrappedValue.isEmpty else { return } + // Since we can't be sure the behaviours of 'databaseDidChange' and 'databaseDidCommit' won't change in // the future we extract and clear the values in 'changesInCommit' since it's 'Atomic' so will different // threads modifying the data resulting in us missing a change @@ -174,9 +177,6 @@ public class PagedDatabaseObserver: TransactionObserver where } private func processDatabaseCommit(committedChanges: Set) { - // Do nothing when there are no changes - guard !committedChanges.isEmpty else { return } - typealias AssociatedDataInfo = [(hasChanges: Bool, data: ErasedAssociatedRecord)] typealias UpdatedData = (cache: DataCache, pageInfo: PagedData.PageInfo, hasChanges: Bool, associatedData: AssociatedDataInfo) From 868b4cc24e540dffd839ce1bceeca90cdf6d3338 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Mon, 3 Jul 2023 09:06:39 +1000 Subject: [PATCH 115/135] [WIP] Started looking at creating the CI config file --- .drone.jsonnet | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 .drone.jsonnet diff --git a/.drone.jsonnet b/.drone.jsonnet new file mode 100644 index 000000000..74ad7f5bf --- /dev/null +++ b/.drone.jsonnet @@ -0,0 +1,74 @@ +local submodule_commands = ['git fetch --tags', 'git submodule update --init --recursive --depth=1']; + +local submodules = { + name: 'submodules', + image: 'drone/git', + commands: submodule_commands, +}; + +// cmake options for static deps mirror +local ci_dep_mirror(want_mirror) = (if want_mirror then ' -DLOCAL_MIRROR=https://oxen.rocks/deps ' else ''); + +// Macos build +local mac_builder(name, + build_type='Release', + werror=true, + cmake_extra='', + local_mirror=true, + extra_cmds=[], + jobs=6, + codesign='-DCODESIGN=OFF', + allow_fail=false) = { + kind: 'pipeline', + type: 'exec', + name: name, + platform: { os: 'darwin', arch: 'amd64' }, + steps: [ + { name: 'submodules', commands: submodule_commands }, + { + name: 'build', + environment: { SSH_KEY: { from_secret: 'SSH_KEY' } }, + commands: [ + 'echo "Building on ${DRONE_STAGE_MACHINE}"', + // If you don't do this then the C compiler doesn't have an include path containing + // basic system headers. WTF apple: + 'export SDKROOT="$(xcrun --sdk macosx --show-sdk-path)"', + 'ulimit -n 1024', // because macos sets ulimit to 256 for some reason yeah idk + './contrib/mac-configure.sh ' + + ci_dep_mirror(local_mirror) + + (if build_type == 'Debug' then ' -DWARN_DEPRECATED=OFF ' else '') + + codesign, + 'cd build-mac', + // We can't use the 'package' target here because making a .dmg requires an active logged in + // macos gui to invoke Finder to invoke the partitioning tool to create a partitioned (!) + // disk image. Most likely the GUI is required because if you lose sight of how pretty the + // surface of macOS is you might see how ugly the insides are. + 'ninja -j' + jobs + ' assemble_gui', + 'cd ..', + ] + extra_cmds, + }, + ], +}; + + +[ + // TODO: Unit tests + // TODO: Build for UI testing + // TODO: Production build + { + kind: 'pipeline', + type: 'exec', + name: 'MacOS', + platform: { os: 'darwin', arch: 'amd64' }, + steps: [ + // TODO: Need a depth of 2? (libSession-util has it's own submodules) + { name: 'submodules', commands: submodule_commands }, + { + name: 'build', + commands: [ + 'echo "This is a test message"', + ], + }, + ], + }, +] \ No newline at end of file From 43b2aaf8bbb6f30300ae610e165f949f2c3c1add Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 12 Jul 2023 15:03:42 +1000 Subject: [PATCH 116/135] Trying to progress on the CI builds Reworked the way libSession-util builds to use a static library and be less hacky Updated to the latest version of WebRTC-lib (no longer includes bitcode) Removed the 'skip_web_rtc_re_rsync' patch as it's no longer an issue since the bitcode was removed so the framework is much smaller --- .drone-static-upload.sh | 65 +++ .drone.jsonnet | 101 ++-- Podfile | 10 - Podfile.lock | 6 +- Scripts/build_libSession_util.sh | 234 +++++++--- Scripts/skip_web_rtc_re_rsync.patch | 12 - Session.xcodeproj/project.pbxproj | 433 ++++++++---------- .../xcshareddata/xcschemes/Session.xcscheme | 18 - .../xcschemes/SessionMessagingKit.xcscheme | 20 +- ...ssionNotificationServiceExtension.xcscheme | 18 - .../xcschemes/SessionShareExtension.xcscheme | 18 - .../xcschemes/SessionUtilitiesKit.xcscheme | 20 +- .../xcschemes/SignalUtilitiesKit.xcscheme | 20 +- .../Jobs/Types/ConfigurationSyncJob.swift | 1 - .../Open Groups/OpenGroupManagerSpec.swift | 2 +- 15 files changed, 475 insertions(+), 503 deletions(-) create mode 100644 .drone-static-upload.sh delete mode 100644 Scripts/skip_web_rtc_re_rsync.patch diff --git a/.drone-static-upload.sh b/.drone-static-upload.sh new file mode 100644 index 000000000..4730e5c30 --- /dev/null +++ b/.drone-static-upload.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash + +# Script used with Drone CI to upload build artifacts (because specifying all this in +# .drone.jsonnet is too painful). + + + +set -o errexit + +if [ -z "$SSH_KEY" ]; then + echo -e "\n\n\n\e[31;1mUnable to upload artifact: SSH_KEY not set\e[0m" + # Just warn but don't fail, so that this doesn't trigger a build failure for untrusted builds + exit 0 +fi + +echo "$SSH_KEY" >ssh_key + +set -o xtrace # Don't start tracing until *after* we write the ssh key + +chmod 600 ssh_key + +if [ -n "$DRONE_TAG" ]; then + # For a tag build use something like `session-ios-v1.2.3` + base="session-ios-$DRONE_TAG" +else + # Otherwise build a length name from the datetime and commit hash, such as: + # session-ios-20200522T212342Z-04d7dcc54 + base="session-ios-$(date --date=@$DRONE_BUILD_CREATED +%Y%m%dT%H%M%SZ)-${DRONE_COMMIT:0:9}" +fi + +mkdir -v "$base" + +# Copy over the build products +cp -av build/Build/Products/App\ Store\ Release-iphonesimulator/Session.app "$base" + +# tar dat shiz up yo +archive="$base.tar.xz" +tar cJvf "$archive" "$base" + +upload_to="oxen.rocks/${DRONE_REPO// /_}/${DRONE_BRANCH// /_}" + +# sftp doesn't have any equivalent to mkdir -p, so we have to split the above up into a chain of +# -mkdir a/, -mkdir a/b/, -mkdir a/b/c/, ... commands. The leading `-` allows the command to fail +# without error. +upload_dirs=(${upload_to//\// }) +put_debug= +mkdirs= +dir_tmp="" +for p in "${upload_dirs[@]}"; do + dir_tmp="$dir_tmp$p/" + mkdirs="$mkdirs +-mkdir $dir_tmp" +done +if [ -e "$base-debug-symbols.tar.xz" ] ; then + put_debug="put $base-debug-symbols.tar.xz $upload_to" +fi +sftp -i ssh_key -b - -o StrictHostKeyChecking=off drone@oxen.rocks <&1 # Save original stdout + +# Ensure the build directory exists (in case we need it before XCode creates it) +mkdir -p "${TARGET_BUILD_DIR}/libSessionUtil" + +# Remove any old build errors +rm -rf "${TARGET_BUILD_DIR}/libSessionUtil/libsession_util_output.log" + +# Restore stdout and stderr and redirect it to the 'libsession_util_output.log' file +exec &> "${TARGET_BUILD_DIR}/libSessionUtil/libsession_util_output.log" + +# Define a function to echo a message. +function echo_message() { + exec 1>&3 # Restore stdout + echo "$1" + exec >> "${TARGET_BUILD_DIR}/libSessionUtil/libsession_util_output.log" # Redirect all output to the log file +} + +echo_message "info: Validating build requirements" + +set -x # Ensure the build directory exists (in case we need it before XCode creates it) mkdir -p "${TARGET_BUILD_DIR}" -# Remove any old build errors -rm -rf "${TARGET_BUILD_DIR}/libsession_util_error.log" - -# First ensure cmake is installed (store the error in a log and exit with a success status - xcode will output the error) -echo "info: Validating build requirements" - if ! which cmake > /dev/null; then - touch "${TARGET_BUILD_DIR}/libsession_util_error.log" - echo "error: cmake is required to build, please install (can install via homebrew with 'brew install cmake')." - echo "error: cmake is required to build, please install (can install via homebrew with 'brew install cmake')." > "${TARGET_BUILD_DIR}/libsession_util_error.log" + echo_message "error: cmake is required to build, please install (can install via homebrew with 'brew install cmake')." exit 0 fi # Check if we have the `LibSession-Util` submodule checked out and if not (depending on the 'SHOULD_AUTO_INIT_SUBMODULES' argument) perform the checkout if [ ! -d "${SRCROOT}/LibSession-Util" ] || [ ! -d "${SRCROOT}/LibSession-Util/src" ] || [ ! "$(ls -A "${SRCROOT}/LibSession-Util")" ]; then - if [ "${SHOULD_AUTO_INIT_SUBMODULES}" != "false" ] & command -v git >/dev/null 2>&1; then - echo "info: LibSession-Util submodule doesn't exist, resetting and checking out recusively now" - git submodule foreach --recursive git reset --hard - git submodule update --init --recursive - echo "info: Checkout complete" - else - touch "${TARGET_BUILD_DIR}/libsession_util_error.log" - echo "error: Need to fetch LibSession-Util submodule (git submodule update --init --recursive)." - echo "error: Need to fetch LibSession-Util submodule (git submodule update --init --recursive)." > "${TARGET_BUILD_DIR}/libsession_util_error.log" - exit 0 - fi + echo_message "error: Need to fetch LibSession-Util submodule (git submodule update --init --recursive)." + exit 0 else are_submodules_valid() { local PARENT_PATH=$1 @@ -82,7 +86,7 @@ else # If the child path doesn't exist then it's invalid if [ ! -d "${PARENT_PATH}/${CHILD_PATH}" ]; then - echo "info: Submodule '${RELATIVE_PATH}/${CHILD_PATH}' doesn't exist." + echo_message "info: Submodule '${RELATIVE_PATH}/${CHILD_PATH}' doesn't exist." return 1 fi @@ -90,7 +94,7 @@ else local RESULT=$? if [ "${RESULT}" -eq 1 ]; then - echo "info: Submodule '${RELATIVE_PATH}/${CHILD_PATH}' is in an invalid state." + echo_message "info: Submodule '${RELATIVE_PATH}/${CHILD_PATH}' is in an invalid state." return 1 fi done @@ -104,18 +108,8 @@ else HAS_INVALID_SUBMODULE=$? if [ "${HAS_INVALID_SUBMODULE}" -eq 1 ]; then - if [ "${SHOULD_AUTO_INIT_SUBMODULES}" != "false" ] && command -v git >/dev/null 2>&1; then - echo "info: Submodules are in an invalid state, resetting and checking out recusively now" - cd "${SRCROOT}/LibSession-Util" - git submodule foreach --recursive git reset --hard - git submodule update --init --recursive - echo "info: Checkout complete" - else - touch "${TARGET_BUILD_DIR}/libsession_util_error.log" - echo "error: Submodules are in an invalid state, please delete 'LibSession-Util' and run 'git submodule update --init --recursive'." - echo "error: Submodules are in an invalid state, please delete 'LibSession-Util' and run 'git submodule update --init --recursive'." > "${TARGET_BUILD_DIR}/libsession_util_error.log" - exit 0 - fi + echo_message "error: Submodules are in an invalid state, please delete 'LibSession-Util' and run 'git submodule update --init --recursive'." + exit 0 fi fi @@ -125,49 +119,143 @@ echo "info: Checking for changes to source" NEW_SOURCE_HASH=$(find "${SRCROOT}/LibSession-Util/src" -type f -exec md5 {} + | awk '{print $NF}' | sort | md5 | awk '{print $NF}') NEW_HEADER_HASH=$(find "${SRCROOT}/LibSession-Util/include" -type f -exec md5 {} + | awk '{print $NF}' | sort | md5 | awk '{print $NF}') -if [ -f "${TARGET_BUILD_DIR}/libsession_util_source_hash.log" ]; then - read -r OLD_SOURCE_HASH < "${TARGET_BUILD_DIR}/libsession_util_source_hash.log" +if [ -f "${TARGET_BUILD_DIR}/libSessionUtil/libsession_util_source_hash.log" ]; then + read -r OLD_SOURCE_HASH < "${TARGET_BUILD_DIR}/libSessionUtil/libsession_util_source_hash.log" fi -if [ -f "${TARGET_BUILD_DIR}/libsession_util_header_hash.log" ]; then - read -r OLD_HEADER_HASH < "${TARGET_BUILD_DIR}/libsession_util_header_hash.log" +if [ -f "${TARGET_BUILD_DIR}/libSessionUtil/libsession_util_header_hash.log" ]; then + read -r OLD_HEADER_HASH < "${TARGET_BUILD_DIR}/libSessionUtil/libsession_util_header_hash.log" fi -if [ -f "${TARGET_BUILD_DIR}/libsession_util_archs.log" ]; then - read -r OLD_ARCHS < "${TARGET_BUILD_DIR}/libsession_util_archs.log" +if [ -f "${TARGET_BUILD_DIR}/libSessionUtil/libsession_util_archs.log" ]; then + read -r OLD_ARCHS < "${TARGET_BUILD_DIR}/libSessionUtil/libsession_util_archs.log" fi -# Start the libSession-util build if it doesn't already exists -if [ "${NEW_SOURCE_HASH}" != "${OLD_SOURCE_HASH}" ] || [ "${NEW_HEADER_HASH}" != "${OLD_HEADER_HASH}" ] || [ "${ARCHS[*]}" != "${OLD_ARCHS}" ] || [ ! -d "${TARGET_BUILD_DIR}/libsession-util.xcframework" ]; then - echo "info: Build is not up-to-date - creating new build" - echo "" - - # Remove any existing build files (just to be safe) - rm -rf "${TARGET_BUILD_DIR}/libsession-util.a" - rm -rf "${TARGET_BUILD_DIR}/libsession-util.xcframework" - rm -rf "${BUILD_DIR}/libsession-util.xcframework" - - # Trigger the new build - cd "${SRCROOT}/LibSession-Util" - result=$(./utils/ios.sh "libsession-util" false) - - if [ $? -ne 0 ]; then - touch "${TARGET_BUILD_DIR}/libsession_util_error.log" - echo "error: Failed to build libsession-util (See details in '${TARGET_BUILD_DIR}/pre-action-output.log')." - echo "error: Failed to build libsession-util (See details in '${TARGET_BUILD_DIR}/pre-action-output.log')." > "${TARGET_BUILD_DIR}/libsession_util_error.log" - exit 0 - fi - - # Save the updated source hash to disk to prevent rebuilds when there were no changes - echo "${NEW_SOURCE_HASH}" > "${TARGET_BUILD_DIR}/libsession_util_source_hash.log" - echo "${NEW_HEADER_HASH}" > "${TARGET_BUILD_DIR}/libsession_util_header_hash.log" - echo "${ARCHS[*]}" > "${TARGET_BUILD_DIR}/libsession_util_archs.log" - echo "" - echo "info: Build complete" -else - echo "info: Build is up-to-date" +# If all of the hashes match, the archs match and there is a library file then we can just stop here +if [ "${NEW_SOURCE_HASH}" == "${OLD_SOURCE_HASH}" ] && [ "${NEW_HEADER_HASH}" == "${OLD_HEADER_HASH}" ] && [ "${ARCHS[*]}" == "${OLD_ARCHS}" ] && [ -f "${TARGET_BUILD_DIR}/libSessionUtil/libSessionUtil.a" ]; then + echo_message "info: Build is up-to-date" + exit 0 fi -# Move the target-specific libSession-util build to the parent build directory (so XCode can have a reference to a single build) -rm -rf "${BUILD_DIR}/libsession-util.xcframework" -cp -r "${TARGET_BUILD_DIR}/libsession-util.xcframework" "${BUILD_DIR}/libsession-util.xcframework" +# If any of the above differ then we need to rebuild +echo_message "info: Build is not up-to-date - creating new build" + +# Import settings from XCode (defaulting values if not present) +VALID_SIM_ARCHS=(arm64 x86_64) +VALID_DEVICE_ARCHS=(arm64) +VALID_SIM_ARCH_PLATFORMS=(SIMULATORARM64 SIMULATOR64) +VALID_DEVICE_ARCH_PLATFORMS=(OS64) + +OUTPUT_DIR="${TARGET_BUILD_DIR}" +IPHONEOS_DEPLOYMENT_TARGET=${IPHONEOS_DEPLOYMENT_TARGET} +ENABLE_BITCODE=${ENABLE_BITCODE} + +# Generate the target architectures we want to build for +TARGET_ARCHS=() +TARGET_PLATFORMS=() +TARGET_SIM_ARCHS=() +TARGET_DEVICE_ARCHS=() + +if [ -z $PLATFORM_NAME ] || [ $PLATFORM_NAME = "iphonesimulator" ]; then + for i in "${!VALID_SIM_ARCHS[@]}"; do + ARCH="${VALID_SIM_ARCHS[$i]}" + ARCH_PLATFORM="${VALID_SIM_ARCH_PLATFORMS[$i]}" + + if [[ " ${ARCHS[*]} " =~ " ${ARCH} " ]]; then + TARGET_ARCHS+=("sim-${ARCH}") + TARGET_PLATFORMS+=("${ARCH_PLATFORM}") + TARGET_SIM_ARCHS+=("sim-${ARCH}") + fi + done +fi + +if [ -z $PLATFORM_NAME ] || [ $PLATFORM_NAME = "iphoneos" ]; then + for i in "${!VALID_DEVICE_ARCHS[@]}"; do + ARCH="${VALID_DEVICE_ARCHS[$i]}" + ARCH_PLATFORM="${VALID_DEVICE_ARCH_PLATFORMS[$i]}" + + if [[ " ${ARCHS[*]} " =~ " ${ARCH} " ]]; then + TARGET_ARCHS+=("ios-${ARCH}") + TARGET_PLATFORMS+=("${ARCH_PLATFORM}") + TARGET_DEVICE_ARCHS+=("ios-${ARCH}") + fi + done +fi + +# Build the individual architectures +for i in "${!TARGET_ARCHS[@]}"; do + build="${TARGET_BUILD_DIR}/libSessionUtil/${TARGET_ARCHS[$i]}" + platform="${TARGET_PLATFORMS[$i]}" + echo_message "Building ${TARGET_ARCHS[$i]} for $platform in $build" + + cd "${SRCROOT}/LibSession-Util" + ./utils/static-bundle.sh "$build" "" \ + -DCMAKE_TOOLCHAIN_FILE="${SRCROOT}/LibSession-Util/external/ios-cmake/ios.toolchain.cmake" \ + -DPLATFORM=$platform \ + -DDEPLOYMENT_TARGET=$IPHONEOS_DEPLOYMENT_TARGET \ + -DENABLE_BITCODE=$ENABLE_BITCODE + + if [ $? -ne 0 ]; then + LAST_OUTPUT=$(tail -n 4 "${TARGET_BUILD_DIR}/libSessionUtil/libsession_util_output.log" | head -n 1) + echo_message "error: $LAST_OUTPUT" + exit 1 + fi +done + +# Remove the old static library file +rm -rf "${TARGET_BUILD_DIR}/libSessionUtil/libSessionUtil.a" +rm -rf "${TARGET_BUILD_DIR}/libSessionUtil/Headers" + +# If needed combine simulator builds into a multi-arch lib +if [ "${#TARGET_SIM_ARCHS[@]}" -eq "1" ]; then + # Single device build + cp "${TARGET_BUILD_DIR}/libSessionUtil/${TARGET_SIM_ARCHS[0]}/libsession-util.a" "${TARGET_BUILD_DIR}/libSessionUtil/libSessionUtil.a" +elif [ "${#TARGET_SIM_ARCHS[@]}" -gt "1" ]; then + # Combine multiple device builds into a multi-arch lib + echo_message "info: Built multiple architectures, merging into single static library" + lipo -create "${TARGET_BUILD_DIR}/libSessionUtil"/sim-*/libsession-util.a -output "${TARGET_BUILD_DIR}/libSessionUtil/libSessionUtil.a" +fi + +# If needed combine device builds into a multi-arch lib +if [ "${#TARGET_DEVICE_ARCHS[@]}" -eq "1" ]; then + cp "${TARGET_BUILD_DIR}/libSessionUtil/${TARGET_DEVICE_ARCHS[0]}/libsession-util.a" "${TARGET_BUILD_DIR}/libSessionUtil/libSessionUtil.a" +elif [ "${#TARGET_DEVICE_ARCHS[@]}" -gt "1" ]; then + # Combine multiple device builds into a multi-arch lib + echo_message "info: Built multiple architectures, merging into single static library" + lipo -create "${TARGET_BUILD_DIR}/libSessionUtil"/ios-*/libsession-util.a -output "${TARGET_BUILD_DIR}/libSessionUtil/libSessionUtil.a" +fi + +# Save the updated hashes to disk to prevent rebuilds when there were no changes +echo "${NEW_SOURCE_HASH}" > "${TARGET_BUILD_DIR}/libSessionUtil/libsession_util_source_hash.log" +echo "${NEW_HEADER_HASH}" > "${TARGET_BUILD_DIR}/libSessionUtil/libsession_util_header_hash.log" +echo "${ARCHS[*]}" > "${TARGET_BUILD_DIR}/libSessionUtil/libsession_util_archs.log" +echo_message "info: Build complete" + +# Copy the headers across +echo_message "info: Copy headers and prepare modulemap" +mkdir -p "${TARGET_BUILD_DIR}/libSessionUtil/Headers" +cp -r "${SRCROOT}/LibSession-Util/include/session" "${TARGET_BUILD_DIR}/libSessionUtil/Headers" + +# The 'module.modulemap' is needed for XCode to be able to find the headers +modmap="${TARGET_BUILD_DIR}/libSessionUtil/Headers/module.modulemap" +echo "module SessionUtil {" >"$modmap" +echo " module capi {" >>"$modmap" +for x in $(cd include && find session -name '*.h'); do + echo " header \"$x\"" >>"$modmap" +done +echo -e " export *\n }" >>"$modmap" +if false; then + # If we include the cpp headers like this then Xcode will try to load them as C headers (which + # of course breaks) and doesn't provide any way to only load the ones you need (because this is + # Apple land, why would anything useful be available?). So we include the headers in the + # archive but can't let xcode discover them because it will do it wrong. + echo -e "\n module cppapi {" >>"$modmap" + for x in $(cd include && find session -name '*.hpp'); do + echo " header \"$x\"" >>"$modmap" + done + echo -e " export *\n }" >>"$modmap" +fi +echo "}" >>"$modmap" + +# Output to XCode just so the output is good +echo_message "info: libSessionUtil Ready" \ No newline at end of file diff --git a/Scripts/skip_web_rtc_re_rsync.patch b/Scripts/skip_web_rtc_re_rsync.patch deleted file mode 100644 index 6b8232e50..000000000 --- a/Scripts/skip_web_rtc_re_rsync.patch +++ /dev/null @@ -1,12 +0,0 @@ -@@ -41,0 +41,11 @@ -+ # Skip the rsync step for the WebRTC-lib in simulator builds -+ if [[ "$PLATFORM_NAME" == "iphonesimulator" ]] && [[ "$source" == *WebRTC.framework* ]]; then -+ if [[ -f "${source}/../already_rsynced.nonce" ]]; then -+ echo "Already rsynced WebRTC, skipping" -+ return 0 -+ fi -+ -+ echo "About to rsync a simulator WebRTC, creating nonce to prevent future rsyncing" -+ touch "${source}/../already_rsynced.nonce" -+ fi -+ diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 11e85bdf7..4e3e4eb73 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -744,6 +744,8 @@ FD97B2402A3FEB050027DD57 /* ARC4RandomNumberGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD97B23F2A3FEB050027DD57 /* ARC4RandomNumberGenerator.swift */; }; FD97B2422A3FEBF30027DD57 /* UnreadMarkerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD97B2412A3FEBF30027DD57 /* UnreadMarkerCell.swift */; }; FD9B30F3293EA0BF008DEE3E /* BatchResponseSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9B30F2293EA0BF008DEE3E /* BatchResponseSpec.swift */; }; + FD9BDE002A5D22B7005F1EBC /* libSessionUtil.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FD9BDDF82A5D2294005F1EBC /* libSessionUtil.a */; }; + FD9BDE012A5D24EA005F1EBC /* SessionUIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C331FF1B2558F9D300070591 /* SessionUIKit.framework */; }; FDA1E83629A5748F00C5C3BD /* ConfigUserGroupsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA1E83529A5748F00C5C3BD /* ConfigUserGroupsSpec.swift */; }; FDA1E83929A5771A00C5C3BD /* LibSessionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA1E83829A5771A00C5C3BD /* LibSessionSpec.swift */; }; FDA1E83B29A5F2D500C5C3BD /* SessionUtil+Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA1E83A29A5F2D500C5C3BD /* SessionUtil+Shared.swift */; }; @@ -902,7 +904,6 @@ FDF848F529413EEC007DCAE5 /* SessionCell+Styling.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848F429413EEC007DCAE5 /* SessionCell+Styling.swift */; }; FDF848F729414477007DCAE5 /* CurrentUserPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848F629414477007DCAE5 /* CurrentUserPoller.swift */; }; FDFC4D9A29F0C51500992FB6 /* String+Trimming.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D22553860900C340D1 /* String+Trimming.swift */; }; - FDFC4E1929F1F9A600992FB6 /* libsession-util.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FDFC4E1829F1F9A600992FB6 /* libsession-util.xcframework */; }; FDFD645927F26C6800808CA1 /* Array+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D12553860800C340D1 /* Array+Utilities.swift */; }; FDFD645D27F273F300808CA1 /* MockGeneralCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */; }; FDFDE124282D04F20098B17F /* MediaDismissAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE123282D04F20098B17F /* MediaDismissAnimationController.swift */; }; @@ -1054,20 +1055,6 @@ remoteGlobalIDString = C3C2A6EF25539DE700C340D1; remoteInfo = SessionMessagingKit; }; - FDCDB8EB28179EAF00352A0C /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = D221A080169C9E5E00537ABF /* Project object */; - proxyType = 1; - remoteGlobalIDString = D221A088169C9E5E00537ABF; - remoteInfo = Session; - }; - FDCDB8ED28179EB200352A0C /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = D221A080169C9E5E00537ABF /* Project object */; - proxyType = 1; - remoteGlobalIDString = D221A088169C9E5E00537ABF; - remoteInfo = Session; - }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -1876,6 +1863,7 @@ FD97B23F2A3FEB050027DD57 /* ARC4RandomNumberGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ARC4RandomNumberGenerator.swift; sourceTree = ""; }; FD97B2412A3FEBF30027DD57 /* UnreadMarkerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnreadMarkerCell.swift; sourceTree = ""; }; FD9B30F2293EA0BF008DEE3E /* BatchResponseSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchResponseSpec.swift; sourceTree = ""; }; + FD9BDDF82A5D2294005F1EBC /* libSessionUtil.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libSessionUtil.a; sourceTree = BUILT_PRODUCTS_DIR; }; FDA1E83529A5748F00C5C3BD /* ConfigUserGroupsSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigUserGroupsSpec.swift; sourceTree = ""; }; FDA1E83829A5771A00C5C3BD /* LibSessionSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibSessionSpec.swift; sourceTree = ""; }; FDA1E83A29A5F2D500C5C3BD /* SessionUtil+Shared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionUtil+Shared.swift"; sourceTree = ""; }; @@ -2036,7 +2024,6 @@ FDF848F229413DB0007DCAE5 /* ImagePickerHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImagePickerHandler.swift; sourceTree = ""; }; FDF848F429413EEC007DCAE5 /* SessionCell+Styling.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SessionCell+Styling.swift"; sourceTree = ""; }; FDF848F629414477007DCAE5 /* CurrentUserPoller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CurrentUserPoller.swift; sourceTree = ""; }; - FDFC4E1829F1F9A600992FB6 /* libsession-util.xcframework */ = {isa = PBXFileReference; explicitFileType = wrapper.xcframework; includeInIndex = 0; path = "libsession-util.xcframework"; sourceTree = BUILD_DIR; }; FDFD645A27F26D4600808CA1 /* Data+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+Utilities.swift"; sourceTree = ""; }; FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockGeneralCache.swift; sourceTree = ""; }; FDFDE123282D04F20098B17F /* MediaDismissAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaDismissAnimationController.swift; sourceTree = ""; }; @@ -2114,7 +2101,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - FDFC4E1929F1F9A600992FB6 /* libsession-util.xcframework in Frameworks */, + FD9BDE002A5D22B7005F1EBC /* libSessionUtil.a in Frameworks */, + FD9BDE012A5D24EA005F1EBC /* SessionUIKit.framework in Frameworks */, FDC4386C27B4E90300C60D73 /* SessionUtilitiesKit.framework in Frameworks */, C3C2A70B25539E1E00C340D1 /* SessionSnodeKit.framework in Frameworks */, BE25D9230CA2C3A40A9216EF /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework in Frameworks */, @@ -3492,6 +3480,7 @@ FDC4388E27B9FFC700C60D73 /* SessionMessagingKitTests.xctest */, FD83B9AF27CF200A005E1583 /* SessionUtilitiesKitTests.xctest */, FD71160928D00BAE00B47552 /* SessionTests.xctest */, + FD9BDDF82A5D2294005F1EBC /* libSessionUtil.a */, ); name = Products; sourceTree = ""; @@ -3499,7 +3488,6 @@ D221A08C169C9E5E00537ABF /* Frameworks */ = { isa = PBXGroup; children = ( - FDFC4E1829F1F9A600992FB6 /* libsession-util.xcframework */, B8DE1FAF26C228780079C9CE /* SignalRingRTC.framework */, C35E8AA22485C72300ACB629 /* SwiftCSV.framework */, B847570023D568EB00759540 /* SignalServiceKit.framework */, @@ -4484,7 +4472,6 @@ buildConfigurationList = 453518761FC635DD00210559 /* Build configuration list for PBXNativeTarget "SessionShareExtension" */; buildPhases = ( 55CE11E14880742A24ADC127 /* [CP] Check Pods Manifest.lock */, - FD7692EC2A524320000E4B70 /* Validate pre-build actions */, 453518641FC635DD00210559 /* Sources */, 453518651FC635DD00210559 /* Frameworks */, 453518661FC635DD00210559 /* Resources */, @@ -4508,7 +4495,6 @@ buildConfigurationList = 7BC01A45241F40AB00BC7C55 /* Build configuration list for PBXNativeTarget "SessionNotificationServiceExtension" */; buildPhases = ( 18CDA58AE057F8C9AE71F46E /* [CP] Check Pods Manifest.lock */, - FD7692ED2A52433E000E4B70 /* Validate pre-build actions */, 7BC01A37241F40AB00BC7C55 /* Sources */, 7BC01A38241F40AB00BC7C55 /* Frameworks */, 7BC01A39241F40AB00BC7C55 /* Resources */, @@ -4531,7 +4517,6 @@ buildConfigurationList = C331FF262558F9D400070591 /* Build configuration list for PBXNativeTarget "SessionUIKit" */; buildPhases = ( D5AFDC09857840D2D2631E2D /* [CP] Check Pods Manifest.lock */, - FD7692EF2A52436A000E4B70 /* Validate pre-build actions */, C331FF162558F9D300070591 /* Headers */, C331FF172558F9D300070591 /* Sources */, C331FF182558F9D300070591 /* Frameworks */, @@ -4552,7 +4537,6 @@ buildConfigurationList = C33FD9B6255A548A00E217F9 /* Build configuration list for PBXNativeTarget "SignalUtilitiesKit" */; buildPhases = ( 5CE8055024B876590AED6DEA /* [CP] Check Pods Manifest.lock */, - FD7692EE2A524357000E4B70 /* Validate pre-build actions */, C33FD9A6255A548A00E217F9 /* Headers */, C33FD9A7255A548A00E217F9 /* Sources */, C33FD9A8255A548A00E217F9 /* Frameworks */, @@ -4572,7 +4556,6 @@ buildConfigurationList = C3C2A5AA255385C100C340D1 /* Build configuration list for PBXNativeTarget "SessionSnodeKit" */; buildPhases = ( 77F55C879DAF28750120D343 /* [CP] Check Pods Manifest.lock */, - FD7692F02A524393000E4B70 /* Validate pre-build actions */, C3C2A59A255385C100C340D1 /* Headers */, C3C2A59B255385C100C340D1 /* Sources */, C3C2A59C255385C100C340D1 /* Frameworks */, @@ -4592,7 +4575,6 @@ buildConfigurationList = C3C2A684255388CC00C340D1 /* Build configuration list for PBXNativeTarget "SessionUtilitiesKit" */; buildPhases = ( 446B0E16474DF9F15509BC64 /* [CP] Check Pods Manifest.lock */, - FD7692F12A5243AE000E4B70 /* Validate pre-build actions */, C3C2A674255388CC00C340D1 /* Headers */, C3C2A675255388CC00C340D1 /* Sources */, C3C2A676255388CC00C340D1 /* Frameworks */, @@ -4612,7 +4594,6 @@ buildConfigurationList = C3C2A6F925539DE700C340D1 /* Build configuration list for PBXNativeTarget "SessionMessagingKit" */; buildPhases = ( 2014435DF351DF6C60122751 /* [CP] Check Pods Manifest.lock */, - FDFC4E1729F14F7A00992FB6 /* Validate pre-build actions */, C3C2A6EB25539DE700C340D1 /* Headers */, C3C2A6EC25539DE700C340D1 /* Sources */, C3C2A6ED25539DE700C340D1 /* Frameworks */, @@ -4632,7 +4613,6 @@ buildConfigurationList = D221A0BC169C9E5F00537ABF /* Build configuration list for PBXNativeTarget "Session" */; buildPhases = ( 351E727E03A8F141EA25FBF4 /* [CP] Check Pods Manifest.lock */, - FD7692EA2A524303000E4B70 /* Validate pre-build actions */, FDE7214D287E50820093DF33 /* Lint Localizable.strings */, D221A085169C9E5E00537ABF /* Sources */, D221A086169C9E5E00537ABF /* Frameworks */, @@ -4663,7 +4643,6 @@ buildConfigurationList = FD71160F28D00BAE00B47552 /* Build configuration list for PBXNativeTarget "SessionTests" */; buildPhases = ( 19CD7B4EDC153293FB61CBA1 /* [CP] Check Pods Manifest.lock */, - FD7692F22A5243C3000E4B70 /* Validate pre-build actions */, FD71160528D00BAE00B47552 /* Sources */, FD71160628D00BAE00B47552 /* Frameworks */, FD71160728D00BAE00B47552 /* Resources */, @@ -4684,7 +4663,6 @@ buildConfigurationList = FD83B9B627CF200A005E1583 /* Build configuration list for PBXNativeTarget "SessionUtilitiesKitTests" */; buildPhases = ( EDDFB3BFBD5E1378BD03AAAB /* [CP] Check Pods Manifest.lock */, - FD7692F42A5243EC000E4B70 /* Validate pre-build actions */, FD83B9AB27CF200A005E1583 /* Sources */, FD83B9AC27CF200A005E1583 /* Frameworks */, FD83B9AD27CF200A005E1583 /* Resources */, @@ -4694,19 +4672,32 @@ ); dependencies = ( FD83B9B527CF200A005E1583 /* PBXTargetDependency */, - FDCDB8EE28179EB200352A0C /* PBXTargetDependency */, ); name = SessionUtilitiesKitTests; productName = SessionUtilitiesKitTests; productReference = FD83B9AF27CF200A005E1583 /* SessionUtilitiesKitTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + FD9BDDF72A5D2294005F1EBC /* SessionUtil */ = { + isa = PBXNativeTarget; + buildConfigurationList = FD9BDDFC2A5D2294005F1EBC /* Build configuration list for PBXNativeTarget "SessionUtil" */; + buildPhases = ( + FD9BDDFF2A5D229B005F1EBC /* Build libSessionUtil if Needed */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SessionUtil; + productName = libSession4; + productReference = FD9BDDF82A5D2294005F1EBC /* libSessionUtil.a */; + productType = "com.apple.product-type.library.static"; + }; FDC4388D27B9FFC700C60D73 /* SessionMessagingKitTests */ = { isa = PBXNativeTarget; buildConfigurationList = FDC4389527B9FFC700C60D73 /* Build configuration list for PBXNativeTarget "SessionMessagingKitTests" */; buildPhases = ( 0E6C1748F41E48ED59563D96 /* [CP] Check Pods Manifest.lock */, - FD7692F32A5243DA000E4B70 /* Validate pre-build actions */, FDC4388A27B9FFC700C60D73 /* Sources */, FDC4388B27B9FFC700C60D73 /* Frameworks */, FDC4388C27B9FFC700C60D73 /* Resources */, @@ -4716,7 +4707,6 @@ ); dependencies = ( FDC4389427B9FFC700C60D73 /* PBXTargetDependency */, - FDCDB8EC28179EAF00352A0C /* PBXTargetDependency */, ); name = SessionMessagingKitTests; productName = SessionMessagingKitTests; @@ -4827,6 +4817,9 @@ FD83B9AE27CF200A005E1583 = { CreatedOnToolsVersion = 13.2.1; }; + FD9BDDF72A5D2294005F1EBC = { + CreatedOnToolsVersion = 14.3; + }; FDC4388D27B9FFC700C60D73 = { CreatedOnToolsVersion = 13.2.1; }; @@ -4877,6 +4870,7 @@ FD71160828D00BAE00B47552 /* SessionTests */, FDC4388D27B9FFC700C60D73 /* SessionMessagingKitTests */, FD83B9AE27CF200A005E1583 /* SessionUtilitiesKitTests */, + FD9BDDF72A5D2294005F1EBC /* SessionUtil */, ); }; /* End PBXProject section */ @@ -5334,7 +5328,7 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - FD7692EA2A524303000E4B70 /* Validate pre-build actions */ = { + FD9BDDFF2A5D229B005F1EBC /* Build libSessionUtil if Needed */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; @@ -5344,198 +5338,19 @@ ); inputPaths = ( ); - name = "Validate pre-build actions"; + name = "Build libSessionUtil if Needed"; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "if [ -f \"${TARGET_BUILD_DIR}/libsession_util_error.log\" ]; then\n read -r line < \"${TARGET_BUILD_DIR}/libsession_util_error.log\"\n echo \"${line}\"\n exit 1\nfi\n"; - showEnvVarsInLog = 0; - }; - FD7692EC2A524320000E4B70 /* Validate pre-build actions */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Validate pre-build actions"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "if [ -f \"${TARGET_BUILD_DIR}/libsession_util_error.log\" ]; then\n read -r line < \"${TARGET_BUILD_DIR}/libsession_util_error.log\"\n echo \"${line}\"\n exit 1\nfi\n"; - showEnvVarsInLog = 0; - }; - FD7692ED2A52433E000E4B70 /* Validate pre-build actions */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Validate pre-build actions"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "if [ -f \"${TARGET_BUILD_DIR}/libsession_util_error.log\" ]; then\n read -r line < \"${TARGET_BUILD_DIR}/libsession_util_error.log\"\n echo \"${line}\"\n exit 1\nfi\n"; - showEnvVarsInLog = 0; - }; - FD7692EE2A524357000E4B70 /* Validate pre-build actions */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Validate pre-build actions"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "if [ -f \"${TARGET_BUILD_DIR}/libsession_util_error.log\" ]; then\n read -r line < \"${TARGET_BUILD_DIR}/libsession_util_error.log\"\n echo \"${line}\"\n exit 1\nfi\n"; - showEnvVarsInLog = 0; - }; - FD7692EF2A52436A000E4B70 /* Validate pre-build actions */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Validate pre-build actions"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "if [ -f \"${TARGET_BUILD_DIR}/libsession_util_error.log\" ]; then\n read -r line < \"${TARGET_BUILD_DIR}/libsession_util_error.log\"\n echo \"${line}\"\n exit 1\nfi\n"; - showEnvVarsInLog = 0; - }; - FD7692F02A524393000E4B70 /* Validate pre-build actions */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Validate pre-build actions"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "if [ -f \"${TARGET_BUILD_DIR}/libsession_util_error.log\" ]; then\n read -r line < \"${TARGET_BUILD_DIR}/libsession_util_error.log\"\n echo \"${line}\"\n exit 1\nfi\n"; - showEnvVarsInLog = 0; - }; - FD7692F12A5243AE000E4B70 /* Validate pre-build actions */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Validate pre-build actions"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "if [ -f \"${TARGET_BUILD_DIR}/libsession_util_error.log\" ]; then\n read -r line < \"${TARGET_BUILD_DIR}/libsession_util_error.log\"\n echo \"${line}\"\n exit 1\nfi\n"; - showEnvVarsInLog = 0; - }; - FD7692F22A5243C3000E4B70 /* Validate pre-build actions */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Validate pre-build actions"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "if [ -f \"${TARGET_BUILD_DIR}/libsession_util_error.log\" ]; then\n read -r line < \"${TARGET_BUILD_DIR}/libsession_util_error.log\"\n echo \"${line}\"\n exit 1\nfi\n"; - showEnvVarsInLog = 0; - }; - FD7692F32A5243DA000E4B70 /* Validate pre-build actions */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Validate pre-build actions"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "if [ -f \"${TARGET_BUILD_DIR}/libsession_util_error.log\" ]; then\n read -r line < \"${TARGET_BUILD_DIR}/libsession_util_error.log\"\n echo \"${line}\"\n exit 1\nfi\n"; - showEnvVarsInLog = 0; - }; - FD7692F42A5243EC000E4B70 /* Validate pre-build actions */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Validate pre-build actions"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "if [ -f \"${TARGET_BUILD_DIR}/libsession_util_error.log\" ]; then\n read -r line < \"${TARGET_BUILD_DIR}/libsession_util_error.log\"\n echo \"${line}\"\n exit 1\nfi\n"; + shellScript = "\"${SRCROOT}/Scripts/build_libSession_util.sh\"\n"; showEnvVarsInLog = 0; }; FDD82C422A2085B900425F05 /* Add Commit Hash To Build Info Plist */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -5575,26 +5390,6 @@ shellScript = "\"${SRCROOT}/Scripts/LintLocalizableStrings.swift\"\n"; showEnvVarsInLog = 0; }; - FDFC4E1729F14F7A00992FB6 /* Validate pre-build actions */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Validate pre-build actions"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "if [ -f \"${TARGET_BUILD_DIR}/libsession_util_error.log\" ]; then\n read -r line < \"${TARGET_BUILD_DIR}/libsession_util_error.log\"\n echo \"${line}\"\n exit 1\nfi\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -6524,16 +6319,6 @@ target = C3C2A6EF25539DE700C340D1 /* SessionMessagingKit */; targetProxy = FDC4389327B9FFC700C60D73 /* PBXContainerItemProxy */; }; - FDCDB8EC28179EAF00352A0C /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = D221A088169C9E5E00537ABF /* Session */; - targetProxy = FDCDB8EB28179EAF00352A0C /* PBXContainerItemProxy */; - }; - FDCDB8EE28179EB200352A0C /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = D221A088169C9E5E00537ABF /* Session */; - targetProxy = FDCDB8ED28179EB200352A0C /* PBXContainerItemProxy */; - }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -7450,6 +7235,24 @@ GCC_OPTIMIZATION_LEVEL = 0; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "\"${PODS_CONFIGURATION_BUILD_DIR}/CocoaLumberjack/CocoaLumberjack.framework/Headers\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/Curve25519Kit/Curve25519Kit.framework/Headers\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/DifferenceKit/DifferenceKit.framework/Headers\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/GRDB.swift/GRDB.framework/Headers\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/PureLayout/PureLayout.framework/Headers\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/Reachability/Reachability.framework/Headers\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/SAMKeychain/SAMKeychain.framework/Headers\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/SQLCipher/SQLCipher.framework/Headers\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/SignalCoreKit/SignalCoreKit.framework/Headers\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/Sodium/Sodium.framework/Headers\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/SwiftProtobuf/SwiftProtobuf.framework/Headers\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/YapDatabase/YapDatabase.framework/Headers\"", + "\"${PODS_XCFRAMEWORKS_BUILD_DIR}/Sodium/Headers\"", + "$(PODS_ROOT)/SQLCipher", + "${SRCROOT}/LibSession-Util/include/**", + ); INFOPLIST_FILE = SessionMessagingKit/Meta/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -7458,6 +7261,13 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}\"", + "\"${PODS_XCFRAMEWORKS_BUILD_DIR}/Sodium\"", + /usr/lib/swift, + "\"$(TARGET_BUILD_DIR)/libSessionUtil\"", + ); MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.SessionMessagingKit"; @@ -7465,6 +7275,7 @@ SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_INCLUDE_PATHS = "$(inherited) \"${PODS_XCFRAMEWORKS_BUILD_DIR}/Clibsodium\" \"$(TARGET_BUILD_DIR)/libSessionUtil\""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; @@ -7528,6 +7339,24 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "\"${PODS_CONFIGURATION_BUILD_DIR}/CocoaLumberjack/CocoaLumberjack.framework/Headers\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/Curve25519Kit/Curve25519Kit.framework/Headers\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/DifferenceKit/DifferenceKit.framework/Headers\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/GRDB.swift/GRDB.framework/Headers\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/PureLayout/PureLayout.framework/Headers\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/Reachability/Reachability.framework/Headers\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/SAMKeychain/SAMKeychain.framework/Headers\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/SQLCipher/SQLCipher.framework/Headers\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/SignalCoreKit/SignalCoreKit.framework/Headers\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/Sodium/Sodium.framework/Headers\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/SwiftProtobuf/SwiftProtobuf.framework/Headers\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/YapDatabase/YapDatabase.framework/Headers\"", + "\"${PODS_XCFRAMEWORKS_BUILD_DIR}/Sodium/Headers\"", + "$(PODS_ROOT)/SQLCipher", + "${SRCROOT}/LibSession-Util/include/**", + ); INFOPLIST_FILE = SessionMessagingKit/Meta/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -7536,6 +7365,13 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}\"", + "\"${PODS_XCFRAMEWORKS_BUILD_DIR}/Sodium\"", + /usr/lib/swift, + "\"$(TARGET_BUILD_DIR)/libSessionUtil\"", + ); MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.SessionMessagingKit"; @@ -7544,6 +7380,7 @@ SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = NO; SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_INCLUDE_PATHS = "$(inherited) \"${PODS_XCFRAMEWORKS_BUILD_DIR}/Clibsodium\" \"$(TARGET_BUILD_DIR)/libSessionUtil\""; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; @@ -8058,6 +7895,105 @@ }; name = "App Store Release"; }; + FD9BDDFD2A5D2294005F1EBC /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DEFINES_MODULE = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MODULEMAP_FILE = "$(SRCROOT)/SessionMessagingKit/Meta/SessionUtil.modulemap"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + FD9BDDFE2A5D2294005F1EBC /* App Store Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MODULEMAP_FILE = "$(SRCROOT)/SessionMessagingKit/Meta/SessionUtil.modulemap"; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = "App Store Release"; + }; FDC4389627B9FFC700C60D73 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 8727C47348B6EFA767EE583A /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.debug.xcconfig */; @@ -8267,6 +8203,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = "App Store Release"; }; + FD9BDDFC2A5D2294005F1EBC /* Build configuration list for PBXNativeTarget "SessionUtil" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + FD9BDDFD2A5D2294005F1EBC /* Debug */, + FD9BDDFE2A5D2294005F1EBC /* App Store Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = "App Store Release"; + }; FDC4389527B9FFC700C60D73 /* Build configuration list for PBXNativeTarget "SessionMessagingKitTests" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme b/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme index f2e5c8744..ea85c66b2 100644 --- a/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme +++ b/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme @@ -5,24 +5,6 @@ - - - - - - - - - - + version = "1.3"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + version = "1.3"> - - - - - - - - - - + version = "1.3"> - - - - - - - - - - Data? in From 7b06329454be9c9ec3357cbaaa328e536aa924e4 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 12 Jul 2023 15:04:54 +1000 Subject: [PATCH 117/135] Fixed an incorrect bash command --- .drone.jsonnet | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.jsonnet b/.drone.jsonnet index d85ed0cd4..e05549573 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -51,7 +51,7 @@ local xcpretty_commands = [ name: 'build', commands: [ 'mkdir build', - 'xcodebuild -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphonesimulator -derivedDataPath ./build -destination 'generic/platform=iOS Simulator' | xcpretty', + 'xcodebuild -workspace Session.xcworkspace -scheme Session -configuration "App Store Release" -sdk iphonesimulator -derivedDataPath ./build -destination "generic/platform=iOS Simulator" | xcpretty', './.drone-static-upload.sh' ], }, From 0464439e8d9413420f895cf654e2340fbd7763fb Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 12 Jul 2023 15:05:51 +1000 Subject: [PATCH 118/135] Fixed some formatting errors --- .drone.jsonnet | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.drone.jsonnet b/.drone.jsonnet index e05549573..99a5857d1 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -27,7 +27,7 @@ local xcpretty_commands = [ steps: [ { name: 'submodules', commands: submodule_commands }, { name: 'xcpretty', commands: xcpretty_commands }, - { name: 'pods', commands: 'pod install' }, + { name: 'pods', commands: ['pod install'] }, { name: 'Run Unit Tests', commands: [ @@ -46,7 +46,7 @@ local xcpretty_commands = [ steps: [ { name: 'submodules', commands: submodule_commands }, { name: 'xcpretty', commands: xcpretty_commands }, - { name: 'pods', commands: 'pod install' }, + { name: 'pods', commands: ['pod install'] }, { name: 'build', commands: [ @@ -66,7 +66,7 @@ local xcpretty_commands = [ // steps: [ // { name: 'submodules', commands: submodule_commands }, // { name: 'xcpretty', commands: xcpretty_commands }, -// { name: 'pods', commands: 'pod install' }, +// { name: 'pods', commands: ['pod install'] }, // { // name: 'build', // commands: [ From c8c70c448e952b94f8f8ff5ee6914c9f396c9795 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 12 Jul 2023 15:13:42 +1000 Subject: [PATCH 119/135] Tweaks to the submodule command --- .drone.jsonnet | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.drone.jsonnet b/.drone.jsonnet index 99a5857d1..58bf9c6df 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -2,7 +2,7 @@ local submodule_commands = ['git fetch --tags', 'git submodule update --init --recursive --depth=2']; local submodules = { - name: 'submodules', + name: 'Clone Submodules', image: 'drone/git', commands: submodule_commands, }; @@ -25,9 +25,9 @@ local xcpretty_commands = [ name: 'Unit Tests', platform: { os: 'darwin', arch: 'amd64' }, steps: [ - { name: 'submodules', commands: submodule_commands }, - { name: 'xcpretty', commands: xcpretty_commands }, - { name: 'pods', commands: ['pod install'] }, + submodules, + { name: 'Install XCPretty', commands: xcpretty_commands }, + { name: 'Install CocoaPods', commands: ['pod install'] }, { name: 'Run Unit Tests', commands: [ @@ -44,11 +44,11 @@ local xcpretty_commands = [ name: 'Simulator Build', platform: { os: 'darwin', arch: 'amd64' }, steps: [ - { name: 'submodules', commands: submodule_commands }, - { name: 'xcpretty', commands: xcpretty_commands }, - { name: 'pods', commands: ['pod install'] }, + submodules, + { name: 'Install XCPretty', commands: xcpretty_commands }, + { name: 'Install CocoaPods', commands: ['pod install'] }, { - name: 'build', + name: 'Build', commands: [ 'mkdir build', 'xcodebuild -workspace Session.xcworkspace -scheme Session -configuration "App Store Release" -sdk iphonesimulator -derivedDataPath ./build -destination "generic/platform=iOS Simulator" | xcpretty', @@ -64,11 +64,11 @@ local xcpretty_commands = [ // name: 'AppStore Build', // platform: { os: 'darwin', arch: 'amd64' }, // steps: [ -// { name: 'submodules', commands: submodule_commands }, -// { name: 'xcpretty', commands: xcpretty_commands }, -// { name: 'pods', commands: ['pod install'] }, +// submodules, +// { name: 'Install XCPretty', commands: xcpretty_commands }, +// { name: 'Install CocoaPods', commands: ['pod install'] }, // { -// name: 'build', +// name: 'Build', // commands: [ // 'mkdir build', // 'xcodebuild archive -workspace Session.xcworkspace -scheme Session -archivePath ./build/Session.xcarchive -destination "platform=generic/iOS" | xcpretty' From 8de4a66865212341561f0c61b3a463128454f95b Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 12 Jul 2023 15:15:39 +1000 Subject: [PATCH 120/135] Reverting last change --- .drone.jsonnet | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/.drone.jsonnet b/.drone.jsonnet index 58bf9c6df..bf6c23802 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -1,12 +1,6 @@ // Intentionally doing a depth of 2 as libSession-util has it's own submodules (and libLokinet likely will as well) local submodule_commands = ['git fetch --tags', 'git submodule update --init --recursive --depth=2']; -local submodules = { - name: 'Clone Submodules', - image: 'drone/git', - commands: submodule_commands, -}; - // cmake options for static deps mirror local ci_dep_mirror(want_mirror) = (if want_mirror then ' -DLOCAL_MIRROR=https://oxen.rocks/deps ' else ''); @@ -25,7 +19,7 @@ local xcpretty_commands = [ name: 'Unit Tests', platform: { os: 'darwin', arch: 'amd64' }, steps: [ - submodules, + { name: 'Clone Submodules', commands: submodule_commands }, { name: 'Install XCPretty', commands: xcpretty_commands }, { name: 'Install CocoaPods', commands: ['pod install'] }, { @@ -44,7 +38,7 @@ local xcpretty_commands = [ name: 'Simulator Build', platform: { os: 'darwin', arch: 'amd64' }, steps: [ - submodules, + { name: 'Clone Submodules', commands: submodule_commands }, { name: 'Install XCPretty', commands: xcpretty_commands }, { name: 'Install CocoaPods', commands: ['pod install'] }, { @@ -64,7 +58,7 @@ local xcpretty_commands = [ // name: 'AppStore Build', // platform: { os: 'darwin', arch: 'amd64' }, // steps: [ -// submodules, +// { name: 'Clone Submodules', commands: submodule_commands }, // { name: 'Install XCPretty', commands: xcpretty_commands }, // { name: 'Install CocoaPods', commands: ['pod install'] }, // { From 3c81e3a4878b3d04fbc5cd28bac70cfdbb9416c1 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 12 Jul 2023 15:28:28 +1000 Subject: [PATCH 121/135] Changed the submodule to be via HTTPS instead of SSH Cleaned out some old, unused references from project files --- .gitmodules | 2 +- Session.xcodeproj/project.pbxproj | 110 +++++++--------- .../xcshareddata/Signal.xcscmblueprint | 121 ------------------ Session/Meta/Session-Info.plist | 9 +- 4 files changed, 48 insertions(+), 194 deletions(-) delete mode 100644 Session.xcworkspace/xcshareddata/Signal.xcscmblueprint diff --git a/.gitmodules b/.gitmodules index ae3d45a8c..b8c1e3809 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "LibSession-Util"] path = LibSession-Util - url = git@github.com:oxen-io/libsession-util.git + url = https://github.com/oxen-io/libsession-util.git diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 4e3e4eb73..b1d01de68 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 056ED47155A04437A1EF58C2 /* Pods_GlobalDependencies_Session_SessionTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 32C10A2A536B58FC42C46C3C /* Pods_GlobalDependencies_Session_SessionTests.framework */; }; 3427C64320F500E000EEC730 /* OWSMessageTimerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3427C64220F500DF00EEC730 /* OWSMessageTimerView.m */; }; 3430FE181F7751D4000EC51B /* GiphyAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3430FE171F7751D4000EC51B /* GiphyAPI.swift */; }; 34661FB820C1C0D60056EDD6 /* message_sent.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 34661FB720C1C0D60056EDD6 /* message_sent.aiff */; }; @@ -33,8 +32,7 @@ 34D1F0521F7E8EA30066283D /* GiphyDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0511F7E8EA30066283D /* GiphyDownloader.swift */; }; 34D99CE4217509C2000AFB39 /* AppEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D99CE3217509C1000AFB39 /* AppEnvironment.swift */; }; 34F308A21ECB469700BB7697 /* OWSBezierPathView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F308A11ECB469700BB7697 /* OWSBezierPathView.m */; }; - 41BA6B5C1C693C3A86070C15 /* Pods_GlobalDependencies_Session.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CC8D3B6504946442A0CF775C /* Pods_GlobalDependencies_Session.framework */; }; - 42C48489AFF26BC9034C736C /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 86A3D36084020C9118DBCEE3 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit.framework */; }; + 3B59D92C6C15D82844A6BF16 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76B2DECEBFEEEFA2221BA817 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework */; }; 4503F1BE20470A5B00CEE724 /* classic-quiet.aifc in Resources */ = {isa = PBXBuildFile; fileRef = 4503F1BB20470A5B00CEE724 /* classic-quiet.aifc */; }; 4503F1BF20470A5B00CEE724 /* classic.aifc in Resources */ = {isa = PBXBuildFile; fileRef = 4503F1BC20470A5B00CEE724 /* classic.aifc */; }; 450DF2091E0DD2C6003D14BE /* UserNotificationsAdaptee.swift in Sources */ = {isa = PBXBuildFile; fileRef = 450DF2081E0DD2C6003D14BE /* UserNotificationsAdaptee.swift */; }; @@ -85,17 +83,15 @@ 4C1885D2218F8E1C00B67051 /* PhotoGridViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1885D1218F8E1C00B67051 /* PhotoGridViewCell.swift */; }; 4C21D5D8223AC60F00EF8A77 /* PhotoCapture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C21D5D7223AC60F00EF8A77 /* PhotoCapture.swift */; }; 4C4AE6A1224AF35700D4AF6F /* SendMediaNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4AE69F224AF21900D4AF6F /* SendMediaNavigationController.swift */; }; - 4C4FE46740136D591D04261F /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E0B0F4C34363FE679EE3F203 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework */; }; 4C586926224FAB83003FD070 /* AVAudioSession+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C586925224FAB83003FD070 /* AVAudioSession+OWS.m */; }; 4C63CC00210A620B003AE45C /* SignalTSan.supp in Resources */ = {isa = PBXBuildFile; fileRef = 4C63CBFF210A620B003AE45C /* SignalTSan.supp */; }; 4C6F527C20FFE8400097DEEE /* SignalUBSan.supp in Resources */ = {isa = PBXBuildFile; fileRef = 4C6F527B20FFE8400097DEEE /* SignalUBSan.supp */; }; - 4C9CA25D217E676900607C63 /* ZXingObjC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C9CA25C217E676900607C63 /* ZXingObjC.framework */; }; 4CA46F4C219CCC630038ABDE /* CaptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA46F4B219CCC630038ABDE /* CaptionView.swift */; }; 4CA485BB2232339F004B9E7D /* PhotoCaptureViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA485BA2232339F004B9E7D /* PhotoCaptureViewController.swift */; }; 4CC613362227A00400E21A3A /* ConversationSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC613352227A00400E21A3A /* ConversationSearch.swift */; }; - 6C1ADD1127CED42854542F78 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D3BE3061D535DBC1DF0C94D4 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework */; }; + 58860CDCE675B63CBDB1EEED /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E36D73700ED95C005B6BA026 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit.framework */; }; + 6D6C9F16F244E7FADB1ACBE9 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionSnodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8B0BA5257C58DC6FF797278 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionSnodeKit.framework */; }; 70377AAB1918450100CAF501 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 70377AAA1918450100CAF501 /* MobileCoreServices.framework */; }; - 768A1A2B17FC9CD300E00ED8 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 768A1A2A17FC9CD300E00ED8 /* libz.dylib */; }; 76C87F19181EFCE600C4ACAB /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76C87F18181EFCE600C4ACAB /* MediaPlayer.framework */; }; 7B0EFDEE274F598600FFAAE7 /* TimestampUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0EFDED274F598600FFAAE7 /* TimestampUtils.swift */; }; 7B0EFDF0275084AA00FFAAE7 /* CallMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0EFDEF275084AA00FFAAE7 /* CallMessageCell.swift */; }; @@ -167,17 +163,18 @@ 7BFD1A8A2745C4F000FB91B9 /* Permissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD1A892745C4F000FB91B9 /* Permissions.swift */; }; 7BFD1A8C2747150E00FB91B9 /* TurnServerInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD1A8B2747150E00FB91B9 /* TurnServerInfo.swift */; }; 7BFD1A972747689000FB91B9 /* Session-Turn-Server in Resources */ = {isa = PBXBuildFile; fileRef = 7BFD1A962747689000FB91B9 /* Session-Turn-Server */; }; - 9A88F90C33C394513CB4C18A /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit_SessionUtilitiesKitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 07B7038C849F53378CD36B83 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit_SessionUtilitiesKitTests.framework */; }; - A0A69C9CB213DDDD85BF2207 /* Pods_GlobalDependencies_SessionUIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 678CFB04E76F7E388AFCFA86 /* Pods_GlobalDependencies_SessionUIKit.framework */; }; + 99978E3F7A80275823CA9014 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 29E827FDF6C1032BB985740C /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework */; }; A11CD70D17FA230600A2D1B1 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A11CD70C17FA230600A2D1B1 /* QuartzCore.framework */; }; A163E8AB16F3F6AA0094D68B /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A163E8AA16F3F6A90094D68B /* Security.framework */; }; A1C32D5017A06538000A904E /* AddressBookUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1C32D4F17A06537000A904E /* AddressBookUI.framework */; }; A1C32D5117A06544000A904E /* AddressBook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1C32D4D17A0652C000A904E /* AddressBook.framework */; }; + AFF4D58C089CAD659A6A7D31 /* Pods_GlobalDependencies_Session_SessionTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 164F5FAD2DCE932054F61E78 /* Pods_GlobalDependencies_Session_SessionTests.framework */; }; B66DBF4A19D5BBC8006EA940 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B66DBF4919D5BBC8006EA940 /* Images.xcassets */; }; B67EBF5D19194AC60084CCFD /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = B67EBF5C19194AC60084CCFD /* Settings.bundle */; }; B6B226971BE4B7D200860F4D /* ContactsUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6B226961BE4B7D200860F4D /* ContactsUI.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; B6F509971AA53F760068F56A /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B6F509951AA53F760068F56A /* Localizable.strings */; }; B6FE7EB71ADD62FA00A6D22F /* PushKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6FE7EB61ADD62FA00A6D22F /* PushKit.framework */; }; + B7ED5A721C85869B379140C0 /* Pods_GlobalDependencies_Session.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1677131E9189A0043FF97ACB /* Pods_GlobalDependencies_Session.framework */; }; B8041A9525C8FA1D003C2166 /* MediaLoaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8041A9425C8FA1D003C2166 /* MediaLoaderView.swift */; }; B8041AA725C90927003C2166 /* TypingIndicatorCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8041AA625C90927003C2166 /* TypingIndicatorCell.swift */; }; B806ECA126C4A7E4008BDA44 /* WebRTCSession+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = B806ECA026C4A7E4008BDA44 /* WebRTCSession+UI.swift */; }; @@ -262,8 +259,8 @@ B8FF8E6225C10DA5004D1F22 /* GeoLite2-Country-Blocks-IPv4 in Resources */ = {isa = PBXBuildFile; fileRef = B8FF8E6125C10DA5004D1F22 /* GeoLite2-Country-Blocks-IPv4 */; }; B8FF8E7425C10FC3004D1F22 /* GeoLite2-Country-Locations-English in Resources */ = {isa = PBXBuildFile; fileRef = B8FF8E7325C10FC3004D1F22 /* GeoLite2-Country-Locations-English */; }; B8FF8EA625C11FEF004D1F22 /* IPv4.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8FF8EA525C11FEF004D1F22 /* IPv4.swift */; }; + B93FD3F211AE04B892F2F08A /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit_SessionUtilitiesKitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93359C81CF2660040B7CD106 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit_SessionUtilitiesKitTests.framework */; }; B9EB5ABD1884C002007CBB57 /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B9EB5ABC1884C002007CBB57 /* MessageUI.framework */; }; - BE25D9230CA2C3A40A9216EF /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05E68C7F291EC08B8A43A534 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework */; }; C300A5D32554B05A00555489 /* TypingIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C300A5D22554B05A00555489 /* TypingIndicator.swift */; }; C300A5F22554B09800555489 /* MessageSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = C300A5F12554B09800555489 /* MessageSender.swift */; }; C300A60D2554B31900555489 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5CE2553860700C340D1 /* Logging.swift */; }; @@ -459,7 +456,6 @@ C3DB66C3260ACCE6001EFC55 /* OpenGroupPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DB66C2260ACCE6001EFC55 /* OpenGroupPoller.swift */; }; C3E5C2FA251DBABB0040DFFC /* EditClosedGroupVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E5C2F9251DBABB0040DFFC /* EditClosedGroupVC.swift */; }; C3F0A530255C80BC007BE2A3 /* NoopNotificationsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3F0A52F255C80BC007BE2A3 /* NoopNotificationsManager.swift */; }; - CB54B7E519F525FF27A7DAD3 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C5828D9A55CD76B75E8FA367 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework */; }; D2179CFC16BB0B3A0006F3AB /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2179CFB16BB0B3A0006F3AB /* CoreTelephony.framework */; }; D2179CFE16BB0B480006F3AB /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2179CFD16BB0B480006F3AB /* SystemConfiguration.framework */; }; D221A08E169C9E5E00537ABF /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D221A08D169C9E5E00537ABF /* UIKit.framework */; }; @@ -467,7 +463,9 @@ D221A0E8169DFFC500537ABF /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D221A0E7169DFFC500537ABF /* AVFoundation.framework */; }; D24B5BD5169F568C00681372 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D24B5BD4169F568C00681372 /* AudioToolbox.framework */; }; D2AEACDC16C426DA00C364C0 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2AEACDB16C426DA00C364C0 /* CFNetwork.framework */; }; - DE0001574AC103562A7CF31F /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionSnodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9E358E7590EB145A5047F885 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionSnodeKit.framework */; }; + D53824DFCED7AE6BB61E7640 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92E8569C96285EE3CDB5960D /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework */; }; + DE53B40717A9417578FB40DE /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BE35EBADE13F7B7D979B71D5 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework */; }; + E984778319BDF24A7ADF100A /* Pods_GlobalDependencies_SessionUIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3E517F8611575B37B76BCC54 /* Pods_GlobalDependencies_SessionUIKit.framework */; }; FC3BD9881A30A790005B96BB /* Social.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC3BD9871A30A790005B96BB /* Social.framework */; }; FCB11D8C1A129A76002F93FB /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FCB11D8B1A129A76002F93FB /* CoreMedia.framework */; }; FD078E4827E02561000769AF /* CommonMockedExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD078E4727E02561000769AF /* CommonMockedExtensions.swift */; }; @@ -911,7 +909,7 @@ FDFDE128282D05530098B17F /* MediaPresentationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE127282D05530098B17F /* MediaPresentationContext.swift */; }; FDFDE12A282D056B0098B17F /* MediaZoomAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE129282D056B0098B17F /* MediaZoomAnimationController.swift */; }; FDFF61D729F2600300F95FB0 /* Identity+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFF61D629F2600300F95FB0 /* Identity+Utilities.swift */; }; - FE43694493EC2E1E438EBEB3 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 13D1714FDC4DAB121DA2C73A /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework */; }; + FE5FDED6D91BB4B3FA5C104D /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A9C113D2086D3C8A68A371C /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -1089,12 +1087,11 @@ /* Begin PBXFileReference section */ 05C76EFA593DD507061C50B2 /* Pods-GlobalDependencies-SessionUIKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-SessionUIKit.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-SessionUIKit/Pods-GlobalDependencies-SessionUIKit.app store release.xcconfig"; sourceTree = ""; }; - 05E68C7F291EC08B8A43A534 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0772459E7D5F6747EDC889F3 /* Pods-GlobalDependencies-Session-SessionTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-Session-SessionTests.debug.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-Session-SessionTests/Pods-GlobalDependencies-Session-SessionTests.debug.xcconfig"; sourceTree = ""; }; - 07B7038C849F53378CD36B83 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit_SessionUtilitiesKitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit_SessionUtilitiesKitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 13D1714FDC4DAB121DA2C73A /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 164F5FAD2DCE932054F61E78 /* Pods_GlobalDependencies_Session_SessionTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_Session_SessionTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 1677131E9189A0043FF97ACB /* Pods_GlobalDependencies_Session.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_Session.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 285705D20F792E174C8A9BBA /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig"; sourceTree = ""; }; - 32C10A2A536B58FC42C46C3C /* Pods_GlobalDependencies_Session_SessionTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_Session_SessionTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 29E827FDF6C1032BB985740C /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 34040971CC7AF9C8A6C1E838 /* Pods-GlobalDependencies-Session.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-Session.debug.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-Session/Pods-GlobalDependencies-Session.debug.xcconfig"; sourceTree = ""; }; 3427C64120F500DE00EEC730 /* OWSMessageTimerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageTimerView.h; sourceTree = ""; }; 3427C64220F500DF00EEC730 /* OWSMessageTimerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSMessageTimerView.m; sourceTree = ""; }; @@ -1124,9 +1121,9 @@ 34D99CE3217509C1000AFB39 /* AppEnvironment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppEnvironment.swift; sourceTree = ""; }; 34F308A01ECB469700BB7697 /* OWSBezierPathView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBezierPathView.h; sourceTree = ""; }; 34F308A11ECB469700BB7697 /* OWSBezierPathView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSBezierPathView.m; sourceTree = ""; }; + 3E517F8611575B37B76BCC54 /* Pods_GlobalDependencies_SessionUIKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_SessionUIKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4503F1BB20470A5B00CEE724 /* classic-quiet.aifc */ = {isa = PBXFileReference; lastKnownFileType = file; path = "classic-quiet.aifc"; sourceTree = ""; }; 4503F1BC20470A5B00CEE724 /* classic.aifc */ = {isa = PBXFileReference; lastKnownFileType = file; path = classic.aifc; sourceTree = ""; }; - 4509E7991DD653700025A59F /* WebRTC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebRTC.framework; path = ThirdParty/WebRTC/Build/WebRTC.framework; sourceTree = ""; }; 450DF2081E0DD2C6003D14BE /* UserNotificationsAdaptee.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = UserNotificationsAdaptee.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 451A13B01E13DED2000A50FD /* AppNotifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = AppNotifications.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 4520D8D41D417D8E00123472 /* Photos.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Photos.framework; path = System/Library/Frameworks/Photos.framework; sourceTree = SDKROOT; }; @@ -1182,7 +1179,6 @@ 4C586925224FAB83003FD070 /* AVAudioSession+OWS.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "AVAudioSession+OWS.m"; sourceTree = ""; }; 4C63CBFF210A620B003AE45C /* SignalTSan.supp */ = {isa = PBXFileReference; lastKnownFileType = text; path = SignalTSan.supp; sourceTree = ""; }; 4C6F527B20FFE8400097DEEE /* SignalUBSan.supp */ = {isa = PBXFileReference; lastKnownFileType = text; path = SignalUBSan.supp; sourceTree = ""; }; - 4C9CA25C217E676900607C63 /* ZXingObjC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ZXingObjC.framework; path = ThirdParty/Carthage/Build/iOS/ZXingObjC.framework; sourceTree = ""; }; 4CA46F4B219CCC630038ABDE /* CaptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaptionView.swift; sourceTree = ""; }; 4CA485BA2232339F004B9E7D /* PhotoCaptureViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoCaptureViewController.swift; sourceTree = ""; }; 4CC613352227A00400E21A3A /* ConversationSearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationSearch.swift; sourceTree = ""; }; @@ -1190,12 +1186,12 @@ 5DA3BDDFFB9E937A49C35FCC /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.app store release.xcconfig"; sourceTree = ""; }; 621B42AC592F3456ACD82F8B /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.app store release.xcconfig"; sourceTree = ""; }; 62B512CEB14BD4A4A53CF532 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.app store release.xcconfig"; sourceTree = ""; }; - 678CFB04E76F7E388AFCFA86 /* Pods_GlobalDependencies_SessionUIKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_SessionUIKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 6A71AD9BEAFF0C9E8016BC23 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionShareExtension.app store release.xcconfig"; sourceTree = ""; }; 6DA09080DD9779C860023A60 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.debug.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.debug.xcconfig"; sourceTree = ""; }; 70377AAA1918450100CAF501 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; - 768A1A2A17FC9CD300E00ED8 /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; }; + 76B2DECEBFEEEFA2221BA817 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 76C87F18181EFCE600C4ACAB /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = System/Library/Frameworks/MediaPlayer.framework; sourceTree = SDKROOT; }; + 7A9C113D2086D3C8A68A371C /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7B0EFDED274F598600FFAAE7 /* TimestampUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimestampUtils.swift; sourceTree = ""; }; 7B0EFDEF275084AA00FFAAE7 /* CallMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallMessageCell.swift; sourceTree = ""; }; 7B0EFDF3275490EA00FFAAE7 /* ringing.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = ringing.mp3; sourceTree = ""; }; @@ -1272,10 +1268,10 @@ 8448EFF76CD3CA5B2283B8A0 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.debug.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit-SessionUtilitiesKitTests.debug.xcconfig"; sourceTree = ""; }; 847091A12D82E41B1EBB8FB3 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.debug.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.debug.xcconfig"; sourceTree = ""; }; 8603226ED1C6F61F1F2D3734 /* Pods-GlobalDependencies-Session.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-Session.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-Session/Pods-GlobalDependencies-Session.app store release.xcconfig"; sourceTree = ""; }; - 86A3D36084020C9118DBCEE3 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 8727C47348B6EFA767EE583A /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.debug.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.debug.xcconfig"; sourceTree = ""; }; 8E946CB54A221018E23599DE /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig"; sourceTree = ""; }; - 9E358E7590EB145A5047F885 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionSnodeKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionSnodeKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 92E8569C96285EE3CDB5960D /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 93359C81CF2660040B7CD106 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit_SessionUtilitiesKitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit_SessionUtilitiesKitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A11CD70C17FA230600A2D1B1 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; A163E8AA16F3F6A90094D68B /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; A1C32D4D17A0652C000A904E /* AddressBook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBook.framework; path = System/Library/Frameworks/AddressBook.framework; sourceTree = SDKROOT; }; @@ -1293,7 +1289,6 @@ B68CB7DC1AA547100065AC3F /* pt_BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt_BR; path = pt_BR.lproj/Localizable.strings; sourceTree = ""; }; B68CB7E01AA548420065AC3F /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; B68CB7E61AA548870065AC3F /* zh_CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh_CN; path = zh_CN.lproj/Localizable.strings; sourceTree = ""; }; - B69CD25019773E79005CE69A /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; B6B226961BE4B7D200860F4D /* ContactsUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ContactsUI.framework; path = System/Library/Frameworks/ContactsUI.framework; sourceTree = SDKROOT; }; B6FE7EB61ADD62FA00A6D22F /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = System/Library/Frameworks/PushKit.framework; sourceTree = SDKROOT; }; B8041A9425C8FA1D003C2166 /* MediaLoaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaLoaderView.swift; sourceTree = ""; }; @@ -1318,7 +1313,6 @@ B83524A425C3BA4B0089A44F /* InfoMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoMessageCell.swift; sourceTree = ""; }; B83F2B87240CB75A000A54AB /* UIImage+Scaling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Scaling.swift"; sourceTree = ""; }; B84664F4235022F30083A1CD /* MentionUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionUtilities.swift; sourceTree = ""; }; - B847570023D568EB00759540 /* SignalServiceKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SignalServiceKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B849789525D4A2F500D0D0B3 /* LinkPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkPreviewView.swift; sourceTree = ""; }; B84A89BB25DE328A0040017D /* ProfilePictureVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePictureVC.swift; sourceTree = ""; }; B85357BE23A1AE0800AAF6CD /* SeedReminderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedReminderView.swift; sourceTree = ""; }; @@ -1372,7 +1366,6 @@ B8D0A26825E4A2C200C1835E /* Onboarding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Onboarding.swift; sourceTree = ""; }; B8D84EA225DF745A005A043E /* LinkPreviewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkPreviewState.swift; sourceTree = ""; }; B8D84ECE25E3108A005A043E /* ExpandingAttachmentsButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpandingAttachmentsButton.swift; sourceTree = ""; }; - B8DE1FAF26C228780079C9CE /* SignalRingRTC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SignalRingRTC.framework; path = Dependencies/SignalRingRTC.framework; sourceTree = ""; }; B8DE1FB326C22F2F0079C9CE /* WebRTCSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRTCSession.swift; sourceTree = ""; }; B8DE1FB526C22FCB0079C9CE /* CallMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallMessage.swift; sourceTree = ""; }; B8EB20E6263F7E4B00773E52 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Localizable.strings; sourceTree = ""; }; @@ -1385,6 +1378,7 @@ B8FF8E7325C10FC3004D1F22 /* GeoLite2-Country-Locations-English */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; name = "GeoLite2-Country-Locations-English"; path = "Countries/GeoLite2-Country-Locations-English"; sourceTree = ""; }; B8FF8EA525C11FEF004D1F22 /* IPv4.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPv4.swift; sourceTree = ""; }; B9EB5ABC1884C002007CBB57 /* MessageUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessageUI.framework; path = System/Library/Frameworks/MessageUI.framework; sourceTree = SDKROOT; }; + BE35EBADE13F7B7D979B71D5 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C300A5B12554AF9800555489 /* VisibleMessage+Profile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VisibleMessage+Profile.swift"; sourceTree = ""; }; C300A5BC2554B00D00555489 /* ReadReceipt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadReceipt.swift; sourceTree = ""; }; C300A5D22554B05A00555489 /* TypingIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypingIndicator.swift; sourceTree = ""; }; @@ -1484,7 +1478,6 @@ C3548F0724456AB6009433A8 /* UIView+Wrapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Wrapping.swift"; sourceTree = ""; }; C354E75923FE2A7600CE22E3 /* BaseVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseVC.swift; sourceTree = ""; }; C35D0DB425AE5F1200B6BF49 /* UIEdgeInsets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIEdgeInsets.swift; sourceTree = ""; }; - C35E8AA22485C72300ACB629 /* SwiftCSV.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftCSV.framework; path = ThirdParty/Carthage/Build/iOS/SwiftCSV.framework; sourceTree = ""; }; C35E8AAD2485E51D00ACB629 /* IP2Country.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IP2Country.swift; sourceTree = ""; }; C374EEEA25DA3CA70073A857 /* ConversationTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationTitleView.swift; sourceTree = ""; }; C374EEF325DB31D40073A857 /* VoiceMessageRecordingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRecordingView.swift; sourceTree = ""; }; @@ -1610,8 +1603,6 @@ C3ECBF7A257056B700EA7FCE /* Threading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Threading.swift; sourceTree = ""; }; C3F0A52F255C80BC007BE2A3 /* NoopNotificationsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoopNotificationsManager.swift; sourceTree = ""; }; C3F0A5B2255C915C007BE2A3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; - C5828D9A55CD76B75E8FA367 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - CC8D3B6504946442A0CF775C /* Pods_GlobalDependencies_Session.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_Session.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D2179CFB16BB0B3A0006F3AB /* CoreTelephony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreTelephony.framework; path = System/Library/Frameworks/CoreTelephony.framework; sourceTree = SDKROOT; }; D2179CFD16BB0B480006F3AB /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; D221A089169C9E5E00537ABF /* Session.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Session.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1622,14 +1613,14 @@ D221A0E7169DFFC500537ABF /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = ../../../../../../System/Library/Frameworks/AVFoundation.framework; sourceTree = ""; }; D24B5BD4169F568C00681372 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = ../../../../../../System/Library/Frameworks/AudioToolbox.framework; sourceTree = ""; }; D2AEACDB16C426DA00C364C0 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; - D3BE3061D535DBC1DF0C94D4 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - E0B0F4C34363FE679EE3F203 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E1A0AD8B16E13FDD0071E604 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; + E36D73700ED95C005B6BA026 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EB5B8ACA4C6F512FA3E21859 /* Pods-GlobalDependencies-SessionUIKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-SessionUIKit.debug.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-SessionUIKit/Pods-GlobalDependencies-SessionUIKit.debug.xcconfig"; sourceTree = ""; }; EED1CF82CAB23FE3345564F9 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionSnodeKit.app store release.xcconfig"; sourceTree = ""; }; F154A10CE1ADA33C16B45357 /* Pods-GlobalDependencies-Session-SessionTests.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-Session-SessionTests.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-Session-SessionTests/Pods-GlobalDependencies-Session-SessionTests.app store release.xcconfig"; sourceTree = ""; }; F390F8E34CA76B3F7D3B1826 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.app store release.xcconfig"; sourceTree = ""; }; F60C5B6CD14329816B0E8CC0 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.app store release.xcconfig"; sourceTree = ""; }; + F8B0BA5257C58DC6FF797278 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionSnodeKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionSnodeKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; FC3BD9871A30A790005B96BB /* Social.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Social.framework; path = System/Library/Frameworks/Social.framework; sourceTree = SDKROOT; }; FCB11D8B1A129A76002F93FB /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; }; FD078E4727E02561000769AF /* CommonMockedExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonMockedExtensions.swift; sourceTree = ""; }; @@ -2043,7 +2034,7 @@ C3D90A5C25773A25002C9DF5 /* SessionUtilitiesKit.framework in Frameworks */, C3402FE52559036600EA6424 /* SessionUIKit.framework in Frameworks */, B8D64FCB25BA78A90029CFC0 /* SignalUtilitiesKit.framework in Frameworks */, - 4C4FE46740136D591D04261F /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework in Frameworks */, + FE5FDED6D91BB4B3FA5C104D /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2055,7 +2046,7 @@ B8D64FBD25BA78310029CFC0 /* SessionSnodeKit.framework in Frameworks */, B8D64FBE25BA78310029CFC0 /* SessionUtilitiesKit.framework in Frameworks */, C38EF00C255B61CC007E1867 /* SignalUtilitiesKit.framework in Frameworks */, - CB54B7E519F525FF27A7DAD3 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework in Frameworks */, + 99978E3F7A80275823CA9014 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2064,7 +2055,7 @@ buildActionMask = 2147483647; files = ( FD37E9EF28A5ED70003AE748 /* SessionUtilitiesKit.framework in Frameworks */, - A0A69C9CB213DDDD85BF2207 /* Pods_GlobalDependencies_SessionUIKit.framework in Frameworks */, + E984778319BDF24A7ADF100A /* Pods_GlobalDependencies_SessionUIKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2076,7 +2067,7 @@ C33FD9C2255A54EF00E217F9 /* SessionMessagingKit.framework in Frameworks */, C33FD9C4255A54EF00E217F9 /* SessionSnodeKit.framework in Frameworks */, C33FD9C5255A54EF00E217F9 /* SessionUtilitiesKit.framework in Frameworks */, - 6C1ADD1127CED42854542F78 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework in Frameworks */, + D53824DFCED7AE6BB61E7640 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2085,7 +2076,7 @@ buildActionMask = 2147483647; files = ( C3C2A6C62553896A00C340D1 /* SessionUtilitiesKit.framework in Frameworks */, - DE0001574AC103562A7CF31F /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionSnodeKit.framework in Frameworks */, + 6D6C9F16F244E7FADB1ACBE9 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionSnodeKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2093,7 +2084,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 42C48489AFF26BC9034C736C /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit.framework in Frameworks */, + 58860CDCE675B63CBDB1EEED /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2105,7 +2096,7 @@ FD9BDE012A5D24EA005F1EBC /* SessionUIKit.framework in Frameworks */, FDC4386C27B4E90300C60D73 /* SessionUtilitiesKit.framework in Frameworks */, C3C2A70B25539E1E00C340D1 /* SessionSnodeKit.framework in Frameworks */, - BE25D9230CA2C3A40A9216EF /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework in Frameworks */, + DE53B40717A9417578FB40DE /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2121,7 +2112,6 @@ 455A16DE1F1FEA0000F86704 /* MetalKit.framework in Frameworks */, 45847E871E4283C30080EAB3 /* Intents.framework in Frameworks */, 4520D8D51D417D8E00123472 /* Photos.framework in Frameworks */, - 4C9CA25D217E676900607C63 /* ZXingObjC.framework in Frameworks */, B6B226971BE4B7D200860F4D /* ContactsUI.framework in Frameworks */, 45BD60821DE9547E00A8F436 /* Contacts.framework in Frameworks */, B6FE7EB71ADD62FA00A6D22F /* PushKit.framework in Frameworks */, @@ -2131,7 +2121,6 @@ B9EB5ABD1884C002007CBB57 /* MessageUI.framework in Frameworks */, 3496956021A2FC8100DCFE74 /* CloudKit.framework in Frameworks */, 76C87F19181EFCE600C4ACAB /* MediaPlayer.framework in Frameworks */, - 768A1A2B17FC9CD300E00ED8 /* libz.dylib in Frameworks */, A11CD70D17FA230600A2D1B1 /* QuartzCore.framework in Frameworks */, A163E8AB16F3F6AA0094D68B /* Security.framework in Frameworks */, A1C32D5117A06544000A904E /* AddressBook.framework in Frameworks */, @@ -2144,7 +2133,7 @@ D221A090169C9E5E00537ABF /* Foundation.framework in Frameworks */, D221A0E8169DFFC500537ABF /* AVFoundation.framework in Frameworks */, D24B5BD5169F568C00681372 /* AudioToolbox.framework in Frameworks */, - 41BA6B5C1C693C3A86070C15 /* Pods_GlobalDependencies_Session.framework in Frameworks */, + B7ED5A721C85869B379140C0 /* Pods_GlobalDependencies_Session.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2152,7 +2141,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 056ED47155A04437A1EF58C2 /* Pods_GlobalDependencies_Session_SessionTests.framework in Frameworks */, + AFF4D58C089CAD659A6A7D31 /* Pods_GlobalDependencies_Session_SessionTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2161,7 +2150,7 @@ buildActionMask = 2147483647; files = ( FD83B9B327CF200A005E1583 /* SessionUtilitiesKit.framework in Frameworks */, - 9A88F90C33C394513CB4C18A /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit_SessionUtilitiesKitTests.framework in Frameworks */, + B93FD3F211AE04B892F2F08A /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit_SessionUtilitiesKitTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2170,7 +2159,7 @@ buildActionMask = 2147483647; files = ( FDC4389227B9FFC700C60D73 /* SessionMessagingKit.framework in Frameworks */, - FE43694493EC2E1E438EBEB3 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework in Frameworks */, + 3B59D92C6C15D82844A6BF16 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3488,30 +3477,23 @@ D221A08C169C9E5E00537ABF /* Frameworks */ = { isa = PBXGroup; children = ( - B8DE1FAF26C228780079C9CE /* SignalRingRTC.framework */, - C35E8AA22485C72300ACB629 /* SwiftCSV.framework */, - B847570023D568EB00759540 /* SignalServiceKit.framework */, 3496955F21A2FC8100DCFE74 /* CloudKit.framework */, - 4C9CA25C217E676900607C63 /* ZXingObjC.framework */, 455A16DB1F1FEA0000F86704 /* Metal.framework */, 455A16DC1F1FEA0000F86704 /* MetalKit.framework */, 45847E861E4283C30080EAB3 /* Intents.framework */, 45BD60811DE9547E00A8F436 /* Contacts.framework */, - 4509E7991DD653700025A59F /* WebRTC.framework */, 4520D8D41D417D8E00123472 /* Photos.framework */, B6B226961BE4B7D200860F4D /* ContactsUI.framework */, B6FE7EB61ADD62FA00A6D22F /* PushKit.framework */, FC3BD9871A30A790005B96BB /* Social.framework */, B60EDE031A05A01700D73516 /* AudioToolbox.framework */, FCB11D8B1A129A76002F93FB /* CoreMedia.framework */, - B69CD25019773E79005CE69A /* XCTest.framework */, 70377AAA1918450100CAF501 /* MobileCoreServices.framework */, B9EB5ABC1884C002007CBB57 /* MessageUI.framework */, A1C32D4D17A0652C000A904E /* AddressBook.framework */, A1C32D4F17A06537000A904E /* AddressBookUI.framework */, A163E8AA16F3F6A90094D68B /* Security.framework */, 76C87F18181EFCE600C4ACAB /* MediaPlayer.framework */, - 768A1A2A17FC9CD300E00ED8 /* libz.dylib */, A11CD70C17FA230600A2D1B1 /* QuartzCore.framework */, E1A0AD8B16E13FDD0071E604 /* CoreFoundation.framework */, A1FDCBEE16DAA6C300868894 /* AVFoundation.framework */, @@ -3523,17 +3505,17 @@ D221A08D169C9E5E00537ABF /* UIKit.framework */, D221A08F169C9E5E00537ABF /* Foundation.framework */, D221A091169C9E5E00537ABF /* CoreGraphics.framework */, - 05E68C7F291EC08B8A43A534 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework */, - 13D1714FDC4DAB121DA2C73A /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework */, - E0B0F4C34363FE679EE3F203 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework */, - 86A3D36084020C9118DBCEE3 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit.framework */, - 07B7038C849F53378CD36B83 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit_SessionUtilitiesKitTests.framework */, - D3BE3061D535DBC1DF0C94D4 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework */, - C5828D9A55CD76B75E8FA367 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework */, - 9E358E7590EB145A5047F885 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionSnodeKit.framework */, - CC8D3B6504946442A0CF775C /* Pods_GlobalDependencies_Session.framework */, - 32C10A2A536B58FC42C46C3C /* Pods_GlobalDependencies_Session_SessionTests.framework */, - 678CFB04E76F7E388AFCFA86 /* Pods_GlobalDependencies_SessionUIKit.framework */, + BE35EBADE13F7B7D979B71D5 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework */, + 76B2DECEBFEEEFA2221BA817 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework */, + 7A9C113D2086D3C8A68A371C /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework */, + E36D73700ED95C005B6BA026 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit.framework */, + 93359C81CF2660040B7CD106 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit_SessionUtilitiesKitTests.framework */, + 92E8569C96285EE3CDB5960D /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework */, + 29E827FDF6C1032BB985740C /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework */, + F8B0BA5257C58DC6FF797278 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionSnodeKit.framework */, + 1677131E9189A0043FF97ACB /* Pods_GlobalDependencies_Session.framework */, + 164F5FAD2DCE932054F61E78 /* Pods_GlobalDependencies_Session_SessionTests.framework */, + 3E517F8611575B37B76BCC54 /* Pods_GlobalDependencies_SessionUIKit.framework */, ); name = Frameworks; sourceTree = ""; @@ -5357,8 +5339,8 @@ inputFileListPaths = ( ); inputPaths = ( - $BUILT_PRODUCTS_DIR/$INFOPLIST_PATH, - $TARGET_BUILD_DIR/$INFOPLIST_PATH, + "$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH", + "$TARGET_BUILD_DIR/$INFOPLIST_PATH", ); name = "Add Commit Hash To Build Info Plist"; outputFileListPaths = ( diff --git a/Session.xcworkspace/xcshareddata/Signal.xcscmblueprint b/Session.xcworkspace/xcshareddata/Signal.xcscmblueprint deleted file mode 100644 index 15a2ce9a2..000000000 --- a/Session.xcworkspace/xcshareddata/Signal.xcscmblueprint +++ /dev/null @@ -1,121 +0,0 @@ -{ - "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "5D79A077E31B3FE97A3C6613CBFFDD71C314D14C+++2D5CBAE", - "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { - - }, - "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { - "8176314449001F06FB0E5B588C62133EAA2FE911+++72E8629" : 9223372036854775807, - "01DE8628B025BC69C8C7D8B4612D57BE2C08B62C+++6A1C9FC" : 0, - "5D79A077E31B3FE97A3C6613CBFFDD71C314D14C+++0BB03DB" : 0, - "ABB939127996C66F7E852A780552ADEEF03C6B13+++69179A3" : 0, - "90530B99EB0008E7A50951FDFBE02169118FA649+++EF2C0B3" : 0, - "5D79A077E31B3FE97A3C6613CBFFDD71C314D14C+++ED4C31A" : 0, - "D74FB800F048CB516BB4BC70047F7CC676D291B9+++375B249" : 0, - "8176314449001F06FB0E5B588C62133EAA2FE911+++692B8E4" : 9223372036854775807, - "37054CE35CE656680D6FFFA9EE19249E0D149C5E+++901E7D4" : 0, - "5D79A077E31B3FE97A3C6613CBFFDD71C314D14C+++2D5CBAE" : 0, - "8176314449001F06FB0E5B588C62133EAA2FE911+++E19D6E3" : 9223372036854775807, - "5D79A077E31B3FE97A3C6613CBFFDD71C314D14C+++03D0758" : 0, - "37054CE35CE656680D6FFFA9EE19249E0D149C5E+++3F8B703" : 9223372036854775807, - "37054CE35CE656680D6FFFA9EE19249E0D149C5E+++E57A04A" : 0, - "8176314449001F06FB0E5B588C62133EAA2FE911+++31C7255" : 9223372036854775807 - }, - "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "D0F297E7-A82D-4657-A941-96B268F80ABC", - "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { - "8176314449001F06FB0E5B588C62133EAA2FE911+++72E8629" : "Signal-iOS-2\/Carthage\/", - "01DE8628B025BC69C8C7D8B4612D57BE2C08B62C+++6A1C9FC" : "SignalProtocolKit\/", - "5D79A077E31B3FE97A3C6613CBFFDD71C314D14C+++0BB03DB" : "Signal-iOS-2\/", - "ABB939127996C66F7E852A780552ADEEF03C6B13+++69179A3" : "SocketRocket\/", - "90530B99EB0008E7A50951FDFBE02169118FA649+++EF2C0B3" : "JSQMessagesViewController\/", - "5D79A077E31B3FE97A3C6613CBFFDD71C314D14C+++ED4C31A" : "Signal-iOS\/", - "D74FB800F048CB516BB4BC70047F7CC676D291B9+++375B249" : "Signal-iOS\/Pods\/", - "8176314449001F06FB0E5B588C62133EAA2FE911+++692B8E4" : "Signal-iOS-4\/Carthage\/", - "37054CE35CE656680D6FFFA9EE19249E0D149C5E+++901E7D4" : "SignalServiceKit\/", - "5D79A077E31B3FE97A3C6613CBFFDD71C314D14C+++2D5CBAE" : "Signal-iOS-4\/", - "8176314449001F06FB0E5B588C62133EAA2FE911+++E19D6E3" : "Signal-iOS\/Carthage\/", - "5D79A077E31B3FE97A3C6613CBFFDD71C314D14C+++03D0758" : "Signal-iOS-5\/", - "37054CE35CE656680D6FFFA9EE19249E0D149C5E+++3F8B703" : "SignalServiceKit-2\/", - "37054CE35CE656680D6FFFA9EE19249E0D149C5E+++E57A04A" : "SignalServiceKit\/", - "8176314449001F06FB0E5B588C62133EAA2FE911+++31C7255" : "Signal-iOS-5\/Carthage\/" - }, - "DVTSourceControlWorkspaceBlueprintNameKey" : "Signal", - "DVTSourceControlWorkspaceBlueprintVersion" : 204, - "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "Signal.xcworkspace", - "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ - { - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:WhisperSystems\/SignalProtocolKit.git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "01DE8628B025BC69C8C7D8B4612D57BE2C08B62C+++6A1C9FC" - }, - { - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:WhisperSystems\/SignalServiceKit.git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "37054CE35CE656680D6FFFA9EE19249E0D149C5E+++3F8B703" - }, - { - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:WhisperSystems\/SignalProtocolKit.git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "37054CE35CE656680D6FFFA9EE19249E0D149C5E+++901E7D4" - }, - { - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:FredericJacobs\/TextSecureKit.git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "37054CE35CE656680D6FFFA9EE19249E0D149C5E+++E57A04A" - }, - { - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:WhisperSystems\/Signal-iOS.git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "5D79A077E31B3FE97A3C6613CBFFDD71C314D14C+++03D0758" - }, - { - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:WhisperSystems\/Signal-iOS.git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "5D79A077E31B3FE97A3C6613CBFFDD71C314D14C+++0BB03DB" - }, - { - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:WhisperSystems\/Signal-iOS.git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "5D79A077E31B3FE97A3C6613CBFFDD71C314D14C+++2D5CBAE" - }, - { - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:WhisperSystems\/Signal-iOS.git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "5D79A077E31B3FE97A3C6613CBFFDD71C314D14C+++ED4C31A" - }, - { - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:michaelkirk\/Signal-Carthage.git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "8176314449001F06FB0E5B588C62133EAA2FE911+++31C7255" - }, - { - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/WhisperSystems\/Signal-Carthage.git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "8176314449001F06FB0E5B588C62133EAA2FE911+++692B8E4" - }, - { - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/WhisperSystems\/Signal-Carthage.git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "8176314449001F06FB0E5B588C62133EAA2FE911+++72E8629" - }, - { - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:WhisperSystems\/Signal-Carthage.git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "8176314449001F06FB0E5B588C62133EAA2FE911+++E19D6E3" - }, - { - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:WhisperSystems\/JSQMessagesViewController.git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "90530B99EB0008E7A50951FDFBE02169118FA649+++EF2C0B3" - }, - { - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:WhisperSystems\/SocketRocket.git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "ABB939127996C66F7E852A780552ADEEF03C6B13+++69179A3" - }, - { - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/FredericJacobs\/Precompiled-Signal-Dependencies.git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "D74FB800F048CB516BB4BC70047F7CC676D291B9+++375B249" - } - ] -} \ No newline at end of file diff --git a/Session/Meta/Session-Info.plist b/Session/Meta/Session-Info.plist index be0439a2f..87cdc72f3 100644 --- a/Session/Meta/Session-Info.plist +++ b/Session/Meta/Session-Info.plist @@ -2,13 +2,6 @@ - BuildDetails - - CarthageVersion - 0.36.0 - OSXVersion - 10.15.6 - CFBundleDevelopmentRegion en CFBundleDisplayName @@ -88,7 +81,7 @@ NSCameraUsageDescription Session needs camera access to take pictures and scan QR codes. NSFaceIDUsageDescription - Session's Screen Lock feature uses Face ID. + Session's Screen Lock feature uses Face ID. NSHumanReadableCopyright com.loki-project.loki-messenger NSMicrophoneUsageDescription From 5bd0d5d640557a8147a4586cd1ecd148f47e42a9 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 12 Jul 2023 15:33:33 +1000 Subject: [PATCH 122/135] Attempting multiline bash command for xcpretty --- .drone.jsonnet | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.drone.jsonnet b/.drone.jsonnet index bf6c23802..9f4bf0f48 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -6,8 +6,16 @@ local ci_dep_mirror(want_mirror) = (if want_mirror then ' -DLOCAL_MIRROR=https:/ // xcpretty local xcpretty_commands = [ - 'if [[ $(command -v brew) != "" ]]; then; brew install xcpretty; fi;', - 'if [[ $(command -v brew) == "" ]]; then; gem install xcpretty; fi;' + ||| + if [[ $(command -v brew) != "" ]]; then + brew install xcpretty + fi + |||, + ||| + if [[ $(command -v brew) == "" ]]; then + gem install xcpretty + fi + |||, ]; From f8b69cd03c2f4ad4ea628baf65857c7b2739429b Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 12 Jul 2023 15:36:12 +1000 Subject: [PATCH 123/135] Disabling XCPretty due to permission issue --- .drone.jsonnet | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.drone.jsonnet b/.drone.jsonnet index 9f4bf0f48..606c37427 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -28,13 +28,13 @@ local xcpretty_commands = [ platform: { os: 'darwin', arch: 'amd64' }, steps: [ { name: 'Clone Submodules', commands: submodule_commands }, - { name: 'Install XCPretty', commands: xcpretty_commands }, + // { name: 'Install XCPretty', commands: xcpretty_commands }, { name: 'Install CocoaPods', commands: ['pod install'] }, { name: 'Run Unit Tests', commands: [ 'mkdir build', - 'xcodebuild test -workspace Session.xcworkspace -scheme Session -destination "platform=iOS Simulator,name=iPhone 14 Pro" | xcpretty --report html' + 'xcodebuild test -workspace Session.xcworkspace -scheme Session -destination "platform=iOS Simulator,name=iPhone 14 Pro"' // | xcpretty --report html' ], }, ], @@ -47,13 +47,13 @@ local xcpretty_commands = [ platform: { os: 'darwin', arch: 'amd64' }, steps: [ { name: 'Clone Submodules', commands: submodule_commands }, - { name: 'Install XCPretty', commands: xcpretty_commands }, + // { name: 'Install XCPretty', commands: xcpretty_commands }, { name: 'Install CocoaPods', commands: ['pod install'] }, { name: 'Build', commands: [ 'mkdir build', - 'xcodebuild -workspace Session.xcworkspace -scheme Session -configuration "App Store Release" -sdk iphonesimulator -derivedDataPath ./build -destination "generic/platform=iOS Simulator" | xcpretty', + 'xcodebuild -workspace Session.xcworkspace -scheme Session -configuration "App Store Release" -sdk iphonesimulator -derivedDataPath ./build -destination "generic/platform=iOS Simulator"', // | xcpretty', './.drone-static-upload.sh' ], }, @@ -67,7 +67,7 @@ local xcpretty_commands = [ // platform: { os: 'darwin', arch: 'amd64' }, // steps: [ // { name: 'Clone Submodules', commands: submodule_commands }, -// { name: 'Install XCPretty', commands: xcpretty_commands }, +// // { name: 'Install XCPretty', commands: xcpretty_commands }, // { name: 'Install CocoaPods', commands: ['pod install'] }, // { // name: 'Build', From f623db678edf7bfb4abeb1a4bcb45fbd20ecd9f3 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 12 Jul 2023 15:43:36 +1000 Subject: [PATCH 124/135] Attempt to work around a stupid CocoaPods restriction --- .drone.jsonnet | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.drone.jsonnet b/.drone.jsonnet index 606c37427..40295c503 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -29,7 +29,7 @@ local xcpretty_commands = [ steps: [ { name: 'Clone Submodules', commands: submodule_commands }, // { name: 'Install XCPretty', commands: xcpretty_commands }, - { name: 'Install CocoaPods', commands: ['pod install'] }, + { name: 'Install CocoaPods', commands: ['LANG=en_US.UTF-8 pod install'] }, { name: 'Run Unit Tests', commands: [ @@ -48,7 +48,7 @@ local xcpretty_commands = [ steps: [ { name: 'Clone Submodules', commands: submodule_commands }, // { name: 'Install XCPretty', commands: xcpretty_commands }, - { name: 'Install CocoaPods', commands: ['pod install'] }, + { name: 'Install CocoaPods', commands: ['LANG=en_US.UTF-8 pod install'] }, { name: 'Build', commands: [ @@ -68,7 +68,7 @@ local xcpretty_commands = [ // steps: [ // { name: 'Clone Submodules', commands: submodule_commands }, // // { name: 'Install XCPretty', commands: xcpretty_commands }, -// { name: 'Install CocoaPods', commands: ['pod install'] }, +// { name: 'Install CocoaPods', commands: ['LANG=en_US.UTF-8 pod install'] }, // { // name: 'Build', // commands: [ From c7f6b5a94e507cc6107b8a6f3396ed36b161d61b Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 12 Jul 2023 17:13:59 +1000 Subject: [PATCH 125/135] Replaced the 'ZXingObjC' dependency with a native implementation for scanning QR Codes --- Podfile | 1 - Podfile.lock | 8 +- Session/Home/New Conversation/NewDMVC.swift | 17 +- Session/Onboarding/LinkDeviceVC.swift | 12 +- Session/Open Groups/JoinOpenGroupVC.swift | 26 +-- Session/Settings/QRCodeVC.swift | 9 +- .../Shared/QRCodeScanningViewController.swift | 161 ++++++++++++------ 7 files changed, 142 insertions(+), 92 deletions(-) diff --git a/Podfile b/Podfile index 5747d4620..76cf57a85 100644 --- a/Podfile +++ b/Podfile @@ -22,7 +22,6 @@ abstract_target 'GlobalDependencies' do pod 'PureLayout', '~> 3.1.8' pod 'NVActivityIndicatorView' pod 'YYImage/libwebp', git: 'https://github.com/signalapp/YYImage' - pod 'ZXingObjC' pod 'DifferenceKit' target 'SessionTests' do diff --git a/Podfile.lock b/Podfile.lock index 8810dd341..d0b6c6490 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -108,9 +108,6 @@ PODS: - YYImage/libwebp (1.0.4): - libwebp - YYImage/Core - - ZXingObjC (3.6.5): - - ZXingObjC/All (= 3.6.5) - - ZXingObjC/All (3.6.5) DEPENDENCIES: - Curve25519Kit (from `https://github.com/oxen-io/session-ios-curve-25519-kit.git`, branch `session-version`) @@ -129,7 +126,6 @@ DEPENDENCIES: - WebRTC-lib - YapDatabase/SQLCipher (from `https://github.com/oxen-io/session-ios-yap-database.git`, branch `signal-release`) - YYImage/libwebp (from `https://github.com/signalapp/YYImage`) - - ZXingObjC SPEC REPOS: https://github.com/CocoaPods/Specs.git: @@ -147,7 +143,6 @@ SPEC REPOS: - SQLCipher - SwiftProtobuf - WebRTC-lib - - ZXingObjC EXTERNAL SOURCES: Curve25519Kit: @@ -202,8 +197,7 @@ SPEC CHECKSUMS: WebRTC-lib: d83df8976fa608b980f1d85796b3de66d60a1953 YapDatabase: b418a4baa6906e8028748938f9159807fd039af4 YYImage: f1ddd15ac032a58b78bbed1e012b50302d318331 - ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: 68799237a4dc046f5ac25c573af03b559f5b10c4 +PODFILE CHECKSUM: 4705728e69454d50805c70272479a7d4a04209d5 COCOAPODS: 1.12.1 diff --git a/Session/Home/New Conversation/NewDMVC.swift b/Session/Home/New Conversation/NewDMVC.swift index 2d900083d..4b9f80591 100644 --- a/Session/Home/New Conversation/NewDMVC.swift +++ b/Session/Home/New Conversation/NewDMVC.swift @@ -165,12 +165,12 @@ final class NewDMVC: BaseVC, UIPageViewControllerDataSource, UIPageViewControlle dismiss(animated: true, completion: nil) } - func controller(_ controller: QRCodeScanningViewController, didDetectQRCodeWith string: String) { + func controller(_ controller: QRCodeScanningViewController, didDetectQRCodeWith string: String, onError: (() -> ())?) { let hexEncodedPublicKey = string - startNewDMIfPossible(with: hexEncodedPublicKey) + startNewDMIfPossible(with: hexEncodedPublicKey, onError: onError) } - fileprivate func startNewDMIfPossible(with onsNameOrPublicKey: String) { + fileprivate func startNewDMIfPossible(with onsNameOrPublicKey: String, onError: (() -> ())?) { let maybeSessionId: SessionId? = SessionId(from: onsNameOrPublicKey) if KeyPair.isValidHexEncodedPublicKey(candidate: onsNameOrPublicKey) { @@ -185,7 +185,8 @@ final class NewDMVC: BaseVC, UIPageViewControllerDataSource, UIPageViewControlle title: "ALERT_ERROR_TITLE".localized(), body: .text("DM_ERROR_DIRECT_BLINDED_ID".localized()), cancelTitle: "BUTTON_OK".localized(), - cancelStyle: .alert_text + cancelStyle: .alert_text, + afterClosed: onError ) ) self.present(modal, animated: true) @@ -197,7 +198,8 @@ final class NewDMVC: BaseVC, UIPageViewControllerDataSource, UIPageViewControlle title: "ALERT_ERROR_TITLE".localized(), body: .text("DM_ERROR_INVALID".localized()), cancelTitle: "BUTTON_OK".localized(), - cancelStyle: .alert_text + cancelStyle: .alert_text, + afterClosed: onError ) ) self.present(modal, animated: true) @@ -243,7 +245,8 @@ final class NewDMVC: BaseVC, UIPageViewControllerDataSource, UIPageViewControlle title: "ALERT_ERROR_TITLE".localized(), body: .text(message), cancelTitle: "BUTTON_OK".localized(), - cancelStyle: .alert_text + cancelStyle: .alert_text, + afterClosed: onError ) ) self?.present(modal, animated: true) @@ -663,7 +666,7 @@ private final class EnterPublicKeyVC: UIViewController { @objc fileprivate func startNewDMIfPossible() { let text = publicKeyTextView.text?.trimmingCharacters(in: .whitespaces) ?? "" - NewDMVC.startNewDMIfPossible(with: text) + NewDMVC.startNewDMIfPossible(with: text, onError: nil) } } diff --git a/Session/Onboarding/LinkDeviceVC.swift b/Session/Onboarding/LinkDeviceVC.swift index 0c4b0af0a..bef5d2460 100644 --- a/Session/Onboarding/LinkDeviceVC.swift +++ b/Session/Onboarding/LinkDeviceVC.swift @@ -134,12 +134,12 @@ final class LinkDeviceVC: BaseVC, UIPageViewControllerDataSource, UIPageViewCont dismiss(animated: true, completion: nil) } - func controller(_ controller: QRCodeScanningViewController, didDetectQRCodeWith string: String) { + func controller(_ controller: QRCodeScanningViewController, didDetectQRCodeWith string: String, onError: (() -> ())?) { let seed = Data(hex: string) - continueWithSeed(seed) + continueWithSeed(seed, onError: onError) } - func continueWithSeed(_ seed: Data) { + func continueWithSeed(_ seed: Data, onError: (() -> ())?) { if (seed.count != 16) { let modal: ConfirmationModal = ConfirmationModal( info: ConfirmationModal.Info( @@ -147,9 +147,7 @@ final class LinkDeviceVC: BaseVC, UIPageViewControllerDataSource, UIPageViewCont body: .text("INVALID_RECOVERY_PHRASE_MESSAGE".localized()), cancelTitle: "BUTTON_OK".localized(), cancelStyle: .alert_text, - afterClosed: { [weak self] in - self?.scanQRCodeWrapperVC.startCapture() - } + afterClosed: onError ) ) present(modal, animated: true) @@ -319,7 +317,7 @@ private final class RecoveryPhraseVC: UIViewController { let hexEncodedSeed = try Mnemonic.decode(mnemonic: mnemonic) let seed = Data(hex: hexEncodedSeed) mnemonicTextView.resignFirstResponder() - linkDeviceVC.continueWithSeed(seed) + linkDeviceVC.continueWithSeed(seed, onError: nil) } catch let error { let error = error as? Mnemonic.DecodingError ?? Mnemonic.DecodingError.generic showError(title: error.errorDescription!) diff --git a/Session/Open Groups/JoinOpenGroupVC.swift b/Session/Open Groups/JoinOpenGroupVC.swift index 71d39ec73..b9983fa8c 100644 --- a/Session/Open Groups/JoinOpenGroupVC.swift +++ b/Session/Open Groups/JoinOpenGroupVC.swift @@ -144,25 +144,26 @@ final class JoinOpenGroupVC: BaseVC, UIPageViewControllerDataSource, UIPageViewC dismiss(animated: true, completion: nil) } - func controller(_ controller: QRCodeScanningViewController, didDetectQRCodeWith string: String) { - joinOpenGroup(with: string) + func controller(_ controller: QRCodeScanningViewController, didDetectQRCodeWith string: String, onError: (() -> ())?) { + joinOpenGroup(with: string, onError: onError) } - fileprivate func joinOpenGroup(with urlString: String) { + fileprivate func joinOpenGroup(with urlString: String, onError: (() -> ())?) { // A V2 open group URL will look like: + + + + // The host doesn't parse if no explicit scheme is provided guard let (room, server, publicKey) = SessionUtil.parseCommunity(url: urlString) else { showError( title: "invalid_url".localized(), - message: "COMMUNITY_ERROR_INVALID_URL".localized() + message: "COMMUNITY_ERROR_INVALID_URL".localized(), + onError: onError ) return } - joinOpenGroup(roomToken: room, server: server, publicKey: publicKey, shouldOpenCommunity: true) + joinOpenGroup(roomToken: room, server: server, publicKey: publicKey, shouldOpenCommunity: true, onError: onError) } - fileprivate func joinOpenGroup(roomToken: String, server: String, publicKey: String, shouldOpenCommunity: Bool) { + fileprivate func joinOpenGroup(roomToken: String, server: String, publicKey: String, shouldOpenCommunity: Bool, onError: (() -> ())?) { guard !isJoining, let navigationController: UINavigationController = navigationController else { return } isJoining = true @@ -209,7 +210,8 @@ final class JoinOpenGroupVC: BaseVC, UIPageViewControllerDataSource, UIPageViewC self?.dismiss(animated: true) { // Dismiss the loader self?.showError( title: "COMMUNITY_ERROR_GENERIC".localized(), - message: error.localizedDescription + message: error.localizedDescription, + onError: onError ) } @@ -232,13 +234,14 @@ final class JoinOpenGroupVC: BaseVC, UIPageViewControllerDataSource, UIPageViewC // MARK: - Convenience - private func showError(title: String, message: String = "") { + private func showError(title: String, message: String = "", onError: (() -> ())?) { let confirmationModal: ConfirmationModal = ConfirmationModal( info: ConfirmationModal.Info( title: title, body: .text(message), cancelTitle: "BUTTON_OK".localized(), - cancelStyle: .alert_text + cancelStyle: .alert_text, + afterClosed: onError ) ) self.navigationController?.present(confirmationModal, animated: true, completion: nil) @@ -399,13 +402,14 @@ private final class EnterURLVC: UIViewController, UIGestureRecognizerDelegate, O roomToken: room.token, server: OpenGroupAPI.defaultServer, publicKey: OpenGroupAPI.defaultServerPublicKey, - shouldOpenCommunity: true + shouldOpenCommunity: true, + onError: nil ) } @objc private func joinOpenGroup() { let url = urlTextView.text?.trimmingCharacters(in: .whitespaces) ?? "" - joinOpenGroupVC?.joinOpenGroup(with: url) + joinOpenGroupVC?.joinOpenGroup(with: url, onError: nil) } // MARK: - Updating diff --git a/Session/Settings/QRCodeVC.swift b/Session/Settings/QRCodeVC.swift index 7f16b4bc7..e5dc85397 100644 --- a/Session/Settings/QRCodeVC.swift +++ b/Session/Settings/QRCodeVC.swift @@ -119,12 +119,12 @@ final class QRCodeVC : BaseVC, UIPageViewControllerDataSource, UIPageViewControl dismiss(animated: true, completion: nil) } - func controller(_ controller: QRCodeScanningViewController, didDetectQRCodeWith string: String) { + func controller(_ controller: QRCodeScanningViewController, didDetectQRCodeWith string: String, onError: (() -> ())?) { let hexEncodedPublicKey = string - startNewPrivateChatIfPossible(with: hexEncodedPublicKey) + startNewPrivateChatIfPossible(with: hexEncodedPublicKey, onError: onError) } - fileprivate func startNewPrivateChatIfPossible(with hexEncodedPublicKey: String) { + fileprivate func startNewPrivateChatIfPossible(with hexEncodedPublicKey: String, onError: (() -> ())?) { if !KeyPair.isValidHexEncodedPublicKey(candidate: hexEncodedPublicKey) { let modal: ConfirmationModal = ConfirmationModal( targetView: self.view, @@ -132,7 +132,8 @@ final class QRCodeVC : BaseVC, UIPageViewControllerDataSource, UIPageViewControl title: "invalid_session_id".localized(), body: .text("INVALID_SESSION_ID_MESSAGE".localized()), cancelTitle: "BUTTON_OK".localized(), - cancelStyle: .alert_text + cancelStyle: .alert_text, + afterClosed: onError ) ) self.present(modal, animated: true) diff --git a/Session/Shared/QRCodeScanningViewController.swift b/Session/Shared/QRCodeScanningViewController.swift index 011823fa1..b29db5fa5 100644 --- a/Session/Shared/QRCodeScanningViewController.swift +++ b/Session/Shared/QRCodeScanningViewController.swift @@ -2,55 +2,36 @@ import UIKit import AVFoundation -import ZXingObjC import SessionUIKit +import SessionUtilitiesKit protocol QRScannerDelegate: AnyObject { - func controller(_ controller: QRCodeScanningViewController, didDetectQRCodeWith string: String) + func controller(_ controller: QRCodeScanningViewController, didDetectQRCodeWith string: String, onError: (() -> ())?) } -class QRCodeScanningViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate, ZXCaptureDelegate { +class QRCodeScanningViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate { public weak var scanDelegate: QRScannerDelegate? private let captureQueue: DispatchQueue = DispatchQueue.global(qos: .default) - private var capture: ZXCapture? + private var capture: AVCaptureSession? + private var captureLayer: AVCaptureVideoPreviewLayer? private var captureEnabled: Bool = false // MARK: - Initialization deinit { - self.capture?.layer.removeFromSuperlayer() + self.captureLayer?.removeFromSuperlayer() } // MARK: - Components - private let maskingView: UIView = { - let result: OWSBezierPathView = OWSBezierPathView() - result.configureShapeLayerBlock = { layer, bounds in - // Add a circular mask - let path: UIBezierPath = UIBezierPath(rect: bounds) - let margin: CGFloat = ScaleFromIPhone5To7Plus(24, 48) - let radius: CGFloat = ((min(bounds.size.width, bounds.size.height) * 0.5) - margin) - - // Center the circle's bounding rectangle - let circleRect: CGRect = CGRect( - x: ((bounds.size.width * 0.5) - radius), - y: ((bounds.size.height * 0.5) - radius), - width: (radius * 2), - height: (radius * 2) - ) - let circlePath: UIBezierPath = UIBezierPath.init( - roundedRect: circleRect, - cornerRadius: 16 - ) - path.append(circlePath) - path.usesEvenOddFillRule = true - - layer.path = path.cgPath - layer.fillRule = .evenOdd - layer.themeFillColor = .black - layer.opacity = 0.32 - } + private let maskingView: UIView = UIView() + + private lazy var maskLayer: CAShapeLayer = { + let result: CAShapeLayer = CAShapeLayer() + result.fillRule = .evenOdd + result.themeFillColor = .black + result.opacity = 0.32 return result }() @@ -61,7 +42,8 @@ class QRCodeScanningViewController: UIViewController, AVCaptureMetadataOutputObj super.loadView() self.view.addSubview(maskingView) - maskingView.pin(to: self.view) + + maskingView.layer.addSublayer(maskLayer) } override func viewWillAppear(_ animated: Bool) { @@ -81,11 +63,28 @@ class QRCodeScanningViewController: UIViewController, AVCaptureMetadataOutputObj override func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() - // Note: When accessing 'capture.layer' if the setup hasn't been completed it - // will result in a layout being triggered which creates an infinite loop, this - // check prevents that case - if let capture: ZXCapture = self.capture { - capture.layer.frame = self.view.bounds + captureLayer?.frame = self.view.bounds + + if maskingView.frame != self.view.bounds { + // Add a circular mask + let path: UIBezierPath = UIBezierPath(rect: self.view.bounds) + let radius: CGFloat = ((min(self.view.bounds.size.width, self.view.bounds.size.height) * 0.5) - Values.largeSpacing) + + // Center the circle's bounding rectangle + let circleRect: CGRect = CGRect( + x: ((self.view.bounds.size.width * 0.5) - radius), + y: ((self.view.bounds.size.height * 0.5) - radius), + width: (radius * 2), + height: (radius * 2) + ) + let clippingPath: UIBezierPath = UIBezierPath.init( + roundedRect: circleRect, + cornerRadius: 16 + ) + path.append(clippingPath) + path.usesEvenOddFillRule = true + + maskLayer.path = path.cgPath } } @@ -101,31 +100,76 @@ class QRCodeScanningViewController: UIViewController, AVCaptureMetadataOutputObj #else if self.capture == nil { self.captureQueue.async { [weak self] in - let capture: ZXCapture = ZXCapture() - capture.camera = capture.back() - capture.focusMode = .autoFocus - capture.delegate = self - capture.start() + let maybeDevice: AVCaptureDevice? = { + if let result = AVCaptureDevice.default(.builtInDualCamera, for: .video, position: .back) { + return result + } + + return AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) + }() - // Note: When accessing the 'layer' for the first time it will create - // an instance of 'AVCaptureVideoPreviewLayer', this can hang a little - // so we do this on the background thread first - if capture.layer != nil {} + // Set the input device to autoFocus (since we don't have the interaction setup for + // doing it manually) + maybeDevice?.focusMode = .continuousAutoFocus + + // Device input + guard + let device: AVCaptureDevice = maybeDevice, + let input: AVCaptureInput = try? AVCaptureDeviceInput(device: device) + else { + return SNLog("Failed to retrieve the device for enabling the QRCode scanning camera") + } + + // Image output + let output: AVCaptureVideoDataOutput = AVCaptureVideoDataOutput() + output.alwaysDiscardsLateVideoFrames = true + + // Metadata output the session + let metadataOutput: AVCaptureMetadataOutput = AVCaptureMetadataOutput() + metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main) + + let capture: AVCaptureSession = AVCaptureSession() + capture.beginConfiguration() + if capture.canAddInput(input) { capture.addInput(input) } + if capture.canAddOutput(output) { capture.addOutput(output) } + if capture.canAddOutput(metadataOutput) { capture.addOutput(metadataOutput) } + + guard !capture.inputs.isEmpty && capture.outputs.count == 2 else { + return SNLog("Failed to attach the input/output to the capture session") + } + + guard metadataOutput.availableMetadataObjectTypes.contains(.qr) else { + return SNLog("The output is unable to process QR codes") + } + + // Specify that we want to capture QR Codes (Needs to be done after being added + // to the session, 'availableMetadataObjectTypes' is empty beforehand) + metadataOutput.metadataObjectTypes = [.qr] + + capture.commitConfiguration() + + // Create the layer for rendering the camera video + let layer: AVCaptureVideoPreviewLayer = AVCaptureVideoPreviewLayer(session: capture) + layer.videoGravity = AVLayerVideoGravity.resizeAspectFill + + // Start running the capture session + capture.startRunning() DispatchQueue.main.async { - capture.layer.frame = (self?.view.bounds ?? .zero) - self?.view.layer.addSublayer(capture.layer) + layer.frame = (self?.view.bounds ?? .zero) + self?.view.layer.addSublayer(layer) if let maskingView: UIView = self?.maskingView { self?.view.bringSubviewToFront(maskingView) } self?.capture = capture + self?.captureLayer = layer } } } else { - self.capture?.start() + self.capture?.startRunning() } #endif } @@ -133,18 +177,25 @@ class QRCodeScanningViewController: UIViewController, AVCaptureMetadataOutputObj private func stopCapture() { self.captureEnabled = false self.captureQueue.async { [weak self] in - self?.capture?.stop() + self?.capture?.stopRunning() } } - internal func captureResult(_ capture: ZXCapture, result: ZXResult) { - guard self.captureEnabled else { return } + func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { + guard + self.captureEnabled, + let metadata: AVMetadataObject = metadataObjects.first(where: { ($0 as? AVMetadataMachineReadableCodeObject)?.type == .qr }), + let qrCodeInfo: AVMetadataMachineReadableCodeObject = metadata as? AVMetadataMachineReadableCodeObject, + let qrCode: String = qrCodeInfo.stringValue + else { return } self.stopCapture() // Vibrate AudioServicesPlaySystemSound(kSystemSoundID_Vibrate) - - self.scanDelegate?.controller(self, didDetectQRCodeWith: result.text) + + self.scanDelegate?.controller(self, didDetectQRCodeWith: qrCode) { [weak self] in + self?.startCapture() + } } } From 2833cef5e4e9ca116d943e9c58e80ba0eeba06fd Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 12 Jul 2023 17:15:21 +1000 Subject: [PATCH 126/135] Tweaks to test the static upload script --- .drone-static-upload.sh | 7 ++- .drone.jsonnet | 126 +++++++++++++++++++++++++--------------- 2 files changed, 85 insertions(+), 48 deletions(-) mode change 100644 => 100755 .drone-static-upload.sh diff --git a/.drone-static-upload.sh b/.drone-static-upload.sh old mode 100644 new mode 100755 index 4730e5c30..b1f7c0887 --- a/.drone-static-upload.sh +++ b/.drone-static-upload.sh @@ -30,8 +30,11 @@ fi mkdir -v "$base" -# Copy over the build products -cp -av build/Build/Products/App\ Store\ Release-iphonesimulator/Session.app "$base" +# Copy over the build products +mkdir build +echo "Test" > "build/test.txt" +cp -av build/test.txt "$base" +# cp -av build/Build/Products/App\ Store\ Release-iphonesimulator/Session.app "$base" # tar dat shiz up yo archive="$base.tar.xz" diff --git a/.drone.jsonnet b/.drone.jsonnet index 40295c503..1d204720a 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -1,64 +1,98 @@ // Intentionally doing a depth of 2 as libSession-util has it's own submodules (and libLokinet likely will as well) -local submodule_commands = ['git fetch --tags', 'git submodule update --init --recursive --depth=2']; +local clone_submodules = { + name: 'Clone Submodules', + commands: ['git fetch --tags', 'git submodule update --init --recursive --depth=2'] +}; // cmake options for static deps mirror local ci_dep_mirror(want_mirror) = (if want_mirror then ' -DLOCAL_MIRROR=https://oxen.rocks/deps ' else ''); // xcpretty -local xcpretty_commands = [ - ||| - if [[ $(command -v brew) != "" ]]; then - brew install xcpretty - fi - |||, - ||| - if [[ $(command -v brew) == "" ]]; then - gem install xcpretty - fi - |||, -]; +local install_xcpretty = { + name: 'Install XCPretty', + commands: [ + ||| + if [[ $(command -v brew) != "" ]]; then + brew install xcpretty + fi + |||, + ||| + if [[ $(command -v brew) == "" ]]; then + gem install xcpretty + fi + |||, + ] +}; + +// Cocoapods +// +// Unfortunately Cocoapods has a dumb restriction which requires you to use UTF-8 for the +// 'LANG' env var so we need to work around the with https://github.com/CocoaPods/CocoaPods/issues/6333 +local install_cocoapods = { + name: 'Install CocoaPods', + commands: ['LANG=en_US.UTF-8 pod install'] +}; [ - // Unit tests { kind: 'pipeline', type: 'exec', - name: 'Unit Tests', + name: 'Test Upload', platform: { os: 'darwin', arch: 'amd64' }, steps: [ - { name: 'Clone Submodules', commands: submodule_commands }, - // { name: 'Install XCPretty', commands: xcpretty_commands }, - { name: 'Install CocoaPods', commands: ['LANG=en_US.UTF-8 pod install'] }, { - name: 'Run Unit Tests', + name: 'Upload artifacts', commands: [ - 'mkdir build', - 'xcodebuild test -workspace Session.xcworkspace -scheme Session -destination "platform=iOS Simulator,name=iPhone 14 Pro"' // | xcpretty --report html' - ], - }, - ], - }, - // Simulator build - { - kind: 'pipeline', - type: 'exec', - name: 'Simulator Build', - platform: { os: 'darwin', arch: 'amd64' }, - steps: [ - { name: 'Clone Submodules', commands: submodule_commands }, - // { name: 'Install XCPretty', commands: xcpretty_commands }, - { name: 'Install CocoaPods', commands: ['LANG=en_US.UTF-8 pod install'] }, - { - name: 'Build', - commands: [ - 'mkdir build', - 'xcodebuild -workspace Session.xcworkspace -scheme Session -configuration "App Store Release" -sdk iphonesimulator -derivedDataPath ./build -destination "generic/platform=iOS Simulator"', // | xcpretty', './.drone-static-upload.sh' - ], - }, - ], + ] + } + ] }, +// // Unit tests +// { +// kind: 'pipeline', +// type: 'exec', +// name: 'Unit Tests', +// platform: { os: 'darwin', arch: 'amd64' }, +// steps: [ +// clone_submodules, +// // install_xcpretty, +// install_cocoapods, +// { +// name: 'Run Unit Tests', +// commands: [ +// 'mkdir build', +// 'xcodebuild test -workspace Session.xcworkspace -scheme Session -destination "platform=iOS Simulator,name=iPhone 14 Pro"' // | xcpretty --report html' +// ], +// }, +// ], +// }, +// // Simulator build +// { +// kind: 'pipeline', +// type: 'exec', +// name: 'Simulator Build', +// platform: { os: 'darwin', arch: 'amd64' }, +// steps: [ +// clone_submodules, +// // install_xcpretty, +// install_cocoapods, +// { +// name: 'Build', +// commands: [ +// 'mkdir build', +// 'xcodebuild -workspace Session.xcworkspace -scheme Session -configuration "App Store Release" -sdk iphonesimulator -derivedDataPath ./build -destination "generic/platform=iOS Simulator"' // | xcpretty' +// ], +// }, +// { +// name: 'Upload artifacts', +// commands: [ +// './.drone-static-upload.sh' +// ] +// } +// ], +// }, // // AppStore build (generate an archive to be signed later) // { // kind: 'pipeline', @@ -66,9 +100,9 @@ local xcpretty_commands = [ // name: 'AppStore Build', // platform: { os: 'darwin', arch: 'amd64' }, // steps: [ -// { name: 'Clone Submodules', commands: submodule_commands }, -// // { name: 'Install XCPretty', commands: xcpretty_commands }, -// { name: 'Install CocoaPods', commands: ['LANG=en_US.UTF-8 pod install'] }, +// clone_submodules, +// // install_xcpretty, +// install_cocoapods, // { // name: 'Build', // commands: [ From b72bf426054043130c5894252c4e559b7df870a0 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 13 Jul 2023 14:47:10 +1000 Subject: [PATCH 127/135] Updated the CI and fixed a couple of config bugs Updated to the 1.0.0 release of libSession Set the User Config feature flag to July 31st 10am AEST Shifted quote thumbnail generation out of the DBWrite thread Stopped the CurrentUserPoller from polling the user config namespaces if the feature flag is off Fixed an issue where the scrollToBottom behaviour could be a little buggy when an optimistic update is replaced with the proper change Fixed an issue where the 'attachmentsNotUploaded' error wouldn't result in a message entering an error state Fixed a bug where sync messages with attachments weren't being sent --- .drone.jsonnet | 159 ++++++------- LibSession-Util | 2 +- Scripts/LintLocalizableStrings.swift | 1 + .../drone-static-upload.sh | 18 +- .../ConversationVC+Interaction.swift | 119 +++++----- Session/Conversations/ConversationVC.swift | 31 ++- .../GIFs/GiphyAPI.swift | 4 +- .../Jobs/Types/MessageSendJob.swift | 214 +++++++++--------- .../Sending & Receiving/MessageSender.swift | 16 +- .../Pollers/CurrentUserPoller.swift | 7 +- .../Quotes/QuotedReplyModel.swift | 13 -- .../SessionUtil/SessionUtil.swift | 4 +- .../Shared Models/MessageViewModel.swift | 5 +- 13 files changed, 309 insertions(+), 284 deletions(-) rename .drone-static-upload.sh => Scripts/drone-static-upload.sh (83%) diff --git a/.drone.jsonnet b/.drone.jsonnet index 1d204720a..07634ce4c 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -7,23 +7,6 @@ local clone_submodules = { // cmake options for static deps mirror local ci_dep_mirror(want_mirror) = (if want_mirror then ' -DLOCAL_MIRROR=https://oxen.rocks/deps ' else ''); -// xcpretty -local install_xcpretty = { - name: 'Install XCPretty', - commands: [ - ||| - if [[ $(command -v brew) != "" ]]; then - brew install xcpretty - fi - |||, - ||| - if [[ $(command -v brew) == "" ]]; then - gem install xcpretty - fi - |||, - ] -}; - // Cocoapods // // Unfortunately Cocoapods has a dumb restriction which requires you to use UTF-8 for the @@ -35,81 +18,89 @@ local install_cocoapods = { [ + // Unit tests { kind: 'pipeline', type: 'exec', - name: 'Test Upload', + name: 'Unit Tests', platform: { os: 'darwin', arch: 'amd64' }, steps: [ + clone_submodules, + // install_xcpretty, + install_cocoapods, + { + name: 'Run Unit Tests', + commands: [ + 'mkdir build', + ||| + if command -v xcpretty >/dev/null 2>&1; then + 'xcodebuild test -workspace Session.xcworkspace -scheme Session -destination "platform=iOS Simulator,name=iPhone 14 Pro" | xcpretty' + else + 'xcodebuild test -workspace Session.xcworkspace -scheme Session -destination "platform=iOS Simulator,name=iPhone 14 Pro"' + fi + ||| + ], + }, + ], + }, + // Simulator build + { + kind: 'pipeline', + type: 'exec', + name: 'Simulator Build', + platform: { os: 'darwin', arch: 'amd64' }, + steps: [ + clone_submodules, + install_cocoapods, + { + name: 'Build', + commands: [ + 'mkdir build', + ||| + if command -v xcpretty >/dev/null 2>&1; then + xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphonesimulator -derivedDataPath ./build -archivePath ./build/Session_sim.xcarchive -destination 'generic/platform=iOS Simulator' | xcpretty + else + xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphonesimulator -derivedDataPath ./build -archivePath ./build/Session_sim.xcarchive -destination 'generic/platform=iOS Simulator' + fi + ||| + ], + }, { name: 'Upload artifacts', commands: [ - './.drone-static-upload.sh' + './Scripts/drone-static-upload.sh' ] - } - ] + }, + ], + }, + // AppStore build (generate an archive to be signed later) + { + kind: 'pipeline', + type: 'exec', + name: 'AppStore Build', + platform: { os: 'darwin', arch: 'amd64' }, + steps: [ + clone_submodules, + install_cocoapods, + { + name: 'Build', + commands: [ + 'mkdir build', + ||| + if command -v xcpretty >/dev/null 2>&1; then + xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphoneos -derivedDataPath ./build -archivePath ./build/Session.xcarchive -destination 'generic/platform=iOS' | xcpretty + else + xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphoneos -derivedDataPath ./build -archivePath ./build/Session.xcarchive -destination 'generic/platform=iOS' + fi + ||| + ], + }, + { + name: 'Upload artifacts', + commands: [ + './Scripts/drone-static-upload.sh' + ] + }, + ], }, -// // Unit tests -// { -// kind: 'pipeline', -// type: 'exec', -// name: 'Unit Tests', -// platform: { os: 'darwin', arch: 'amd64' }, -// steps: [ -// clone_submodules, -// // install_xcpretty, -// install_cocoapods, -// { -// name: 'Run Unit Tests', -// commands: [ -// 'mkdir build', -// 'xcodebuild test -workspace Session.xcworkspace -scheme Session -destination "platform=iOS Simulator,name=iPhone 14 Pro"' // | xcpretty --report html' -// ], -// }, -// ], -// }, -// // Simulator build -// { -// kind: 'pipeline', -// type: 'exec', -// name: 'Simulator Build', -// platform: { os: 'darwin', arch: 'amd64' }, -// steps: [ -// clone_submodules, -// // install_xcpretty, -// install_cocoapods, -// { -// name: 'Build', -// commands: [ -// 'mkdir build', -// 'xcodebuild -workspace Session.xcworkspace -scheme Session -configuration "App Store Release" -sdk iphonesimulator -derivedDataPath ./build -destination "generic/platform=iOS Simulator"' // | xcpretty' -// ], -// }, -// { -// name: 'Upload artifacts', -// commands: [ -// './.drone-static-upload.sh' -// ] -// } -// ], -// }, -// // AppStore build (generate an archive to be signed later) -// { -// kind: 'pipeline', -// type: 'exec', -// name: 'AppStore Build', -// platform: { os: 'darwin', arch: 'amd64' }, -// steps: [ -// clone_submodules, -// // install_xcpretty, -// install_cocoapods, -// { -// name: 'Build', -// commands: [ -// 'mkdir build', -// 'xcodebuild archive -workspace Session.xcworkspace -scheme Session -archivePath ./build/Session.xcarchive -destination "platform=generic/iOS" | xcpretty' -// ], -// }, -// ], -// }, ] \ No newline at end of file diff --git a/LibSession-Util b/LibSession-Util index e0b994201..d8f07fa92 160000 --- a/LibSession-Util +++ b/LibSession-Util @@ -1 +1 @@ -Subproject commit e0b994201a016cc5bf9065526a0ceb4291f60d5a +Subproject commit d8f07fa92c12c5c2409774e03e03395d7847d1c2 diff --git a/Scripts/LintLocalizableStrings.swift b/Scripts/LintLocalizableStrings.swift index 12bd626aa..910179348 100755 --- a/Scripts/LintLocalizableStrings.swift +++ b/Scripts/LintLocalizableStrings.swift @@ -26,6 +26,7 @@ var pathFiles: [String] = { return fileUrls .filter { ((try? $0.resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory == false) && // No directories + !$0.path.contains("build/") && // Exclude files under the build folder (CI) !$0.path.contains("Pods/") && // Exclude files under the pods folder !$0.path.contains(".xcassets") && // Exclude asset bundles !$0.path.contains(".app/") && // Exclude files in the app build directories diff --git a/.drone-static-upload.sh b/Scripts/drone-static-upload.sh similarity index 83% rename from .drone-static-upload.sh rename to Scripts/drone-static-upload.sh index b1f7c0887..4fd13faa5 100755 --- a/.drone-static-upload.sh +++ b/Scripts/drone-static-upload.sh @@ -31,10 +31,20 @@ fi mkdir -v "$base" # Copy over the build products +prod_path="build/Session.xcarchive" +sim_path="build/Session_sim.xcarchive/Products/Applications/Session.app" + mkdir build echo "Test" > "build/test.txt" -cp -av build/test.txt "$base" -# cp -av build/Build/Products/App\ Store\ Release-iphonesimulator/Session.app "$base" + +if [ ! -d $prod_path ]; then + cp -av $prod_path "$base" +else if [ ! -d $sim_path ]; then + cp -av $sim_path "$base" +else + echo "Expected a file to upload, found none" >&2 + exit 1 +fi # tar dat shiz up yo archive="$base.tar.xz" @@ -54,9 +64,7 @@ for p in "${upload_dirs[@]}"; do mkdirs="$mkdirs -mkdir $dir_tmp" done -if [ -e "$base-debug-symbols.tar.xz" ] ; then - put_debug="put $base-debug-symbols.tar.xz $upload_to" -fi + sftp -i ssh_key -b - -o StrictHostKeyChecking=off drone@oxen.rocks < 1 && + changeset[changeset.count - 2].elementDeleted == changeset[changeset.count - 1].elementInserted + else { return false } + + let deletedModels: [MessageViewModel] = changeset[changeset.count - 2] + .elementDeleted + .map { self.viewModel.interactionData[$0.section].elements[$0.element] } + let insertedModels: [MessageViewModel] = changeset[changeset.count - 1] + .elementInserted + .map { updatedData[$0.section].elements[$0.element] } + + // Make sure all the deleted models were optimistic updates, the inserted models were not + // optimistic updates and they have the same timestamps + return ( + deletedModels.map { $0.id }.asSet() == [MessageViewModel.optimisticUpdateId] && + insertedModels.map { $0.id }.asSet() != [MessageViewModel.optimisticUpdateId] && + deletedModels.map { $0.timestampMs }.asSet() == insertedModels.map { $0.timestampMs }.asSet() + ) + }() let wasOnlyUpdates: Bool = ( - changeset.count == 1 && - changeset[0].elementUpdated.count == changeset[0].changeCount + onlyReplacedOptimisticUpdate || ( + changeset.count == 1 && + changeset[0].elementUpdated.count == changeset[0].changeCount + ) ) self.viewModel.sentMessageBeforeUpdate = false @@ -912,13 +937,13 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers guard !didSendMessageBeforeUpdate && !wasOnlyUpdates else { self.viewModel.updateInteractionData(updatedData) self.tableView.reloadData() - self.tableView.layoutIfNeeded() // If we just sent a message then we want to jump to the bottom of the conversation instantly if didSendMessageBeforeUpdate { // We need to dispatch to the next run loop because it seems trying to scroll immediately after // triggering a 'reloadData' doesn't work DispatchQueue.main.async { [weak self] in + self?.tableView.layoutIfNeeded() self?.scrollToBottom(isAnimated: false) // Note: The scroll button alpha won't get set correctly in this case so we forcibly set it to diff --git a/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift b/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift index 1a3f6eef5..57ce17769 100644 --- a/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift +++ b/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift @@ -299,7 +299,7 @@ enum GiphyAPI { return HTTPError.generic } .map { data, _ in - Logger.error("search request succeeded") + Logger.debug("search request succeeded") guard let imageInfos = self.parseGiphyImages(responseData: data) else { Logger.error("unable to parse trending images") @@ -347,7 +347,7 @@ enum GiphyAPI { return HTTPError.generic } .tryMap { data, _ -> [GiphyImageInfo] in - Logger.error("search request succeeded") + Logger.debug("search request succeeded") guard let imageInfos = self.parseGiphyImages(responseData: data) else { throw HTTPError.invalidResponse diff --git a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift index ad8f54c77..a9c4ffb8d 100644 --- a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift +++ b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift @@ -39,8 +39,7 @@ public enum MessageSendJob: JobExecutor { /// already have attachments in a valid state if details.message is VisibleMessage, - (details.message as? VisibleMessage)?.reaction == nil && - details.isSyncMessage == false + (details.message as? VisibleMessage)?.reaction == nil { guard let jobId: Int64 = job.id, @@ -51,122 +50,111 @@ public enum MessageSendJob: JobExecutor { return } - // If the original interaction no longer exists then don't bother sending the message (ie. the - // message was deleted before it even got sent) - guard Storage.shared.read({ db in try Interaction.exists(db, id: interactionId) }) == true else { - SNLog("[MessageSendJob] Failing due to missing interaction") - failure(job, StorageError.objectNotFound, true) - return - } - - // Check if there are any attachments associated to this message, and if so - // upload them now - // - // Note: Normal attachments should be sent in a non-durable way but any - // attachments for LinkPreviews and Quotes will be processed through this mechanism - let attachmentState: (shouldFail: Bool, shouldDefer: Bool, fileIds: [String])? = Storage.shared.write { db in - let allAttachmentStateInfo: [Attachment.StateInfo] = try Attachment - .stateInfo(interactionId: interactionId) - .fetchAll(db) - let maybeFileIds: [String?] = allAttachmentStateInfo - .sorted { lhs, rhs in lhs.albumIndex < rhs.albumIndex } - .map { Attachment.fileId(for: $0.downloadUrl) } - let fileIds: [String] = maybeFileIds.compactMap { $0 } - - // If there were failed attachments then this job should fail (can't send a - // message which has associated attachments if the attachments fail to upload) - guard !allAttachmentStateInfo.contains(where: { $0.state == .failedDownload }) else { - return (true, false, fileIds) - } - - // Create jobs for any pending (or failed) attachment jobs and insert them into the - // queue before the current job (this will mean the current job will re-run - // after these inserted jobs complete) - // - // Note: If there are any 'downloaded' attachments then they also need to be - // uploaded (as a 'downloaded' attachment will be on the current users device - // but not on the message recipients device - both LinkPreview and Quote can - // have this case) - try allAttachmentStateInfo - .filter { attachment -> Bool in - // Non-media quotes won't have thumbnails so so don't try to upload them - guard attachment.downloadUrl != Attachment.nonMediaQuoteFileId else { return false } - - switch attachment.state { - case .uploading, .pendingDownload, .downloading, .failedUpload, .downloaded: - return true - - default: return false + // Retrieve the current attachment state + typealias AttachmentState = (error: Error?, pendingUploadAttachmentIds: [String], preparedFileIds: [String]) + + let attachmentState: AttachmentState = Storage.shared + .read { db in + // If the original interaction no longer exists then don't bother sending the message (ie. the + // message was deleted before it even got sent) + guard try Interaction.exists(db, id: interactionId) else { + SNLog("[MessageSendJob] Failing due to missing interaction") + return (StorageError.objectNotFound, [], []) + } + + // Get the current state of the attachments + let allAttachmentStateInfo: [Attachment.StateInfo] = try Attachment + .stateInfo(interactionId: interactionId) + .fetchAll(db) + let maybeFileIds: [String?] = allAttachmentStateInfo + .sorted { lhs, rhs in lhs.albumIndex < rhs.albumIndex } + .map { Attachment.fileId(for: $0.downloadUrl) } + let fileIds: [String] = maybeFileIds.compactMap { $0 } + + // If there were failed attachments then this job should fail (can't send a + // message which has associated attachments if the attachments fail to upload) + guard !allAttachmentStateInfo.contains(where: { $0.state == .failedDownload }) else { + SNLog("[MessageSendJob] Failing due to failed attachment upload") + return (AttachmentError.notUploaded, [], fileIds) + } + + /// Find all attachmentIds for attachments which need to be uploaded + /// + /// **Note:** If there are any 'downloaded' attachments then they also need to be uploaded (as a + /// 'downloaded' attachment will be on the current users device but not on the message recipients + /// device - both `LinkPreview` and `Quote` can have this case) + let pendingUploadAttachmentIds: [String] = allAttachmentStateInfo + .filter { attachment -> Bool in + // Non-media quotes won't have thumbnails so so don't try to upload them + guard attachment.downloadUrl != Attachment.nonMediaQuoteFileId else { return false } + + switch attachment.state { + case .uploading, .pendingDownload, .downloading, .failedUpload, .downloaded: + return true + + default: return false + } } - } - .filter { stateInfo in - // Don't add a new job if there is one already in the queue - !JobRunner.hasPendingOrRunningJob( - with: .attachmentUpload, - details: AttachmentUploadJob.Details( - messageSendJobId: jobId, - attachmentId: stateInfo.attachmentId - ) - ) - } - .compactMap { stateInfo -> (jobId: Int64, job: Job)? in - JobRunner - .insert( - db, - job: Job( - variant: .attachmentUpload, - behaviour: .runOnce, - threadId: job.threadId, - interactionId: interactionId, - details: AttachmentUploadJob.Details( - messageSendJobId: jobId, - attachmentId: stateInfo.attachmentId - ) - ), - before: job - ) - } - .forEach { otherJobId, _ in - // Create the dependency between the jobs - try JobDependencies( - jobId: jobId, - dependantId: otherJobId - ) - .insert(db) - } - - // If there were pending or uploading attachments then stop here (we want to - // upload them first and then re-run this send job - the 'JobRunner.insert' - // method will take care of this) - let isMissingFileIds: Bool = (maybeFileIds.count != fileIds.count) - let hasPendingUploads: Bool = allAttachmentStateInfo.contains(where: { $0.state != .uploaded }) - - return ( - (isMissingFileIds && !hasPendingUploads), - hasPendingUploads, - fileIds - ) - } - - // Don't send messages with failed attachment uploads - // - // Note: If we have gotten to this point then any dependant attachment upload - // jobs will have permanently failed so this message send should also do so - guard attachmentState?.shouldFail == false else { - SNLog("[MessageSendJob] Failing due to failed attachment upload") - failure(job, AttachmentError.notUploaded, true) - return + .map { $0.attachmentId } + + return (nil, pendingUploadAttachmentIds, fileIds) + } + .defaulting(to: (MessageSenderError.invalidMessage, [], [])) + + /// If we got an error when trying to retrieve the attachment state then this job is actually invalid so it + /// should permanently fail + guard attachmentState.error == nil else { + return failure(job, (attachmentState.error ?? MessageSenderError.invalidMessage), true) } - // Defer the job if we found incomplete uploads - guard attachmentState?.shouldDefer == false else { - SNLog("[MessageSendJob] Deferring pending attachment uploads") - deferred(job) - return + /// If we have any pending (or failed) attachment uploads then we should create jobs for them and insert them into the + /// queue before the current job and defer it (this will mean the current job will re-run after these inserted jobs complete) + guard attachmentState.pendingUploadAttachmentIds.isEmpty else { + Storage.shared.write { db in + try attachmentState.pendingUploadAttachmentIds + .filter { attachmentId in + // Don't add a new job if there is one already in the queue + !JobRunner.hasPendingOrRunningJob( + with: .attachmentUpload, + details: AttachmentUploadJob.Details( + messageSendJobId: jobId, + attachmentId: attachmentId + ) + ) + } + .compactMap { attachmentId -> (jobId: Int64, job: Job)? in + JobRunner + .insert( + db, + job: Job( + variant: .attachmentUpload, + behaviour: .runOnce, + threadId: job.threadId, + interactionId: interactionId, + details: AttachmentUploadJob.Details( + messageSendJobId: jobId, + attachmentId: attachmentId + ) + ), + before: job + ) + } + .forEach { otherJobId, _ in + // Create the dependency between the jobs + try JobDependencies( + jobId: jobId, + dependantId: otherJobId + ) + .insert(db) + } + } + + SNLog("[MessageSendJob] Deferring due to pending attachment uploads") + return deferred(job) } - + // Store the fileIds so they can be sent with the open group message content - messageFileIds = (attachmentState?.fileIds ?? []) + messageFileIds = attachmentState.preparedFileIds } // Store the sentTimestamp from the message in case it fails due to a clockOutOfSync error diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index 9005bf1b8..2893c8aa1 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -606,6 +606,21 @@ public final class MessageSender { ) guard expectedAttachmentUploadCount == preparedSendData.totalAttachmentsUploaded else { + // Make sure to actually handle this as a failure (if we don't then the message + // won't go into an error state correctly) + if let message: Message = preparedSendData.message { + dependencies.storage.read { db in + MessageSender.handleFailedMessageSend( + db, + message: message, + with: .attachmentsNotUploaded, + interactionId: preparedSendData.interactionId, + isSyncMessage: (preparedSendData.isSyncMessage == true), + using: dependencies + ) + } + } + return Fail(error: MessageSenderError.attachmentsNotUploaded) .eraseToAnyPublisher() } @@ -992,7 +1007,6 @@ public final class MessageSender { isSyncMessage: Bool = false, using dependencies: SMKDependencies = SMKDependencies() ) -> Error { - // TODO: Revert the local database change // If the message was a reaction then we don't want to do anything to the original // interaciton (which the 'interactionId' is pointing to guard (message as? VisibleMessage)?.reaction == nil else { return error } diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift index 02b793160..3936baf4f 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift @@ -14,7 +14,12 @@ public final class CurrentUserPoller: Poller { // MARK: - Settings - override var namespaces: [SnodeAPI.Namespace] { CurrentUserPoller.namespaces } + override var namespaces: [SnodeAPI.Namespace] { + // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent + guard SessionUtil.userConfigsEnabled else { return [.default] } + + return CurrentUserPoller.namespaces + } /// After polling a given snode this many times we always switch to a new one. /// diff --git a/SessionMessagingKit/Sending & Receiving/Quotes/QuotedReplyModel.swift b/SessionMessagingKit/Sending & Receiving/Quotes/QuotedReplyModel.swift index 562621a36..94a6c2ee4 100644 --- a/SessionMessagingKit/Sending & Receiving/Quotes/QuotedReplyModel.swift +++ b/SessionMessagingKit/Sending & Receiving/Quotes/QuotedReplyModel.swift @@ -71,16 +71,3 @@ public struct QuotedReplyModel { ) } } - -// MARK: - Convenience - -public extension QuotedReplyModel { - func generateAttachmentThumbnailIfNeeded(_ db: Database) throws -> String? { - guard let sourceAttachment: Attachment = self.attachment else { return nil } - - return try sourceAttachment - .cloneAsQuoteThumbnail()? - .inserted(db) - .id - } -} diff --git a/SessionMessagingKit/SessionUtil/SessionUtil.swift b/SessionMessagingKit/SessionUtil/SessionUtil.swift index 62b77c9fe..d933238a5 100644 --- a/SessionMessagingKit/SessionUtil/SessionUtil.swift +++ b/SessionMessagingKit/SessionUtil/SessionUtil.swift @@ -10,9 +10,7 @@ import SessionUtilitiesKit public extension Features { static func useSharedUtilForUserConfig(_ db: Database? = nil) -> Bool { - return true - // TODO: Need to set this timestamp to the correct date (currently start of 2030) -// guard Date().timeIntervalSince1970 < 1893456000 else { return true } + guard Date().timeIntervalSince1970 < 1690761600 else { return true } guard !SessionUtil.hasCheckedMigrationsCompleted.wrappedValue else { return SessionUtil.userConfigsEnabledIgnoringFeatureFlag } diff --git a/SessionMessagingKit/Shared Models/MessageViewModel.swift b/SessionMessagingKit/Shared Models/MessageViewModel.swift index 67d719d74..57fd9acc8 100644 --- a/SessionMessagingKit/Shared Models/MessageViewModel.swift +++ b/SessionMessagingKit/Shared Models/MessageViewModel.swift @@ -527,6 +527,7 @@ public extension MessageViewModel { public extension MessageViewModel { static let genericId: Int64 = -1 static let typingIndicatorId: Int64 = -2 + static let optimisticUpdateId: Int64 = -3 /// This init method is only used for system-created cells or empty states init( @@ -634,8 +635,8 @@ public extension MessageViewModel { // Interaction Info - self.rowId = -1 - self.id = -1 + self.rowId = MessageViewModel.optimisticUpdateId + self.id = MessageViewModel.optimisticUpdateId self.openGroupServerMessageId = nil self.variant = .standardOutgoing self.timestampMs = timestampMs From f15f16be708d88b3eb70f929669f0bef8a833279 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 13 Jul 2023 15:35:35 +1000 Subject: [PATCH 128/135] More CI tweaks Updated the Podfile to use the CocoaPods CDN (hopefully much faster than the master spec repo) Removed the custom derivedDataPath (seemed to break the Copy Frameworks step of CocoaPods) --- .drone.jsonnet | 8 ++++---- Podfile | 3 ++- Podfile.lock | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.drone.jsonnet b/.drone.jsonnet index 07634ce4c..16946af5d 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -58,9 +58,9 @@ local install_cocoapods = { 'mkdir build', ||| if command -v xcpretty >/dev/null 2>&1; then - xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphonesimulator -derivedDataPath ./build -archivePath ./build/Session_sim.xcarchive -destination 'generic/platform=iOS Simulator' | xcpretty + xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphonesimulator -archivePath ./build/Session_sim.xcarchive -destination 'generic/platform=iOS Simulator' | xcpretty else - xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphonesimulator -derivedDataPath ./build -archivePath ./build/Session_sim.xcarchive -destination 'generic/platform=iOS Simulator' + xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphonesimulator -archivePath ./build/Session_sim.xcarchive -destination 'generic/platform=iOS Simulator' fi ||| ], @@ -88,9 +88,9 @@ local install_cocoapods = { 'mkdir build', ||| if command -v xcpretty >/dev/null 2>&1; then - xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphoneos -derivedDataPath ./build -archivePath ./build/Session.xcarchive -destination 'generic/platform=iOS' | xcpretty + xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphoneos -archivePath ./build/Session.xcarchive -destination 'generic/platform=iOS' | xcpretty else - xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphoneos -derivedDataPath ./build -archivePath ./build/Session.xcarchive -destination 'generic/platform=iOS' + xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphoneos -archivePath ./build/Session.xcarchive -destination 'generic/platform=iOS' fi ||| ], diff --git a/Podfile b/Podfile index 76cf57a85..69fbc174f 100644 --- a/Podfile +++ b/Podfile @@ -1,9 +1,10 @@ platform :ios, '13.0' -source 'https://github.com/CocoaPods/Specs.git' use_frameworks! inhibit_all_warnings! +install! 'cocoapods', :warn_for_unused_master_specs_repo => false + # Dependencies to be included in the app and all extensions/frameworks abstract_target 'GlobalDependencies' do # FIXME: If https://github.com/jedisct1/swift-sodium/pull/249 gets resolved then revert this back to the standard pod diff --git a/Podfile.lock b/Podfile.lock index d0b6c6490..a2e1e4dae 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -198,6 +198,6 @@ SPEC CHECKSUMS: YapDatabase: b418a4baa6906e8028748938f9159807fd039af4 YYImage: f1ddd15ac032a58b78bbed1e012b50302d318331 -PODFILE CHECKSUM: 4705728e69454d50805c70272479a7d4a04209d5 +PODFILE CHECKSUM: f56c28baefe3077effcb3a2ea5941b52c4cc6e86 COCOAPODS: 1.12.1 From 9bdae9dee8c469da2bdbd0916fabd62e15beff0d Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 13 Jul 2023 15:40:32 +1000 Subject: [PATCH 129/135] Fixed a typo in the Ci config --- .drone.jsonnet | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.drone.jsonnet b/.drone.jsonnet index 16946af5d..f0a2e8beb 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -34,9 +34,9 @@ local install_cocoapods = { 'mkdir build', ||| if command -v xcpretty >/dev/null 2>&1; then - 'xcodebuild test -workspace Session.xcworkspace -scheme Session -destination "platform=iOS Simulator,name=iPhone 14 Pro" | xcpretty' + xcodebuild test -workspace Session.xcworkspace -scheme Session -destination "platform=iOS Simulator,name=iPhone 14 Pro" | xcpretty else - 'xcodebuild test -workspace Session.xcworkspace -scheme Session -destination "platform=iOS Simulator,name=iPhone 14 Pro"' + xcodebuild test -workspace Session.xcworkspace -scheme Session -destination "platform=iOS Simulator,name=iPhone 14 Pro" fi ||| ], @@ -58,9 +58,9 @@ local install_cocoapods = { 'mkdir build', ||| if command -v xcpretty >/dev/null 2>&1; then - xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphonesimulator -archivePath ./build/Session_sim.xcarchive -destination 'generic/platform=iOS Simulator' | xcpretty + xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphonesimulator -archivePath ./build/Session_sim.xcarchive -destination "generic/platform=iOS Simulator" | xcpretty else - xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphonesimulator -archivePath ./build/Session_sim.xcarchive -destination 'generic/platform=iOS Simulator' + xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphonesimulator -archivePath ./build/Session_sim.xcarchive -destination "generic/platform=iOS Simulator" fi ||| ], @@ -88,9 +88,9 @@ local install_cocoapods = { 'mkdir build', ||| if command -v xcpretty >/dev/null 2>&1; then - xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphoneos -archivePath ./build/Session.xcarchive -destination 'generic/platform=iOS' | xcpretty + xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphoneos -archivePath ./build/Session.xcarchive -destination "generic/platform=iOS" | xcpretty else - xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphoneos -archivePath ./build/Session.xcarchive -destination 'generic/platform=iOS' + xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphoneos -archivePath ./build/Session.xcarchive -destination "generic/platform=iOS" fi ||| ], From 69ddb782a1a23e4a8f01e42b354b7902b7adb4d7 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 13 Jul 2023 16:01:57 +1000 Subject: [PATCH 130/135] Attempting to cache the Pods folder to speed up the CI Fixed a CocoaPods warning --- .drone.jsonnet | 50 +++++++++++++++++++++++++++++-- Session.xcodeproj/project.pbxproj | 7 +++-- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/.drone.jsonnet b/.drone.jsonnet index f0a2e8beb..c610d730b 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -16,6 +16,45 @@ local install_cocoapods = { commands: ['LANG=en_US.UTF-8 pod install'] }; +// Load from the cached CocoaPods directory (to speed up the build) +local load_cocoapods_cache = { + name: 'Load CocoaPods Cache', + commands: [ + ||| + while test -e /Users/drone/.cocoapods_cache.lock; do + sleep 1 + done + |||, + 'touch /Users/drone/.cocoapods_cache.lock' + ||| + if [[ -d /Users/drone/.cocoapods_cache ]]; then + cp -r /Users/drone/.cocoapods_cache ./Pods + fi + |||, + 'rm /Users/drone/.cocoapods_cache.lock' + ] +}; + +// Override the cached CocoaPods directory (to speed up the next build) +local update_cocoapods_cache = { + name: 'Update CocoaPods Cache', + commands: [ + ||| + while test -e /Users/drone/.cocoapods_cache.lock; do + sleep 1 + done + |||, + 'touch /Users/drone/.cocoapods_cache.lock' + ||| + if [[ -d ./Pods ]]; then + rm -rf /Users/drone/.cocoapods_cache + cp -r ./Pods /Users/drone/.cocoapods_cache + fi + |||, + 'rm /Users/drone/.cocoapods_cache.lock' + ] +}; + [ // Unit tests @@ -26,7 +65,7 @@ local install_cocoapods = { platform: { os: 'darwin', arch: 'amd64' }, steps: [ clone_submodules, - // install_xcpretty, + load_cocoapods_cache, install_cocoapods, { name: 'Run Unit Tests', @@ -41,6 +80,7 @@ local install_cocoapods = { ||| ], }, + update_cocoapods_cache ], }, // Simulator build @@ -51,6 +91,7 @@ local install_cocoapods = { platform: { os: 'darwin', arch: 'amd64' }, steps: [ clone_submodules, + load_cocoapods_cache, install_cocoapods, { name: 'Build', @@ -65,6 +106,7 @@ local install_cocoapods = { ||| ], }, + update_cocoapods_cache, { name: 'Upload artifacts', commands: [ @@ -81,6 +123,7 @@ local install_cocoapods = { platform: { os: 'darwin', arch: 'amd64' }, steps: [ clone_submodules, + load_cocoapods_cache, install_cocoapods, { name: 'Build', @@ -88,13 +131,14 @@ local install_cocoapods = { 'mkdir build', ||| if command -v xcpretty >/dev/null 2>&1; then - xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphoneos -archivePath ./build/Session.xcarchive -destination "generic/platform=iOS" | xcpretty + xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphoneos -archivePath ./build/Session.xcarchive -destination "generic/platform=iOS" -allowProvisioningUpdates | xcpretty else - xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphoneos -archivePath ./build/Session.xcarchive -destination "generic/platform=iOS" + xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphoneos -archivePath ./build/Session.xcarchive -destination "generic/platform=iOS" -allowProvisioningUpdates fi ||| ], }, + update_cocoapods_cache, { name: 'Upload artifacts', commands: [ diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index b1d01de68..927af5747 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -5339,8 +5339,8 @@ inputFileListPaths = ( ); inputPaths = ( - "$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH", - "$TARGET_BUILD_DIR/$INFOPLIST_PATH", + $BUILT_PRODUCTS_DIR/$INFOPLIST_PATH, + $TARGET_BUILD_DIR/$INFOPLIST_PATH, ); name = "Add Commit Hash To Build Info Plist"; outputFileListPaths = ( @@ -7677,6 +7677,7 @@ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = "$(inherited)"; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; @@ -7731,7 +7732,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = "$(inherited)"; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; From 6e32e759c50363317bdc64172e54a654306ca843 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 13 Jul 2023 16:03:00 +1000 Subject: [PATCH 131/135] Added missing commas in CI config file --- .drone.jsonnet | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.drone.jsonnet b/.drone.jsonnet index c610d730b..b18251c5a 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -25,7 +25,7 @@ local load_cocoapods_cache = { sleep 1 done |||, - 'touch /Users/drone/.cocoapods_cache.lock' + 'touch /Users/drone/.cocoapods_cache.lock', ||| if [[ -d /Users/drone/.cocoapods_cache ]]; then cp -r /Users/drone/.cocoapods_cache ./Pods @@ -44,7 +44,7 @@ local update_cocoapods_cache = { sleep 1 done |||, - 'touch /Users/drone/.cocoapods_cache.lock' + 'touch /Users/drone/.cocoapods_cache.lock', ||| if [[ -d ./Pods ]]; then rm -rf /Users/drone/.cocoapods_cache From c86cc0ed9c7b5ac4c07cae9f19ca28a0377c9bdd Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 13 Jul 2023 17:57:08 +1000 Subject: [PATCH 132/135] CI tweaks and incremented build number Added the XCBeautify pod (so the CI doesn't need to separately install something) Updated the CI build script to use XCBeautify Fixed some broken unit tests --- .drone.jsonnet | 24 ++-------- Podfile | 3 ++ Podfile.lock | 7 ++- Session.xcodeproj/project.pbxproj | 12 ++--- SessionMessagingKit/Configuration.swift | 7 +-- .../Open Groups/OpenGroupAPISpec.swift | 6 +-- .../Open Groups/OpenGroupManagerSpec.swift | 6 +-- .../MessageReceiverDecryptionSpec.swift | 6 +-- .../MessageSenderEncryptionSpec.swift | 6 +-- SessionSnodeKit/Configuration.swift | 5 ++- ...eadDisappearingMessagesViewModelSpec.swift | 10 ++--- .../ThreadSettingsViewModelSpec.swift | 10 ++--- .../NotificationContentViewModelSpec.swift | 10 ++--- SessionUIKit/Configuration.swift | 5 ++- SessionUtilitiesKit/Configuration.swift | 5 ++- SessionUtilitiesKit/Database/Storage.swift | 45 ++++++++++++------- .../Database/Types/TargetMigrations.swift | 5 +++ .../Database/Models/IdentitySpec.swift | 4 +- .../PersistableRecordUtilitiesSpec.swift | 20 ++++++--- SignalUtilitiesKit/Utilities/AppSetup.swift | 10 ++--- 20 files changed, 113 insertions(+), 93 deletions(-) diff --git a/.drone.jsonnet b/.drone.jsonnet index b18251c5a..f6b9eb6d4 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -71,13 +71,7 @@ local update_cocoapods_cache = { name: 'Run Unit Tests', commands: [ 'mkdir build', - ||| - if command -v xcpretty >/dev/null 2>&1; then - xcodebuild test -workspace Session.xcworkspace -scheme Session -destination "platform=iOS Simulator,name=iPhone 14 Pro" | xcpretty - else - xcodebuild test -workspace Session.xcworkspace -scheme Session -destination "platform=iOS Simulator,name=iPhone 14 Pro" - fi - ||| + 'NSUnbufferedIO=YES set -o pipefail && xcodebuild test -workspace Session.xcworkspace -scheme Session -destination "platform=iOS Simulator,name=iPhone 14 Pro" -maximum-test-execution-time-allowance 2 -collect-test-diagnostics never 2>&1 | ./Pods/xcbeautify/xcbeautify' ], }, update_cocoapods_cache @@ -97,13 +91,7 @@ local update_cocoapods_cache = { name: 'Build', commands: [ 'mkdir build', - ||| - if command -v xcpretty >/dev/null 2>&1; then - xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphonesimulator -archivePath ./build/Session_sim.xcarchive -destination "generic/platform=iOS Simulator" | xcpretty - else - xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphonesimulator -archivePath ./build/Session_sim.xcarchive -destination "generic/platform=iOS Simulator" - fi - ||| + 'xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphonesimulator -archivePath ./build/Session_sim.xcarchive -destination "generic/platform=iOS Simulator" | ./Pods/xcbeautify/xcbeautify' ], }, update_cocoapods_cache, @@ -129,13 +117,7 @@ local update_cocoapods_cache = { name: 'Build', commands: [ 'mkdir build', - ||| - if command -v xcpretty >/dev/null 2>&1; then - xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphoneos -archivePath ./build/Session.xcarchive -destination "generic/platform=iOS" -allowProvisioningUpdates | xcpretty - else - xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphoneos -archivePath ./build/Session.xcarchive -destination "generic/platform=iOS" -allowProvisioningUpdates - fi - ||| + 'xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphoneos -archivePath ./build/Session.xcarchive -destination "generic/platform=iOS" -allowProvisioningUpdates | ./Pods/xcbeautify/xcbeautify' ], }, update_cocoapods_cache, diff --git a/Podfile b/Podfile index 69fbc174f..201db8853 100644 --- a/Podfile +++ b/Podfile @@ -5,6 +5,9 @@ inhibit_all_warnings! install! 'cocoapods', :warn_for_unused_master_specs_repo => false +# CI Dependencies +pod 'xcbeautify' + # Dependencies to be included in the app and all extensions/frameworks abstract_target 'GlobalDependencies' do # FIXME: If https://github.com/jedisct1/swift-sodium/pull/249 gets resolved then revert this back to the standard pod diff --git a/Podfile.lock b/Podfile.lock index a2e1e4dae..4a101f497 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -42,6 +42,7 @@ PODS: - SQLCipher/common - SwiftProtobuf (1.5.0) - WebRTC-lib (114.0.0) + - xcbeautify (0.17.0) - YapDatabase/SQLCipher (3.1.1): - YapDatabase/SQLCipher/Core (= 3.1.1) - YapDatabase/SQLCipher/Extensions (= 3.1.1) @@ -124,6 +125,7 @@ DEPENDENCIES: - SQLCipher (~> 4.5.3) - SwiftProtobuf (~> 1.5.0) - WebRTC-lib + - xcbeautify - YapDatabase/SQLCipher (from `https://github.com/oxen-io/session-ios-yap-database.git`, branch `signal-release`) - YYImage/libwebp (from `https://github.com/signalapp/YYImage`) @@ -143,6 +145,8 @@ SPEC REPOS: - SQLCipher - SwiftProtobuf - WebRTC-lib + trunk: + - xcbeautify EXTERNAL SOURCES: Curve25519Kit: @@ -195,9 +199,10 @@ SPEC CHECKSUMS: SQLCipher: 57fa9f863fa4a3ed9dd3c90ace52315db8c0fdca SwiftProtobuf: 241400280f912735c1e1b9fe675fdd2c6c4d42e2 WebRTC-lib: d83df8976fa608b980f1d85796b3de66d60a1953 + xcbeautify: 6e2f57af5c3a86d490376d5758030a8dcc201c1b YapDatabase: b418a4baa6906e8028748938f9159807fd039af4 YYImage: f1ddd15ac032a58b78bbed1e012b50302d318331 -PODFILE CHECKSUM: f56c28baefe3077effcb3a2ea5941b52c4cc6e86 +PODFILE CHECKSUM: dd814a5a92577bb2a94dac6a1cc482f193721cdf COCOAPODS: 1.12.1 diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 927af5747..8dd26fc33 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -6366,7 +6366,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 418; + CURRENT_PROJECT_VERSION = 419; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6438,7 +6438,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 418; + CURRENT_PROJECT_VERSION = 419; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6503,7 +6503,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 418; + CURRENT_PROJECT_VERSION = 419; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6577,7 +6577,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 418; + CURRENT_PROJECT_VERSION = 419; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -7537,7 +7537,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 418; + CURRENT_PROJECT_VERSION = 419; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -7608,7 +7608,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 418; + CURRENT_PROJECT_VERSION = 419; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/SessionMessagingKit/Configuration.swift b/SessionMessagingKit/Configuration.swift index c6d92487c..384aa7249 100644 --- a/SessionMessagingKit/Configuration.swift +++ b/SessionMessagingKit/Configuration.swift @@ -1,8 +1,9 @@ import Foundation +import GRDB import SessionUtilitiesKit -public enum SNMessagingKit { // Just to make the external API nice - public static func migrations() -> TargetMigrations { +public enum SNMessagingKit: MigratableTarget { // Just to make the external API nice + public static func migrations(_ db: Database) -> TargetMigrations { return TargetMigrations( identifier: .messagingKit, migrations: [ @@ -33,7 +34,7 @@ public enum SNMessagingKit { // Just to make the external API nice // Wait until the feature is turned on before doing the migration that generates // the config dump data // FIXME: Remove this once `useSharedUtilForUserConfig` is permanent - (Features.useSharedUtilForUserConfig() ? + (Features.useSharedUtilForUserConfig(db) ? _014_GenerateInitialUserConfigDumps.self : (nil as Migration.Type?) ) diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift b/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift index c74bf5c2c..d87bfdca3 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift +++ b/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift @@ -37,9 +37,9 @@ class OpenGroupAPISpec: QuickSpec { beforeEach { mockStorage = Storage( customWriter: try! DatabaseQueue(), - customMigrations: [ - SNUtilitiesKit.migrations(), - SNMessagingKit.migrations() + customMigrationTargets: [ + SNUtilitiesKit.self, + SNMessagingKit.self ] ) mockSodium = MockSodium() diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift index cfc807596..5386c3207 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift +++ b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift @@ -103,9 +103,9 @@ class OpenGroupManagerSpec: QuickSpec { mockGeneralCache = MockGeneralCache() mockStorage = Storage( customWriter: try! DatabaseQueue(), - customMigrations: [ - SNUtilitiesKit.migrations(), - SNMessagingKit.migrations() + customMigrationTargets: [ + SNUtilitiesKit.self, + SNMessagingKit.self ] ) mockSodium = MockSodium() diff --git a/SessionMessagingKitTests/Sending & Receiving/MessageReceiverDecryptionSpec.swift b/SessionMessagingKitTests/Sending & Receiving/MessageReceiverDecryptionSpec.swift index b9066ba26..7f9a45a13 100644 --- a/SessionMessagingKitTests/Sending & Receiving/MessageReceiverDecryptionSpec.swift +++ b/SessionMessagingKitTests/Sending & Receiving/MessageReceiverDecryptionSpec.swift @@ -27,9 +27,9 @@ class MessageReceiverDecryptionSpec: QuickSpec { beforeEach { mockStorage = Storage( customWriter: try! DatabaseQueue(), - customMigrations: [ - SNUtilitiesKit.migrations(), - SNMessagingKit.migrations() + customMigrationTargets: [ + SNUtilitiesKit.self, + SNMessagingKit.self ] ) mockSodium = MockSodium() diff --git a/SessionMessagingKitTests/Sending & Receiving/MessageSenderEncryptionSpec.swift b/SessionMessagingKitTests/Sending & Receiving/MessageSenderEncryptionSpec.swift index 6334229a1..f937b3744 100644 --- a/SessionMessagingKitTests/Sending & Receiving/MessageSenderEncryptionSpec.swift +++ b/SessionMessagingKitTests/Sending & Receiving/MessageSenderEncryptionSpec.swift @@ -24,9 +24,9 @@ class MessageSenderEncryptionSpec: QuickSpec { beforeEach { mockStorage = Storage( customWriter: try! DatabaseQueue(), - customMigrations: [ - SNUtilitiesKit.migrations(), - SNMessagingKit.migrations() + customMigrationTargets: [ + SNUtilitiesKit.self, + SNMessagingKit.self ] ) mockBox = MockBox() diff --git a/SessionSnodeKit/Configuration.swift b/SessionSnodeKit/Configuration.swift index dcf685839..7f8a597d4 100644 --- a/SessionSnodeKit/Configuration.swift +++ b/SessionSnodeKit/Configuration.swift @@ -1,10 +1,11 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import GRDB import SessionUtilitiesKit -public enum SNSnodeKit { // Just to make the external API nice - public static func migrations() -> TargetMigrations { +public enum SNSnodeKit: MigratableTarget { // Just to make the external API nice + public static func migrations(_ db: Database) -> TargetMigrations { return TargetMigrations( identifier: .snodeKit, migrations: [ diff --git a/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift b/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift index 89d8453f7..0a9dfcf21 100644 --- a/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift +++ b/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift @@ -26,11 +26,11 @@ class ThreadDisappearingMessagesSettingsViewModelSpec: QuickSpec { beforeEach { mockStorage = Storage( customWriter: try! DatabaseQueue(), - customMigrations: [ - SNUtilitiesKit.migrations(), - SNSnodeKit.migrations(), - SNMessagingKit.migrations(), - SNUIKit.migrations() + customMigrationTargets: [ + SNUtilitiesKit.self, + SNSnodeKit.self, + SNMessagingKit.self, + SNUIKit.self ] ) dependencies = Dependencies( diff --git a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift index eaf4b915b..60ed929db 100644 --- a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift +++ b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift @@ -28,11 +28,11 @@ class ThreadSettingsViewModelSpec: QuickSpec { beforeEach { mockStorage = SynchronousStorage( customWriter: try! DatabaseQueue(), - customMigrations: [ - SNUtilitiesKit.migrations(), - SNSnodeKit.migrations(), - SNMessagingKit.migrations(), - SNUIKit.migrations() + customMigrationTargets: [ + SNUtilitiesKit.self, + SNSnodeKit.self, + SNMessagingKit.self, + SNUIKit.self ] ) mockGeneralCache = MockGeneralCache() diff --git a/SessionTests/Settings/NotificationContentViewModelSpec.swift b/SessionTests/Settings/NotificationContentViewModelSpec.swift index 81a838f15..e6d1e5999 100644 --- a/SessionTests/Settings/NotificationContentViewModelSpec.swift +++ b/SessionTests/Settings/NotificationContentViewModelSpec.swift @@ -25,11 +25,11 @@ class NotificationContentViewModelSpec: QuickSpec { beforeEach { mockStorage = Storage( customWriter: try! DatabaseQueue(), - customMigrations: [ - SNUtilitiesKit.migrations(), - SNSnodeKit.migrations(), - SNMessagingKit.migrations(), - SNUIKit.migrations() + customMigrationTargets: [ + SNUtilitiesKit.self, + SNSnodeKit.self, + SNMessagingKit.self, + SNUIKit.self ] ) viewModel = NotificationContentViewModel(storage: mockStorage, scheduling: .immediate) diff --git a/SessionUIKit/Configuration.swift b/SessionUIKit/Configuration.swift index d305968e9..798ba98eb 100644 --- a/SessionUIKit/Configuration.swift +++ b/SessionUIKit/Configuration.swift @@ -1,10 +1,11 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import GRDB import SessionUtilitiesKit -public enum SNUIKit { - public static func migrations() -> TargetMigrations { +public enum SNUIKit: MigratableTarget { + public static func migrations(_ db: Database) -> TargetMigrations { return TargetMigrations( identifier: .uiKit, migrations: [ diff --git a/SessionUtilitiesKit/Configuration.swift b/SessionUtilitiesKit/Configuration.swift index 616e27ed3..df5b5b366 100644 --- a/SessionUtilitiesKit/Configuration.swift +++ b/SessionUtilitiesKit/Configuration.swift @@ -1,9 +1,10 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import GRDB -public enum SNUtilitiesKit { // Just to make the external API nice - public static func migrations() -> TargetMigrations { +public enum SNUtilitiesKit: MigratableTarget { // Just to make the external API nice + public static func migrations(_ db: Database) -> TargetMigrations { return TargetMigrations( identifier: .utilitiesKit, migrations: [ diff --git a/SessionUtilitiesKit/Database/Storage.swift b/SessionUtilitiesKit/Database/Storage.swift index 1667f1384..c00a2a45f 100644 --- a/SessionUtilitiesKit/Database/Storage.swift +++ b/SessionUtilitiesKit/Database/Storage.swift @@ -47,14 +47,14 @@ open class Storage { public init( customWriter: DatabaseWriter? = nil, - customMigrations: [TargetMigrations]? = nil + customMigrationTargets: [MigratableTarget.Type]? = nil ) { - configureDatabase(customWriter: customWriter, customMigrations: customMigrations) + configureDatabase(customWriter: customWriter, customMigrationTargets: customMigrationTargets) } private func configureDatabase( customWriter: DatabaseWriter? = nil, - customMigrations: [TargetMigrations]? = nil + customMigrationTargets: [MigratableTarget.Type]? = nil ) { // Create the database directory if needed and ensure it's protection level is set before attempting to // create the database KeySpec or the database itself @@ -66,7 +66,12 @@ open class Storage { dbWriter = customWriter isValid = true Storage.internalHasCreatedValidInstance.mutate { $0 = true } - perform(migrations: (customMigrations ?? []), async: false, onProgressUpdate: nil, onComplete: { _, _ in }) + perform( + migrationTargets: (customMigrationTargets ?? []), + async: false, + onProgressUpdate: nil, + onComplete: { _, _ in } + ) return } @@ -128,7 +133,7 @@ open class Storage { } public func perform( - migrations: [TargetMigrations], + migrationTargets: [MigratableTarget.Type], async: Bool = true, onProgressUpdate: ((CGFloat, TimeInterval) -> ())?, onComplete: @escaping (Swift.Result, Bool) -> () @@ -141,18 +146,28 @@ open class Storage { } typealias MigrationInfo = (identifier: TargetMigrations.Identifier, migrations: TargetMigrations.MigrationSet) - let sortedMigrationInfo: [MigrationInfo] = migrations - .sorted() - .reduce(into: [[MigrationInfo]]()) { result, next in - next.migrations.enumerated().forEach { index, migrationSet in - if result.count <= index { - result.append([]) - } + let maybeSortedMigrationInfo: [MigrationInfo]? = try? dbWriter + .read { db -> [MigrationInfo] in + migrationTargets + .map { target -> TargetMigrations in target.migrations(db) } + .sorted() + .reduce(into: [[MigrationInfo]]()) { result, next in + next.migrations.enumerated().forEach { index, migrationSet in + if result.count <= index { + result.append([]) + } - result[index] = (result[index] + [(next.identifier, migrationSet)]) - } + result[index] = (result[index] + [(next.identifier, migrationSet)]) + } + } + .reduce(into: []) { result, next in result.append(contentsOf: next) } } - .reduce(into: []) { result, next in result.append(contentsOf: next) } + + guard let sortedMigrationInfo: [MigrationInfo] = maybeSortedMigrationInfo else { + SNLog("[Database Error] Statup failed with error: Unable to prepare migrations") + onComplete(.failure(StorageError.startupFailed), false) + return + } // Setup and run any required migrations migrator = { [weak self] in diff --git a/SessionUtilitiesKit/Database/Types/TargetMigrations.swift b/SessionUtilitiesKit/Database/Types/TargetMigrations.swift index ad305ab73..f9627a7e8 100644 --- a/SessionUtilitiesKit/Database/Types/TargetMigrations.swift +++ b/SessionUtilitiesKit/Database/Types/TargetMigrations.swift @@ -1,6 +1,11 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import GRDB + +public protocol MigratableTarget { + static func migrations(_ db: Database) -> TargetMigrations +} public struct TargetMigrations: Comparable { /// This identifier is used to determine the order each set of migrations should run in. diff --git a/SessionUtilitiesKitTests/Database/Models/IdentitySpec.swift b/SessionUtilitiesKitTests/Database/Models/IdentitySpec.swift index 7f5ed9da6..7eeb39293 100644 --- a/SessionUtilitiesKitTests/Database/Models/IdentitySpec.swift +++ b/SessionUtilitiesKitTests/Database/Models/IdentitySpec.swift @@ -18,8 +18,8 @@ class IdentitySpec: QuickSpec { beforeEach { mockStorage = Storage( customWriter: try! DatabaseQueue(), - customMigrations: [ - SNUtilitiesKit.migrations() + customMigrationTargets: [ + SNUtilitiesKit.self ] ) } diff --git a/SessionUtilitiesKitTests/Database/Utilities/PersistableRecordUtilitiesSpec.swift b/SessionUtilitiesKitTests/Database/Utilities/PersistableRecordUtilitiesSpec.swift index 2c7b09ce4..e17536fe7 100644 --- a/SessionUtilitiesKitTests/Database/Utilities/PersistableRecordUtilitiesSpec.swift +++ b/SessionUtilitiesKitTests/Database/Utilities/PersistableRecordUtilitiesSpec.swift @@ -84,6 +84,17 @@ class PersistableRecordUtilitiesSpec: QuickSpec { } } + private struct TestTarget: MigratableTarget { + static func migrations(_ db: Database) -> TargetMigrations { + return TargetMigrations( + identifier: .test, + migrations: (0..<100) + .map { _ in [] } + .appending([TestInsertTestTypeMigration.self]) + ) + } + } + // MARK: - Spec override func spec() { @@ -96,13 +107,8 @@ class PersistableRecordUtilitiesSpec: QuickSpec { PersistableRecordUtilitiesSpec.customWriter = customWriter mockStorage = Storage( customWriter: customWriter, - customMigrations: [ - TargetMigrations( - identifier: .test, - migrations: (0..<100) - .map { _ in [] } - .appending([TestInsertTestTypeMigration.self]) - ) + customMigrationTargets: [ + TestTarget.self ] ) } diff --git a/SignalUtilitiesKit/Utilities/AppSetup.swift b/SignalUtilitiesKit/Utilities/AppSetup.swift index efc6d1c25..58f0c6856 100644 --- a/SignalUtilitiesKit/Utilities/AppSetup.swift +++ b/SignalUtilitiesKit/Utilities/AppSetup.swift @@ -66,11 +66,11 @@ public enum AppSetup { var backgroundTask: OWSBackgroundTask? = (backgroundTask ?? OWSBackgroundTask(labelStr: #function)) Storage.shared.perform( - migrations: [ - SNUtilitiesKit.migrations(), - SNSnodeKit.migrations(), - SNMessagingKit.migrations(), - SNUIKit.migrations() + migrationTargets: [ + SNUtilitiesKit.self, + SNSnodeKit.self, + SNMessagingKit.self, + SNUIKit.self ], onProgressUpdate: migrationProgressChanged, onComplete: { result, needsConfigSync in From f13f75eedf3ca10336e3400a7ca773ff62ceddd9 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 13 Jul 2023 17:58:15 +1000 Subject: [PATCH 133/135] Fixed some bad CI script commas --- .drone.jsonnet | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.drone.jsonnet b/.drone.jsonnet index f6b9eb6d4..1377b86f4 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -91,7 +91,7 @@ local update_cocoapods_cache = { name: 'Build', commands: [ 'mkdir build', - 'xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphonesimulator -archivePath ./build/Session_sim.xcarchive -destination "generic/platform=iOS Simulator" | ./Pods/xcbeautify/xcbeautify' + 'xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration "App Store Release" -sdk iphonesimulator -archivePath ./build/Session_sim.xcarchive -destination "generic/platform=iOS Simulator" | ./Pods/xcbeautify/xcbeautify' ], }, update_cocoapods_cache, @@ -117,7 +117,7 @@ local update_cocoapods_cache = { name: 'Build', commands: [ 'mkdir build', - 'xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphoneos -archivePath ./build/Session.xcarchive -destination "generic/platform=iOS" -allowProvisioningUpdates | ./Pods/xcbeautify/xcbeautify' + 'xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration "App Store Release" -sdk iphoneos -archivePath ./build/Session.xcarchive -destination "generic/platform=iOS" -allowProvisioningUpdates | ./Pods/xcbeautify/xcbeautify' ], }, update_cocoapods_cache, From bc5d8d0931d4e24aedbece642aa26ddd3015eded Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 14 Jul 2023 14:36:59 +1000 Subject: [PATCH 134/135] Cleaning up final things before release Added basic support for the '25' blinded prefix Fixed a unit test CI issue --- .../Context Menu/ContextMenuVC+Action.swift | 6 +- .../ConversationVC+Interaction.swift | 8 ++- Session/Conversations/ConversationVC.swift | 5 +- .../Conversations/ConversationViewModel.swift | 65 ++++++++++++------- .../Conversations/Input View/InputView.swift | 3 +- .../Content Views/QuoteView.swift | 15 +++-- .../Message Cells/VisibleMessageCell.swift | 11 +++- Session/Home/HomeVC.swift | 6 +- Session/Home/HomeViewModel.swift | 9 ++- .../MessageRequestsViewModel.swift | 9 ++- Session/Home/New Conversation/NewDMVC.swift | 4 +- Session/Notifications/AppNotifications.swift | 14 +++- Session/Shared/FullConversationCell.swift | 18 +++-- Session/Utilities/MentionUtilities.swift | 12 ++-- .../Database/Models/Interaction.swift | 5 +- .../Database/Models/SessionThread.swift | 7 +- .../Messages/Message+Destination.swift | 4 +- SessionMessagingKit/Messages/Message.swift | 19 +++++- .../Open Groups/Models/SOGSMessage.swift | 2 +- .../Open Groups/OpenGroupAPI.swift | 2 +- .../Open Groups/OpenGroupManager.swift | 18 +++-- ...essageReceiver+ConfigurationMessages.swift | 2 +- .../MessageReceiver+MessageRequests.swift | 5 +- .../MessageReceiver+VisibleMessages.swift | 9 ++- .../Sending & Receiving/MessageReceiver.swift | 2 +- .../MessageSender+Encryption.swift | 7 +- .../Sending & Receiving/MessageSender.swift | 2 +- .../Quotes/QuotedReplyModel.swift | 15 +++-- .../SessionUtil+Contacts.swift | 3 +- .../Shared Models/MentionInfo.swift | 10 +-- .../Shared Models/MessageViewModel.swift | 32 ++++++--- .../SessionThreadViewModel.swift | 30 ++++++--- .../Utilities/Sodium+Utilities.swift | 19 ++++-- SessionUtilitiesKit/General/SessionId.swift | 3 +- .../General/SessionIdSpec.swift | 7 +- _SharedTestUtilities/MockGeneralCache.swift | 2 - 36 files changed, 263 insertions(+), 127 deletions(-) diff --git a/Session/Conversations/Context Menu/ContextMenuVC+Action.swift b/Session/Conversations/Context Menu/ContextMenuVC+Action.swift index bcef043e3..f71cfde88 100644 --- a/Session/Conversations/Context Menu/ContextMenuVC+Action.swift +++ b/Session/Conversations/Context Menu/ContextMenuVC+Action.swift @@ -146,7 +146,8 @@ extension ContextMenuVC { for cellViewModel: MessageViewModel, recentEmojis: [EmojiWithSkinTones], currentUserPublicKey: String, - currentUserBlindedPublicKey: String?, + currentUserBlinded15PublicKey: String?, + currentUserBlinded25PublicKey: String?, currentUserIsOpenGroupModerator: Bool, currentThreadIsMessageRequest: Bool, delegate: ContextMenuActionDelegate? @@ -204,7 +205,8 @@ extension ContextMenuVC { cellViewModel.threadVariant != .community || currentUserIsOpenGroupModerator || cellViewModel.authorId == currentUserPublicKey || - cellViewModel.authorId == currentUserBlindedPublicKey || + cellViewModel.authorId == currentUserBlinded15PublicKey || + cellViewModel.authorId == currentUserBlinded25PublicKey || cellViewModel.state == .failed ) let canBan: Bool = ( diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index e2a78ce64..617d4ec6c 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -739,7 +739,8 @@ extension ConversationVC: for: cellViewModel, recentEmojis: (self.viewModel.threadData.recentReactionEmoji ?? []).compactMap { EmojiWithSkinTones(rawValue: $0) }, currentUserPublicKey: self.viewModel.threadData.currentUserPublicKey, - currentUserBlindedPublicKey: self.viewModel.threadData.currentUserBlindedPublicKey, + currentUserBlinded15PublicKey: self.viewModel.threadData.currentUserBlinded15PublicKey, + currentUserBlinded25PublicKey: self.viewModel.threadData.currentUserBlinded25PublicKey, currentUserIsOpenGroupModerator: OpenGroupManager.isUserModeratorOrAdmin( self.viewModel.threadData.currentUserPublicKey, for: self.viewModel.threadData.openGroupRoomToken, @@ -1018,7 +1019,7 @@ extension ConversationVC: func startThread(with sessionId: String, openGroupServer: String?, openGroupPublicKey: String?) { guard viewModel.threadData.canWrite else { return } - guard SessionId.Prefix(from: sessionId) == .blinded else { + guard SessionId.Prefix(from: sessionId) == .blinded15 || SessionId.Prefix(from: sessionId) == .blinded25 else { Storage.shared.write { db in try SessionThread .fetchOrCreate(db, id: sessionId, variant: .contact, shouldBeVisible: nil) @@ -1661,7 +1662,8 @@ extension ConversationVC: attachments: cellViewModel.attachments, linkPreviewAttachment: cellViewModel.linkPreviewAttachment, currentUserPublicKey: cellViewModel.currentUserPublicKey, - currentUserBlindedPublicKey: cellViewModel.currentUserBlindedPublicKey + currentUserBlinded15PublicKey: cellViewModel.currentUserBlinded15PublicKey, + currentUserBlinded25PublicKey: cellViewModel.currentUserBlinded25PublicKey ) guard let quoteDraft: QuotedReplyModel = maybeQuoteDraft else { return } diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index 6242fd410..895c6a9c7 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -638,7 +638,10 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers // and need to swap over to the new one guard let sessionId: String = self?.viewModel.threadData.threadId, - SessionId.Prefix(from: sessionId) == .blinded, + ( + SessionId.Prefix(from: sessionId) == .blinded15 || + SessionId.Prefix(from: sessionId) == .blinded25 + ), let blindedLookup: BlindedIdLookup = Storage.shared.read({ db in try BlindedIdLookup .filter(id: sessionId) diff --git a/Session/Conversations/ConversationViewModel.swift b/Session/Conversations/ConversationViewModel.swift index 7c30437dc..425db2210 100644 --- a/Session/Conversations/ConversationViewModel.swift +++ b/Session/Conversations/ConversationViewModel.swift @@ -71,7 +71,8 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { threadIsBlocked: Bool, currentUserIsClosedGroupMember: Bool?, openGroupPermissions: OpenGroup.Permissions?, - blindedKey: String? + blinded15Key: String?, + blinded25Key: String? ) let initialData: InitialData? = Storage.shared.read { db -> InitialData in @@ -110,10 +111,17 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { .asRequest(of: OpenGroup.Permissions.self) .fetchOne(db) ) - let blindedKey: String? = SessionThread.getUserHexEncodedBlindedKey( + let blinded15Key: String? = SessionThread.getUserHexEncodedBlindedKey( db, threadId: threadId, - threadVariant: threadVariant + threadVariant: threadVariant, + blindingPrefix: .blinded15 + ) + let blinded25Key: String? = SessionThread.getUserHexEncodedBlindedKey( + db, + threadId: threadId, + threadVariant: threadVariant, + blindingPrefix: .blinded25 ) return ( @@ -122,7 +130,8 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { threadIsBlocked, currentUserIsClosedGroupMember, openGroupPermissions, - blindedKey + blinded15Key, + blinded25Key ) } @@ -138,7 +147,10 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { threadIsBlocked: initialData?.threadIsBlocked, currentUserIsClosedGroupMember: initialData?.currentUserIsClosedGroupMember, openGroupPermissions: initialData?.openGroupPermissions - ).populatingCurrentUserBlindedKey(currentUserBlindedPublicKeyForThisThread: initialData?.blindedKey) + ).populatingCurrentUserBlindedKeys( + currentUserBlinded15PublicKeyForThisThread: initialData?.blinded15Key, + currentUserBlinded25PublicKeyForThisThread: initialData?.blinded25Key + ) self.pagedDataObserver = nil // Note: Since this references self we need to finish initializing before setting it, we @@ -148,10 +160,8 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { self.pagedDataObserver = self.setupPagedObserver( for: threadId, userPublicKey: (initialData?.currentUserPublicKey ?? getUserHexEncodedPublicKey()), - blindedPublicKey: SessionThread.getUserHexEncodedBlindedKey( - threadId: threadId, - threadVariant: threadVariant - ) + blinded15PublicKey: initialData?.blinded15Key, + blinded25PublicKey: initialData?.blinded25Key ) // Run the initial query on a background thread so we don't block the push transition @@ -197,9 +207,10 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { return threadViewModel .map { $0.with(recentReactionEmoji: recentReactionEmoji) } .map { viewModel -> SessionThreadViewModel in - viewModel.populatingCurrentUserBlindedKey( + viewModel.populatingCurrentUserBlindedKeys( db, - currentUserBlindedPublicKeyForThisThread: self?.threadData.currentUserBlindedPublicKey + currentUserBlinded15PublicKeyForThisThread: self?.threadData.currentUserBlinded15PublicKey, + currentUserBlinded25PublicKeyForThisThread: self?.threadData.currentUserBlinded25PublicKey ) } } @@ -237,7 +248,12 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { } } - private func setupPagedObserver(for threadId: String, userPublicKey: String, blindedPublicKey: String?) -> PagedDatabaseObserver { + private func setupPagedObserver( + for threadId: String, + userPublicKey: String, + blinded15PublicKey: String?, + blinded25PublicKey: String? + ) -> PagedDatabaseObserver { return PagedDatabaseObserver( pagedTable: Interaction.self, pageSize: ConversationViewModel.pageSize, @@ -285,7 +301,8 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { orderSQL: MessageViewModel.orderSQL, dataQuery: MessageViewModel.baseQuery( userPublicKey: userPublicKey, - blindedPublicKey: blindedPublicKey, + blinded15PublicKey: blinded15PublicKey, + blinded25PublicKey: blinded25PublicKey, orderSQL: MessageViewModel.orderSQL, groupSQL: MessageViewModel.groupSQL ), @@ -391,12 +408,14 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { cellViewModel.id == sortedData .filter { $0.authorId == threadData.currentUserPublicKey || - $0.authorId == threadData.currentUserBlindedPublicKey + $0.authorId == threadData.currentUserBlinded15PublicKey || + $0.authorId == threadData.currentUserBlinded25PublicKey } .last? .id ), - currentUserBlindedPublicKey: threadData.currentUserBlindedPublicKey + currentUserBlinded15PublicKey: threadData.currentUserBlinded15PublicKey, + currentUserBlinded25PublicKey: threadData.currentUserBlinded25PublicKey ) } .reduce([]) { result, message in @@ -460,14 +479,15 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { let currentUserProfile: Profile = Profile.fetchOrCreateCurrentUser() let interaction: Interaction = Interaction( threadId: threadData.threadId, - authorId: (threadData.currentUserBlindedPublicKey ?? threadData.currentUserPublicKey), + authorId: (threadData.currentUserBlinded15PublicKey ?? threadData.currentUserPublicKey), variant: .standardOutgoing, body: text, timestampMs: sentTimestampMs, hasMention: Interaction.isUserMentioned( publicKeysToCheck: [ threadData.currentUserPublicKey, - threadData.currentUserBlindedPublicKey + threadData.currentUserBlinded15PublicKey, + threadData.currentUserBlinded25PublicKey ].compactMap { $0 }, body: text ), @@ -601,9 +621,9 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { .fetchSet(db) ) .defaulting(to: []) - let targetPrefix: SessionId.Prefix = (capabilities.contains(.blind) ? - .blinded : - .standard + let targetPrefixes: [SessionId.Prefix] = (capabilities.contains(.blind) ? + [.blinded15, .blinded25] : + [.standard] ) return (try MentionInfo @@ -611,7 +631,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { userPublicKey: userPublicKey, threadId: threadData.threadId, threadVariant: threadData.threadVariant, - targetPrefix: targetPrefix, + targetPrefixes: targetPrefixes, pattern: pattern )? .fetchAll(db)) @@ -706,7 +726,8 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { self.pagedDataObserver = self.setupPagedObserver( for: updatedThreadId, userPublicKey: getUserHexEncodedPublicKey(), - blindedPublicKey: nil + blinded15PublicKey: nil, + blinded25PublicKey: nil ) // Try load everything up to the initial visible message, fallback to just the initial page of messages diff --git a/Session/Conversations/Input View/InputView.swift b/Session/Conversations/Input View/InputView.swift index 5ea9ceddd..60e67ba4f 100644 --- a/Session/Conversations/Input View/InputView.swift +++ b/Session/Conversations/Input View/InputView.swift @@ -265,7 +265,8 @@ final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, M quotedText: quoteDraftInfo.model.body, threadVariant: threadVariant, currentUserPublicKey: quoteDraftInfo.model.currentUserPublicKey, - currentUserBlindedPublicKey: quoteDraftInfo.model.currentUserBlindedPublicKey, + currentUserBlinded15PublicKey: quoteDraftInfo.model.currentUserBlinded15PublicKey, + currentUserBlinded25PublicKey: quoteDraftInfo.model.currentUserBlinded25PublicKey, direction: (quoteDraftInfo.isOutgoing ? .outgoing : .incoming), attachment: quoteDraftInfo.model.attachment, hInset: hInset, diff --git a/Session/Conversations/Message Cells/Content Views/QuoteView.swift b/Session/Conversations/Message Cells/Content Views/QuoteView.swift index 08e300b08..bda79ae51 100644 --- a/Session/Conversations/Message Cells/Content Views/QuoteView.swift +++ b/Session/Conversations/Message Cells/Content Views/QuoteView.swift @@ -30,7 +30,8 @@ final class QuoteView: UIView { quotedText: String?, threadVariant: SessionThread.Variant, currentUserPublicKey: String?, - currentUserBlindedPublicKey: String?, + currentUserBlinded15PublicKey: String?, + currentUserBlinded25PublicKey: String?, direction: Direction, attachment: Attachment?, hInset: CGFloat, @@ -47,7 +48,8 @@ final class QuoteView: UIView { quotedText: quotedText, threadVariant: threadVariant, currentUserPublicKey: currentUserPublicKey, - currentUserBlindedPublicKey: currentUserBlindedPublicKey, + currentUserBlinded15PublicKey: currentUserBlinded15PublicKey, + currentUserBlinded25PublicKey: currentUserBlinded25PublicKey, direction: direction, attachment: attachment, hInset: hInset, @@ -69,7 +71,8 @@ final class QuoteView: UIView { quotedText: String?, threadVariant: SessionThread.Variant, currentUserPublicKey: String?, - currentUserBlindedPublicKey: String?, + currentUserBlinded15PublicKey: String?, + currentUserBlinded25PublicKey: String?, direction: Direction, attachment: Attachment?, hInset: CGFloat, @@ -211,7 +214,8 @@ final class QuoteView: UIView { in: $0, threadVariant: threadVariant, currentUserPublicKey: currentUserPublicKey, - currentUserBlindedPublicKey: currentUserBlindedPublicKey, + currentUserBlinded15PublicKey: currentUserBlinded15PublicKey, + currentUserBlinded25PublicKey: currentUserBlinded25PublicKey, isOutgoingMessage: (direction == .outgoing), textColor: textColor, theme: theme, @@ -234,7 +238,8 @@ final class QuoteView: UIView { let isCurrentUser: Bool = [ currentUserPublicKey, - currentUserBlindedPublicKey, + currentUserBlinded15PublicKey, + currentUserBlinded25PublicKey ] .compactMap { $0 } .asSet() diff --git a/Session/Conversations/Message Cells/VisibleMessageCell.swift b/Session/Conversations/Message Cells/VisibleMessageCell.swift index 509e7592c..a15cccf37 100644 --- a/Session/Conversations/Message Cells/VisibleMessageCell.swift +++ b/Session/Conversations/Message Cells/VisibleMessageCell.swift @@ -542,7 +542,8 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { quotedText: quote.body, threadVariant: cellViewModel.threadVariant, currentUserPublicKey: cellViewModel.currentUserPublicKey, - currentUserBlindedPublicKey: cellViewModel.currentUserBlindedPublicKey, + currentUserBlinded15PublicKey: cellViewModel.currentUserBlinded15PublicKey, + currentUserBlinded25PublicKey: cellViewModel.currentUserBlinded25PublicKey, direction: (cellViewModel.variant == .standardOutgoing ? .outgoing : .incoming @@ -868,7 +869,10 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { if profilePictureView.bounds.contains(profilePictureView.convert(location, from: self)), cellViewModel.shouldShowProfile { // For open groups only attempt to start a conversation if the author has a blinded id guard cellViewModel.threadVariant != .community else { - guard SessionId.Prefix(from: cellViewModel.authorId) == .blinded else { return } + guard + SessionId.Prefix(from: cellViewModel.authorId) == .blinded15 || + SessionId.Prefix(from: cellViewModel.authorId) == .blinded25 + else { return } delegate?.startThread( with: cellViewModel.authorId, @@ -1118,7 +1122,8 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { in: (cellViewModel.body ?? ""), threadVariant: cellViewModel.threadVariant, currentUserPublicKey: cellViewModel.currentUserPublicKey, - currentUserBlindedPublicKey: cellViewModel.currentUserBlindedPublicKey, + currentUserBlinded15PublicKey: cellViewModel.currentUserBlinded15PublicKey, + currentUserBlinded25PublicKey: cellViewModel.currentUserBlinded25PublicKey, isOutgoingMessage: isOutgoing, textColor: actualTextColor, theme: theme, diff --git a/Session/Home/HomeVC.swift b/Session/Home/HomeVC.swift index bc524a220..c8d774f2e 100644 --- a/Session/Home/HomeVC.swift +++ b/Session/Home/HomeVC.swift @@ -704,13 +704,15 @@ final class HomeVC: BaseVC, SessionUtilRespondingViewController, UITableViewData // Cannot properly sync outgoing blinded message requests so only provide valid options let shouldHavePinAction: Bool = ( - sessionIdPrefix != .blinded + sessionIdPrefix != .blinded15 && + sessionIdPrefix != .blinded25 ) let shouldHaveMuteAction: Bool = { switch threadViewModel.threadVariant { case .contact: return ( !threadViewModel.threadIsNoteToSelf && - sessionIdPrefix != .blinded + sessionIdPrefix != .blinded15 && + sessionIdPrefix != .blinded25 ) case .legacyGroup, .group: return ( diff --git a/Session/Home/HomeViewModel.swift b/Session/Home/HomeViewModel.swift index f081d5594..9ce940a93 100644 --- a/Session/Home/HomeViewModel.swift +++ b/Session/Home/HomeViewModel.swift @@ -361,10 +361,13 @@ public class HomeViewModel { return lhs.lastInteractionDate > rhs.lastInteractionDate } .map { viewModel -> SessionThreadViewModel in - viewModel.populatingCurrentUserBlindedKey( - currentUserBlindedPublicKeyForThisThread: groupedOldData[viewModel.threadId]? + viewModel.populatingCurrentUserBlindedKeys( + currentUserBlinded15PublicKeyForThisThread: groupedOldData[viewModel.threadId]? .first? - .currentUserBlindedPublicKey + .currentUserBlinded15PublicKey, + currentUserBlinded25PublicKeyForThisThread: groupedOldData[viewModel.threadId]? + .first? + .currentUserBlinded25PublicKey ) } ) diff --git a/Session/Home/Message Requests/MessageRequestsViewModel.swift b/Session/Home/Message Requests/MessageRequestsViewModel.swift index 27c426335..08e6eff32 100644 --- a/Session/Home/Message Requests/MessageRequestsViewModel.swift +++ b/Session/Home/Message Requests/MessageRequestsViewModel.swift @@ -156,10 +156,13 @@ public class MessageRequestsViewModel { elements: data .sorted { lhs, rhs -> Bool in lhs.lastInteractionDate > rhs.lastInteractionDate } .map { viewModel -> SessionThreadViewModel in - viewModel.populatingCurrentUserBlindedKey( - currentUserBlindedPublicKeyForThisThread: groupedOldData[viewModel.threadId]? + viewModel.populatingCurrentUserBlindedKeys( + currentUserBlinded15PublicKeyForThisThread: groupedOldData[viewModel.threadId]? .first? - .currentUserBlindedPublicKey + .currentUserBlinded15PublicKey, + currentUserBlinded25PublicKeyForThisThread: groupedOldData[viewModel.threadId]? + .first? + .currentUserBlinded25PublicKey ) } ) diff --git a/Session/Home/New Conversation/NewDMVC.swift b/Session/Home/New Conversation/NewDMVC.swift index 4b9f80591..e4765fee1 100644 --- a/Session/Home/New Conversation/NewDMVC.swift +++ b/Session/Home/New Conversation/NewDMVC.swift @@ -178,7 +178,7 @@ final class NewDMVC: BaseVC, UIPageViewControllerDataSource, UIPageViewControlle case .standard: startNewDM(with: onsNameOrPublicKey) - case .blinded: + case .blinded15, .blinded25: let modal: ConfirmationModal = ConfirmationModal( targetView: self.view, info: ConfirmationModal.Info( @@ -233,7 +233,7 @@ final class NewDMVC: BaseVC, UIPageViewControllerDataSource, UIPageViewControlle return messageOrNil } - return (maybeSessionId?.prefix == .blinded ? + return (maybeSessionId?.prefix == .blinded15 || maybeSessionId?.prefix == .blinded25 ? "DM_ERROR_DIRECT_BLINDED_ID".localized() : "DM_ERROR_INVALID".localized() ) diff --git a/Session/Notifications/AppNotifications.swift b/Session/Notifications/AppNotifications.swift index 31ea7d6b0..8f4bb263a 100644 --- a/Session/Notifications/AppNotifications.swift +++ b/Session/Notifications/AppNotifications.swift @@ -239,10 +239,17 @@ public class NotificationPresenter: NotificationsProtocol { ] let userPublicKey: String = getUserHexEncodedPublicKey(db) - let userBlindedKey: String? = SessionThread.getUserHexEncodedBlindedKey( + let userBlinded15Key: String? = SessionThread.getUserHexEncodedBlindedKey( db, threadId: thread.id, - threadVariant: thread.variant + threadVariant: thread.variant, + blindingPrefix: .blinded15 + ) + let userBlinded25Key: String? = SessionThread.getUserHexEncodedBlindedKey( + db, + threadId: thread.id, + threadVariant: thread.variant, + blindingPrefix: .blinded25 ) let fallbackSound: Preferences.Sound = db[.defaultNotificationSound] .defaulting(to: Preferences.Sound.defaultNotificationSound) @@ -257,7 +264,8 @@ public class NotificationPresenter: NotificationsProtocol { in: (notificationBody ?? ""), threadVariant: thread.variant, currentUserPublicKey: userPublicKey, - currentUserBlindedPublicKey: userBlindedKey + currentUserBlinded15PublicKey: userBlinded15Key, + currentUserBlinded25PublicKey: userBlinded25Key ) self.adaptee.notify( diff --git a/Session/Shared/FullConversationCell.swift b/Session/Shared/FullConversationCell.swift index feba6c06d..e1e5c5d30 100644 --- a/Session/Shared/FullConversationCell.swift +++ b/Session/Shared/FullConversationCell.swift @@ -310,7 +310,8 @@ public final class FullConversationCell: UITableViewCell, SwipeActionOptimisticC nil ), currentUserPublicKey: cellViewModel.currentUserPublicKey, - currentUserBlindedPublicKey: cellViewModel.currentUserBlindedPublicKey, + currentUserBlinded15PublicKey: cellViewModel.currentUserBlinded15PublicKey, + currentUserBlinded25PublicKey: cellViewModel.currentUserBlinded25PublicKey, searchText: searchText.lowercased(), fontSize: Values.smallFontSize, textColor: textColor @@ -339,7 +340,8 @@ public final class FullConversationCell: UITableViewCell, SwipeActionOptimisticC displayNameLabel?.attributedText = self?.getHighlightedSnippet( content: cellViewModel.displayName, currentUserPublicKey: cellViewModel.currentUserPublicKey, - currentUserBlindedPublicKey: cellViewModel.currentUserBlindedPublicKey, + currentUserBlinded15PublicKey: cellViewModel.currentUserBlinded15PublicKey, + currentUserBlinded25PublicKey: cellViewModel.currentUserBlinded25PublicKey, searchText: searchText.lowercased(), fontSize: Values.mediumFontSize, textColor: textColor @@ -358,7 +360,8 @@ public final class FullConversationCell: UITableViewCell, SwipeActionOptimisticC snippetLabel?.attributedText = self?.getHighlightedSnippet( content: (cellViewModel.threadMemberNames ?? ""), currentUserPublicKey: cellViewModel.currentUserPublicKey, - currentUserBlindedPublicKey: cellViewModel.currentUserBlindedPublicKey, + currentUserBlinded15PublicKey: cellViewModel.currentUserBlinded15PublicKey, + currentUserBlinded25PublicKey: cellViewModel.currentUserBlinded25PublicKey, searchText: searchText.lowercased(), fontSize: Values.smallFontSize, textColor: textColor @@ -598,7 +601,8 @@ public final class FullConversationCell: UITableViewCell, SwipeActionOptimisticC in: previewText, threadVariant: cellViewModel.threadVariant, currentUserPublicKey: cellViewModel.currentUserPublicKey, - currentUserBlindedPublicKey: cellViewModel.currentUserBlindedPublicKey + currentUserBlinded15PublicKey: cellViewModel.currentUserBlinded15PublicKey, + currentUserBlinded25PublicKey: cellViewModel.currentUserBlinded25PublicKey ), attributes: [ .foregroundColor: textColor ] )) @@ -610,7 +614,8 @@ public final class FullConversationCell: UITableViewCell, SwipeActionOptimisticC content: String, authorName: String? = nil, currentUserPublicKey: String, - currentUserBlindedPublicKey: String?, + currentUserBlinded15PublicKey: String?, + currentUserBlinded25PublicKey: String?, searchText: String, fontSize: CGFloat, textColor: UIColor @@ -633,7 +638,8 @@ public final class FullConversationCell: UITableViewCell, SwipeActionOptimisticC in: content, threadVariant: .contact, currentUserPublicKey: currentUserPublicKey, - currentUserBlindedPublicKey: currentUserBlindedPublicKey + currentUserBlinded15PublicKey: currentUserBlinded15PublicKey, + currentUserBlinded25PublicKey: currentUserBlinded25PublicKey ) let result: NSMutableAttributedString = NSMutableAttributedString( string: mentionReplacedContent, diff --git a/Session/Utilities/MentionUtilities.swift b/Session/Utilities/MentionUtilities.swift index 737856878..bff0eb9b3 100644 --- a/Session/Utilities/MentionUtilities.swift +++ b/Session/Utilities/MentionUtilities.swift @@ -10,14 +10,16 @@ public enum MentionUtilities { in string: String, threadVariant: SessionThread.Variant, currentUserPublicKey: String, - currentUserBlindedPublicKey: String? + currentUserBlinded15PublicKey: String?, + currentUserBlinded25PublicKey: String? ) -> String { /// **Note:** We are returning the string here so the 'textColor' and 'primaryColor' values are irrelevant return highlightMentions( in: string, threadVariant: threadVariant, currentUserPublicKey: currentUserPublicKey, - currentUserBlindedPublicKey: currentUserBlindedPublicKey, + currentUserBlinded15PublicKey: currentUserBlinded15PublicKey, + currentUserBlinded25PublicKey: currentUserBlinded25PublicKey, isOutgoingMessage: false, textColor: .black, theme: .classicDark, @@ -30,7 +32,8 @@ public enum MentionUtilities { in string: String, threadVariant: SessionThread.Variant, currentUserPublicKey: String?, - currentUserBlindedPublicKey: String?, + currentUserBlinded15PublicKey: String?, + currentUserBlinded25PublicKey: String?, isOutgoingMessage: Bool, textColor: UIColor, theme: Theme, @@ -48,7 +51,8 @@ public enum MentionUtilities { var mentions: [(range: NSRange, isCurrentUser: Bool)] = [] let currentUserPublicKeys: Set = [ currentUserPublicKey, - currentUserBlindedPublicKey + currentUserBlinded15PublicKey, + currentUserBlinded25PublicKey ] .compactMap { $0 } .asSet() diff --git a/SessionMessagingKit/Database/Models/Interaction.swift b/SessionMessagingKit/Database/Models/Interaction.swift index 60fe57b9a..21b29295b 100644 --- a/SessionMessagingKit/Database/Models/Interaction.swift +++ b/SessionMessagingKit/Database/Models/Interaction.swift @@ -815,9 +815,8 @@ public extension Interaction { genericHash: sodium.genericHash ) { - publicKeysToCheck.append( - SessionId(.blinded, publicKey: blindedKeyPair.publicKey).hexString - ) + publicKeysToCheck.append(SessionId(.blinded15, publicKey: blindedKeyPair.publicKey).hexString) + publicKeysToCheck.append(SessionId(.blinded25, publicKey: blindedKeyPair.publicKey).hexString) } } diff --git a/SessionMessagingKit/Database/Models/SessionThread.swift b/SessionMessagingKit/Database/Models/SessionThread.swift index baefc44d1..958e7fc30 100644 --- a/SessionMessagingKit/Database/Models/SessionThread.swift +++ b/SessionMessagingKit/Database/Models/SessionThread.swift @@ -524,12 +524,13 @@ public extension SessionThread { static func getUserHexEncodedBlindedKey( _ db: Database? = nil, threadId: String, - threadVariant: Variant + threadVariant: Variant, + blindingPrefix: SessionId.Prefix ) -> String? { guard threadVariant == .community else { return nil } guard let db: Database = db else { return Storage.shared.read { db in - getUserHexEncodedBlindedKey(db, threadId: threadId, threadVariant: threadVariant) + getUserHexEncodedBlindedKey(db, threadId: threadId, threadVariant: threadVariant, blindingPrefix: blindingPrefix) } } @@ -567,7 +568,7 @@ public extension SessionThread { ) return blindedKeyPair.map { keyPair -> String in - SessionId(.blinded, publicKey: keyPair.publicKey).hexString + SessionId(blindingPrefix, publicKey: keyPair.publicKey).hexString } } } diff --git a/SessionMessagingKit/Messages/Message+Destination.swift b/SessionMessagingKit/Messages/Message+Destination.swift index 9b713e295..6dbc8aeec 100644 --- a/SessionMessagingKit/Messages/Message+Destination.swift +++ b/SessionMessagingKit/Messages/Message+Destination.swift @@ -34,7 +34,9 @@ public extension Message { ) throws -> Message.Destination { switch threadVariant { case .contact: - if SessionId.Prefix(from: threadId) == .blinded { + let prefix: SessionId.Prefix? = SessionId.Prefix(from: threadId) + + if prefix == .blinded15 || prefix == .blinded25 { guard let lookup: BlindedIdLookup = try? BlindedIdLookup.fetchOne(db, id: threadId) else { preconditionFailure("Attempting to send message to blinded id without the Open Group information") } diff --git a/SessionMessagingKit/Messages/Message.swift b/SessionMessagingKit/Messages/Message.swift index 9e382e7a2..7de4f560e 100644 --- a/SessionMessagingKit/Messages/Message.swift +++ b/SessionMessagingKit/Messages/Message.swift @@ -404,12 +404,21 @@ public extension Message { var results: [Reaction] = [] guard let reactions = message.reactions else { return results } let userPublicKey: String = getUserHexEncodedPublicKey(db) - let blindedUserPublicKey: String? = SessionThread + let blinded15UserPublicKey: String? = SessionThread .getUserHexEncodedBlindedKey( db, threadId: openGroupId, - threadVariant: .community + threadVariant: .community, + blindingPrefix: .blinded15 ) + let blinded25UserPublicKey: String? = SessionThread + .getUserHexEncodedBlindedKey( + db, + threadId: openGroupId, + threadVariant: .community, + blindingPrefix: .blinded25 + ) + for (encodedEmoji, rawReaction) in reactions { if let decodedEmoji = encodedEmoji.removingPercentEncoding, rawReaction.count > 0, @@ -456,7 +465,11 @@ public extension Message { let timestampMs: Int64 = SnodeAPI.currentOffsetTimestampMs() let maxLength: Int = shouldAddSelfReaction ? 4 : 5 let desiredReactorIds: [String] = reactors - .filter { $0 != blindedUserPublicKey && $0 != userPublicKey } // Remove current user for now, will add back if needed + .filter { id -> Bool in + id != blinded15UserPublicKey && + id != blinded25UserPublicKey && + id != userPublicKey + } // Remove current user for now, will add back if needed .prefix(maxLength) .map{ $0 } diff --git a/SessionMessagingKit/Open Groups/Models/SOGSMessage.swift b/SessionMessagingKit/Open Groups/Models/SOGSMessage.swift index 5bbccaf02..b266c26e1 100644 --- a/SessionMessagingKit/Open Groups/Models/SOGSMessage.swift +++ b/SessionMessagingKit/Open Groups/Models/SOGSMessage.swift @@ -77,7 +77,7 @@ extension OpenGroupAPI.Message { let publicKey: Data = Data(hex: sender.removingIdPrefixIfNeeded()) switch SessionId.Prefix(from: sender) { - case .blinded: + case .blinded15, .blinded25: guard dependencies.sign.verify(message: data.bytes, publicKey: publicKey.bytes, signature: signature.bytes) else { SNLog("Ignoring message with invalid signature.") throw HTTPError.parsingFailed diff --git a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift index 6f51415f8..3797dd755 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift @@ -1237,7 +1237,7 @@ public enum OpenGroupAPI { } return ( - publicKey: SessionId(.blinded, publicKey: blindedKeyPair.publicKey).hexString, + publicKey: SessionId(.blinded15, publicKey: blindedKeyPair.publicKey).hexString, signature: signatureResult ) } diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index 5c351ec76..72d3e023b 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -927,7 +927,7 @@ public final class OpenGroupManager { let targetRoles: [GroupMember.Role] = [.moderator, .admin] return dependencies.storage - .read { db in + .read { db -> Bool in let isDirectModOrAdmin: Bool = GroupMember .filter(GroupMember.Columns.groupId == groupId) .filter(GroupMember.Columns.profileId == publicKey) @@ -959,7 +959,7 @@ public final class OpenGroupManager { } fallthrough - case .blinded: + case .blinded15, .blinded25: guard let userEdKeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db), let openGroupPublicKey: String = try? OpenGroup @@ -973,9 +973,14 @@ public final class OpenGroupManager { genericHash: dependencies.genericHash ) else { return false } - guard sessionId.prefix != .blinded || publicKey == SessionId(.blinded, publicKey: blindedKeyPair.publicKey).hexString else { - return false - } + guard + ( + sessionId.prefix != .blinded15 && + sessionId.prefix != .blinded25 + ) || + publicKey == SessionId(.blinded15, publicKey: blindedKeyPair.publicKey).hexString || + publicKey == SessionId(.blinded25, publicKey: blindedKeyPair.publicKey).hexString + else { return false } // If we got to here that means that the 'publicKey' value matches one of the current // users 'standard', 'unblinded' or 'blinded' keys and as such we should check if any @@ -983,7 +988,8 @@ public final class OpenGroupManager { let possibleKeys: Set = Set([ userPublicKey, SessionId(.unblinded, publicKey: userEdKeyPair.publicKey).hexString, - SessionId(.blinded, publicKey: blindedKeyPair.publicKey).hexString + SessionId(.blinded15, publicKey: blindedKeyPair.publicKey).hexString, + SessionId(.blinded25, publicKey: blindedKeyPair.publicKey).hexString ]) return GroupMember diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift index b61e3bcb4..1935d7619 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift @@ -79,7 +79,7 @@ extension MessageReceiver { // If the contact is a blinded contact then only add them if they haven't already been // unblinded - if SessionId.Prefix(from: sessionId) == .blinded { + if SessionId.Prefix(from: sessionId) == .blinded15 || SessionId.Prefix(from: sessionId) == .blinded25 { let hasUnblindedContact: Bool = BlindedIdLookup .filter(BlindedIdLookup.Columns.blindedId == sessionId) .filter(BlindedIdLookup.Columns.sessionId != nil) diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift index 4921a2d48..1582d5896 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift @@ -55,7 +55,10 @@ extension MessageReceiver { let blindedThreadIds: Set = (try? SessionThread .select(.id) .filter(SessionThread.Columns.variant == SessionThread.Variant.contact) - .filter(SessionThread.Columns.id.like("\(SessionId.Prefix.blinded.rawValue)%")) + .filter( + SessionThread.Columns.id.like("\(SessionId.Prefix.blinded15.rawValue)%") || + SessionThread.Columns.id.like("\(SessionId.Prefix.blinded25.rawValue)%") + ) .asRequest(of: String.self) .fetchSet(db)) .defaulting(to: []) diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift index 693f84da7..7f972a4c3 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift @@ -85,7 +85,7 @@ extension MessageReceiver { // Need to check if the blinded id matches for open groups switch senderSessionId.prefix { - case .blinded: + case .blinded15, .blinded25: let sodium: Sodium = Sodium() guard @@ -97,7 +97,12 @@ extension MessageReceiver { ) else { return .standardIncoming } - return (sender == SessionId(.blinded, publicKey: blindedKeyPair.publicKey).hexString ? + let senderIdCurrentUserBlinded: Bool = ( + sender == SessionId(.blinded15, publicKey: blindedKeyPair.publicKey).hexString || + sender == SessionId(.blinded25, publicKey: blindedKeyPair.publicKey).hexString + ) + + return (senderIdCurrentUserBlinded ? .standardOutgoing : .standardIncoming ) diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index 847f17805..5eec855eb 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -45,7 +45,7 @@ public enum MessageReceiver { (plaintext, sender) = try decryptWithSessionProtocol(ciphertext: ciphertext, using: userX25519KeyPair) - case .blinded: + case .blinded15, .blinded25: guard let otherBlindedPublicKey: String = otherBlindedPublicKey else { throw MessageReceiverError.noData } diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender+Encryption.swift b/SessionMessagingKit/Sending & Receiving/MessageSender+Encryption.swift index 57d8ae287..a9d4dca47 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender+Encryption.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender+Encryption.swift @@ -38,9 +38,10 @@ extension MessageSender { openGroupPublicKey: String, using dependencies: SMKDependencies = SMKDependencies() ) throws -> Data { - guard SessionId.Prefix(from: recipientBlindedId) == .blinded else { - throw MessageSenderError.signingFailed - } + guard + SessionId.Prefix(from: recipientBlindedId) == .blinded15 || + SessionId.Prefix(from: recipientBlindedId) == .blinded25 + else { throw MessageSenderError.signingFailed } guard let userEd25519KeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db) else { throw MessageSenderError.noUserED25519KeyPair } diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index 2893c8aa1..76681d309 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -411,7 +411,7 @@ public final class MessageSender { preconditionFailure() } - return SessionId(.blinded, publicKey: blindedKeyPair.publicKey).hexString + return SessionId(.blinded15, publicKey: blindedKeyPair.publicKey).hexString }() // Validate the message diff --git a/SessionMessagingKit/Sending & Receiving/Quotes/QuotedReplyModel.swift b/SessionMessagingKit/Sending & Receiving/Quotes/QuotedReplyModel.swift index 94a6c2ee4..ae99e4453 100644 --- a/SessionMessagingKit/Sending & Receiving/Quotes/QuotedReplyModel.swift +++ b/SessionMessagingKit/Sending & Receiving/Quotes/QuotedReplyModel.swift @@ -13,7 +13,8 @@ public struct QuotedReplyModel { public let sourceFileName: String? public let thumbnailDownloadFailed: Bool public let currentUserPublicKey: String? - public let currentUserBlindedPublicKey: String? + public let currentUserBlinded15PublicKey: String? + public let currentUserBlinded25PublicKey: String? // MARK: - Initialization @@ -27,7 +28,8 @@ public struct QuotedReplyModel { sourceFileName: String?, thumbnailDownloadFailed: Bool, currentUserPublicKey: String?, - currentUserBlindedPublicKey: String? + currentUserBlinded15PublicKey: String?, + currentUserBlinded25PublicKey: String? ) { self.attachment = attachment self.threadId = threadId @@ -38,7 +40,8 @@ public struct QuotedReplyModel { self.sourceFileName = sourceFileName self.thumbnailDownloadFailed = thumbnailDownloadFailed self.currentUserPublicKey = currentUserPublicKey - self.currentUserBlindedPublicKey = currentUserBlindedPublicKey + self.currentUserBlinded15PublicKey = currentUserBlinded15PublicKey + self.currentUserBlinded25PublicKey = currentUserBlinded25PublicKey } public static func quotedReplyForSending( @@ -50,7 +53,8 @@ public struct QuotedReplyModel { attachments: [Attachment]?, linkPreviewAttachment: Attachment?, currentUserPublicKey: String?, - currentUserBlindedPublicKey: String? + currentUserBlinded15PublicKey: String?, + currentUserBlinded25PublicKey: String? ) -> QuotedReplyModel? { guard variant == .standardOutgoing || variant == .standardIncoming else { return nil } guard (body != nil && body?.isEmpty == false) || attachments?.isEmpty == false else { return nil } @@ -67,7 +71,8 @@ public struct QuotedReplyModel { sourceFileName: targetAttachment?.sourceFilename, thumbnailDownloadFailed: false, currentUserPublicKey: currentUserPublicKey, - currentUserBlindedPublicKey: currentUserBlindedPublicKey + currentUserBlinded15PublicKey: currentUserBlinded15PublicKey, + currentUserBlinded25PublicKey: currentUserBlinded25PublicKey ) } } diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift index 1d142893d..019b19829 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift @@ -193,7 +193,8 @@ internal extension SessionUtil { let threadIdsToRemove: [String] = try SessionThread .filter(!syncedContactIds.contains(SessionThread.Columns.id)) .filter(SessionThread.Columns.variant == SessionThread.Variant.contact) - .filter(!SessionThread.Columns.id.like("\(SessionId.Prefix.blinded.rawValue)%")) + .filter(!SessionThread.Columns.id.like("\(SessionId.Prefix.blinded15.rawValue)%")) + .filter(!SessionThread.Columns.id.like("\(SessionId.Prefix.blinded25.rawValue)%")) .select(.id) .asRequest(of: String.self) .fetchAll(db) diff --git a/SessionMessagingKit/Shared Models/MentionInfo.swift b/SessionMessagingKit/Shared Models/MentionInfo.swift index ac1c9e7fa..984ddf63d 100644 --- a/SessionMessagingKit/Shared Models/MentionInfo.swift +++ b/SessionMessagingKit/Shared Models/MentionInfo.swift @@ -21,7 +21,7 @@ public extension MentionInfo { userPublicKey: String, threadId: String, threadVariant: SessionThread.Variant, - targetPrefix: SessionId.Prefix, + targetPrefixes: [SessionId.Prefix], pattern: FTS5Pattern? ) -> AdaptedFetchRequest>? { guard threadVariant != .contact || userPublicKey != threadId else { return nil } @@ -31,7 +31,9 @@ public extension MentionInfo { let openGroup: TypedTableAlias = TypedTableAlias() let groupMember: TypedTableAlias = TypedTableAlias() - let prefixLiteral: SQL = SQL(stringLiteral: "\(targetPrefix.rawValue)%") + let prefixesLiteral: SQLExpression = targetPrefixes + .map { SQL("\(profile[.id]) LIKE '\(SQL(stringLiteral: "\($0.rawValue)%"))'") } + .joined(operator: .or) let profileFullTextSearch: SQL = SQL(stringLiteral: Profile.fullTextSearchTableName) /// The query needs to differ depending on the thread variant because the behaviour should be different: @@ -50,7 +52,7 @@ public extension MentionInfo { \(Profile.self).rowid = \(profileFullTextSearch).rowid AND \(SQL("\(profile[.id]) != \(userPublicKey)")) AND ( \(SQL("\(threadVariant) != \(SessionThread.Variant.community)")) OR - \(SQL("\(profile[.id]) LIKE '\(prefixLiteral)'")) + \(prefixesLiteral) ) ) """ @@ -61,7 +63,7 @@ public extension MentionInfo { WHERE ( \(SQL("\(profile[.id]) != \(userPublicKey)")) AND ( \(SQL("\(threadVariant) != \(SessionThread.Variant.community)")) OR - \(SQL("\(profile[.id]) LIKE '\(prefixLiteral)'")) + \(prefixesLiteral) ) ) """ diff --git a/SessionMessagingKit/Shared Models/MessageViewModel.swift b/SessionMessagingKit/Shared Models/MessageViewModel.swift index 57fd9acc8..735d971a1 100644 --- a/SessionMessagingKit/Shared Models/MessageViewModel.swift +++ b/SessionMessagingKit/Shared Models/MessageViewModel.swift @@ -162,8 +162,11 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable, public let isLastOutgoing: Bool - /// This is the users blinded key (will only be set for messages within open groups) - public let currentUserBlindedPublicKey: String? + /// This is the users blinded15 key (will only be set for messages within open groups) + public let currentUserBlinded15PublicKey: String? + + /// This is the users blinded25 key (will only be set for messages within open groups) + public let currentUserBlinded25PublicKey: String? // MARK: - Mutation @@ -217,7 +220,8 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable, isOnlyMessageInCluster: self.isOnlyMessageInCluster, isLast: self.isLast, isLastOutgoing: self.isLastOutgoing, - currentUserBlindedPublicKey: self.currentUserBlindedPublicKey + currentUserBlinded15PublicKey: self.currentUserBlinded15PublicKey, + currentUserBlinded25PublicKey: self.currentUserBlinded25PublicKey ) } @@ -226,7 +230,8 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable, nextModel: MessageViewModel?, isLast: Bool, isLastOutgoing: Bool, - currentUserBlindedPublicKey: String? + currentUserBlinded15PublicKey: String?, + currentUserBlinded25PublicKey: String? ) -> MessageViewModel { let cellType: CellType = { guard self.isTypingIndicator != true else { return .typingIndicator } @@ -441,7 +446,8 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable, isOnlyMessageInCluster: isOnlyMessageInCluster, isLast: isLast, isLastOutgoing: isLastOutgoing, - currentUserBlindedPublicKey: currentUserBlindedPublicKey + currentUserBlinded15PublicKey: currentUserBlinded15PublicKey, + currentUserBlinded25PublicKey: currentUserBlinded25PublicKey ) } } @@ -599,7 +605,8 @@ public extension MessageViewModel { self.isOnlyMessageInCluster = true self.isLast = isLast self.isLastOutgoing = isLastOutgoing - self.currentUserBlindedPublicKey = nil + self.currentUserBlinded15PublicKey = nil + self.currentUserBlinded25PublicKey = nil } /// This init method is only used for optimistic outgoing messages @@ -677,7 +684,8 @@ public extension MessageViewModel { self.isOnlyMessageInCluster = true self.isLast = false self.isLastOutgoing = false - self.currentUserBlindedPublicKey = nil + self.currentUserBlinded15PublicKey = nil + self.currentUserBlinded25PublicKey = nil } } @@ -744,7 +752,8 @@ public extension MessageViewModel { static func baseQuery( userPublicKey: String, - blindedPublicKey: String?, + blinded15PublicKey: String?, + blinded25PublicKey: String?, orderSQL: SQL, groupSQL: SQL? ) -> (([Int64]) -> AdaptedFetchRequest>) { @@ -859,8 +868,11 @@ public extension MessageViewModel { \(quoteInteraction).\(authorIdColumn) = \(quote[.authorId]) OR ( -- A users outgoing message is stored in some cases using their standard id -- but the quote will use their blinded id so handle that case - \(quote[.authorId]) = \(blindedPublicKey ?? "''") AND - \(quoteInteraction).\(authorIdColumn) = \(userPublicKey) + \(quoteInteraction).\(authorIdColumn) = \(userPublicKey) AND + ( + \(quote[.authorId]) = \(blinded15PublicKey ?? "''") OR + \(quote[.authorId]) = \(blinded25PublicKey ?? "''") + ) ) ) ) diff --git a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift index bee72d0d0..0ee0f5f5a 100644 --- a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift +++ b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift @@ -152,7 +152,8 @@ public struct SessionThreadViewModel: FetchableRecordWithRowId, Decodable, Equat private let threadContactNameInternal: String? private let authorNameInternal: String? public let currentUserPublicKey: String - public let currentUserBlindedPublicKey: String? + public let currentUserBlinded15PublicKey: String? + public let currentUserBlinded25PublicKey: String? public let recentReactionEmoji: [String]? // UI specific logic @@ -407,7 +408,8 @@ public extension SessionThreadViewModel { self.threadContactNameInternal = nil self.authorNameInternal = nil self.currentUserPublicKey = getUserHexEncodedPublicKey() - self.currentUserBlindedPublicKey = nil + self.currentUserBlinded15PublicKey = nil + self.currentUserBlinded25PublicKey = nil self.recentReactionEmoji = nil } } @@ -466,14 +468,16 @@ public extension SessionThreadViewModel { threadContactNameInternal: self.threadContactNameInternal, authorNameInternal: self.authorNameInternal, currentUserPublicKey: self.currentUserPublicKey, - currentUserBlindedPublicKey: self.currentUserBlindedPublicKey, + currentUserBlinded15PublicKey: self.currentUserBlinded15PublicKey, + currentUserBlinded25PublicKey: self.currentUserBlinded25PublicKey, recentReactionEmoji: (recentReactionEmoji ?? self.recentReactionEmoji) ) } - func populatingCurrentUserBlindedKey( + func populatingCurrentUserBlindedKeys( _ db: Database? = nil, - currentUserBlindedPublicKeyForThisThread: String? = nil + currentUserBlinded15PublicKeyForThisThread: String? = nil, + currentUserBlinded25PublicKeyForThisThread: String? = nil ) -> SessionThreadViewModel { return SessionThreadViewModel( rowId: self.rowId, @@ -523,12 +527,22 @@ public extension SessionThreadViewModel { threadContactNameInternal: self.threadContactNameInternal, authorNameInternal: self.authorNameInternal, currentUserPublicKey: self.currentUserPublicKey, - currentUserBlindedPublicKey: ( - currentUserBlindedPublicKeyForThisThread ?? + currentUserBlinded15PublicKey: ( + currentUserBlinded15PublicKeyForThisThread ?? SessionThread.getUserHexEncodedBlindedKey( db, threadId: self.threadId, - threadVariant: self.threadVariant + threadVariant: self.threadVariant, + blindingPrefix: .blinded15 + ) + ), + currentUserBlinded25PublicKey: ( + currentUserBlinded25PublicKeyForThisThread ?? + SessionThread.getUserHexEncodedBlindedKey( + db, + threadId: self.threadId, + threadVariant: self.threadVariant, + blindingPrefix: .blinded25 ) ), recentReactionEmoji: self.recentReactionEmoji diff --git a/SessionMessagingKit/Utilities/Sodium+Utilities.swift b/SessionMessagingKit/Utilities/Sodium+Utilities.swift index 8e5210a73..e785ef186 100644 --- a/SessionMessagingKit/Utilities/Sodium+Utilities.swift +++ b/SessionMessagingKit/Utilities/Sodium+Utilities.swift @@ -203,11 +203,16 @@ extension Sodium { /// This method should be used to check if a users standard sessionId matches a blinded one public func sessionId(_ standardSessionId: String, matchesBlindedId blindedSessionId: String, serverPublicKey: String, genericHash: GenericHashType) -> Bool { // Only support generating blinded keys for standard session ids - guard let sessionId: SessionId = SessionId(from: standardSessionId), sessionId.prefix == .standard else { return false } - guard let blindedId: SessionId = SessionId(from: blindedSessionId), blindedId.prefix == .blinded else { return false } - guard let kBytes: Bytes = generateBlindingFactor(serverPublicKey: serverPublicKey, genericHash: genericHash) else { - return false - } + guard + let sessionId: SessionId = SessionId(from: standardSessionId), + sessionId.prefix == .standard, + let blindedId: SessionId = SessionId(from: blindedSessionId), + ( + blindedId.prefix == .blinded15 || + blindedId.prefix == .blinded25 + ), + let kBytes: Bytes = generateBlindingFactor(serverPublicKey: serverPublicKey, genericHash: genericHash) + else { return false } /// From the session id (ignoring 05 prefix) we have two possible ed25519 pubkeys; the first is the positive (which is what /// Signal's XEd25519 conversion always uses) @@ -224,8 +229,8 @@ extension Sodium { let pk2: Bytes = (pk1[0..<31] + [(pk1[31] ^ 0b1000_0000)]) return ( - SessionId(.blinded, publicKey: pk1).publicKey == blindedId.publicKey || - SessionId(.blinded, publicKey: pk2).publicKey == blindedId.publicKey + SessionId(.blinded15, publicKey: pk1).publicKey == blindedId.publicKey || + SessionId(.blinded15, publicKey: pk2).publicKey == blindedId.publicKey ) } } diff --git a/SessionUtilitiesKit/General/SessionId.swift b/SessionUtilitiesKit/General/SessionId.swift index c9391c6c7..ecf4bc3a5 100644 --- a/SessionUtilitiesKit/General/SessionId.swift +++ b/SessionUtilitiesKit/General/SessionId.swift @@ -8,7 +8,8 @@ public struct SessionId { public enum Prefix: String, CaseIterable { case standard = "05" // Used for identified users, open groups, etc. - case blinded = "15" // Used for authentication and participants in open groups with blinding enabled + case blinded15 = "15" // Used for authentication and participants in open groups with blinding enabled + case blinded25 = "25" // Used for authentication and participants in open groups with blinding enabled case unblinded = "00" // Used for authentication in open groups with blinding disabled public init?(from stringValue: String?) { diff --git a/SessionUtilitiesKitTests/General/SessionIdSpec.swift b/SessionUtilitiesKitTests/General/SessionIdSpec.swift index c3f22512a..0c0429bd6 100644 --- a/SessionUtilitiesKitTests/General/SessionIdSpec.swift +++ b/SessionUtilitiesKitTests/General/SessionIdSpec.swift @@ -45,8 +45,10 @@ class SessionIdSpec: QuickSpec { .to(equal("0088672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b")) expect(SessionId(.standard, publicKey: Data(hex: TestConstants.publicKey).bytes).hexString) .to(equal("0588672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b")) - expect(SessionId(.blinded, publicKey: Data(hex: TestConstants.publicKey).bytes).hexString) + expect(SessionId(.blinded15, publicKey: Data(hex: TestConstants.publicKey).bytes).hexString) .to(equal("1588672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b")) + expect(SessionId(.blinded25, publicKey: Data(hex: TestConstants.publicKey).bytes).hexString) + .to(equal("2588672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b")) } } @@ -56,7 +58,8 @@ class SessionIdSpec: QuickSpec { it("succeeds when valid") { expect(SessionId.Prefix(from: "00")).to(equal(.unblinded)) expect(SessionId.Prefix(from: "05")).to(equal(.standard)) - expect(SessionId.Prefix(from: "15")).to(equal(.blinded)) + expect(SessionId.Prefix(from: "15")).to(equal(.blinded15)) + expect(SessionId.Prefix(from: "s5")).to(equal(.blinded25)) } it("fails when nil") { diff --git a/_SharedTestUtilities/MockGeneralCache.swift b/_SharedTestUtilities/MockGeneralCache.swift index b847a01dd..fe19b7a0f 100644 --- a/_SharedTestUtilities/MockGeneralCache.swift +++ b/_SharedTestUtilities/MockGeneralCache.swift @@ -3,8 +3,6 @@ import Foundation import SessionUtilitiesKit -@testable import SessionMessagingKit - class MockGeneralCache: Mock, MutableGeneralCacheType { var encodedPublicKey: String? { get { return accept() as? String } From 703b1d9788a44116af32d44e43522a78d25e89c4 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 14 Jul 2023 16:48:53 +1000 Subject: [PATCH 135/135] CI tweaks and tweak for initial UX issue with 'blinded25' id Added parallelisation for the unit tests Added the SSH_KEY env variable for the 'Upload artifacts' step Disabled xcbeautify for the prod build (as I expect it's suppressing the build error) Disabled support for starting a conversation with a 'blinded25' id (would be invalid at this stage) Fixed a broken unit test --- .drone.jsonnet | 8 +++++--- Session/Conversations/ConversationVC+Interaction.swift | 4 +++- .../Message Cells/VisibleMessageCell.swift | 6 ++---- .../Open Groups/OpenGroupManagerSpec.swift | 10 +++++----- SessionUtilitiesKitTests/General/SessionIdSpec.swift | 2 +- 5 files changed, 16 insertions(+), 14 deletions(-) diff --git a/.drone.jsonnet b/.drone.jsonnet index 1377b86f4..d1f21a6d6 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -71,7 +71,7 @@ local update_cocoapods_cache = { name: 'Run Unit Tests', commands: [ 'mkdir build', - 'NSUnbufferedIO=YES set -o pipefail && xcodebuild test -workspace Session.xcworkspace -scheme Session -destination "platform=iOS Simulator,name=iPhone 14 Pro" -maximum-test-execution-time-allowance 2 -collect-test-diagnostics never 2>&1 | ./Pods/xcbeautify/xcbeautify' + 'NSUnbufferedIO=YES set -o pipefail && xcodebuild test -workspace Session.xcworkspace -scheme Session -destination "platform=iOS Simulator,name=iPhone 14" -destination "platform=iOS Simulator,name=iPhone 14 Pro Max" -parallel-testing-enabled YES -test-timeouts-enabled YES -maximum-test-execution-time-allowance 2 -collect-test-diagnostics never 2>&1 | ./Pods/xcbeautify/xcbeautify --is-ci --report junit --report-path ./build/reports --junit-report-filename junit2.xml' ], }, update_cocoapods_cache @@ -91,12 +91,13 @@ local update_cocoapods_cache = { name: 'Build', commands: [ 'mkdir build', - 'xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration "App Store Release" -sdk iphonesimulator -archivePath ./build/Session_sim.xcarchive -destination "generic/platform=iOS Simulator" | ./Pods/xcbeautify/xcbeautify' + 'xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration "App Store Release" -sdk iphonesimulator -archivePath ./build/Session_sim.xcarchive -destination "generic/platform=iOS Simulator" | ./Pods/xcbeautify/xcbeautify --is-ci' ], }, update_cocoapods_cache, { name: 'Upload artifacts', + environment: { SSH_KEY: { from_secret: 'SSH_KEY' } }, commands: [ './Scripts/drone-static-upload.sh' ] @@ -117,12 +118,13 @@ local update_cocoapods_cache = { name: 'Build', commands: [ 'mkdir build', - 'xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration "App Store Release" -sdk iphoneos -archivePath ./build/Session.xcarchive -destination "generic/platform=iOS" -allowProvisioningUpdates | ./Pods/xcbeautify/xcbeautify' + 'xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration "App Store Release" -sdk iphoneos -archivePath ./build/Session.xcarchive -destination "generic/platform=iOS" -allowProvisioningUpdates' ], }, update_cocoapods_cache, { name: 'Upload artifacts', + environment: { SSH_KEY: { from_secret: 'SSH_KEY' } }, commands: [ './Scripts/drone-static-upload.sh' ] diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 617d4ec6c..f597cc418 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -1019,7 +1019,9 @@ extension ConversationVC: func startThread(with sessionId: String, openGroupServer: String?, openGroupPublicKey: String?) { guard viewModel.threadData.canWrite else { return } - guard SessionId.Prefix(from: sessionId) == .blinded15 || SessionId.Prefix(from: sessionId) == .blinded25 else { + // FIXME: Add in support for starting a thread with a 'blinded25' id + guard SessionId.Prefix(from: sessionId) != .blinded25 else { return } + guard SessionId.Prefix(from: sessionId) == .blinded15 else { Storage.shared.write { db in try SessionThread .fetchOrCreate(db, id: sessionId, variant: .contact, shouldBeVisible: nil) diff --git a/Session/Conversations/Message Cells/VisibleMessageCell.swift b/Session/Conversations/Message Cells/VisibleMessageCell.swift index a15cccf37..6019d5a4d 100644 --- a/Session/Conversations/Message Cells/VisibleMessageCell.swift +++ b/Session/Conversations/Message Cells/VisibleMessageCell.swift @@ -869,10 +869,8 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { if profilePictureView.bounds.contains(profilePictureView.convert(location, from: self)), cellViewModel.shouldShowProfile { // For open groups only attempt to start a conversation if the author has a blinded id guard cellViewModel.threadVariant != .community else { - guard - SessionId.Prefix(from: cellViewModel.authorId) == .blinded15 || - SessionId.Prefix(from: cellViewModel.authorId) == .blinded25 - else { return } + // FIXME: Add in support for opening a conversation with a 'blinded25' id + guard SessionId.Prefix(from: cellViewModel.authorId) == .blinded15 else { return } delegate?.startThread( with: cellViewModel.authorId, diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift index 5386c3207..1a0ab6ace 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift +++ b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift @@ -3335,7 +3335,7 @@ class OpenGroupManagerSpec: QuickSpec { }.thenReturn(()) } - it("caches the promise if there is no cached promise") { + it("caches the publisher if there is no cached publisher") { let publisher = OpenGroupManager.getDefaultRoomsIfNeeded(using: dependencies) expect(mockOGMCache) @@ -3344,7 +3344,7 @@ class OpenGroupManagerSpec: QuickSpec { }) } - it("returns the cached promise if there is one") { + it("returns the cached publisher if there is one") { let uniqueRoomInstance: OpenGroupAPI.Room = OpenGroupAPI.Room( token: "UniqueId", name: "", @@ -3484,7 +3484,7 @@ class OpenGroupManagerSpec: QuickSpec { expect(TestRoomsApi.callCounter).to(equal(9)) // First attempt + 8 retries } - it("removes the cache promise if all retries fail") { + it("removes the cache publisher if all retries fail") { class TestRoomsApi: TestOnionRequestAPI { override class var mockResponse: Data? { return nil } } @@ -3626,7 +3626,7 @@ class OpenGroupManagerSpec: QuickSpec { } } - it("retrieves the image retrieval promise from the cache if it exists") { + it("retrieves the image retrieval publisher from the cache if it exists") { let publisher = Future { resolver in resolver(Result.success(Data([5, 4, 3, 2, 1]))) } @@ -3705,7 +3705,7 @@ class OpenGroupManagerSpec: QuickSpec { ) } - it("adds the image retrieval promise to the cache") { + it("adds the image retrieval publisher to the cache") { class TestNeverReturningApi: OnionRequestAPIType { static func sendOnionRequest(_ request: URLRequest, to server: String, with x25519PublicKey: String, timeout: TimeInterval) -> AnyPublisher<(ResponseInfoType, Data?), Error> { return Future<(ResponseInfoType, Data?), Error> { _ in }.eraseToAnyPublisher() diff --git a/SessionUtilitiesKitTests/General/SessionIdSpec.swift b/SessionUtilitiesKitTests/General/SessionIdSpec.swift index 0c0429bd6..62a76e33c 100644 --- a/SessionUtilitiesKitTests/General/SessionIdSpec.swift +++ b/SessionUtilitiesKitTests/General/SessionIdSpec.swift @@ -59,7 +59,7 @@ class SessionIdSpec: QuickSpec { expect(SessionId.Prefix(from: "00")).to(equal(.unblinded)) expect(SessionId.Prefix(from: "05")).to(equal(.standard)) expect(SessionId.Prefix(from: "15")).to(equal(.blinded15)) - expect(SessionId.Prefix(from: "s5")).to(equal(.blinded25)) + expect(SessionId.Prefix(from: "25")).to(equal(.blinded25)) } it("fails when nil") {

  • a4^f=*weZ2) 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 z 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%0A?;B>0Q( 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Ð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%0A)_2Vlfs9q*AtsL=!ajTfofTF%f9&}Qfty7S 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 diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64_x86_64-simulator/libsession-util.a b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64_x86_64-simulator/libsession-util.a deleted file mode 100644 index 9a1e7ea9ded501bb1e03ce137ed2461bc472353a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4601496 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

  • +n2$Wb|jp9k%fKW8)$LQuh}09ZvTqy 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 0 indicates the index of the config we used if we did not merge (i.e. there was only one + // config, or there were multiple but one of them referenced all the others). + int unmerged_ = -1; public: constexpr static int DEFAULT_DIFF_LAGS = 5; @@ -147,19 +151,20 @@ class ConfigMessage { /// set, thus allowing unsigned messages (though messages with an invalid signature are still /// not allowed). This option is ignored when verifier is not set. /// - /// error_callback - if set then any config message parsing error will be passed to this - /// function for handling: the callback typically warns and, if the overall construction should - /// abort, rethrows the error. If this function is omitted then the default skips (without - /// failing) individual parse errors and only aborts construction if *all* messages fail to - /// parse. A simple handler such as `[](const auto& e) { throw e; }` can be used to make any - /// parse error of any message fatal. + /// error_handler - if set then any config message parsing error will be passed to this function + /// for handling with the index of `configs` that failed and the error exception: the callback + /// typically warns and, if the overall construction should abort, rethrows the error. If this + /// function is omitted then the default skips (without failing) individual parse errors and + /// only aborts construction if *all* messages fail to parse. A simple handler such as + /// `[](size_t, const auto& e) { throw e; }` can be used to make any parse error of any message + /// fatal. explicit ConfigMessage( const std::vector& configs, verify_callable verifier = nullptr, sign_callable signer = nullptr, int lag = DEFAULT_DIFF_LAGS, bool signature_optional = false, - std::function error_handler = nullptr); + std::function error_handler = nullptr); /// Returns a read-only reference to the contained data. (To get a mutable config object use /// MutableConfigMessage). @@ -198,7 +203,13 @@ class ConfigMessage { /// After loading multiple config files this flag indicates whether or not we had to produce a /// new, merged configuration message (true) or did not need to merge (false). (For config /// messages that were not loaded from serialized data this is always true). - bool merged() const { return merged_; } + bool merged() const { return unmerged_ == -1; } + + /// After loading multiple config files this field contains the index of the single config we + /// used if we didn't need to merge (that is: there was only one config or one config that + /// superceded all the others). If we had to merge (or this wasn't loaded from serialized + /// data), this will return -1. + int unmerged_index() const { return unmerged_; } /// Returns true if this message contained a valid, verified signature when it was parsed. /// Returns false otherwise (e.g. not loaded from verification at all; loaded without a @@ -273,7 +284,7 @@ class MutableConfigMessage : public ConfigMessage { sign_callable signer = nullptr, int lag = DEFAULT_DIFF_LAGS, bool signature_optional = false, - std::function error_handler = nullptr); + std::function error_handler = nullptr); /// Wrapper around the above that takes a single string view to load a single message, doesn't /// take an error handler and instead always throws on parse errors (the above also throws for diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.h index 2619511dd..9ba7c4bd1 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.h +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.h @@ -55,31 +55,50 @@ int16_t config_storage_namespace(const config_object* conf); /// config object may be unchanged, complete replaced, or updated and needing a push, depending on /// the messages that are merged; the caller should check config_needs_push(). /// -/// `configs` is an array of pointers to the start of the strings; `lengths` is an array of string -/// lengths; `count` is the length of those two arrays. +/// `msg_hashes` is an array of null-terminated C strings containing the hashes of the configs being +/// provided. +/// `configs` is an array of pointers to the start of the (binary) data. +/// `lengths` is an array of lengths of the binary data +/// `count` is the length of all three arrays. int config_merge( - config_object* conf, const unsigned char** configs, const size_t* lengths, size_t count); + config_object* conf, + const char** msg_hashes, + const unsigned char** configs, + const size_t* lengths, + size_t count); /// Returns true if this config object contains updated data that has not yet been confirmed stored /// on the server. bool config_needs_push(const config_object* conf); -/// Obtains the configuration data that needs to be pushed to the server. A new buffer of the -/// appropriate size is malloc'd and set to `out` The output is written to a new malloc'ed buffer of -/// the appropriate size; the buffer and the output length are set in the `out` and `outlen` -/// parameters. Note that this is binary data, *not* a null-terminated C string. +/// Returned struct of config push data. +typedef struct config_push_data { + // The config seqno (to be provided later in `config_confirm_pushed`). + seqno_t seqno; + // The config message to push (binary data, not null-terminated). + unsigned char* config; + // The length of `config` + size_t config_len; + // Array of obsolete message hashes to delete; each element is a null-terminated C string + char** obsolete; + // length of `obsolete` + size_t obsolete_len; +} config_push_data; + +/// Obtains the configuration data that needs to be pushed to the server. /// /// Generally this call should be guarded by a call to `config_needs_push`, however it can be used /// to re-obtain the current serialized config even if no push is needed (for example, if the client /// wants to re-submit it after a network error). /// -/// NB: The returned buffer belongs to the caller: that is, the caller *MUST* free() it when done -/// with it. -seqno_t config_push(config_object* conf, unsigned char** out, size_t* outlen); +/// NB: The returned pointer belongs to the caller: that is, the caller *MUST* free() it when +/// done with it. +config_push_data* config_push(config_object* conf); -/// Reports that data obtained from `config_push` has been successfully stored on the server. The -/// seqno value is the one returned by the config_push call that yielded the config data. -void config_confirm_pushed(config_object* conf, seqno_t seqno); +/// Reports that data obtained from `config_push` has been successfully stored on the server with +/// message hash `msg_hash`. The seqno value is the one returned by the config_push call that +/// yielded the config data. +void config_confirm_pushed(config_object* conf, seqno_t seqno, const char* msg_hash); /// Returns a binary dump of the current state of the config object. This dump can be used to /// resurrect the object at a later point (e.g. after a restart). Allocates a new buffer and sets @@ -96,6 +115,20 @@ void config_dump(config_object* conf, unsigned char** out, size_t* outlen); /// and saving the `config_dump()` data again. bool config_needs_dump(const config_object* conf); +/// Struct containing a list of C strings. Typically where this is returned by this API it must be +/// freed (via `free()`) when done with it. +typedef struct config_string_list { + char** value; // array of null-terminated C strings + size_t len; // length of `value` +} config_string_list; + +/// Obtains the current active hashes. Note that this will be empty if the current hash is unknown +/// or not yet determined (for example, because the current state is dirty or because the most +/// recent push is still pending and we don't know the hash yet). +/// +/// The returned pointer belongs to the caller and must be freed via `free()` when done with it. +config_string_list* config_current_hashes(const config_object* conf); + /// Config key management; see the corresponding method docs in base.hpp. All `key` arguments here /// are 32-byte binary buffers (and since fixed-length, there is no keylen argument). void config_add_key(config_object* conf, const unsigned char* key); diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.hpp index deb7b16f0..46b71c2f8 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.hpp +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/base.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -64,6 +65,15 @@ class ConfigBase { size_t _keys_size = 0; size_t _keys_capacity = 0; + // Contains the current active message hash, as fed into us in `confirm_pushed()`. Empty if we + // don't know it yet. When we dirty the config this value gets moved into `old_hashes_` to be + // removed by the next push. + std::string _curr_hash; + + // Contains obsolete known message hashes that are obsoleted by the most recent merge or push; + // these are returned (and cleared) when `push` is called. + std::unordered_set _old_hashes; + protected: // Constructs a base config by loading the data from a dump as produced by `dump()`. If the // dump is nullopt then an empty base config is constructed with no config settings and seqno @@ -74,11 +84,10 @@ class ConfigBase { // calling set_state, which sets to to true implicitly). bool _needs_dump = false; - // Sets the current state; this also sets _needs_dump to true. - void set_state(ConfigState s) { - _state = s; - _needs_dump = true; - } + // Sets the current state; this also sets _needs_dump to true. If transitioning to a dirty + // state and we know our current message hash, that hash gets added to `old_hashes_` to be + // deleted at the next push. + void set_state(ConfigState s); // Invokes the `logger` callback if set, does nothing if there is no logger. void log(LogLevel lvl, std::string msg) { @@ -445,18 +454,21 @@ class ConfigBase { // This takes all of the messages pulled down from the server and does whatever is necessary to // merge (or replace) the current values. // + // Values are pairs of the message hash (as provided by the server) and the raw message body. + // // After this call the caller should check `needs_push()` to see if the data on hand was updated - // and needs to be pushed to the server again. + // and needs to be pushed to the server again (for example, because the data contained conflicts + // that required another update to resolve). // // Returns the number of the given config messages that were successfully parsed. // // Will throw on serious error (i.e. if neither the current nor any of the given configs are // parseable). This should not happen (the current config, at least, should always be // re-parseable). - virtual int merge(const std::vector& configs); + virtual int merge(const std::vector>& configs); - // Same as above but takes a vector of ustring's as sometimes that is more convenient. - int merge(const std::vector& configs); + // Same as above but takes the values as ustring's as sometimes that is more convenient. + int merge(const std::vector>& configs); // Returns true if we are currently dirty (i.e. have made changes that haven't been serialized // yet). @@ -466,31 +478,51 @@ class ConfigBase { // unmodified). bool is_clean() const { return _state == ConfigState::Clean; } + // The current config hash(es); this can be empty if the current hash is unknown or the current + // state is not clean (i.e. a push is needed or pending). + std::vector current_hashes() const; + // Returns true if this object contains updated data that has not yet been confirmed stored on // the server. This will be true whenever `is_clean()` is false: that is, if we are currently // "dirty" (i.e. have changes that haven't been pushed) or are still awaiting confirmation of // storage of the most recent serialized push data. virtual bool needs_push() const; - // Returns the data messages to push to the server along with the seqno value of the data. If - // the config is currently dirty (i.e. has previously unsent modifications) then this marks it - // as awaiting-confirmation instead of dirty so that any future change immediately increments - // the seqno. + // Returns a tuple of three elements: + // - the seqno value of the data + // - the data message to push to the server + // - a list of known message hashes that are obsoleted by this push. + // + // Additionally, if the internal state is currently dirty (i.e. there are unpushed changes), the + // internal state will be marked as awaiting-confirmation. Any further data changes made after + // this call will re-dirty the data (incrementing seqno and requiring another push). + // + // The client is expected to send a sequence request to the server that stores the message and + // deletes the hashes (if any). It is strongly recommended to use a sequence rather than a + // batch so that the deletions won't happen if the store fails for some reason. + // + // Upon successful completion of the store+deletion requests the client should call + // `confirm_pushed` with the seqno value to confirm that the message has been stored. // // Subclasses that need to perform pre-push tasks (such as pruning stale data) can override this // to prune and then call the base method to perform the actual push generation. - virtual std::pair push(); + virtual std::tuple> push(); // Should be called after the push is confirmed stored on the storage server swarm to let the - // object know the data is stored. (Once this is called `needs_push` will start returning false - // until something changes). Takes the seqno that was pushed so that the object can ensure that - // the latest version was pushed (i.e. in case there have been other changes since the `push()` - // call that returned this seqno). + // object know the config message has been stored and, ideally, that the obsolete messages + // returned by `push()` are deleted. Once this is called `needs_push` will start returning + // false until something changes. Takes the seqno that was pushed so that the object can ensure + // that the latest version was pushed (i.e. in case there have been other changes since the + // `push()` call that returned this seqno). + // + // Ideally the caller should have both stored the returned message and deleted the given + // messages. The deletion step isn't critical (it is just cleanup) and callers should call this + // as long as the store succeeded even if there were errors in the deletions. // // It is safe to call this multiple times with the same seqno value, and with out-of-order // seqnos (e.g. calling with seqno 122 after having called with 123; the duplicates and earlier // ones will just be ignored). - virtual void confirm_pushed(seqno_t seqno); + virtual void confirm_pushed(seqno_t seqno, std::string msg_hash); // Returns a dump of the current state for storage in the database; this value would get passed // into the constructor to reconstitute the object (including the push/not pushed status). This diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/community.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/community.h new file mode 100644 index 000000000..e21f6c018 --- /dev/null +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/community.h @@ -0,0 +1,44 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +// Maximum string length of a community base URL +extern const size_t COMMUNITY_BASE_URL_MAX_LENGTH; + +// Maximum string length of a community room token +extern const size_t COMMUNITY_ROOM_MAX_LENGTH; + +// Maximum string length of a full URL as produced by the community_make_full_url() function. +// Unlike the above constants, this *includes* space for a NULL string terminator. +extern const size_t COMMUNITY_FULL_URL_MAX_LENGTH; + +// Parses a community URL. Writes the canonical base url, room token, and pubkey bytes into the +// given pointers. base_url must be at least BASE_URL_MAX_LENGTH+1; room must be at least +// ROOM_MAX_LENGTH+1; and pubkey must be (at least) 32 bytes. +// +// Returns true if the url was parsed successfully, false if parsing failed (i.e. an invalid URL). +bool community_parse_full_url( + const char* full_url, char* base_url, char* room_token, unsigned char* pubkey); + +// Similar to the above, but allows a URL to omit the pubkey. If no pubkey is found, `pubkey` is +// left unchanged and `has_pubkey` is set to false; otherwise `pubkey` is written and `has_pubkey` +// is set to true. `pubkey` may be set to NULL, in which case it is never written. `has_pubkey` +// may be NULL in which case it is not set (typically both pubkey arguments would be null for cases +// where you don't care at all about the pubkey). +bool community_parse_partial_url( + const char* full_url, char* base_url, char* room_token, unsigned char* pubkey, bool* has_pubkey); + +// Produces a standard full URL from a given base_url (c string), room token (c string), and pubkey +// (fixed-length 32 byte buffer). The full URL is written to `full_url`, which must be at least +// COMMUNITY_FULL_URL_MAX_LENGTH in size. +void community_make_full_url( + const char* base_url, const char* room, const unsigned char* pubkey, char* full_url); + +#ifdef __cplusplus +} +#endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/community.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/community.hpp index daffb7886..04cc8ac8d 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/community.hpp +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/community.hpp @@ -16,7 +16,7 @@ namespace session::config { struct community { // 267 = len('https://') + 253 (max valid DNS name length) + len(':XXXXX') - static constexpr size_t URL_MAX_LENGTH = 267; + static constexpr size_t BASE_URL_MAX_LENGTH = 267; static constexpr size_t ROOM_MAX_LENGTH = 64; community() = default; @@ -86,6 +86,16 @@ struct community { std::string pubkey_b64() const; // Accesses the server pubkey as unpadded base64 (43 from // alphanumeric, '+', and '/'). + // Constructs and returns the full URL for this room. See below. + std::string full_url() const; + + // Constructs and returns the full URL for a given base, room, and pubkey. Currently this + // returns it in a Session-compatibility form (https://server.com/RoomName?public_key=....), but + // future versions are expected to change to use (https://server.com/r/RoomName?public_key=...), + // which this library also accepts. + static std::string full_url( + std::string_view base_url, std::string_view room, ustring_view pubkey); + // Takes a base URL as input and returns it in canonical form. This involves doing things // like lower casing it and removing redundant ports (e.g. :80 when using http://). Throws // std::invalid_argument if given an invalid base URL. @@ -115,6 +125,11 @@ struct community { // Throw std::invalid_argument if anything in the URL is unparseable or invalid. static std::tuple parse_full_url(std::string_view full_url); + // Takes a full or partial room URL (partial here meaning missing the ?public_key=...) and + // splits it up into canonical url, room, and (if present) pubkey. + static std::tuple> parse_partial_url( + std::string_view url); + protected: // The canonical base url and room (i.e. lower-cased, URL cleaned up): std::string base_url_, room_; diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.h index a8303b41c..9aa30b3a4 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.h +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.h @@ -28,7 +28,7 @@ typedef struct contacts_contact { int priority; CONVO_EXPIRATION_MODE exp_mode; - int exp_minutes; + int exp_seconds; } contacts_contact; diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.hpp index 01bc8ce55..d42d1a04a 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.hpp +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/contacts.hpp @@ -38,7 +38,7 @@ namespace session::config { /// e - Disappearing messages expiration type. Omitted if disappearing messages are not enabled /// for the conversation with this contact; 1 for delete-after-send, and 2 for /// delete-after-read. -/// E - Disappearing message timer, in minutes. Omitted when `e` is omitted. +/// E - Disappearing message timer, in seconds. Omitted when `e` is omitted. /// Struct containing contact info. struct contact_info { @@ -56,7 +56,7 @@ struct contact_info { int priority = 0; // If >0 then this message is pinned; higher values mean higher priority // (i.e. pinned earlier in the pinned list). expiration_mode exp_mode = expiration_mode::none; // The expiry time; none if not expiring. - std::chrono::minutes exp_timer{0}; // The expiration timer (in minutes) + std::chrono::seconds exp_timer{0}; // The expiration timer (in seconds) explicit contact_info(std::string sid); @@ -132,7 +132,7 @@ class Contacts : public ConfigBase { void set_expiry( std::string_view session_id, expiration_mode exp_mode, - std::chrono::minutes expiration_timer = 0min); + std::chrono::seconds expiration_timer = 0min); /// Removes a contact, if present. Returns true if it was found and removed, false otherwise. /// Note that this removes all fields related to a contact, even fields we do not know about. diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.hpp index 16e256fa4..149fdcbce 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.hpp +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/convo_info_volatile.hpp @@ -136,7 +136,7 @@ class ConvoInfoVolatile : public ConfigBase { static constexpr auto PRUNE_HIGH = 45 * 24h; /// Overrides push() to prune stale last-read values before we do the push. - std::pair push() override; + std::tuple> push() override; /// Looks up and returns a contact by session ID (hex). Returns nullopt if the session ID was /// not found, otherwise returns a filled out `convo::one_to_one`. @@ -148,6 +148,10 @@ class ConvoInfoVolatile : public ConfigBase { std::optional get_community( std::string_view base_url, std::string_view room) const; + /// Shortcut for calling community::parse_partial_url then calling the above with the base url + /// and room. The URL is not required to contain the pubkey (if present it will be ignored). + std::optional get_community(std::string_view partial_url) const; + /// Looks up and returns a legacy group conversation by ID. The ID looks like a hex Session ID, /// but isn't really a Session ID. Returns nullopt if there is no record of the group /// conversation. @@ -172,6 +176,9 @@ class ConvoInfoVolatile : public ConfigBase { convo::community get_or_construct_community( std::string_view base_url, std::string_view room, ustring_view pubkey) const; + // Shortcut for calling community::parse_full_url then calling the above + convo::community get_or_construct_community(std::string_view full_url) const; + /// Inserts or replaces existing conversation info. For example, to update a 1-to-1 /// conversation last read time you would do: /// diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.h index 232e67977..533614ad5 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.h +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.h @@ -10,12 +10,9 @@ extern "C" { // Maximum length of a group name, in bytes extern const size_t GROUP_NAME_MAX_LENGTH; -// Maximum length of a community full URL -extern const size_t COMMUNITY_URL_MAX_LENGTH; - -// Maximum length of a community room token -extern const size_t COMMUNITY_ROOM_MAX_LENGTH; - +/// Struct holding legacy group info; this struct owns allocated memory and *must* be freed via +/// either `ugroups_legacy_group_free()` or `user_groups_set_free_legacy_group()` when finished with +/// it. typedef struct ugroups_legacy_group_info { char session_id[67]; // in hex; 66 hex chars + null terminator. @@ -32,6 +29,10 @@ typedef struct ugroups_legacy_group_info { bool hidden; // true if hidden from the convo list int priority; // pinned message priority; 0 = unpinned, larger means pinned higher (i.e. higher // priority conversations come first). + + // For members use the ugroups_legacy_group_members and associated calls. + + void* _internal; // Internal storage, do not touch. } ugroups_legacy_group_info; typedef struct ugroups_community_info { @@ -82,35 +83,99 @@ bool user_groups_get_or_construct_community( const char* room, unsigned const char* pubkey) __attribute__((warn_unused_result)); -/// Fills `group` with the conversation info given a legacy group ID (specified as a null-terminated -/// hex string), if the conversation exists, and returns true. If the conversation does not exist -/// then `group` is left unchanged and false is returned. -bool user_groups_get_legacy_group( - const config_object* conf, ugroups_legacy_group_info* group, const char* id) +/// Returns a ugroups_legacy_group_info pointer containing the conversation info for a given legacy +/// group ID (specified as a null-terminated hex string), if the conversation exists. If the +/// conversation does not exist, returns NULL. +/// +/// The returned pointer *must* be freed either by calling `ugroups_legacy_group_free()` when done +/// with it, or by passing it to `user_groups_set_free_legacy_group()`. +ugroups_legacy_group_info* user_groups_get_legacy_group(const config_object* conf, const char* id) __attribute__((warn_unused_result)); /// Same as the above except that when the conversation does not exist, this sets all the group /// fields to defaults and loads it with the given id. /// -/// Returns true as long as it is given a valid legacy group group id (i.e. same format as a session -/// id). A false return is considered an error, and means the id was not a valid session id. +/// Returns a ugroups_legacy_group_info as long as it is given a valid legacy group id (i.e. same +/// format as a session id); it will return NULL only if the given id is invalid (and so the caller +/// needs to either pre-validate the id, or post-validate the return value). +/// +/// The returned pointer *must* be freed either by calling `ugroups_legacy_group_free()` when done +/// with it, or by passing it to `user_groups_set_free_legacy_group()`. /// /// This is the method that should usually be used to create or update a conversation, followed by /// setting fields in the group, and then giving it to user_groups_set(). -bool user_groups_get_or_construct_legacy_group( - const config_object* conf, ugroups_legacy_group_info* group, const char* id) - __attribute__((warn_unused_result)); +ugroups_legacy_group_info* user_groups_get_or_construct_legacy_group( + const config_object* conf, const char* id) __attribute__((warn_unused_result)); -/// Adds or updates a conversation from the given group info +/// Properly frees memory associated with a ugroups_legacy_group_info pointer (as returned by +/// get_legacy_group/get_or_construct_legacy_group). +void ugroups_legacy_group_free(ugroups_legacy_group_info* group); + +/// Adds or updates a community conversation from the given group info void user_groups_set_community(config_object* conf, const ugroups_community_info* group); + +/// Adds or updates a legacy group conversation from the into. This version of the method should +/// only be used when you explicitly want the `group` to remain valid; if the set is the last thing +/// you need to do with it (which is common) it is more efficient to call the freeing version, +/// below. void user_groups_set_legacy_group(config_object* conf, const ugroups_legacy_group_info* group); +/// Same as above, except that this also frees the pointer for you, which is commonly what is wanted +/// when updating fields. This is equivalent to, but more efficient than, setting and then freeing. +void user_groups_set_free_legacy_group(config_object* conf, ugroups_legacy_group_info* group); + /// Erases a conversation from the conversation list. Returns true if the conversation was found /// and removed, false if the conversation was not present. You must not call this during /// iteration; see details below. bool user_groups_erase_community(config_object* conf, const char* base_url, const char* room); bool user_groups_erase_legacy_group(config_object* conf, const char* group_id); +typedef struct ugroups_legacy_members_iterator ugroups_legacy_members_iterator; + +/// Group member iteration; this lets you walk through the full group member list. Example usage: +/// +/// const char* session_id; +/// bool admin; +/// ugroups_legacy_members_iterator* it = ugroups_legacy_members_begin(legacy_info); +/// while (ugroups_legacy_members_next(it, &session_id, &admin)) { +/// if (admin) +/// printf("ADMIN: %s", session_id); +/// } +/// ugroups_legacy_members_free(it); +/// +ugroups_legacy_members_iterator* ugroups_legacy_members_begin(ugroups_legacy_group_info* group); +bool ugroups_legacy_members_next( + ugroups_legacy_members_iterator* it, const char** session_id, bool* admin); +void ugroups_legacy_members_free(ugroups_legacy_members_iterator* it); + +/// This erases the group member at the current iteration location during a member iteration, +/// allowing iteration to continue. +/// +/// Example: +/// +/// while (ugroups_legacy_members_next(it, &sid, &admin)) { +/// if (should_remove(sid)) +/// ugroups_legacy_members_erase(it); +/// } +void ugroups_legacy_members_erase(ugroups_legacy_members_iterator* it); + +/// Adds a member (by session id and admin status) to this group. Returns true if the member was +/// inserted or had the admin status changed, false if the member already existed with the given +/// status, or if the session_id is not valid. +bool ugroups_legacy_member_add( + ugroups_legacy_group_info* group, const char* session_id, bool admin); + +/// Removes a member (including admins) from the group given the member's session id. This is not +/// safe to use on the current member during member iteration; for that see the above method +/// instead. Returns true if the session id was found and removed, false if not found. +bool ugroups_legacy_member_remove(ugroups_legacy_group_info* group, const char* session_id); + +/// Accesses the number of members in the group. The overall number is returned (both admins and +/// non-admins); if the given variables are not NULL, they will be populated with the individual +/// counts of members/admins. +size_t ugroups_legacy_members_count( + const ugroups_legacy_group_info* group, size_t* members, size_t* admins); + /// Returns the number of conversations. size_t user_groups_size(const config_object* conf); /// Returns the number of conversations of the specific type. diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.hpp index 2feafa693..a4d0119bd 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.hpp +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_groups.hpp @@ -27,7 +27,7 @@ namespace session::config { /// K - encryption secret key (32 bytes). Optional. /// m - set of member session ids (each 33 bytes). /// a - set of admin session ids (each 33 bytes). -/// E - disappearing messages duration, in minutes, > 0. Omitted if disappearing messages is +/// E - disappearing messages duration, in seconds, > 0. Omitted if disappearing messages is /// disabled. (Note that legacy groups only support expire after-read) /// h - hidden: 1 if the conversation has been removed from the conversation list, omitted if /// visible. @@ -60,7 +60,7 @@ struct legacy_group_info { // set to an empty string. ustring enc_pubkey; // bytes (32 or empty) ustring enc_seckey; // bytes (32 or empty) - std::chrono::minutes disappearing_timer{0}; // 0 == disabled. + std::chrono::seconds disappearing_timer{0}; // 0 == disabled. bool hidden = false; // true if the conversation is hidden from the convo list int priority = 0; // The priority; 0 means unpinned, larger means pinned higher (i.e. // higher priority conversations come first). @@ -88,7 +88,9 @@ struct legacy_group_info { // Internal ctor/method for C API implementations: legacy_group_info(const struct ugroups_legacy_group_info& c); // From c struct - void into(struct ugroups_legacy_group_info& c) const; // Into c struct + legacy_group_info(struct ugroups_legacy_group_info&& c); // From c struct + void into(struct ugroups_legacy_group_info& c) const&; // Copy into c struct + void into(struct ugroups_legacy_group_info& c) &&; // Move into c struct private: // session_id => (is admin) @@ -96,6 +98,12 @@ struct legacy_group_info { friend class UserGroups; + // Private implementations of the to/from C struct methods + struct impl_t {}; + static constexpr inline impl_t impl{}; + legacy_group_info(const struct ugroups_legacy_group_info& c, impl_t); + void into(struct ugroups_legacy_group_info& c, impl_t) const; + void load(const dict& info_dict); }; @@ -154,6 +162,10 @@ class UserGroups : public ConfigBase { std::optional get_community( std::string_view base_url, std::string_view room) const; + /// Looks up a community from a full URL. It is permitted for the URL to omit the pubkey (it + /// is not used or needed by this call). + std::optional get_community(std::string_view partial_url) const; + /// Looks up and returns a legacy group by group ID (hex, looks like a Session ID). Returns /// nullopt if the group was not found, otherwise returns a filled out `legacy_group_info`. std::optional get_legacy_group(std::string_view pubkey_hex) const; @@ -177,6 +189,8 @@ class UserGroups : public ConfigBase { std::string_view pubkey_encoded) const; community_info get_or_construct_community( std::string_view base_url, std::string_view room, ustring_view pubkey) const; + /// Shortcut to pass the url through community::parse_full_url, then call the above. + community_info get_or_construct_community(std::string_view full_url) const; /// Gets or constructs a blank legacy_group_info for the given group id. legacy_group_info get_or_construct_legacy_group(std::string_view pubkey_hex) const; diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.h b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.h index 03f382c85..7a9e800a1 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.h +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.h @@ -50,6 +50,12 @@ user_profile_pic user_profile_get_pic(const config_object* conf); // Sets a user profile int user_profile_set_pic(config_object* conf, user_profile_pic pic); +// Gets the current note-to-self priority level. Will always be >= 0. +int user_profile_get_nts_priority(const config_object* conf); + +// Sets the current note-to-self priority level. Should be >= 0 (negatives will be set to 0). +void user_profile_set_nts_priority(config_object* conf, int priority); + #ifdef __cplusplus } // extern "C" #endif diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.hpp b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.hpp index bcc8afa95..f19377119 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.hpp +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config/user_profile.hpp @@ -14,6 +14,8 @@ namespace session::config { /// n - user profile name /// p - user profile url /// q - user profile decryption key (binary) +/// + - the priority value for the "Note to Self" pseudo-conversation (higher = higher in the +/// conversation list). Omitted when 0. class UserProfile final : public ConfigBase { @@ -52,6 +54,12 @@ class UserProfile final : public ConfigBase { /// one is empty. void set_profile_pic(std::string_view url, ustring_view key); void set_profile_pic(profile_pic pic); + + /// Gets/sets the Note-to-self conversation priority. Will always be >= 0. + int get_nts_priority() const; + + /// Sets the Note-to-self conversation priority. Should be >= 0 (negatives will be set to 0). + void set_nts_priority(int priority); }; } // namespace session::config diff --git a/SessionMessagingKit/Messages/Control Messages/ConfigurationMessage+Convenience.swift b/SessionMessagingKit/Messages/Control Messages/ConfigurationMessage+Convenience.swift index 52dde7478..6c8c5977a 100644 --- a/SessionMessagingKit/Messages/Control Messages/ConfigurationMessage+Convenience.swift +++ b/SessionMessagingKit/Messages/Control Messages/ConfigurationMessage+Convenience.swift @@ -42,8 +42,8 @@ extension ConfigurationMessage { .filter(OpenGroup.Columns.roomToken != "") .filter(OpenGroup.Columns.isActive) .fetchAll(db) - .map { openGroup in - OpenGroup.urlFor( + .compactMap { openGroup in + SessionUtil.communityUrlFor( server: openGroup.server, roomToken: openGroup.roomToken, publicKey: openGroup.publicKey diff --git a/SessionMessagingKit/Messages/Message+Destination.swift b/SessionMessagingKit/Messages/Message+Destination.swift index a40b6ca5d..5b6902b08 100644 --- a/SessionMessagingKit/Messages/Message+Destination.swift +++ b/SessionMessagingKit/Messages/Message+Destination.swift @@ -18,6 +18,14 @@ public extension Message { ) case openGroupInbox(server: String, openGroupPublicKey: String, blindedPublicKey: String) + public var defaultNamespace: SnodeAPI.Namespace? { + switch self { + case .contact: return .`default` + case .closedGroup: return .legacyClosedGroup + default: return nil + } + } + public static func from( _ db: Database, thread: SessionThread, diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index bdbc7fcc5..4d95acfa6 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -207,7 +207,7 @@ public final class OpenGroupManager { roomToken: String, server: String, publicKey: String, - calledFromConfigHandling: Bool = false, + calledFromConfigHandling: Bool, dependencies: OGMDependencies = OGMDependencies() ) -> AnyPublisher { // If we are currently polling for this server and already have a TSGroupThread for this room the do nothing @@ -231,7 +231,15 @@ public final class OpenGroupManager { // Optionally try to insert a new version of the OpenGroup (it will fail if there is already an // inactive one but that won't matter as we then activate it _ = try? SessionThread.fetchOrCreate(db, id: threadId, variant: .community) - _ = try? SessionThread.filter(id: threadId).updateAll(db, SessionThread.Columns.shouldBeVisible.set(to: true)) + + // If we didn't add this open group via config handling then flag it to be visible (if it did + // come via config handling then we want to wait until it actually has messages before making + // it visible) + if !calledFromConfigHandling { + _ = try? SessionThread + .filter(id: threadId) + .updateAll(db, SessionThread.Columns.shouldBeVisible.set(to: true)) + } if (try? OpenGroup.exists(db, id: threadId)) == false { try? OpenGroup @@ -241,18 +249,36 @@ public final class OpenGroupManager { // Set the group to active and reset the sequenceNumber (handle groups which have // been deactivated) - _ = try? OpenGroup - .filter(id: OpenGroup.idFor(roomToken: roomToken, server: targetServer)) - .updateAll( - db, - OpenGroup.Columns.isActive.set(to: true), - OpenGroup.Columns.sequenceNumber.set(to: 0) - ) + if calledFromConfigHandling { + _ = try? OpenGroup + .filter(id: OpenGroup.idFor(roomToken: roomToken, server: targetServer)) + .updateAll( // Handling a config update so don't use `updateAllAndConfig` + db, + OpenGroup.Columns.isActive.set(to: true), + OpenGroup.Columns.sequenceNumber.set(to: 0) + ) + } + else { + _ = try? OpenGroup + .filter(id: OpenGroup.idFor(roomToken: roomToken, server: targetServer)) + .updateAllAndConfig( + db, + OpenGroup.Columns.isActive.set(to: true), + OpenGroup.Columns.sequenceNumber.set(to: 0) + ) + } - // We want to avoid blocking the db write thread so we dispatch the API call to a different thread - // - // Note: We don't do this after the db commit as it can fail (resulting in endless loading) - return Deferred { + /// We want to avoid blocking the db write thread so we return a future which resolves once the db transaction completes + /// and dispatches the result to another queue, this means that the caller can respond to errors resulting from attepting to + /// join the community + return Future { resolver in + db.afterNextTransactionNested { _ in + OpenGroupAPI.workQueue.async { + resolver(Result.success(())) + } + } + } + .flatMap { _ in dependencies.storage .readPublisherFlatMap(receiveOn: OpenGroupAPI.workQueue) { db in // Note: The initial request for room info and it's capabilities should NOT be @@ -272,9 +298,14 @@ public final class OpenGroupManager { .flatMap { response -> Future in Future { resolver in dependencies.storage.write { db in - // Enqueue a config sync job (have a newly added open group to sync) + // Add the new open group to libSession if !calledFromConfigHandling { - ConfigurationSyncJob.enqueue(db) + try SessionUtil.add( + db, + server: server, + rootToken: roomToken, + publicKey: publicKey + ) } // Store the capabilities first @@ -309,12 +340,22 @@ public final class OpenGroupManager { .eraseToAnyPublisher() } - public func delete(_ db: Database, openGroupId: String, dependencies: OGMDependencies = OGMDependencies()) { + public func delete( + _ db: Database, + openGroupId: String, + calledFromConfigHandling: Bool, + dependencies: OGMDependencies = OGMDependencies() + ) { let server: String? = try? OpenGroup .select(.server) .filter(id: openGroupId) .asRequest(of: String.self) .fetchOne(db) + let roomToken: String? = try? OpenGroup + .select(.roomToken) + .filter(id: openGroupId) + .asRequest(of: String.self) + .fetchOne(db) // Stop the poller if needed // @@ -348,13 +389,17 @@ public final class OpenGroupManager { // If it's a session-run room then just set it to inactive _ = try? OpenGroup .filter(id: openGroupId) - .updateAll(db, OpenGroup.Columns.isActive.set(to: false)) + .updateAllAndConfig(db, OpenGroup.Columns.isActive.set(to: false)) } // Remove the thread and associated data _ = try? SessionThread .filter(id: openGroupId) .deleteAll(db) + + if !calledFromConfigHandling, let server: String = server, let roomToken: String = roomToken { + try? SessionUtil.remove(db, server: server, roomToken: roomToken) + } } // MARK: - Response Processing @@ -405,43 +450,34 @@ public final class OpenGroupManager { // Only update the database columns which have changed (this is to prevent the UI from triggering // updates due to changing database columns to the existing value) - let permissions = OpenGroup.Permissions(roomInfo: pollInfo) - + let hasDetails: Bool = (pollInfo.details != nil) + let permissions: OpenGroup.Permissions = OpenGroup.Permissions(roomInfo: pollInfo) + let changes: [ConfigColumnAssignment] = [] + .appending(openGroup.publicKey == maybePublicKey ? nil : + maybePublicKey.map { OpenGroup.Columns.publicKey.set(to: $0) } + ) + .appending(openGroup.userCount == pollInfo.activeUsers ? nil : + OpenGroup.Columns.userCount.set(to: pollInfo.activeUsers) + ) + .appending(openGroup.permissions == permissions ? nil : + OpenGroup.Columns.permissions.set(to: permissions) + ) + .appending(!hasDetails || openGroup.name == pollInfo.details?.name ? nil : + OpenGroup.Columns.name.set(to: pollInfo.details?.name) + ) + .appending(!hasDetails || openGroup.roomDescription == pollInfo.details?.roomDescription ? nil : + OpenGroup.Columns.roomDescription.set(to: pollInfo.details?.roomDescription) + ) + .appending(!hasDetails || openGroup.imageId == pollInfo.details?.imageId ? nil : + OpenGroup.Columns.imageId.set(to: pollInfo.details?.imageId) + ) + .appending(!hasDetails || openGroup.infoUpdates == pollInfo.details?.infoUpdates ? nil : + OpenGroup.Columns.infoUpdates.set(to: pollInfo.details?.infoUpdates) + ) + try OpenGroup .filter(id: openGroup.id) - .updateAll( - db, - [ - (openGroup.publicKey != maybePublicKey ? - maybePublicKey.map { OpenGroup.Columns.publicKey.set(to: $0) } : - nil - ), - (pollInfo.details != nil && openGroup.name != pollInfo.details?.name ? - (pollInfo.details?.name).map { OpenGroup.Columns.name.set(to: $0) } : - nil - ), - (pollInfo.details != nil && openGroup.roomDescription != pollInfo.details?.roomDescription ? - (pollInfo.details?.roomDescription).map { OpenGroup.Columns.roomDescription.set(to: $0) } : - nil - ), - (pollInfo.details != nil && openGroup.imageId != pollInfo.details?.imageId ? - (pollInfo.details?.imageId).map { OpenGroup.Columns.imageId.set(to: $0) } : - nil - ), - (openGroup.userCount != pollInfo.activeUsers ? - OpenGroup.Columns.userCount.set(to: pollInfo.activeUsers) : - nil - ), - (pollInfo.details != nil && openGroup.infoUpdates != pollInfo.details?.infoUpdates ? - (pollInfo.details?.infoUpdates).map { OpenGroup.Columns.infoUpdates.set(to: $0) } : - nil - ), - (openGroup.permissions != permissions ? - OpenGroup.Columns.permissions.set(to: permissions) : - nil - ) - ].compactMap { $0 } - ) + .updateAllAndConfig(db, changes) // Update the admin/moderator group members if let roomDetails: OpenGroupAPI.Room = pollInfo.details { @@ -1120,34 +1156,6 @@ public final class OpenGroupManager { return publisher } - - public static func parseOpenGroup(from string: String) -> (room: String, server: String, publicKey: String)? { - guard let url = URL(string: string), let host = url.host ?? given(string.split(separator: "/").first, { String($0) }), let query = url.query else { return nil } - // Inputs that should work: - // https://sessionopengroup.co/r/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c - // https://sessionopengroup.co/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c - // http://sessionopengroup.co/r/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c - // http://sessionopengroup.co/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c - // sessionopengroup.co/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c (does NOT go to HTTPS) - // sessionopengroup.co/r/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c (does NOT go to HTTPS) - // https://143.198.213.225:443/r/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c - // https://143.198.213.225:443/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c - // 143.198.213.255:80/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c - // 143.198.213.255:80/r/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c - let useTLS = (url.scheme == "https") - - // If there is no scheme then the host is included in the path (so handle that case) - let hostFreePath = (url.host != nil || !url.path.starts(with: host) ? url.path : url.path.substring(from: host.count)) - let updatedPath = (hostFreePath.starts(with: "/r/") ? hostFreePath.substring(from: 2) : hostFreePath) - let room = String(updatedPath.dropFirst()) // Drop the leading slash - let queryParts = query.split(separator: "=") - guard !room.isEmpty && !room.contains("/"), queryParts.count == 2, queryParts[0] == "public_key" else { return nil } - let publicKey = String(queryParts[1]) - guard publicKey.count == 64 && Hex.isValid(publicKey) else { return nil } - var server = (useTLS ? "https://" : "http://") + host - if let port = url.port { server += ":\(port)" } - return (room: room, server: server, publicKey: publicKey) - } } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift index 2f15eb5e4..15cf7b7fb 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift @@ -206,6 +206,7 @@ extension MessageReceiver { sentTimestampMs: nil // Explicitly nil as it's a separate message from above ), to: try Message.Destination.from(db, thread: thread), + namespace: try Message.Destination.from(db, thread: thread).defaultNamespace, interactionId: nil // Explicitly nil as it's a separate message from above ) ) diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift index 6e733d227..eafa7d974 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ConfigurationMessages.swift @@ -196,7 +196,7 @@ extension MessageReceiver { // Open groups for openGroupURL in message.openGroups { - if let (room, server, publicKey) = OpenGroupManager.parseOpenGroup(from: openGroupURL) { + if let (room, server, publicKey) = SessionUtil.parseCommunity(url: openGroupURL) { OpenGroupManager.shared .add( db, diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift index e94804030..da9e460c9 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift @@ -604,7 +604,12 @@ extension MessageSender { receiveCompletion: { result in switch result { case .failure: break - case .finished: try? ClosedGroup.removeKeysAndUnsubscribe(threadId: groupPublicKey) + case .finished: + try? ClosedGroup.removeKeysAndUnsubscribe( + threadId: groupPublicKey, + removeGroupData: false, + calledFromConfigHandling: false + ) } } ) diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift b/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift index 71a8dc7d2..ff71f31d3 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift @@ -80,6 +80,7 @@ extension MessageSender { db, message: VisibleMessage.from(db, interaction: interaction), to: try Message.Destination.from(db, thread: thread), + namespace: try Message.Destination.from(db, thread: thread).defaultNamespace, interactionId: interactionId ) } @@ -95,14 +96,8 @@ extension MessageSender { .eraseToAnyPublisher() } - // Ensure we have the rest of the required data - guard let destination: Message.Destination = preparedSendData.destination else { - return Fail(error: MessageSenderError.invalidMessage) - .eraseToAnyPublisher() - } - let threadId: String = { - switch destination { + switch preparedSendData.destination { case .contact(let publicKey): return publicKey case .closedGroup(let groupPublicKey): return groupPublicKey case .openGroup(let roomToken, let server, _, _, _): diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index 6b409b121..a96288d71 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -12,9 +12,10 @@ public final class MessageSender { public struct PreparedSendData { let shouldSend: Bool + let destination: Message.Destination + let namespace: SnodeAPI.Namespace? let message: Message? - let destination: Message.Destination? let interactionId: Int64? let isSyncMessage: Bool? let totalAttachmentsUploaded: Int @@ -26,7 +27,8 @@ public final class MessageSender { private init( shouldSend: Bool, message: Message?, - destination: Message.Destination?, + destination: Message.Destination, + namespace: SnodeAPI.Namespace?, interactionId: Int64?, isSyncMessage: Bool?, totalAttachmentsUploaded: Int = 0, @@ -38,6 +40,7 @@ public final class MessageSender { self.message = message self.destination = destination + self.namespace = namespace self.interactionId = interactionId self.isSyncMessage = isSyncMessage self.totalAttachmentsUploaded = totalAttachmentsUploaded @@ -47,25 +50,11 @@ public final class MessageSender { self.ciphertext = ciphertext } - // The default constructor creats an instance that doesn't actually send a message - fileprivate init() { - self.shouldSend = false - - self.message = nil - self.destination = nil - self.interactionId = nil - self.isSyncMessage = nil - self.totalAttachmentsUploaded = 0 - - self.snodeMessage = nil - self.plaintext = nil - self.ciphertext = nil - } - /// This should be used to send a message to one-to-one or closed group conversations fileprivate init( message: Message, destination: Message.Destination, + namespace: SnodeAPI.Namespace, interactionId: Int64?, isSyncMessage: Bool?, snodeMessage: SnodeMessage @@ -74,6 +63,7 @@ public final class MessageSender { self.message = message self.destination = destination + self.namespace = namespace self.interactionId = interactionId self.isSyncMessage = isSyncMessage self.totalAttachmentsUploaded = 0 @@ -94,6 +84,7 @@ public final class MessageSender { self.message = message self.destination = destination + self.namespace = nil self.interactionId = interactionId self.isSyncMessage = false self.totalAttachmentsUploaded = 0 @@ -114,6 +105,7 @@ public final class MessageSender { self.message = message self.destination = destination + self.namespace = nil self.interactionId = interactionId self.isSyncMessage = false self.totalAttachmentsUploaded = 0 @@ -129,7 +121,8 @@ public final class MessageSender { return PreparedSendData( shouldSend: shouldSend, message: message, - destination: destination?.with(fileIds: fileIds), + destination: destination.with(fileIds: fileIds), + namespace: namespace, interactionId: interactionId, isSyncMessage: isSyncMessage, totalAttachmentsUploaded: fileIds.count, @@ -144,6 +137,7 @@ public final class MessageSender { _ db: Database, message: Message, to destination: Message.Destination, + namespace: SnodeAPI.Namespace?, interactionId: Int64?, isSyncMessage: Bool = false, using dependencies: SMKDependencies = SMKDependencies() @@ -165,6 +159,7 @@ public final class MessageSender { db, message: updatedMessage, to: destination, + namespace: namespace, interactionId: interactionId, userPublicKey: currentUserPublicKey, messageSendTimestamp: messageSendTimestamp, @@ -199,6 +194,7 @@ public final class MessageSender { _ db: Database, message: Message, to destination: Message.Destination, + namespace: SnodeAPI.Namespace?, interactionId: Int64?, userPublicKey: String, messageSendTimestamp: Int64, @@ -215,7 +211,7 @@ public final class MessageSender { }() // Validate the message - guard message.isValid else { + guard message.isValid, let namespace: SnodeAPI.Namespace = namespace else { throw MessageSender.handleFailedMessageSend( db, message: message, @@ -225,8 +221,8 @@ public final class MessageSender { ) } - // Attach the user's profile if needed (no need to do so for 'Note to Self' or sync messages as they - // will be managed by the user config handling + // Attach the user's profile if needed (no need to do so for 'Note to Self' or sync + // messages as they will be managed by the user config handling let isSelfSend: Bool = (message.recipient == userPublicKey) if !isSelfSend, !isSyncMessage, var messageWithProfile: MessageWithProfile = message as? MessageWithProfile { @@ -356,6 +352,7 @@ public final class MessageSender { return PreparedSendData( message: message, destination: destination, + namespace: namespace, interactionId: interactionId, isSyncMessage: isSyncMessage, snodeMessage: snodeMessage @@ -616,9 +613,6 @@ public final class MessageSender { case .contact, .closedGroup: return sendToSnodeDestination(data: preparedSendData, using: dependencies) case .openGroup: return sendToOpenGroupDestination(data: preparedSendData, using: dependencies) case .openGroupInbox: return sendToOpenGroupInbox(data: preparedSendData, using: dependencies) - case .none: - return Fail(error: MessageSenderError.invalidMessage) - .eraseToAnyPublisher() } } @@ -630,7 +624,7 @@ public final class MessageSender { ) -> AnyPublisher { guard let message: Message = data.message, - let destination: Message.Destination = data.destination, + let namespace: SnodeAPI.Namespace = data.namespace, let isSyncMessage: Bool = data.isSyncMessage, let snodeMessage: SnodeMessage = data.snodeMessage else { @@ -641,12 +635,7 @@ public final class MessageSender { return SnodeAPI .sendMessage( snodeMessage, - in: { - switch destination { - case .closedGroup: return .legacyClosedGroup - default: return .`default` - } - }() + in: namespace ) .subscribe(on: DispatchQueue.global(qos: .default)) .flatMap { response -> AnyPublisher in @@ -676,7 +665,7 @@ public final class MessageSender { try MessageSender.handleSuccessfulMessageSend( db, message: updatedMessage, - to: destination, + to: data.destination, interactionId: data.interactionId, isSyncMessage: isSyncMessage, using: dependencies @@ -755,8 +744,7 @@ public final class MessageSender { ) -> AnyPublisher { guard let message: Message = data.message, - let destination: Message.Destination = data.destination, - case .openGroup(let roomToken, let server, let whisperTo, let whisperMods, let fileIds) = destination, + case .openGroup(let roomToken, let server, let whisperTo, let whisperMods, let fileIds) = data.destination, let plaintext: Data = data.plaintext else { return Fail(error: MessageSenderError.invalidMessage) @@ -789,7 +777,7 @@ public final class MessageSender { try MessageSender.handleSuccessfulMessageSend( db, message: updatedMessage, - to: destination, + to: data.destination, interactionId: data.interactionId, serverTimestampMs: serverTimestampMs, using: dependencies @@ -824,8 +812,7 @@ public final class MessageSender { ) -> AnyPublisher { guard let message: Message = data.message, - let destination: Message.Destination = data.destination, - case .openGroupInbox(let server, _, let recipientBlindedPublicKey) = destination, + case .openGroupInbox(let server, _, let recipientBlindedPublicKey) = data.destination, let ciphertext: Data = data.ciphertext else { return Fail(error: MessageSenderError.invalidMessage) @@ -854,7 +841,7 @@ public final class MessageSender { try MessageSender.handleSuccessfulMessageSend( db, message: updatedMessage, - to: destination, + to: data.destination, interactionId: data.interactionId, serverTimestampMs: UInt64(floor(responseData.posted * 1000)), using: dependencies diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift index cd02aa5df..0819c5abc 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPoller.swift @@ -93,6 +93,7 @@ extension OpenGroupAPI { return dependencies.storage .readPublisherFlatMap(receiveOn: Threading.pollerQueue) { db -> AnyPublisher<(Int64, PollResponse), Error> in let failureCount: Int64 = (try? OpenGroup + .filter(OpenGroup.Columns.server == server) .select(max(OpenGroup.Columns.pollFailureCount)) .asRequest(of: Int64.self) .fetchOne(db)) @@ -176,17 +177,73 @@ extension OpenGroupAPI { .fetchOne(db) } .defaulting(to: 0) + var prunedIds: [String] = [] Storage.shared.writeAsync { db in + struct Info: Decodable, FetchableRecord { + let id: String + let shouldBeVisible: Bool + } + + let rooms: [String] = try OpenGroup + .filter( + OpenGroup.Columns.server == server && + OpenGroup.Columns.isActive == true + ) + .select(.roomToken) + .asRequest(of: String.self) + .fetchAll(db) + let roomsAreVisible: [Info] = try SessionThread + .select(.id, .shouldBeVisible) + .filter( + ids: rooms.map { + OpenGroup.idFor(roomToken: $0, server: server) + } + ) + .asRequest(of: Info.self) + .fetchAll(db) + + // Increase the failure count try OpenGroup .filter(OpenGroup.Columns.server == server) .updateAll( db, - OpenGroup.Columns.pollFailureCount.set(to: (pollFailureCount + 1)) + OpenGroup.Columns.pollFailureCount + .set(to: (pollFailureCount + 1)) ) + + /// If the polling has failed 10+ times then try to prune any invalid rooms that + /// aren't visible (they would have been added via config messages and will + /// likely always fail but the user has no way to delete them) + guard pollFailureCount > 10 else { return } + + prunedIds = roomsAreVisible + .filter { !$0.shouldBeVisible } + .map { $0.id } + + prunedIds.forEach { id in + OpenGroupManager.shared.delete( + db, + openGroupId: id, + /// **Note:** We pass `calledFromConfigHandling` as `true` + /// here because we want to avoid syncing this deletion as the room might + /// not be in an invalid state on other devices - one of the other devices + /// will eventually trigger a new config update which will re-add this room + /// and hopefully at that time it'll work again + calledFromConfigHandling: true + ) + } } - + SNLog("Open group polling failed due to error: \(error). Setting failure count to \(pollFailureCount).") + + // Add a note to the logs that this happened + if !prunedIds.isEmpty { + let rooms: String = prunedIds + .compactMap { $0.components(separatedBy: server).last } + .joined(separator: ", ") + SNLog("Hidden open group failure count surpassed 10, removed hidden rooms \(rooms).") + } } self?.isPolling = false diff --git a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift index 597d241fc..cd202a332 100644 --- a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift +++ b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift @@ -24,7 +24,7 @@ public struct SessionThreadViewModel: FetchableRecordWithRowId, Decodable, Equat public static let threadIsMessageRequestKey: SQL = SQL(stringLiteral: CodingKeys.threadIsMessageRequest.stringValue) public static let threadRequiresApprovalKey: SQL = SQL(stringLiteral: CodingKeys.threadRequiresApproval.stringValue) public static let threadShouldBeVisibleKey: SQL = SQL(stringLiteral: CodingKeys.threadShouldBeVisible.stringValue) - public static let threadIsPinnedKey: SQL = SQL(stringLiteral: CodingKeys.threadIsPinned.stringValue) + public static let threadPinnedPriorityKey: SQL = SQL(stringLiteral: CodingKeys.threadPinnedPriority.stringValue) public static let threadIsBlockedKey: SQL = SQL(stringLiteral: CodingKeys.threadIsBlocked.stringValue) public static let threadMutedUntilTimestampKey: SQL = SQL(stringLiteral: CodingKeys.threadMutedUntilTimestamp.stringValue) public static let threadOnlyNotifyForMentionsKey: SQL = SQL(stringLiteral: CodingKeys.threadOnlyNotifyForMentions.stringValue) @@ -89,7 +89,7 @@ public struct SessionThreadViewModel: FetchableRecordWithRowId, Decodable, Equat /// This flag indicates whether the thread is an incoming message request public let threadRequiresApproval: Bool? public let threadShouldBeVisible: Bool? - public let threadIsPinned: Bool + public let threadPinnedPriority: Int32 public let threadIsBlocked: Bool? public let threadMutedUntilTimestamp: TimeInterval? public let threadOnlyNotifyForMentions: Bool? @@ -346,7 +346,7 @@ public extension SessionThreadViewModel { self.threadIsMessageRequest = false self.threadRequiresApproval = false self.threadShouldBeVisible = false - self.threadIsPinned = false + self.threadPinnedPriority = 0 self.threadIsBlocked = nil self.threadMutedUntilTimestamp = nil self.threadOnlyNotifyForMentions = nil @@ -412,7 +412,7 @@ public extension SessionThreadViewModel { threadIsMessageRequest: self.threadIsMessageRequest, threadRequiresApproval: self.threadRequiresApproval, threadShouldBeVisible: self.threadShouldBeVisible, - threadIsPinned: self.threadIsPinned, + threadPinnedPriority: self.threadPinnedPriority, threadIsBlocked: self.threadIsBlocked, threadMutedUntilTimestamp: self.threadMutedUntilTimestamp, threadOnlyNotifyForMentions: self.threadOnlyNotifyForMentions, @@ -467,7 +467,7 @@ public extension SessionThreadViewModel { threadIsMessageRequest: self.threadIsMessageRequest, threadRequiresApproval: self.threadRequiresApproval, threadShouldBeVisible: self.threadShouldBeVisible, - threadIsPinned: self.threadIsPinned, + threadPinnedPriority: self.threadPinnedPriority, threadIsBlocked: self.threadIsBlocked, threadMutedUntilTimestamp: self.threadMutedUntilTimestamp, threadOnlyNotifyForMentions: self.threadOnlyNotifyForMentions, @@ -570,7 +570,7 @@ public extension SessionThreadViewModel { \(thread[.creationDateTimestamp]) AS \(ViewModel.threadCreationDateTimestampKey), (\(SQL("\(thread[.id]) = \(userPublicKey)"))) AS \(ViewModel.threadIsNoteToSelfKey), - \(thread[.isPinned]) AS \(ViewModel.threadIsPinnedKey), + IFNULL(\(thread[.pinnedPriority]), 0) AS \(ViewModel.threadPinnedPriorityKey), \(contact[.isBlocked]) AS \(ViewModel.threadIsBlockedKey), \(thread[.mutedUntilTimestamp]) AS \(ViewModel.threadMutedUntilTimestampKey), \(thread[.onlyNotifyForMentions]) AS \(ViewModel.threadOnlyNotifyForMentionsKey), @@ -840,7 +840,7 @@ public extension SessionThreadViewModel { ) AS \(ViewModel.threadRequiresApprovalKey), \(thread[.shouldBeVisible]) AS \(ViewModel.threadShouldBeVisibleKey), - \(thread[.isPinned]) AS \(ViewModel.threadIsPinnedKey), + IFNULL(\(thread[.pinnedPriority]), 0) AS \(ViewModel.threadPinnedPriorityKey), \(contact[.isBlocked]) AS \(ViewModel.threadIsBlockedKey), \(thread[.mutedUntilTimestamp]) AS \(ViewModel.threadMutedUntilTimestampKey), \(thread[.onlyNotifyForMentions]) AS \(ViewModel.threadOnlyNotifyForMentionsKey), @@ -938,7 +938,7 @@ public extension SessionThreadViewModel { (\(SQL("\(thread[.id]) = \(userPublicKey)"))) AS \(ViewModel.threadIsNoteToSelfKey), - \(thread[.isPinned]) AS \(ViewModel.threadIsPinnedKey), + IFNULL(\(thread[.pinnedPriority]), 0) AS \(ViewModel.threadPinnedPriorityKey), \(contact[.isBlocked]) AS \(ViewModel.threadIsBlockedKey), \(thread[.mutedUntilTimestamp]) AS \(ViewModel.threadMutedUntilTimestampKey), \(thread[.onlyNotifyForMentions]) AS \(ViewModel.threadOnlyNotifyForMentionsKey), @@ -1106,7 +1106,7 @@ public extension SessionThreadViewModel { \(thread[.creationDateTimestamp]) AS \(ViewModel.threadCreationDateTimestampKey), (\(SQL("\(thread[.id]) = \(userPublicKey)"))) AS \(ViewModel.threadIsNoteToSelfKey), - \(thread[.isPinned]) AS \(ViewModel.threadIsPinnedKey), + IFNULL(\(thread[.pinnedPriority]), 0) AS \(ViewModel.threadPinnedPriorityKey), \(ViewModel.contactProfileKey).*, \(ViewModel.closedGroupProfileFrontKey).*, @@ -1243,7 +1243,7 @@ public extension SessionThreadViewModel { \(groupMemberInfoLiteral).\(ViewModel.threadMemberNamesKey), (\(SQL("\(thread[.id]) = \(userPublicKey)"))) AS \(ViewModel.threadIsNoteToSelfKey), - \(thread[.isPinned]) AS \(ViewModel.threadIsPinnedKey), + IFNULL(\(thread[.pinnedPriority]), 0) AS \(ViewModel.threadPinnedPriorityKey), \(ViewModel.contactProfileKey).*, \(ViewModel.closedGroupProfileFrontKey).*, @@ -1590,7 +1590,7 @@ public extension SessionThreadViewModel { '' AS \(ViewModel.threadMemberNamesKey), true AS \(ViewModel.threadIsNoteToSelfKey), - \(thread[.isPinned]) AS \(ViewModel.threadIsPinnedKey), + IFNULL(\(thread[.pinnedPriority]), 0) AS \(ViewModel.threadPinnedPriorityKey), \(ViewModel.contactProfileKey).*, @@ -1647,7 +1647,7 @@ public extension SessionThreadViewModel { (\(SQL("\(thread[.id]) = \(userPublicKey)"))) AS \(ViewModel.threadIsNoteToSelfKey), - \(thread[.isPinned]) AS \(ViewModel.threadIsPinnedKey), + IFNULL(\(thread[.pinnedPriority]), 0) AS \(ViewModel.threadPinnedPriorityKey), \(contact[.isBlocked]) AS \(ViewModel.threadIsBlockedKey), \(ViewModel.contactProfileKey).*, diff --git a/SessionMessagingKit/Utilities/ProfileManager.swift b/SessionMessagingKit/Utilities/ProfileManager.swift index d2b7b368e..faf722a04 100644 --- a/SessionMessagingKit/Utilities/ProfileManager.swift +++ b/SessionMessagingKit/Utilities/ProfileManager.swift @@ -507,7 +507,7 @@ public struct ProfileManager { ) throws { let isCurrentUser = (publicKey == getUserHexEncodedPublicKey(db, dependencies: dependencies)) let profile: Profile = Profile.fetchOrCreate(id: publicKey) - var profileChanges: [ColumnAssignment] = [] + var profileChanges: [ConfigColumnAssignment] = [] // Name if let name: String = name, !name.isEmpty, name != profile.name { @@ -622,7 +622,7 @@ public struct ProfileManager { let dedupeIdentifier: String = "AvatarDownload-\(publicKey)-\(targetAvatarUrl ?? "remove")" - db.afterNextTransactionNestedOnce(dedupeIdentifier: dedupeIdentifier) { db in + db.afterNextTransactionNestedOnce(dedupeId: dedupeIdentifier) { db in // Need to refetch to ensure the db changes have occurred ProfileManager.downloadAvatar(for: Profile.fetchOrCreate(db, id: publicKey)) } diff --git a/SessionMessagingKitTests/LibSessionUtil/ConfigContactsSpec.swift b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigContactsSpec.swift similarity index 67% rename from SessionMessagingKitTests/LibSessionUtil/ConfigContactsSpec.swift rename to SessionMessagingKitTests/LibSessionUtil/Configs/ConfigContactsSpec.swift index e48b72099..e7f34e1a8 100644 --- a/SessionMessagingKitTests/LibSessionUtil/ConfigContactsSpec.swift +++ b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigContactsSpec.swift @@ -9,13 +9,13 @@ import Quick import Nimble /// This spec is designed to replicate the initial test cases for the libSession-util to ensure the behaviour matches -class ConfigContactsSpec: QuickSpec { +class ConfigContactsSpec { // MARK: - Spec - override func spec() { + static func spec() { it("generates Contact configs correctly") { let seed: Data = Data(hex: "0123456789abcdef0123456789abcdef") - + // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately let identity = try! Identity.generate(from: seed) var edSK: [UInt8] = identity.ed25519KeyPair.secretKey @@ -24,7 +24,7 @@ class ConfigContactsSpec: QuickSpec { expect(identity.x25519KeyPair.publicKey.toHexString()) .to(equal("d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72")) expect(String(edSK.toHexString().prefix(32))).to(equal(seed.toHexString())) - + // Initialize a brand new, empty config because we have no dump data to deal with. let error: UnsafeMutablePointer? = nil var conf: UnsafeMutablePointer? = nil @@ -48,21 +48,13 @@ class ConfigContactsSpec: QuickSpec { expect(contact2.blocked).to(beFalse()) expect(contact2.profile_pic).toNot(beNil()) // Creates an empty instance apparently expect(String(libSessionVal: contact2.profile_pic.url)).to(beEmpty()) - - // We don't need to push anything, since this is a default contact + expect(config_needs_push(conf)).to(beFalse()) - // And we haven't changed anything so don't need to dump to db expect(config_needs_dump(conf)).to(beFalse()) - var toPush: UnsafeMutablePointer? = nil - var toPushLen: Int = 0 - // We don't need to push since we haven't changed anything, so this call is mainly just for - // testing: - let seqno: Int64 = config_push(conf, &toPush, &toPushLen) - expect(toPush).toNot(beNil()) - expect(seqno).to(equal(0)) - expect(toPushLen).to(equal(256)) - toPush?.deallocate() + let pushData1: UnsafeMutablePointer = config_push(conf) + expect(pushData1.pointee.seqno).to(equal(0)) + pushData1.deallocate() // Update the contact data contact2.name = "Joe".toLibSession() @@ -90,18 +82,21 @@ class ConfigContactsSpec: QuickSpec { expect(config_needs_push(conf)).to(beTrue()) expect(config_needs_dump(conf)).to(beTrue()) - var toPush2: UnsafeMutablePointer? = nil - var toPush2Len: Int = 0 - let seqno2: Int64 = config_push(conf, &toPush2, &toPush2Len); // incremented since we made changes (this only increments once between // dumps; even though we changed multiple fields here). - expect(seqno2).to(equal(1)) - toPush2?.deallocate() + let pushData2: UnsafeMutablePointer = config_push(conf) + + // incremented since we made changes (this only increments once between + // dumps; even though we changed multiple fields here). + expect(pushData2.pointee.seqno).to(equal(1)) // Pretend we uploaded it - config_confirm_pushed(conf, seqno2) + let fakeHash1: String = "fakehash1" + var cFakeHash1: [CChar] = fakeHash1.cArray + config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash1) expect(config_needs_push(conf)).to(beFalse()) expect(config_needs_dump(conf)).to(beTrue()) + pushData2.deallocate() // NB: Not going to check encrypted data and decryption here because that's general (not // specific to contacts) and is covered already in the user profile tests. @@ -118,11 +113,9 @@ class ConfigContactsSpec: QuickSpec { expect(config_needs_push(conf2)).to(beFalse()) expect(config_needs_dump(conf2)).to(beFalse()) - var toPush3: UnsafeMutablePointer? = nil - var toPush3Len: Int = 0 - let seqno3: Int64 = config_push(conf, &toPush3, &toPush3Len); - expect(seqno3).to(equal(1)) - toPush3?.deallocate() + let pushData3: UnsafeMutablePointer = config_push(conf2) + expect(pushData3.pointee.seqno).to(equal(1)) + pushData3.deallocate() // Because we just called dump() above, to load up contacts2 expect(config_needs_dump(conf)).to(beFalse()) @@ -154,26 +147,26 @@ class ConfigContactsSpec: QuickSpec { contacts_set(conf2, &contact5) expect(config_needs_push(conf2)).to(beTrue()) - var toPush4: UnsafeMutablePointer? = nil - var toPush4Len: Int = 0 - let seqno4: Int64 = config_push(conf2, &toPush4, &toPush4Len); - expect(seqno4).to(equal(2)) + let pushData4: UnsafeMutablePointer = config_push(conf2) + expect(pushData4.pointee.seqno).to(equal(2)) // Check the merging - var mergeData: [UnsafePointer?] = [UnsafePointer(toPush4)] - var mergeSize: [Int] = [toPush4Len] - expect(config_merge(conf, &mergeData, &mergeSize, 1)).to(equal(1)) - config_confirm_pushed(conf2, seqno4) - toPush4?.deallocate() + let fakeHash2: String = "fakehash2" + var cFakeHash2: [CChar] = fakeHash2.cArray + var mergeHashes: [UnsafePointer?] = [cFakeHash2].unsafeCopy() + var mergeData: [UnsafePointer?] = [UnsafePointer(pushData4.pointee.config)] + var mergeSize: [Int] = [pushData4.pointee.config_len] + expect(config_merge(conf, &mergeHashes, &mergeData, &mergeSize, 1)).to(equal(1)) + config_confirm_pushed(conf2, pushData4.pointee.seqno, &cFakeHash2) + mergeHashes.forEach { $0?.deallocate() } + pushData4.deallocate() expect(config_needs_push(conf)).to(beFalse()) - var toPush5: UnsafeMutablePointer? = nil - var toPush5Len: Int = 0 - let seqno5: Int64 = config_push(conf2, &toPush5, &toPush5Len); - expect(seqno5).to(equal(2)) - toPush5?.deallocate() - + let pushData5: UnsafeMutablePointer = config_push(conf) + expect(pushData5.pointee.seqno).to(equal(2)) + pushData5.deallocate() + // Iterate through and make sure we got everything we expected var sessionIds: [String] = [] var nicknames: [String] = [] @@ -182,7 +175,7 @@ class ConfigContactsSpec: QuickSpec { var contact6: contacts_contact = contacts_contact() let contactIterator: UnsafeMutablePointer = contacts_iterator_new(conf) while !contacts_iterator_done(contactIterator, &contact6) { - sessionIds.append(String(libSessionVal: contact6.session_id) ?? "(N/A)") + sessionIds.append(String(libSessionVal: contact6.session_id)) nicknames.append(String(libSessionVal: contact6.nickname, nullIfEmpty: true) ?? "(N/A)") contacts_iterator_advance(contactIterator) } @@ -194,9 +187,9 @@ class ConfigContactsSpec: QuickSpec { expect(sessionIds.last).to(equal(anotherId)) expect(nicknames.first).to(equal("Joey")) expect(nicknames.last).to(equal("(N/A)")) - + // Conflict! Oh no! - + // On client 1 delete a contact: contacts_erase(conf, definitelyRealId) @@ -215,51 +208,63 @@ class ConfigContactsSpec: QuickSpec { expect(config_needs_push(conf)).to(beTrue()) expect(config_needs_push(conf2)).to(beTrue()) - var toPush6: UnsafeMutablePointer? = nil - var toPush6Len: Int = 0 - let seqno6: Int64 = config_push(conf, &toPush6, &toPush6Len); - expect(seqno6).to(equal(3)) + let pushData6: UnsafeMutablePointer = config_push(conf) + expect(pushData6.pointee.seqno).to(equal(3)) - var toPush7: UnsafeMutablePointer? = nil - var toPush7Len: Int = 0 - let seqno7: Int64 = config_push(conf2, &toPush7, &toPush7Len); - expect(seqno7).to(equal(3)) - - expect(String(pointer: toPush6, length: toPush6Len, encoding: .ascii)) - .toNot(equal(String(pointer: toPush7, length: toPush7Len, encoding: .ascii))) + let pushData7: UnsafeMutablePointer = config_push(conf2) + expect(pushData7.pointee.seqno).to(equal(3)) - config_confirm_pushed(conf, seqno6) - config_confirm_pushed(conf2, seqno7) + let pushData6Str: String = String(pointer: pushData6.pointee.config, length: pushData6.pointee.config_len, encoding: .ascii)! + let pushData7Str: String = String(pointer: pushData7.pointee.config, length: pushData7.pointee.config_len, encoding: .ascii)! + expect(pushData6Str).toNot(equal(pushData7Str)) + expect([String](pointer: pushData6.pointee.obsolete, count: pushData6.pointee.obsolete_len)) + .to(equal([fakeHash2])) + expect([String](pointer: pushData7.pointee.obsolete, count: pushData7.pointee.obsolete_len)) + .to(equal([fakeHash2])) - var mergeData2: [UnsafePointer?] = [UnsafePointer(toPush7)] - var mergeSize2: [Int] = [toPush7Len] - expect(config_merge(conf, &mergeData2, &mergeSize2, 1)).to(equal(1)) + let fakeHash3a: String = "fakehash3a" + var cFakeHash3a: [CChar] = fakeHash3a.cArray + let fakeHash3b: String = "fakehash3b" + var cFakeHash3b: [CChar] = fakeHash3b.cArray + config_confirm_pushed(conf, pushData6.pointee.seqno, &cFakeHash3a) + config_confirm_pushed(conf2, pushData7.pointee.seqno, &cFakeHash3b) + + var mergeHashes2: [UnsafePointer?] = [cFakeHash3b].unsafeCopy() + var mergeData2: [UnsafePointer?] = [UnsafePointer(pushData7.pointee.config)] + var mergeSize2: [Int] = [pushData7.pointee.config_len] + expect(config_merge(conf, &mergeHashes2, &mergeData2, &mergeSize2, 1)).to(equal(1)) expect(config_needs_push(conf)).to(beTrue()) - var mergeData3: [UnsafePointer?] = [UnsafePointer(toPush6)] - var mergeSize3: [Int] = [toPush6Len] - expect(config_merge(conf2, &mergeData3, &mergeSize3, 1)).to(equal(1)) + var mergeHashes3: [UnsafePointer?] = [cFakeHash3a].unsafeCopy() + var mergeData3: [UnsafePointer?] = [UnsafePointer(pushData6.pointee.config)] + var mergeSize3: [Int] = [pushData6.pointee.config_len] + expect(config_merge(conf2, &mergeHashes3, &mergeData3, &mergeSize3, 1)).to(equal(1)) expect(config_needs_push(conf2)).to(beTrue()) - toPush6?.deallocate() - toPush7?.deallocate() + mergeHashes2.forEach { $0?.deallocate() } + mergeHashes3.forEach { $0?.deallocate() } + pushData6.deallocate() + pushData7.deallocate() - var toPush8: UnsafeMutablePointer? = nil - var toPush8Len: Int = 0 - let seqno8: Int64 = config_push(conf, &toPush8, &toPush8Len); - expect(seqno8).to(equal(4)) + let pushData8: UnsafeMutablePointer = config_push(conf) + expect(pushData8.pointee.seqno).to(equal(4)) - var toPush9: UnsafeMutablePointer? = nil - var toPush9Len: Int = 0 - let seqno9: Int64 = config_push(conf2, &toPush9, &toPush9Len); - expect(seqno9).to(equal(seqno8)) + let pushData9: UnsafeMutablePointer = config_push(conf2) + expect(pushData9.pointee.seqno).to(equal(pushData8.pointee.seqno)) - expect(String(pointer: toPush8, length: toPush8Len, encoding: .ascii)) - .to(equal(String(pointer: toPush9, length: toPush9Len, encoding: .ascii))) - toPush8?.deallocate() - toPush9?.deallocate() + let pushData8Str: String = String(pointer: pushData8.pointee.config, length: pushData8.pointee.config_len, encoding: .ascii)! + let pushData9Str: String = String(pointer: pushData9.pointee.config, length: pushData9.pointee.config_len, encoding: .ascii)! + expect(pushData8Str).to(equal(pushData9Str)) + expect([String](pointer: pushData8.pointee.obsolete, count: pushData8.pointee.obsolete_len)) + .to(equal([fakeHash3b, fakeHash3a])) + expect([String](pointer: pushData9.pointee.obsolete, count: pushData9.pointee.obsolete_len)) + .to(equal([fakeHash3a, fakeHash3b])) - config_confirm_pushed(conf, seqno8) - config_confirm_pushed(conf2, seqno9) + let fakeHash4: String = "fakeHash4" + var cFakeHash4: [CChar] = fakeHash4.cArray + config_confirm_pushed(conf, pushData8.pointee.seqno, &cFakeHash4) + config_confirm_pushed(conf2, pushData9.pointee.seqno, &cFakeHash4) + pushData8.deallocate() + pushData9.deallocate() expect(config_needs_push(conf)).to(beFalse()) expect(config_needs_push(conf2)).to(beFalse()) @@ -268,11 +273,11 @@ class ConfigContactsSpec: QuickSpec { var sessionIds2: [String] = [] var nicknames2: [String] = [] expect(contacts_size(conf)).to(equal(2)) - + var contact8: contacts_contact = contacts_contact() let contactIterator2: UnsafeMutablePointer = contacts_iterator_new(conf) while !contacts_iterator_done(contactIterator2, &contact8) { - sessionIds2.append(String(libSessionVal: contact8.session_id) ?? "(N/A)") + sessionIds2.append(String(libSessionVal: contact8.session_id)) nicknames2.append(String(libSessionVal: contact8.nickname, nullIfEmpty: true) ?? "(N/A)") contacts_iterator_advance(contactIterator2) } diff --git a/SessionMessagingKitTests/LibSessionUtil/ConfigConvoInfoVolatileSpec.swift b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigConvoInfoVolatileSpec.swift similarity index 82% rename from SessionMessagingKitTests/LibSessionUtil/ConfigConvoInfoVolatileSpec.swift rename to SessionMessagingKitTests/LibSessionUtil/Configs/ConfigConvoInfoVolatileSpec.swift index 31a9c0a2c..83707b38c 100644 --- a/SessionMessagingKitTests/LibSessionUtil/ConfigConvoInfoVolatileSpec.swift +++ b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigConvoInfoVolatileSpec.swift @@ -9,11 +9,11 @@ import Quick import Nimble /// This spec is designed to replicate the initial test cases for the libSession-util to ensure the behaviour matches -class ConfigConvoInfoVolatileSpec: QuickSpec { +class ConfigConvoInfoVolatileSpec { // MARK: - Spec - override func spec() { - it("generates ConvoInfoVolatileS configs correctly") { + static func spec() { + it("generates ConvoInfoVolatile configs correctly") { let seed: Data = Data(hex: "0123456789abcdef0123456789abcdef") // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately @@ -62,26 +62,26 @@ class ConfigConvoInfoVolatileSpec: QuickSpec { .to(beFalse()) expect(convo_info_volatile_get_1to1(conf, &oneToOne3, &cDefinitelyRealId)).to(beTrue()) expect(oneToOne3.last_read).to(equal(nowTimestampMs)) - + expect(config_needs_push(conf)).to(beTrue()) expect(config_needs_dump(conf)).to(beTrue()) - + let openGroupBaseUrl: String = "http://Example.ORG:5678" var cOpenGroupBaseUrl: [CChar] = openGroupBaseUrl.cArray let openGroupBaseUrlResult: String = openGroupBaseUrl.lowercased() -// ("http://Example.ORG:5678" -// .lowercased() -// .cArray + -// [CChar](repeating: 0, count: (268 - openGroupBaseUrl.count)) -// ) + // ("http://Example.ORG:5678" + // .lowercased() + // .cArray + + // [CChar](repeating: 0, count: (268 - openGroupBaseUrl.count)) + // ) let openGroupRoom: String = "SudokuRoom" var cOpenGroupRoom: [CChar] = openGroupRoom.cArray let openGroupRoomResult: String = openGroupRoom.lowercased() -// ("SudokuRoom" -// .lowercased() -// .cArray + -// [CChar](repeating: 0, count: (65 - openGroupRoom.count)) -// ) + // ("SudokuRoom" + // .lowercased() + // .cArray + + // [CChar](repeating: 0, count: (65 - openGroupRoom.count)) + // ) var cOpenGroupPubkey: [UInt8] = Data(hex: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") .bytes var community1: convo_info_volatile_community = convo_info_volatile_community() @@ -95,19 +95,18 @@ class ConfigConvoInfoVolatileSpec: QuickSpec { // The new data doesn't get stored until we call this: convo_info_volatile_set_community(conf, &community1); - var toPush: UnsafeMutablePointer? = nil - var toPushLen: Int = 0 // We don't need to push since we haven't changed anything, so this call is mainly just for // testing: - let seqno: Int64 = config_push(conf, &toPush, &toPushLen) - expect(toPush).toNot(beNil()) - expect(seqno).to(equal(1)) - toPush?.deallocate() - + var pushData1: UnsafeMutablePointer = config_push(conf) + expect(pushData1.pointee.seqno).to(equal(1)) + // Pretend we uploaded it - config_confirm_pushed(conf, seqno) + let fakeHash1: String = "fakehash1" + var cFakeHash1: [CChar] = fakeHash1.cArray + config_confirm_pushed(conf, pushData1.pointee.seqno, &cFakeHash1) expect(config_needs_dump(conf)).to(beTrue()) expect(config_needs_push(conf)).to(beFalse()) + pushData1.deallocate() var dump1: UnsafeMutablePointer? = nil var dump1Len: Int = 0 @@ -121,7 +120,7 @@ class ConfigConvoInfoVolatileSpec: QuickSpec { expect(config_needs_dump(conf2)).to(beFalse()) expect(config_needs_push(conf2)).to(beFalse()) - + var oneToOne4: convo_info_volatile_1to1 = convo_info_volatile_1to1() expect(convo_info_volatile_get_1to1(conf2, &oneToOne4, &cDefinitelyRealId)).to(equal(true)) expect(oneToOne4.last_read).to(equal(nowTimestampMs)) @@ -135,14 +134,14 @@ class ConfigConvoInfoVolatileSpec: QuickSpec { expect(Data(libSessionVal: community2.pubkey, count: 32).toHexString()) .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) community2.unread = true - + let anotherId: String = "051111111111111111111111111111111111111111111111111111111111111111" var cAnotherId: [CChar] = anotherId.cArray var oneToOne5: convo_info_volatile_1to1 = convo_info_volatile_1to1() expect(convo_info_volatile_get_or_construct_1to1(conf2, &oneToOne5, &cAnotherId)).to(beTrue()) oneToOne5.unread = true convo_info_volatile_set_1to1(conf2, &oneToOne5) - + let thirdId: String = "05cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" var cThirdId: [CChar] = thirdId.cArray var legacyGroup2: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() @@ -150,21 +149,22 @@ class ConfigConvoInfoVolatileSpec: QuickSpec { legacyGroup2.last_read = (nowTimestampMs - 50) convo_info_volatile_set_legacy_group(conf2, &legacyGroup2) expect(config_needs_push(conf2)).to(beTrue()) - - var toPush2: UnsafeMutablePointer? = nil - var toPush2Len: Int = 0 - let seqno2: Int64 = config_push(conf2, &toPush2, &toPush2Len) - expect(seqno2).to(equal(2)) + + var pushData2: UnsafeMutablePointer = config_push(conf2) + expect(pushData2.pointee.seqno).to(equal(2)) // Check the merging - var mergeData: [UnsafePointer?] = [UnsafePointer(toPush2)] - var mergeSize: [Int] = [toPush2Len] - expect(config_merge(conf, &mergeData, &mergeSize, 1)).to(equal(1)) - config_confirm_pushed(conf, seqno) - toPush2?.deallocate() + let fakeHash2: String = "fakehash2" + var cFakeHash2: [CChar] = fakeHash2.cArray + var mergeHashes: [UnsafePointer?] = [cFakeHash2].unsafeCopy() + var mergeData: [UnsafePointer?] = [UnsafePointer(pushData2.pointee.config)] + var mergeSize: [Int] = [pushData2.pointee.config_len] + expect(config_merge(conf, &mergeHashes, &mergeData, &mergeSize, 1)).to(equal(1)) + config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash2) + pushData2.deallocate() expect(config_needs_push(conf)).to(beFalse()) - + for targetConf in [conf, conf2] { // Iterate through and make sure we got everything we expected var seen: [String] = [] @@ -180,30 +180,13 @@ class ConfigConvoInfoVolatileSpec: QuickSpec { while !convo_info_volatile_iterator_done(it) { if convo_info_volatile_it_is_1to1(it, &c1) { - let sessionId: String = String(cString: withUnsafeBytes(of: c1.session_id) { [UInt8]($0) } - .map { CChar($0) } - .nullTerminated() - ) - seen.append("1-to-1: \(sessionId)") + seen.append("1-to-1: \(String(libSessionVal: c1.session_id))") } else if convo_info_volatile_it_is_community(it, &c2) { - let baseUrl: String = String(cString: withUnsafeBytes(of: c2.base_url) { [UInt8]($0) } - .map { CChar($0) } - .nullTerminated() - ) - let room: String = String(cString: withUnsafeBytes(of: c2.room) { [UInt8]($0) } - .map { CChar($0) } - .nullTerminated() - ) - - seen.append("og: \(baseUrl)/r/\(room)") + seen.append("og: \(String(libSessionVal: c2.base_url))/r/\(String(libSessionVal: c2.room))") } else if convo_info_volatile_it_is_legacy_group(it, &c3) { - let groupId: String = String(cString: withUnsafeBytes(of: c3.group_id) { [UInt8]($0) } - .map { CChar($0) } - .nullTerminated() - ) - seen.append("cl: \(groupId)") + seen.append("cl: \(String(libSessionVal: c3.group_id))") } convo_info_volatile_iterator_advance(it) @@ -219,17 +202,16 @@ class ConfigConvoInfoVolatileSpec: QuickSpec { ])) } - var fourthId: [CChar] = "052000000000000000000000000000000000000000000000000000000000000000" - .bytes - .map { CChar(bitPattern: $0) } + let fourthId: String = "052000000000000000000000000000000000000000000000000000000000000000" + var cFourthId: [CChar] = fourthId.cArray expect(config_needs_push(conf)).to(beFalse()) - convo_info_volatile_erase_1to1(conf, &fourthId) + convo_info_volatile_erase_1to1(conf, &cFourthId) expect(config_needs_push(conf)).to(beFalse()) convo_info_volatile_erase_1to1(conf, &cDefinitelyRealId) expect(config_needs_push(conf)).to(beTrue()) expect(convo_info_volatile_size(conf)).to(equal(3)) expect(convo_info_volatile_size_1to1(conf)).to(equal(1)) - + // Check the single-type iterators: var seen1: [String?] = [] var c1: convo_info_volatile_1to1 = convo_info_volatile_1to1() diff --git a/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserGroupsSpec.swift b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserGroupsSpec.swift new file mode 100644 index 000000000..7e98a285c --- /dev/null +++ b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserGroupsSpec.swift @@ -0,0 +1,576 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import Sodium +import SessionUtil +import SessionUtilitiesKit +import SessionMessagingKit + +import Quick +import Nimble + +/// This spec is designed to replicate the initial test cases for the libSession-util to ensure the behaviour matches +class ConfigUserGroupsSpec { + // MARK: - Spec + + static func spec() { + it("parses community URLs correctly") { + let result1 = SessionUtil.parseCommunity(url: [ + "https://example.com/", + "SomeRoom?public_key=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + ].joined()) + let result2 = SessionUtil.parseCommunity(url: [ + "HTTPS://EXAMPLE.COM/", + "sOMErOOM?public_key=0123456789aBcdEF0123456789abCDEF0123456789ABCdef0123456789ABCDEF" + ].joined()) + let result3 = SessionUtil.parseCommunity(url: [ + "HTTPS://EXAMPLE.COM/r/", + "someroom?public_key=0123456789aBcdEF0123456789abCDEF0123456789ABCdef0123456789ABCDEF" + ].joined()) + let result4 = SessionUtil.parseCommunity(url: [ + "http://example.com/r/", + "someroom?public_key=0123456789aBcdEF0123456789abCDEF0123456789ABCdef0123456789ABCDEF" + ].joined()) + let result5 = SessionUtil.parseCommunity(url: [ + "HTTPS://EXAMPLE.com:443/r/", + "someroom?public_key=0123456789aBcdEF0123456789abCDEF0123456789ABCdef0123456789ABCDEF" + ].joined()) + let result6 = SessionUtil.parseCommunity(url: [ + "HTTP://EXAMPLE.com:80/r/", + "someroom?public_key=0123456789aBcdEF0123456789abCDEF0123456789ABCdef0123456789ABCDEF" + ].joined()) + let result7 = SessionUtil.parseCommunity(url: [ + "http://example.com:80/r/", + "someroom?public_key=ASNFZ4mrze8BI0VniavN7wEjRWeJq83vASNFZ4mrze8" + ].joined()) + let result8 = SessionUtil.parseCommunity(url: [ + "http://example.com:80/r/", + "someroom?public_key=yrtwk3hjixg66yjdeiuauk6p7hy1gtm8tgih55abrpnsxnpm3zzo" + ].joined()) + + expect(result1?.server).to(equal("https://example.com")) + expect(result1?.server).to(equal(result2?.server)) + expect(result1?.server).to(equal(result3?.server)) + expect(result1?.server).toNot(equal(result4?.server)) + expect(result4?.server).to(equal("http://example.com")) + expect(result1?.server).to(equal(result5?.server)) + expect(result4?.server).to(equal(result6?.server)) + expect(result4?.server).to(equal(result7?.server)) + expect(result4?.server).to(equal(result8?.server)) + expect(result1?.room).to(equal("SomeRoom")) + expect(result2?.room).to(equal("sOMErOOM")) + expect(result3?.room).to(equal("someroom")) + expect(result4?.room).to(equal("someroom")) + expect(result5?.room).to(equal("someroom")) + expect(result6?.room).to(equal("someroom")) + expect(result7?.room).to(equal("someroom")) + expect(result8?.room).to(equal("someroom")) + expect(result1?.publicKey) + .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) + expect(result2?.publicKey) + .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) + expect(result3?.publicKey) + .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) + expect(result4?.publicKey) + .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) + expect(result5?.publicKey) + .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) + expect(result6?.publicKey) + .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) + expect(result7?.publicKey) + .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) + expect(result8?.publicKey) + .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) + } + + it("generates UserGroup configs correctly") { + let seed: Data = Data(hex: "0123456789abcdef0123456789abcdef") + + // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately + let identity = try! Identity.generate(from: seed) + var edSK: [UInt8] = identity.ed25519KeyPair.secretKey + expect(edSK.toHexString().suffix(64)) + .to(equal("4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7")) + expect(identity.x25519KeyPair.publicKey.toHexString()) + .to(equal("d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72")) + expect(String(edSK.toHexString().prefix(32))).to(equal(seed.toHexString())) + + // Initialize a brand new, empty config because we have no dump data to deal with. + let error: UnsafeMutablePointer? = nil + var conf: UnsafeMutablePointer? = nil + expect(user_groups_init(&conf, &edSK, nil, 0, error)).to(equal(0)) + error?.deallocate() + + // Empty contacts shouldn't have an existing contact + let definitelyRealId: String = "055000000000000000000000000000000000000000000000000000000000000000" + var cDefinitelyRealId: [CChar] = definitelyRealId.cArray + let legacyGroup1: UnsafeMutablePointer? = user_groups_get_legacy_group(conf, &cDefinitelyRealId) + expect(legacyGroup1?.pointee).to(beNil()) + expect(user_groups_size(conf)).to(equal(0)) + + let legacyGroup2: UnsafeMutablePointer = user_groups_get_or_construct_legacy_group(conf, &cDefinitelyRealId) + expect(legacyGroup2.pointee).toNot(beNil()) + expect(String(libSessionVal: legacyGroup2.pointee.session_id)) + .to(equal(definitelyRealId)) + expect(legacyGroup2.pointee.hidden).to(beFalse()) + expect(legacyGroup2.pointee.disappearing_timer).to(equal(0)) + expect(String(libSessionVal: legacyGroup2.pointee.enc_pubkey, fixedLength: 32)).to(equal("")) + expect(String(libSessionVal: legacyGroup2.pointee.enc_seckey, fixedLength: 32)).to(equal("")) + expect(legacyGroup2.pointee.priority).to(equal(0)) + expect(String(libSessionVal: legacyGroup2.pointee.name)).to(equal("")) + + // Iterate through and make sure we got everything we expected + var membersSeen1: [String: Bool] = [:] + var memberSessionId1: UnsafePointer? = nil + var memberAdmin1: Bool = false + let membersIt1: OpaquePointer = ugroups_legacy_members_begin(legacyGroup2) + + while ugroups_legacy_members_next(membersIt1, &memberSessionId1, &memberAdmin1) { + membersSeen1[String(cString: memberSessionId1!)] = memberAdmin1 + } + + ugroups_legacy_members_free(membersIt1) + + expect(membersSeen1).to(beEmpty()) + + // No need to sync a conversation with a default state + expect(config_needs_push(conf)).to(beFalse()) + expect(config_needs_dump(conf)).to(beFalse()) + + // We don't need to push since we haven't changed anything, so this call is mainly just for + // testing: + let pushData1: UnsafeMutablePointer = config_push(conf) + expect(pushData1.pointee.seqno).to(equal(0)) + expect([String](pointer: pushData1.pointee.obsolete, count: pushData1.pointee.obsolete_len)) + .to(beEmpty()) + expect(pushData1.pointee.config_len).to(equal(256)) + pushData1.deallocate() + + let users: [String] = [ + "050000000000000000000000000000000000000000000000000000000000000000", + "051111111111111111111111111111111111111111111111111111111111111111", + "052222222222222222222222222222222222222222222222222222222222222222", + "053333333333333333333333333333333333333333333333333333333333333333", + "054444444444444444444444444444444444444444444444444444444444444444", + "055555555555555555555555555555555555555555555555555555555555555555", + "056666666666666666666666666666666666666666666666666666666666666666" + ] + var cUsers: [[CChar]] = users.map { $0.cArray } + legacyGroup2.pointee.name = "Englishmen".toLibSession() + legacyGroup2.pointee.disappearing_timer = 60 + expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[0], false)).to(beTrue()) + expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[1], true)).to(beTrue()) + expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[2], false)).to(beTrue()) + expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[4], true)).to(beTrue()) + expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[5], false)).to(beTrue()) + expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[2], false)).to(beFalse()) + + // Flip to and from admin + expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[2], true)).to(beTrue()) + expect(ugroups_legacy_member_add(legacyGroup2, &cUsers[1], false)).to(beTrue()) + + expect(ugroups_legacy_member_remove(legacyGroup2, &cUsers[5])).to(beTrue()) + expect(ugroups_legacy_member_remove(legacyGroup2, &cUsers[4])).to(beTrue()) + + var membersSeen2: [String: Bool] = [:] + var memberSessionId2: UnsafePointer? = nil + var memberAdmin2: Bool = false + let membersIt2: OpaquePointer = ugroups_legacy_members_begin(legacyGroup2) + + while ugroups_legacy_members_next(membersIt2, &memberSessionId2, &memberAdmin2) { + membersSeen2[String(cString: memberSessionId2!)] = memberAdmin2 + } + + ugroups_legacy_members_free(membersIt2) + + expect(membersSeen2).to(equal([ + "050000000000000000000000000000000000000000000000000000000000000000": false, + "051111111111111111111111111111111111111111111111111111111111111111": false, + "052222222222222222222222222222222222222222222222222222222222222222": true + ])) + + // FIXME: Would be good to move these into the libSession-util instead of using Sodium separately + let groupSeed: Data = Data(hex: "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff") + let groupEd25519KeyPair = Sodium().sign.keyPair(seed: groupSeed.bytes)! + let groupX25519PublicKey = Sodium().sign.toX25519(ed25519PublicKey: groupEd25519KeyPair.publicKey)! + + // Note: this isn't exactly what Session actually does here for legacy closed + // groups (rather it uses X25519 keys) but for this test the distinction doesn't matter. + legacyGroup2.pointee.enc_pubkey = Data(groupX25519PublicKey).toLibSession() + legacyGroup2.pointee.enc_seckey = Data(groupEd25519KeyPair.secretKey).toLibSession() + legacyGroup2.pointee.priority = 3 + + expect(Data(libSessionVal: legacyGroup2.pointee.enc_pubkey, count: 32).toHexString()) + .to(equal("c5ba413c336f2fe1fb9a2c525f8a86a412a1db128a7841b4e0e217fa9eb7fd5e")) + expect(Data(libSessionVal: legacyGroup2.pointee.enc_seckey, count: 32).toHexString()) + .to(equal("00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff")) + + // The new data doesn't get stored until we call this: + user_groups_set_legacy_group(conf, legacyGroup2) + + let legacyGroup3: UnsafeMutablePointer? = user_groups_get_legacy_group(conf, &cDefinitelyRealId) + expect(legacyGroup3?.pointee).toNot(beNil()) + expect(config_needs_push(conf)).to(beTrue()) + expect(config_needs_dump(conf)).to(beTrue()) + + let communityPubkey: String = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + var cCommunityPubkey: [UInt8] = Data(hex: communityPubkey).cArray + var cCommunityBaseUrl: [CChar] = "http://Example.ORG:5678".cArray + var cCommunityRoom: [CChar] = "SudokuRoom".cArray + var community1: ugroups_community_info = ugroups_community_info() + expect(user_groups_get_or_construct_community(conf, &community1, &cCommunityBaseUrl, &cCommunityRoom, &cCommunityPubkey)) + .to(beTrue()) + + expect(String(libSessionVal: community1.base_url)).to(equal("http://example.org:5678")) // Note: lower-case + expect(String(libSessionVal: community1.room)).to(equal("SudokuRoom")) // Note: case-preserving + expect(Data(libSessionVal: community1.pubkey, count: 32).toHexString()) + .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) + community1.priority = 14 + + // The new data doesn't get stored until we call this: + user_groups_set_community(conf, &community1) + + // incremented since we made changes (this only increments once between + // dumps; even though we changed two fields here). + let pushData2: UnsafeMutablePointer = config_push(conf) + expect(pushData2.pointee.seqno).to(equal(1)) + expect([String](pointer: pushData2.pointee.obsolete, count: pushData2.pointee.obsolete_len)) + .to(beEmpty()) + + // Pretend we uploaded it + let fakeHash1: String = "fakehash1" + var cFakeHash1: [CChar] = fakeHash1.cArray + config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash1) + expect(config_needs_dump(conf)).to(beTrue()) + expect(config_needs_push(conf)).to(beFalse()) + + var dump1: UnsafeMutablePointer? = nil + var dump1Len: Int = 0 + config_dump(conf, &dump1, &dump1Len) + + let error2: UnsafeMutablePointer? = nil + var conf2: UnsafeMutablePointer? = nil + expect(user_groups_init(&conf2, &edSK, dump1, dump1Len, error2)).to(equal(0)) + error2?.deallocate() + dump1?.deallocate() + + expect(config_needs_dump(conf)).to(beFalse()) // Because we just called dump() above, to load up conf2 + expect(config_needs_push(conf)).to(beFalse()) + + let pushData3: UnsafeMutablePointer = config_push(conf) + expect(pushData3.pointee.seqno).to(equal(1)) + expect([String](pointer: pushData3.pointee.obsolete, count: pushData3.pointee.obsolete_len)) + .to(beEmpty()) + pushData3.deallocate() + + let currentHashes1: UnsafeMutablePointer? = config_current_hashes(conf) + expect([String](pointer: currentHashes1?.pointee.value, count: currentHashes1?.pointee.len)) + .to(equal(["fakehash1"])) + currentHashes1?.deallocate() + + expect(config_needs_push(conf2)).to(beFalse()) + expect(config_needs_dump(conf2)).to(beFalse()) + + let pushData4: UnsafeMutablePointer = config_push(conf2) + expect(pushData4.pointee.seqno).to(equal(1)) + expect(config_needs_dump(conf2)).to(beFalse()) + expect([String](pointer: pushData4.pointee.obsolete, count: pushData4.pointee.obsolete_len)) + .to(beEmpty()) + pushData4.deallocate() + + let currentHashes2: UnsafeMutablePointer? = config_current_hashes(conf2) + expect([String](pointer: currentHashes2?.pointee.value, count: currentHashes2?.pointee.len)) + .to(equal(["fakehash1"])) + currentHashes2?.deallocate() + + expect(user_groups_size(conf2)).to(equal(2)) + expect(user_groups_size_communities(conf2)).to(equal(1)) + expect(user_groups_size_legacy_groups(conf2)).to(equal(1)) + + let legacyGroup4: UnsafeMutablePointer? = user_groups_get_legacy_group(conf2, &cDefinitelyRealId) + expect(legacyGroup4?.pointee).toNot(beNil()) + expect(String(libSessionVal: legacyGroup4?.pointee.enc_pubkey, fixedLength: 32)).to(equal("")) + expect(String(libSessionVal: legacyGroup4?.pointee.enc_seckey, fixedLength: 32)).to(equal("")) + expect(legacyGroup4?.pointee.disappearing_timer).to(equal(60)) + expect(String(libSessionVal: legacyGroup4?.pointee.session_id)).to(equal(definitelyRealId)) + expect(legacyGroup4?.pointee.hidden).to(beFalse()) + expect(legacyGroup4?.pointee.priority).to(equal(3)) + expect(String(libSessionVal: legacyGroup4?.pointee.name)).to(equal("Englishmen")) + + var membersSeen3: [String: Bool] = [:] + var memberSessionId3: UnsafePointer? = nil + var memberAdmin3: Bool = false + let membersIt3: OpaquePointer = ugroups_legacy_members_begin(legacyGroup4) + + while ugroups_legacy_members_next(membersIt3, &memberSessionId3, &memberAdmin3) { + membersSeen3[String(cString: memberSessionId3!)] = memberAdmin3 + } + + ugroups_legacy_members_free(membersIt3) + + expect(membersSeen3).to(equal([ + "050000000000000000000000000000000000000000000000000000000000000000": false, + "051111111111111111111111111111111111111111111111111111111111111111": false, + "052222222222222222222222222222222222222222222222222222222222222222": true + ])) + + expect(config_needs_push(conf2)).to(beFalse()) + expect(config_needs_dump(conf2)).to(beFalse()) + + let pushData5: UnsafeMutablePointer = config_push(conf2) + expect(pushData5.pointee.seqno).to(equal(1)) + expect(config_needs_dump(conf2)).to(beFalse()) + pushData5.deallocate() + + for targetConf in [conf, conf2] { + // Iterate through and make sure we got everything we expected + var seen: [String] = [] + + var c1: ugroups_legacy_group_info = ugroups_legacy_group_info() + var c2: ugroups_community_info = ugroups_community_info() + let it: OpaquePointer = user_groups_iterator_new(targetConf) + + while !user_groups_iterator_done(it) { + if user_groups_it_is_legacy_group(it, &c1) { + var memberCount: Int = 0 + var adminCount: Int = 0 + ugroups_legacy_members_count(&c1, &memberCount, &adminCount) + seen.append("legacy: \(String(libSessionVal: c1.name)), \(adminCount) admins, \(memberCount) members") + } + else if user_groups_it_is_community(it, &c2) { + seen.append("community: \(String(libSessionVal: c2.base_url))/r/\(String(libSessionVal: c2.room))") + } + else { + seen.append("unknown") + } + + user_groups_iterator_advance(it) + } + + user_groups_iterator_free(it) + + expect(seen).to(equal([ + "community: http://example.org:5678/r/SudokuRoom", + "legacy: Englishmen, 1 admins, 2 members" + ])) + } + + var cCommunity2BaseUrl: [CChar] = "http://example.org:5678".cArray + var cCommunity2Room: [CChar] = "sudokuRoom".cArray + var community2: ugroups_community_info = ugroups_community_info() + expect(user_groups_get_community(conf2, &community2, &cCommunity2BaseUrl, &cCommunity2Room)) + .to(beTrue()) + expect(String(libSessionVal: community2.base_url)).to(equal("http://example.org:5678")) + expect(String(libSessionVal: community2.room)).to(equal("SudokuRoom")) // Case preserved from the stored value, not the input value + expect(Data(libSessionVal: community2.pubkey, count: 32).toHexString()) + .to(equal("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")) + expect(community2.priority).to(equal(14)) + + expect(config_needs_push(conf2)).to(beFalse()) + expect(config_needs_dump(conf2)).to(beFalse()) + + let pushData6: UnsafeMutablePointer = config_push(conf2) + expect(pushData6.pointee.seqno).to(equal(1)) + expect(config_needs_dump(conf2)).to(beFalse()) + pushData6.deallocate() + + community2.room = "sudokuRoom".toLibSession() // Change capitalization + user_groups_set_community(conf2, &community2) + + expect(config_needs_push(conf2)).to(beTrue()) + expect(config_needs_dump(conf2)).to(beTrue()) + + let fakeHash2: String = "fakehash2" + var cFakeHash2: [CChar] = fakeHash2.cArray + let pushData7: UnsafeMutablePointer = config_push(conf2) + expect(pushData7.pointee.seqno).to(equal(2)) + config_confirm_pushed(conf2, pushData7.pointee.seqno, &cFakeHash2) + expect([String](pointer: pushData7.pointee.obsolete, count: pushData7.pointee.obsolete_len)) + .to(equal([fakeHash1])) + + let currentHashes3: UnsafeMutablePointer? = config_current_hashes(conf2) + expect([String](pointer: currentHashes3?.pointee.value, count: currentHashes3?.pointee.len)) + .to(equal([fakeHash2])) + currentHashes3?.deallocate() + + var dump2: UnsafeMutablePointer? = nil + var dump2Len: Int = 0 + config_dump(conf2, &dump2, &dump2Len) + + expect(config_needs_push(conf2)).to(beFalse()) + expect(config_needs_dump(conf2)).to(beFalse()) + + let pushData8: UnsafeMutablePointer = config_push(conf2) + expect(pushData8.pointee.seqno).to(equal(2)) + config_confirm_pushed(conf2, pushData8.pointee.seqno, &cFakeHash2) + expect(config_needs_dump(conf2)).to(beFalse()) + + var mergeHashes1: [UnsafePointer?] = [cFakeHash2].unsafeCopy() + var mergeData1: [UnsafePointer?] = [UnsafePointer(pushData8.pointee.config)] + var mergeSize1: [Int] = [pushData8.pointee.config_len] + expect(config_merge(conf, &mergeHashes1, &mergeData1, &mergeSize1, 1)).to(equal(1)) + pushData8.deallocate() + + var cCommunity3BaseUrl: [CChar] = "http://example.org:5678".cArray + var cCommunity3Room: [CChar] = "SudokuRoom".cArray + var community3: ugroups_community_info = ugroups_community_info() + expect(user_groups_get_community(conf, &community3, &cCommunity3BaseUrl, &cCommunity3Room)) + .to(beTrue()) + expect(String(libSessionVal: community3.room)).to(equal("sudokuRoom")) // We picked up the capitalization change + + expect(user_groups_size(conf)).to(equal(2)) + expect(user_groups_size_communities(conf)).to(equal(1)) + expect(user_groups_size_legacy_groups(conf)).to(equal(1)) + + let legacyGroup5: UnsafeMutablePointer? = user_groups_get_legacy_group(conf2, &cDefinitelyRealId) + expect(ugroups_legacy_member_add(legacyGroup5, &cUsers[4], false)).to(beTrue()) + expect(ugroups_legacy_member_add(legacyGroup5, &cUsers[5], true)).to(beTrue()) + expect(ugroups_legacy_member_add(legacyGroup5, &cUsers[6], true)).to(beTrue()) + expect(ugroups_legacy_member_remove(legacyGroup5, &cUsers[1])).to(beTrue()) + + expect(config_needs_push(conf2)).to(beFalse()) + expect(config_needs_dump(conf2)).to(beFalse()) + + let pushData9: UnsafeMutablePointer = config_push(conf2) + expect(pushData9.pointee.seqno).to(equal(2)) + expect(config_needs_dump(conf2)).to(beFalse()) + pushData9.deallocate() + + user_groups_set_legacy_group(conf2, legacyGroup5) + expect(config_needs_push(conf2)).to(beTrue()) + expect(config_needs_dump(conf2)).to(beTrue()) + + var cCommunity4BaseUrl: [CChar] = "http://exAMple.ORG:5678".cArray + var cCommunity4Room: [CChar] = "sudokuROOM".cArray + user_groups_erase_community(conf2, &cCommunity4BaseUrl, &cCommunity4Room) + + let fakeHash3: String = "fakehash3" + var cFakeHash3: [CChar] = fakeHash3.cArray + let pushData10: UnsafeMutablePointer = config_push(conf2) + config_confirm_pushed(conf2, pushData10.pointee.seqno, &cFakeHash3) + + expect(pushData10.pointee.seqno).to(equal(3)) + expect([String](pointer: pushData10.pointee.obsolete, count: pushData10.pointee.obsolete_len)) + .to(equal([fakeHash2])) + + let currentHashes4: UnsafeMutablePointer? = config_current_hashes(conf2) + expect([String](pointer: currentHashes4?.pointee.value, count: currentHashes4?.pointee.len)) + .to(equal([fakeHash3])) + currentHashes4?.deallocate() + + var mergeHashes2: [UnsafePointer?] = [cFakeHash3].unsafeCopy() + var mergeData2: [UnsafePointer?] = [UnsafePointer(pushData10.pointee.config)] + var mergeSize2: [Int] = [pushData10.pointee.config_len] + expect(config_merge(conf, &mergeHashes2, &mergeData2, &mergeSize2, 1)).to(equal(1)) + + expect(user_groups_size(conf)).to(equal(1)) + expect(user_groups_size_communities(conf)).to(equal(0)) + expect(user_groups_size_legacy_groups(conf)).to(equal(1)) + + var prio: Int32 = 0 + var cBeanstalkBaseUrl: [CChar] = "http://jacksbeanstalk.org".cArray + var cBeanstalkPubkey: [UInt8] = Data( + hex: "0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff" + ).cArray + + ["fee", "fi", "fo", "fum"].forEach { room in + var cRoom: [CChar] = room.cArray + prio += 1 + + var community4: ugroups_community_info = ugroups_community_info() + expect(user_groups_get_or_construct_community(conf, &community4, &cBeanstalkBaseUrl, &cRoom, &cBeanstalkPubkey)) + .to(beTrue()) + community4.priority = prio + user_groups_set_community(conf, &community4) + } + + expect(user_groups_size(conf)).to(equal(5)) + expect(user_groups_size_communities(conf)).to(equal(4)) + expect(user_groups_size_legacy_groups(conf)).to(equal(1)) + + let fakeHash4: String = "fakehash4" + var cFakeHash4: [CChar] = fakeHash4.cArray + let pushData11: UnsafeMutablePointer = config_push(conf) + config_confirm_pushed(conf, pushData11.pointee.seqno, &cFakeHash4) + expect(pushData11.pointee.seqno).to(equal(4)) + expect([String](pointer: pushData11.pointee.obsolete, count: pushData11.pointee.obsolete_len)) + .to(equal([fakeHash3, fakeHash2, fakeHash1])) + + // Load some obsolete ones in just to check that they get immediately obsoleted + let fakeHash10: String = "fakehash10" + let cFakeHash10: [CChar] = fakeHash10.cArray + let fakeHash11: String = "fakehash11" + let cFakeHash11: [CChar] = fakeHash11.cArray + let fakeHash12: String = "fakehash12" + let cFakeHash12: [CChar] = fakeHash12.cArray + var mergeHashes3: [UnsafePointer?] = [cFakeHash10, cFakeHash11, cFakeHash12, cFakeHash4].unsafeCopy() + var mergeData3: [UnsafePointer?] = [ + UnsafePointer(pushData10.pointee.config), + UnsafePointer(pushData2.pointee.config), + UnsafePointer(pushData7.pointee.config), + UnsafePointer(pushData11.pointee.config) + ] + var mergeSize3: [Int] = [ + pushData10.pointee.config_len, + pushData2.pointee.config_len, + pushData7.pointee.config_len, + pushData11.pointee.config_len + ] + expect(config_merge(conf2, &mergeHashes3, &mergeData3, &mergeSize3, 4)).to(equal(4)) + expect(config_needs_dump(conf2)).to(beTrue()) + expect(config_needs_push(conf2)).to(beFalse()) + pushData2.deallocate() + pushData7.deallocate() + pushData10.deallocate() + pushData11.deallocate() + + let currentHashes5: UnsafeMutablePointer? = config_current_hashes(conf2) + expect([String](pointer: currentHashes5?.pointee.value, count: currentHashes5?.pointee.len)) + .to(equal([fakeHash4])) + currentHashes5?.deallocate() + + let pushData12: UnsafeMutablePointer = config_push(conf2) + expect(pushData12.pointee.seqno).to(equal(4)) + expect([String](pointer: pushData12.pointee.obsolete, count: pushData12.pointee.obsolete_len)) + .to(equal([fakeHash11, fakeHash12, fakeHash10, fakeHash3])) + pushData12.deallocate() + + for targetConf in [conf, conf2] { + // Iterate through and make sure we got everything we expected + var seen: [String] = [] + + var c1: ugroups_legacy_group_info = ugroups_legacy_group_info() + var c2: ugroups_community_info = ugroups_community_info() + let it: OpaquePointer = user_groups_iterator_new(targetConf) + + while !user_groups_iterator_done(it) { + if user_groups_it_is_legacy_group(it, &c1) { + var memberCount: Int = 0 + var adminCount: Int = 0 + ugroups_legacy_members_count(&c1, &memberCount, &adminCount) + + seen.append("legacy: \(String(libSessionVal: c1.name)), \(adminCount) admins, \(memberCount) members") + } + else if user_groups_it_is_community(it, &c2) { + seen.append("community: \(String(libSessionVal: c2.base_url))/r/\(String(libSessionVal: c2.room))") + } + else { + seen.append("unknown") + } + + user_groups_iterator_advance(it) + } + + user_groups_iterator_free(it) + + expect(seen).to(equal([ + "community: http://jacksbeanstalk.org/r/fee", + "community: http://jacksbeanstalk.org/r/fi", + "community: http://jacksbeanstalk.org/r/fo", + "community: http://jacksbeanstalk.org/r/fum", + "legacy: Englishmen, 3 admins, 2 members" + ])) + } + } + } +} diff --git a/SessionMessagingKitTests/LibSessionUtil/ConfigUserProfileSpec.swift b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserProfileSpec.swift similarity index 67% rename from SessionMessagingKitTests/LibSessionUtil/ConfigUserProfileSpec.swift rename to SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserProfileSpec.swift index b36b5cbba..87e2f10d4 100644 --- a/SessionMessagingKitTests/LibSessionUtil/ConfigUserProfileSpec.swift +++ b/SessionMessagingKitTests/LibSessionUtil/Configs/ConfigUserProfileSpec.swift @@ -10,10 +10,10 @@ import Quick import Nimble /// This spec is designed to replicate the initial test cases for the libSession-util to ensure the behaviour matches -class ConfigUserProfileSpec: QuickSpec { +class ConfigUserProfileSpec { // MARK: - Spec - override func spec() { + static func spec() { it("generates UserProfile configs correctly") { let seed: Data = Data(hex: "0123456789abcdef0123456789abcdef") @@ -41,14 +41,12 @@ class ConfigUserProfileSpec: QuickSpec { let namePtr: UnsafePointer? = user_profile_get_name(conf) expect(namePtr).to(beNil()) - var toPush: UnsafeMutablePointer? = nil - var toPushLen: Int = 0 // We don't need to push since we haven't changed anything, so this call is mainly just for // testing: - let seqno: Int64 = config_push(conf, &toPush, &toPushLen) - expect(toPush).toNot(beNil()) - expect(seqno).to(equal(0)) - expect(toPushLen).to(equal(256)) + let pushData1: UnsafeMutablePointer = config_push(conf) + expect(pushData1.pointee).toNot(beNil()) + expect(pushData1.pointee.seqno).to(equal(0)) + expect(pushData1.pointee.config_len).to(equal(256)) let encDomain: [CChar] = "UserProfile" .bytes @@ -56,7 +54,7 @@ class ConfigUserProfileSpec: QuickSpec { expect(String(cString: config_encryption_domain(conf))).to(equal("UserProfile")) var toPushDecSize: Int = 0 - let toPushDecrypted: UnsafeMutablePointer? = config_decrypt(toPush, toPushLen, edSK, encDomain, &toPushDecSize) + let toPushDecrypted: UnsafeMutablePointer? = config_decrypt(pushData1.pointee.config, pushData1.pointee.config_len, edSK, encDomain, &toPushDecSize) let prefixPadding: String = (0..<193) .map { _ in "\0" } .joined() @@ -64,7 +62,7 @@ class ConfigUserProfileSpec: QuickSpec { expect(toPushDecSize).to(equal(216)) // 256 - 40 overhead expect(String(pointer: toPushDecrypted, length: toPushDecSize)) .to(equal("\(prefixPadding)d1:#i0e1:&de1:? = user_profile_get_name(conf) @@ -88,18 +87,17 @@ class ConfigUserProfileSpec: QuickSpec { expect(String(libSessionVal: pic2.url)).to(equal("http://example.org/omg-pic-123.bmp")) expect(Data(libSessionVal: pic2.key, count: ProfileManager.avatarAES256KeyByteLength)) .to(equal("secret78901234567890123456789012".data(using: .utf8))) + expect(user_profile_get_nts_priority(conf)).to(equal(9)) // Since we've made changes, we should need to push new config to the swarm, *and* should need // to dump the updated state: expect(config_needs_push(conf)).to(beTrue()) expect(config_needs_dump(conf)).to(beTrue()) - var toPush2: UnsafeMutablePointer? = nil - var toPush2Len: Int = 0 - let seqno2: Int64 = config_push(conf, &toPush2, &toPush2Len); // incremented since we made changes (this only increments once between // dumps; even though we changed two fields here). - expect(seqno2).to(equal(1)) + let pushData2: UnsafeMutablePointer = config_push(conf) + expect(pushData2.pointee.seqno).to(equal(1)) // Note: This hex value differs from the value in the library tests because // it looks like the library has an "end of cell mark" character added at the @@ -111,6 +109,7 @@ class ConfigUserProfileSpec: QuickSpec { d 1:#i1e 1:& d + 1:+ i9e 1:n 6:Kallie 1:p 34:http://example.org/omg-pic-123.bmp 1:q 32:secret78901234567890123456789012 @@ -124,6 +123,7 @@ class ConfigUserProfileSpec: QuickSpec { de e e 1:= d + 1:+ 0: 1:n 0: 1:p 0: 1:q 0: @@ -131,38 +131,40 @@ class ConfigUserProfileSpec: QuickSpec { e """.removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) // For readability .bytes - ] - .flatMap { $0 } + ].flatMap { $0 } let expPush1Encrypted: [UInt8] = Data(hex: [ - "877c8e0f5d33f5fffa5a4e162785a9a89918e95de1c4b925201f1f5c29d9ee4f8c36e2b278fce1e6", - "b9d999689dd86ff8e79e0a04004fa54d24da89bc2604cb1df8c1356da8f14710543ecec44f2d57fc", - "56ea8b7e73d119c69d755f4d513d5d069f02396b8ec0cbed894169836f57ca4b782ce705895c593b", - "4230d50c175d44a08045388d3f4160bacb617b9ae8de3ebc8d9024245cd09ce102627cab2acf1b91", - "26159211359606611ca5814de320d1a7099a65c99b0eebbefb92a115f5efa6b9132809300ac010c6", - "857cfbd62af71b0fa97eccec75cb95e67edf40b35fdb9cad125a6976693ab085c6bba96a2e51826e", - "81e16b9ec1232af5680f2ced55310486" + "9693a69686da3055f1ecdfb239c3bf8e746951a36d888c2fb7c02e856a5c2091b24e39a7e1af828f", + "1fa09fe8bf7d274afde0a0847ba143c43ffb8722301b5ae32e2f078b9a5e19097403336e50b18c84", + "aade446cd2823b011f97d6ad2116a53feb814efecc086bc172d31f4214b4d7c630b63bbe575b0868", + "2d146da44915063a07a78556ab5eff4f67f6aa26211e8d330b53d28567a931028c393709a325425d", + "e7486ccde24416a7fd4a8ba5fa73899c65f4276dfaddd5b2100adcf0f793104fb235b31ce32ec656", + "056009a9ebf58d45d7d696b74e0c7ff0499c4d23204976f19561dc0dba6dc53a2497d28ce03498ea", + "49bf122762d7bc1d6d9c02f6d54f8384" ].joined()).bytes - expect(String(pointer: toPush2, length: toPush2Len, encoding: .ascii)) - .to(equal(String(pointer: expPush1Encrypted, length: expPush1Encrypted.count, encoding: .ascii))) + let pushData2Str: String = String(pointer: pushData2.pointee.config, length: pushData2.pointee.config_len, encoding: .ascii)! + let expPush1EncryptedStr: String = String(pointer: expPush1Encrypted, length: expPush1Encrypted.count, encoding: .ascii)! + expect(pushData2Str).to(equal(expPush1EncryptedStr)) // Raw decryption doesn't unpad (i.e. the padding is part of the encrypted data) - var toPush2DecSize: Int = 0 - let toPush2Decrypted: UnsafeMutablePointer? = config_decrypt( - toPush2, - toPush2Len, + var pushData2DecSize: Int = 0 + let pushData2Decrypted: UnsafeMutablePointer? = config_decrypt( + pushData2.pointee.config, + pushData2.pointee.config_len, edSK, encDomain, - &toPush2DecSize + &pushData2DecSize ) let prefixPadding2: String = (0..<(256 - 40 - expPush1Decrypted.count)) .map { _ in "\0" } .joined() - expect(toPush2DecSize).to(equal(216)) // 256 - 40 overhead - expect(String(pointer: toPush2Decrypted, length: toPush2DecSize, encoding: .ascii)) - .to(equal(String(pointer: expPush1Decrypted, length: expPush1Decrypted.count, encoding: .ascii).map { "\(prefixPadding2)\($0)" })) - toPush2?.deallocate() - toPush2Decrypted?.deallocate() + expect(pushData2DecSize).to(equal(216)) // 256 - 40 overhead + + let pushData2DecryptedStr: String = String(pointer: pushData2Decrypted, length: pushData2DecSize, encoding: .ascii)! + let expPush1DecryptedStr: String = String(pointer: expPush1Decrypted, length: expPush1Decrypted.count, encoding: .ascii) + .map { "\(prefixPadding2)\($0)" }! + expect(pushData2DecryptedStr).to(equal(expPush1DecryptedStr)) + pushData2Decrypted?.deallocate() // We haven't dumped, so still need to dump: expect(config_needs_dump(conf)).to(beTrue()) @@ -189,18 +191,22 @@ class ConfigUserProfileSpec: QuickSpec { expPush1Decrypted .map { CChar(bitPattern: $0) }, """ + 1:(0: + 1:)le e """.removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) .bytes .map { CChar(bitPattern: $0) } - ] - .flatMap { $0 } + ].flatMap { $0 } expect(String(pointer: dump1, length: dump1Len, encoding: .ascii)) .to(equal(String(pointer: expDump1, length: expDump1.count, encoding: .ascii))) dump1?.deallocate() // So now imagine we got back confirmation from the swarm that the push has been stored: - config_confirm_pushed(conf, seqno2) + let fakeHash1: String = "fakehash1" + var cFakeHash1: [CChar] = fakeHash1.cArray + config_confirm_pushed(conf, pushData2.pointee.seqno, &cFakeHash1) + pushData2.deallocate() expect(config_needs_push(conf)).to(beFalse()) expect(config_needs_dump(conf)).to(beTrue()) // The confirmation changes state, so this makes us need a dump @@ -208,12 +214,34 @@ class ConfigUserProfileSpec: QuickSpec { var dump2: UnsafeMutablePointer? = nil var dump2Len: Int = 0 config_dump(conf, &dump2, &dump2Len) + + let expDump2: [CChar] = [ + """ + d + 1:! i0e + 1:$ \(expPush1Decrypted.count): + """ + .removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) + .bytes + .map { CChar(bitPattern: $0) }, + expPush1Decrypted + .map { CChar(bitPattern: $0) }, + """ + 1:(9:fakehash1 + 1:)le + e + """.removeCharacters(characterSet: CharacterSet.whitespacesAndNewlines) + .bytes + .map { CChar(bitPattern: $0) } + ].flatMap { $0 } + expect(String(pointer: dump2, length: dump2Len, encoding: .ascii)) + .to(equal(String(pointer: expDump2, length: expDump2.count, encoding: .ascii))) dump2?.deallocate() expect(config_needs_dump(conf)).to(beFalse()) // Now we're going to set up a second, competing config object (in the real world this would be // another Session client somewhere). - + // Start with an empty config, as above: let error2: UnsafeMutablePointer? = nil var conf2: UnsafeMutablePointer? = nil @@ -223,11 +251,13 @@ class ConfigUserProfileSpec: QuickSpec { // Now imagine we just pulled down the `exp_push1` string from the swarm; we merge it into // conf2: + var mergeHashes: [UnsafePointer?] = [cFakeHash1].unsafeCopy() var mergeData: [UnsafePointer?] = [expPush1Encrypted].unsafeCopy() var mergeSize: [Int] = [expPush1Encrypted.count] - expect(config_merge(conf2, &mergeData, &mergeSize, 1)).to(equal(1)) + expect(config_merge(conf2, &mergeHashes, &mergeData, &mergeSize, 1)).to(equal(1)) + mergeHashes.forEach { $0?.deallocate() } mergeData.forEach { $0?.deallocate() } - + // Our state has changed, so we need to dump: expect(config_needs_dump(conf2)).to(beTrue()) var dump3: UnsafeMutablePointer? = nil @@ -240,9 +270,9 @@ class ConfigUserProfileSpec: QuickSpec { // We *don't* need to push: even though we updated, all we did is update to the merged data (and // didn't have any sort of merge conflict needed): expect(config_needs_push(conf2)).to(beFalse()) - + // Now let's create a conflicting update: - + // Change the name on both clients: user_profile_set_name(conf, "Nibbler") user_profile_set_name(conf2, "Raz") @@ -257,16 +287,19 @@ class ConfigUserProfileSpec: QuickSpec { // Both have changes, so push need a push expect(config_needs_push(conf)).to(beTrue()) expect(config_needs_push(conf2)).to(beTrue()) - var toPush3: UnsafeMutablePointer? = nil - var toPush3Len: Int = 0 - let seqno3: Int64 = config_push(conf, &toPush3, &toPush3Len) - expect(seqno3).to(equal(2)) // incremented, since we made a field change - var toPush4: UnsafeMutablePointer? = nil - var toPush4Len: Int = 0 - let seqno4: Int64 = config_push(conf2, &toPush4, &toPush4Len) - expect(seqno4).to(equal(2)) // incremented, since we made a field change - + let fakeHash2: String = "fakehash2" + var cFakeHash2: [CChar] = fakeHash2.cArray + let pushData3: UnsafeMutablePointer = config_push(conf) + expect(pushData3.pointee.seqno).to(equal(2)) // incremented, since we made a field change + config_confirm_pushed(conf, pushData3.pointee.seqno, &cFakeHash2) + + let fakeHash3: String = "fakehash3" + var cFakeHash3: [CChar] = fakeHash3.cArray + let pushData4: UnsafeMutablePointer = config_push(conf2) + expect(pushData4.pointee.seqno).to(equal(2)) // incremented, since we made a field change + config_confirm_pushed(conf, pushData4.pointee.seqno, &cFakeHash3) + var dump4: UnsafeMutablePointer? = nil var dump4Len: Int = 0 config_dump(conf, &dump4, &dump4Len); @@ -279,32 +312,34 @@ class ConfigUserProfileSpec: QuickSpec { // Since we set different things, we're going to get back different serialized data to be // pushed: - expect(String(pointer: toPush3, length: toPush3Len, encoding: .ascii)) - .toNot(equal(String(pointer: toPush4, length: toPush4Len, encoding: .ascii))) + let pushData3Str: String? = String(pointer: pushData3.pointee.config, length: pushData3.pointee.config_len, encoding: .ascii) + let pushData4Str: String? = String(pointer: pushData4.pointee.config, length: pushData4.pointee.config_len, encoding: .ascii) + expect(pushData3Str).toNot(equal(pushData4Str)) // Now imagine that each client pushed its `seqno=2` config to the swarm, but then each client // also fetches new messages and pulls down the other client's `seqno=2` value. - + // Feed the new config into each other. (This array could hold multiple configs if we pulled // down more than one). - var mergeData2: [UnsafePointer?] = [UnsafePointer(toPush3)] - var mergeSize2: [Int] = [toPush3Len] - expect(config_merge(conf2, &mergeData2, &mergeSize2, 1)).to(equal(1)) - toPush3?.deallocate() - var mergeData3: [UnsafePointer?] = [UnsafePointer(toPush4)] - var mergeSize3: [Int] = [toPush4Len] - expect(config_merge(conf, &mergeData3, &mergeSize3, 1)).to(equal(1)) - toPush4?.deallocate() + var mergeHashes2: [UnsafePointer?] = [cFakeHash2].unsafeCopy() + var mergeData2: [UnsafePointer?] = [UnsafePointer(pushData3.pointee.config)] + var mergeSize2: [Int] = [pushData3.pointee.config_len] + expect(config_merge(conf2, &mergeHashes2, &mergeData2, &mergeSize2, 1)).to(equal(1)) + pushData3.deallocate() + var mergeHashes3: [UnsafePointer?] = [cFakeHash3].unsafeCopy() + var mergeData3: [UnsafePointer?] = [UnsafePointer(pushData4.pointee.config)] + var mergeSize3: [Int] = [pushData4.pointee.config_len] + expect(config_merge(conf, &mergeHashes3, &mergeData3, &mergeSize3, 1)).to(equal(1)) + pushData4.deallocate() // Now after the merge we *will* want to push from both client, since both will have generated a // merge conflict update (with seqno = 3). expect(config_needs_push(conf)).to(beTrue()) expect(config_needs_push(conf2)).to(beTrue()) - let seqno5: Int64 = config_push(conf, &toPush3, &toPush3Len); - let seqno6: Int64 = config_push(conf2, &toPush4, &toPush4Len); - - expect(seqno5).to(equal(3)) - expect(seqno6).to(equal(3)) + let pushData5: UnsafeMutablePointer = config_push(conf) + let pushData6: UnsafeMutablePointer = config_push(conf2) + expect(pushData5.pointee.seqno).to(equal(3)) + expect(pushData6.pointee.seqno).to(equal(3)) // They should have resolved the conflict to the same thing: expect(String(cString: user_profile_get_name(conf)!)).to(equal("Nibbler")) @@ -312,7 +347,7 @@ class ConfigUserProfileSpec: QuickSpec { // (Note that they could have also both resolved to "Raz" here, but the hash of the serialized // message just happens to have a higher hash -- and thus gets priority -- for this particular // test). - + // Since only one of them set a profile pic there should be no conflict there: let pic3: user_profile_pic = user_profile_get_pic(conf) expect(pic3.url).toNot(beNil()) @@ -326,9 +361,17 @@ class ConfigUserProfileSpec: QuickSpec { expect(pic4.key).toNot(beNil()) expect(Data(libSessionVal: pic4.key, count: 32).toHexString()) .to(equal("7177657274007975696f31323334353637383930313233343536373839303132")) - - config_confirm_pushed(conf, seqno5) - config_confirm_pushed(conf2, seqno6) + expect(user_profile_get_nts_priority(conf)).to(equal(9)) + expect(user_profile_get_nts_priority(conf2)).to(equal(9)) + + let fakeHash4: String = "fakehash4" + var cFakeHash4: [CChar] = fakeHash4.cArray + let fakeHash5: String = "fakehash5" + var cFakeHash5: [CChar] = fakeHash5.cArray + config_confirm_pushed(conf, pushData5.pointee.seqno, &cFakeHash4) + config_confirm_pushed(conf2, pushData6.pointee.seqno, &cFakeHash5) + pushData5.deallocate() + pushData6.deallocate() var dump6: UnsafeMutablePointer? = nil var dump6Len: Int = 0 @@ -339,7 +382,7 @@ class ConfigUserProfileSpec: QuickSpec { // (store in db) dump6?.deallocate() dump7?.deallocate() - + expect(config_needs_dump(conf)).to(beFalse()) expect(config_needs_dump(conf2)).to(beFalse()) expect(config_needs_push(conf)).to(beFalse()) diff --git a/SessionMessagingKitTests/LibSessionUtil/LibSessionSpec.swift b/SessionMessagingKitTests/LibSessionUtil/LibSessionSpec.swift new file mode 100644 index 000000000..be670ad5d --- /dev/null +++ b/SessionMessagingKitTests/LibSessionUtil/LibSessionSpec.swift @@ -0,0 +1,20 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import Sodium +import SessionUtil +import SessionUtilitiesKit + +import Quick +import Nimble + +class LibSessionSpec: QuickSpec { + // MARK: - Spec + + override func spec() { + ConfigContactsSpec.spec() + ConfigUserProfileSpec.spec() + ConfigConvoInfoVolatileSpec.spec() + ConfigUserGroupsSpec.spec() + } +} diff --git a/SessionMessagingKitTests/LibSessionUtil/SessionUtilSpec.swift b/SessionMessagingKitTests/LibSessionUtil/SessionUtilSpec.swift new file mode 100644 index 000000000..e8ac18129 --- /dev/null +++ b/SessionMessagingKitTests/LibSessionUtil/SessionUtilSpec.swift @@ -0,0 +1,212 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +import Foundation +import Sodium +import SessionUtil +import SessionUtilitiesKit +import SessionMessagingKit + +import Quick +import Nimble + +class SessionUtilSpec: QuickSpec { + // MARK: - Spec + + override func spec() { + describe("SessionUtil") { + // MARK: - Parsing URLs + + context("when parsing a community url") { + it("handles the example urls correctly") { + let validUrls: [String] = [ + [ + "https://sessionopengroup.co/r/main?", + "public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c" + ], + [ + "https://sessionopengroup.co/main?", + "public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c" + ], + [ + "http://sessionopengroup.co/r/main?", + "public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c" + ], + [ + "http://sessionopengroup.co/main?", + "public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c" + ], + [ + "https://143.198.213.225:443/r/main?", + "public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c" + ], + [ + "https://143.198.213.225:443/main?", + "public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c" + ], + [ + "http://143.198.213.255:80/main?", + "public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c" + ], + [ + "http://143.198.213.255:80/r/main?", + "public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c" + ] + ].map { $0.joined() } + let processedValues: [(room: String, server: String, publicKey: String)] = validUrls + .map { SessionUtil.parseCommunity(url: $0) } + .compactMap { $0 } + let processedRooms: [String] = processedValues.map { $0.room } + let processedServers: [String] = processedValues.map { $0.server } + let processedPublicKeys: [String] = processedValues.map { $0.publicKey } + let expectedRooms: [String] = [String](repeating: "main", count: 8) + let expectedServers: [String] = [ + "https://sessionopengroup.co", + "https://sessionopengroup.co", + "http://sessionopengroup.co", + "http://sessionopengroup.co", + "https://143.198.213.225", + "https://143.198.213.225", + "http://143.198.213.255", + "http://143.198.213.255" + ] + let expectedPublicKeys: [String] = [String]( + repeating: "658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c", + count: 8 + ) + + expect(processedValues.count).to(equal(validUrls.count)) + expect(processedRooms).to(equal(expectedRooms)) + expect(processedServers).to(equal(expectedServers)) + expect(processedPublicKeys).to(equal(expectedPublicKeys)) + } + + it("handles the r prefix if present") { + let info = SessionUtil.parseCommunity( + url: [ + "https://sessionopengroup.co/r/main?", + "public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c" + ].joined() + ) + + expect(info?.room).to(equal("main")) + expect(info?.server).to(equal("https://sessionopengroup.co")) + expect(info?.publicKey).to(equal("658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c")) + } + + it("fails if no scheme is provided") { + let info = SessionUtil.parseCommunity( + url: [ + "sessionopengroup.co/r/main?", + "public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c" + ].joined() + ) + + expect(info?.room).to(beNil()) + expect(info?.server).to(beNil()) + expect(info?.publicKey).to(beNil()) + } + + it("fails if there is no room") { + let info = SessionUtil.parseCommunity( + url: [ + "https://sessionopengroup.co?", + "public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c" + ].joined() + ) + + expect(info?.room).to(beNil()) + expect(info?.server).to(beNil()) + expect(info?.publicKey).to(beNil()) + } + + it("fails if there is no public key parameter") { + let info = SessionUtil.parseCommunity( + url: "https://sessionopengroup.co/r/main" + ) + + expect(info?.room).to(beNil()) + expect(info?.server).to(beNil()) + expect(info?.publicKey).to(beNil()) + } + + it("fails if the public key parameter is not 64 characters") { + let info = SessionUtil.parseCommunity( + url: [ + "https://sessionopengroup.co/r/main?", + "public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231" + ].joined() + ) + + expect(info?.room).to(beNil()) + expect(info?.server).to(beNil()) + expect(info?.publicKey).to(beNil()) + } + + it("fails if the public key parameter is not a hex string") { + let info = SessionUtil.parseCommunity( + url: [ + "https://sessionopengroup.co/r/main?", + "public_key=!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + ].joined() + ) + + expect(info?.room).to(beNil()) + expect(info?.server).to(beNil()) + expect(info?.publicKey).to(beNil()) + } + + it("maintains the same TLS") { + let server1 = SessionUtil.parseCommunity( + url: [ + "http://sessionopengroup.co/r/main?", + "public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c" + ].joined() + )?.server + let server2 = SessionUtil.parseCommunity( + url: [ + "https://sessionopengroup.co/r/main?", + "public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c" + ].joined() + )?.server + + expect(server1).to(equal("http://sessionopengroup.co")) + expect(server2).to(equal("https://sessionopengroup.co")) + } + + it("maintains the same port") { + let server1 = SessionUtil.parseCommunity( + url: [ + "https://sessionopengroup.co/r/main?", + "public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c" + ].joined() + )?.server + let server2 = SessionUtil.parseCommunity( + url: [ + "https://sessionopengroup.co:1234/r/main?", + "public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c" + ].joined() + )?.server + + expect(server1).to(equal("https://sessionopengroup.co")) + expect(server2).to(equal("https://sessionopengroup.co:1234")) + } + } + + // MARK: - Generating URLs + + context("when generating a url") { + it("generates the url correctly") { + expect(SessionUtil.communityUrlFor(server: "server", roomToken: "room", publicKey: "f8fec9b701000000ffffffff0400008000000000000000000000000000000000")) + .to(equal("server/room?public_key=f8fec9b701000000ffffffff0400008000000000000000000000000000000000")) + } + + it("maintains the casing provided") { + expect(SessionUtil.communityUrlFor(server: "SeRVer", roomToken: "RoOM", publicKey: "f8fec9b701000000ffffffff0400008000000000000000000000000000000000")) + .to(equal("SeRVer/RoOM?public_key=f8fec9b701000000ffffffff0400008000000000000000000000000000000000")) + } + } + } + } +} diff --git a/SessionMessagingKitTests/LibSessionUtil/Utilities/TypeConversionUtilitiesSpec.swift b/SessionMessagingKitTests/LibSessionUtil/Utilities/LibSessionTypeConversionUtilitiesSpec.swift similarity index 62% rename from SessionMessagingKitTests/LibSessionUtil/Utilities/TypeConversionUtilitiesSpec.swift rename to SessionMessagingKitTests/LibSessionUtil/Utilities/LibSessionTypeConversionUtilitiesSpec.swift index 2ca104bc9..ac2f5d825 100644 --- a/SessionMessagingKitTests/LibSessionUtil/Utilities/TypeConversionUtilitiesSpec.swift +++ b/SessionMessagingKitTests/LibSessionUtil/Utilities/LibSessionTypeConversionUtilitiesSpec.swift @@ -8,7 +8,7 @@ import Nimble @testable import SessionMessagingKit -class TypeConversionUtilitiesSpec: QuickSpec { +class LibSessionTypeConversionUtilitiesSpec: QuickSpec { // MARK: - Spec override func spec() { @@ -85,6 +85,13 @@ class TypeConversionUtilitiesSpec: QuickSpec { expect(result).to(equal("TestT")) } + it("returns an empty string when given a value only containing null termination characters with a fixed length") { + let value: (CChar, CChar, CChar, CChar, CChar) = (0, 0, 0, 0, 0) + let result = String(libSessionVal: value, fixedLength: 5) + + expect(result).to(equal("")) + } + it("defaults the fixed length value to none") { let value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 0, 0, 0) let result = String(libSessionVal: value) @@ -134,22 +141,26 @@ class TypeConversionUtilitiesSpec: QuickSpec { } context("when optional") { - context("returns null when null") { + context("returns empty when null") { let value: String? = nil - let result: (CChar, CChar, CChar, CChar, CChar)? = value?.toLibSession() + let result: (CChar, CChar, CChar, CChar, CChar) = value.toLibSession() - expect(result).to(beNil()) + expect(result.0).to(equal(0)) + expect(result.1).to(equal(0)) + expect(result.2).to(equal(0)) + expect(result.3).to(equal(0)) + expect(result.4).to(equal(0)) } context("returns a libSession value when not null") { let value: String? = "Test" - let result: (CChar, CChar, CChar, CChar, CChar)? = value?.toLibSession() + let result: (CChar, CChar, CChar, CChar, CChar) = value.toLibSession() - expect(result?.0).to(equal(84)) - expect(result?.1).to(equal(101)) - expect(result?.2).to(equal(115)) - expect(result?.3).to(equal(116)) - expect(result?.4).to(equal(0)) + expect(result.0).to(equal(84)) + expect(result.1).to(equal(101)) + expect(result.2).to(equal(115)) + expect(result.3).to(equal(116)) + expect(result.4).to(equal(0)) } } } @@ -176,6 +187,20 @@ class TypeConversionUtilitiesSpec: QuickSpec { expect(result).to(equal(Data([1, 2, 3, 4, 5]))) } + + it("returns data when all bytes are zero and nullIfEmpty is false") { + let value: (UInt8, UInt8, UInt8, UInt8, UInt8) = (0, 0, 0, 0, 0) + let result = Data(libSessionVal: value, count: 5, nullIfEmpty: false) + + expect(result).to(equal(Data([0, 0, 0, 0, 0]))) + } + + it("returns null when all bytes are zero and nullIfEmpty is true") { + let value: (UInt8, UInt8, UInt8, UInt8, UInt8) = (0, 0, 0, 0, 0) + let result = Data(libSessionVal: value, count: 5, nullIfEmpty: true) + + expect(result).to(beNil()) + } } context("when converting to a libSession value") { @@ -197,23 +222,38 @@ class TypeConversionUtilitiesSpec: QuickSpec { expect(result.4).to(equal(1)) } + context("fills with empty data when too short") { + let value: Data? = Data([1, 2, 3]) + let result: (Int8, Int8, Int8, Int8, Int8) = value.toLibSession() + + expect(result.0).to(equal(1)) + expect(result.1).to(equal(2)) + expect(result.2).to(equal(3)) + expect(result.3).to(equal(0)) + expect(result.4).to(equal(0)) + } + context("when optional") { context("returns null when null") { let value: Data? = nil - let result: (Int8, Int8, Int8, Int8, Int8)? = value?.toLibSession() + let result: (Int8, Int8, Int8, Int8, Int8) = value.toLibSession() - expect(result).to(beNil()) + expect(result.0).to(equal(0)) + expect(result.1).to(equal(0)) + expect(result.2).to(equal(0)) + expect(result.3).to(equal(0)) + expect(result.4).to(equal(0)) } context("returns a libSession value when not null") { let value: Data? = Data([1, 2, 3, 4, 5]) - let result: (Int8, Int8, Int8, Int8, Int8)? = value?.toLibSession() + let result: (Int8, Int8, Int8, Int8, Int8) = value.toLibSession() - expect(result?.0).to(equal(1)) - expect(result?.1).to(equal(2)) - expect(result?.2).to(equal(3)) - expect(result?.3).to(equal(4)) - expect(result?.4).to(equal(5)) + expect(result.0).to(equal(1)) + expect(result.1).to(equal(2)) + expect(result.2).to(equal(3)) + expect(result.3).to(equal(4)) + expect(result.4).to(equal(5)) } } } @@ -222,6 +262,69 @@ class TypeConversionUtilitiesSpec: QuickSpec { // MARK: - Array describe("an Array") { + context("when initialised with a 2D C array") { + it("returns the correct array") { + var test: [CChar] = ( + "Test1".cArray.nullTerminated() + + "Test2".cArray.nullTerminated() + + "Test3AndExtra".cArray.nullTerminated() + ) + let result = test.withUnsafeMutableBufferPointer { ptr in + var mutablePtr = UnsafeMutablePointer(ptr.baseAddress) + + return [String](pointer: &mutablePtr, count: 3) + } + + expect(result).to(equal(["Test1", "Test2", "Test3AndExtra"])) + } + + it("returns an empty array if given one") { + var test = [CChar]() + let result = test.withUnsafeMutableBufferPointer { ptr in + var mutablePtr = UnsafeMutablePointer(ptr.baseAddress) + + return [String](pointer: &mutablePtr, count: 0) + } + + expect(result).to(equal([])) + } + + it("handles empty strings without issues") { + var test: [CChar] = ( + "Test1".cArray.nullTerminated() + + "".cArray.nullTerminated() + + "Test2".cArray.nullTerminated() + ) + let result = test.withUnsafeMutableBufferPointer { ptr in + var mutablePtr = UnsafeMutablePointer(ptr.baseAddress) + + return [String](pointer: &mutablePtr, count: 3) + } + + expect(result).to(equal(["Test1", "", "Test2"])) + } + + it("returns null when given a null pointer") { + expect([String](pointer: nil, count: 5)).to(beNil()) + } + + it("returns null when given a null count") { + var test: [CChar] = "Test1".cArray.nullTerminated() + let result = test.withUnsafeMutableBufferPointer { ptr in + var mutablePtr = UnsafeMutablePointer(ptr.baseAddress) + + return [String](pointer: &mutablePtr, count: nil) + } + + expect(result).to(beNil()) + } + + it("returns the default value if given null values") { + expect([String](pointer: nil, count: 5, defaultValue: ["Test"])) + .to(equal(["Test"])) + } + } + context("when adding a null terminated character") { it("adds a null termination character when not present") { let value: [CChar] = [1, 2, 3, 4, 5] diff --git a/SessionMessagingKitTests/Open Groups/Models/OpenGroupSpec.swift b/SessionMessagingKitTests/Open Groups/Models/OpenGroupSpec.swift index 5225a130a..ea835122f 100644 --- a/SessionMessagingKitTests/Open Groups/Models/OpenGroupSpec.swift +++ b/SessionMessagingKitTests/Open Groups/Models/OpenGroupSpec.swift @@ -93,18 +93,6 @@ class OpenGroupSpec: QuickSpec { expect(OpenGroup.idFor(roomToken: "RoOM", server: "server")).to(equal("server.RoOM")) } } - - context("when generating a url") { - it("generates the url correctly") { - expect(OpenGroup.urlFor(server: "server", roomToken: "room", publicKey: "key")) - .to(equal("server/room?public_key=key")) - } - - it("maintains the casing provided") { - expect(OpenGroup.urlFor(server: "SeRVer", roomToken: "RoOM", publicKey: "KEy")) - .to(equal("SeRVer/RoOM?public_key=KEy")) - } - } } } } diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift index cb5fa9b3a..775a2a579 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift +++ b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift @@ -822,6 +822,7 @@ class OpenGroupManagerSpec: QuickSpec { roomToken: "testRoom", server: "testServer", publicKey: TestConstants.serverPublicKey, + calledFromConfigHandling: true, // Don't trigger SessionUtil logic dependencies: dependencies ) } @@ -852,6 +853,7 @@ class OpenGroupManagerSpec: QuickSpec { roomToken: "testRoom", server: "testServer", publicKey: TestConstants.serverPublicKey, + calledFromConfigHandling: true, // Don't trigger SessionUtil logic dependencies: dependencies ) } @@ -890,6 +892,7 @@ class OpenGroupManagerSpec: QuickSpec { publicKey: TestConstants.serverPublicKey .replacingOccurrences(of: "c3", with: "00") .replacingOccurrences(of: "b3", with: "00"), + calledFromConfigHandling: true, // Don't trigger SessionUtil logic dependencies: dependencies ) } @@ -943,6 +946,7 @@ class OpenGroupManagerSpec: QuickSpec { roomToken: "testRoom", server: "testServer", publicKey: TestConstants.serverPublicKey, + calledFromConfigHandling: true, // Don't trigger SessionUtil logic dependencies: dependencies ) } @@ -986,6 +990,7 @@ class OpenGroupManagerSpec: QuickSpec { .delete( db, openGroupId: OpenGroup.idFor(roomToken: "testRoom", server: "testServer"), + calledFromConfigHandling: true, // Don't trigger SessionUtil logic dependencies: dependencies ) } @@ -1000,6 +1005,7 @@ class OpenGroupManagerSpec: QuickSpec { .delete( db, openGroupId: OpenGroup.idFor(roomToken: "testRoom", server: "testServer"), + calledFromConfigHandling: true, // Don't trigger SessionUtil logic dependencies: dependencies ) } @@ -1017,6 +1023,7 @@ class OpenGroupManagerSpec: QuickSpec { .delete( db, openGroupId: OpenGroup.idFor(roomToken: "testRoom", server: "testServer"), + calledFromConfigHandling: true, // Don't trigger SessionUtil logic dependencies: dependencies ) } @@ -1030,6 +1037,7 @@ class OpenGroupManagerSpec: QuickSpec { .delete( db, openGroupId: OpenGroup.idFor(roomToken: "testRoom", server: "testServer"), + calledFromConfigHandling: true, // Don't trigger SessionUtil logic dependencies: dependencies ) } @@ -1068,6 +1076,7 @@ class OpenGroupManagerSpec: QuickSpec { .delete( db, openGroupId: OpenGroup.idFor(roomToken: "testRoom", server: "testServer"), + calledFromConfigHandling: true, // Don't trigger SessionUtil logic dependencies: dependencies ) } @@ -1120,6 +1129,7 @@ class OpenGroupManagerSpec: QuickSpec { .delete( db, openGroupId: OpenGroup.idFor(roomToken: "testRoom", server: OpenGroupAPI.defaultServer), + calledFromConfigHandling: true, // Don't trigger SessionUtil logic dependencies: dependencies ) } @@ -1134,6 +1144,7 @@ class OpenGroupManagerSpec: QuickSpec { .delete( db, openGroupId: OpenGroup.idFor(roomToken: "testRoom", server: OpenGroupAPI.defaultServer), + calledFromConfigHandling: true, // Don't trigger SessionUtil logic dependencies: dependencies ) } @@ -3853,158 +3864,6 @@ class OpenGroupManagerSpec: QuickSpec { } } } - - // MARK: - --parseOpenGroup - - context("when parsing an open group url") { - it("handles the example urls correctly") { - let validUrls: [String] = [ - "https://sessionopengroup.co/r/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c", - "https://sessionopengroup.co/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c", - "http://sessionopengroup.co/r/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c", - "http://sessionopengroup.co/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c", - "sessionopengroup.co/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c", - "sessionopengroup.co/r/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c", - "https://143.198.213.225:443/r/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c", - "https://143.198.213.225:443/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c", - "143.198.213.255:80/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c", - "143.198.213.255:80/r/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c" - ] - let processedValues: [(room: String, server: String, publicKey: String)] = validUrls - .map { OpenGroupManager.parseOpenGroup(from: $0) } - .compactMap { $0 } - let processedRooms: [String] = processedValues.map { $0.room } - let processedServers: [String] = processedValues.map { $0.server } - let processedPublicKeys: [String] = processedValues.map { $0.publicKey } - let expectedRooms: [String] = [String](repeating: "main", count: 10) - let expectedServers: [String] = [ - "https://sessionopengroup.co", - "https://sessionopengroup.co", - "http://sessionopengroup.co", - "http://sessionopengroup.co", - "http://sessionopengroup.co", - "http://sessionopengroup.co", - "https://143.198.213.225:443", - "https://143.198.213.225:443", - "http://143.198.213.255:80", - "http://143.198.213.255:80" - ] - let expectedPublicKeys: [String] = [String]( - repeating: "658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c", - count: 10 - ) - - expect(processedValues.count).to(equal(validUrls.count)) - expect(processedRooms).to(equal(expectedRooms)) - expect(processedServers).to(equal(expectedServers)) - expect(processedPublicKeys).to(equal(expectedPublicKeys)) - } - - it("handles the r prefix if present") { - let info = OpenGroupManager.parseOpenGroup( - from: [ - "https://sessionopengroup.co/r/main?", - "public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c" - ].joined() - ) - - expect(info?.room).to(equal("main")) - expect(info?.server).to(equal("https://sessionopengroup.co")) - expect(info?.publicKey).to(equal("658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c")) - } - - it("fails if there is no room") { - let info = OpenGroupManager.parseOpenGroup( - from: [ - "https://sessionopengroup.co?", - "public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c" - ].joined() - ) - - expect(info?.room).to(beNil()) - expect(info?.server).to(beNil()) - expect(info?.publicKey).to(beNil()) - } - - it("fails if there is no public key parameter") { - let info = OpenGroupManager.parseOpenGroup( - from: "https://sessionopengroup.co/r/main" - ) - - expect(info?.room).to(beNil()) - expect(info?.server).to(beNil()) - expect(info?.publicKey).to(beNil()) - } - - it("fails if the public key parameter is not 64 characters") { - let info = OpenGroupManager.parseOpenGroup( - from: [ - "https://sessionopengroup.co/r/main?", - "public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231" - ].joined() - ) - - expect(info?.room).to(beNil()) - expect(info?.server).to(beNil()) - expect(info?.publicKey).to(beNil()) - } - - it("fails if the public key parameter is not a hex string") { - let info = OpenGroupManager.parseOpenGroup( - from: [ - "https://sessionopengroup.co/r/main?", - "public_key=!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" - ].joined() - ) - - expect(info?.room).to(beNil()) - expect(info?.server).to(beNil()) - expect(info?.publicKey).to(beNil()) - } - - it("maintains the same TLS") { - let server1 = OpenGroupManager.parseOpenGroup( - from: [ - "sessionopengroup.co/r/main?", - "public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c" - ].joined() - )?.server - let server2 = OpenGroupManager.parseOpenGroup( - from: [ - "http://sessionopengroup.co/r/main?", - "public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c" - ].joined() - )?.server - let server3 = OpenGroupManager.parseOpenGroup( - from: [ - "https://sessionopengroup.co/r/main?", - "public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c" - ].joined() - )?.server - - expect(server1).to(equal("http://sessionopengroup.co")) - expect(server2).to(equal("http://sessionopengroup.co")) - expect(server3).to(equal("https://sessionopengroup.co")) - } - - it("maintains the same port") { - let server1 = OpenGroupManager.parseOpenGroup( - from: [ - "https://sessionopengroup.co/r/main?", - "public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c" - ].joined() - )?.server - let server2 = OpenGroupManager.parseOpenGroup( - from: [ - "https://sessionopengroup.co:1234/r/main?", - "public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c" - ].joined() - )?.server - - expect(server1).to(equal("https://sessionopengroup.co")) - expect(server2).to(equal("https://sessionopengroup.co:1234")) - } - } } } } diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index fb5ef4037..e4f205a32 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -218,7 +218,9 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension // If we need a config sync then trigger it now if needsConfigSync { - ConfigurationSyncJob.enqueue() + Storage.shared.write { db in + ConfigurationSyncJob.enqueue(db, publicKey: getUserHexEncodedPublicKey(db)) + } } checkIsAppReady() diff --git a/SessionShareExtension/ShareNavController.swift b/SessionShareExtension/ShareNavController.swift index 87d46901b..22832b20f 100644 --- a/SessionShareExtension/ShareNavController.swift +++ b/SessionShareExtension/ShareNavController.swift @@ -92,7 +92,9 @@ final class ShareNavController: UINavigationController, ShareViewDelegate { // If we need a config sync then trigger it now if needsConfigSync { - ConfigurationSyncJob.enqueue() + Storage.shared.write { db in + ConfigurationSyncJob.enqueue(db, publicKey: getUserHexEncodedPublicKey(db)) + } } checkIsAppReady() diff --git a/SessionSnodeKit/Models/UpdateExpiryRequest.swift b/SessionSnodeKit/Models/UpdateExpiryRequest.swift index 388b7a964..dd8579947 100644 --- a/SessionSnodeKit/Models/UpdateExpiryRequest.swift +++ b/SessionSnodeKit/Models/UpdateExpiryRequest.swift @@ -72,10 +72,13 @@ extension SnodeAPI { // MARK: - Abstract Methods override func generateSignature() throws -> [UInt8] { - /// Ed25519 signature of `("expire" || expiry || messages[0] || ... || messages[N])` - /// where `expiry` is the expiry timestamp expressed as a string. The signature must be base64 - /// encoded (json) or bytes (bt). + /// Ed25519 signature of `("expire" || ShortenOrExtend || expiry || messages[0] || ...` + /// ` || messages[N])` where `expiry` is the expiry timestamp expressed as a string. + /// `ShortenOrExtend` is string signature must be base64 "shorten" if the shorten option is given (and true), + /// "extend" if `extend` is true, and empty otherwise. The signature must be base64 encoded (json) or bytes (bt). let verificationBytes: [UInt8] = SnodeAPI.Endpoint.expire.rawValue.bytes + .appending(contentsOf: (shorten == true ? "shorten".bytes : [])) + .appending(contentsOf: (extend == true ? "extend".bytes : [])) .appending(contentsOf: "\(expiryMs)".data(using: .ascii)?.bytes) .appending(contentsOf: messageHashes.joined().bytes) diff --git a/SessionSnodeKit/Networking/SnodeAPI.swift b/SessionSnodeKit/Networking/SnodeAPI.swift index 2f5e1d289..7bdcaa858 100644 --- a/SessionSnodeKit/Networking/SnodeAPI.swift +++ b/SessionSnodeKit/Networking/SnodeAPI.swift @@ -7,8 +7,6 @@ import GRDB import SessionUtilitiesKit public final class SnodeAPI { - public typealias TargetedMessage = (message: SnodeMessage, namespace: Namespace) - internal static let sodium: Atomic = Atomic(Sodium()) private static var hasLoadedSnodePool: Atomic = Atomic(false) @@ -315,18 +313,14 @@ public final class SnodeAPI { namespace: namespace, associatedWith: publicKey ) - - let maybeLastHash: String? = SnodeReceivedMessageInfo + + result[namespace] = SnodeReceivedMessageInfo .fetchLastNotExpired( for: snode, namespace: namespace, associatedWith: publicKey )? .hash - - guard let lastHash: String = maybeLastHash else { return } - - result[namespace] = lastHash } } .flatMap { namespaceLastHash -> AnyPublisher<[SnodeAPI.Namespace: (info: ResponseInfoType, data: (messages: [SnodeReceivedMessage], lastHash: String?)?)], Error> in @@ -625,13 +619,13 @@ public final class SnodeAPI { } public static func sendConfigMessages( - _ targetedMessages: [TargetedMessage], - oldHashes: [String], + _ messages: [(message: SnodeMessage, namespace: Namespace)], + allObsoleteHashes: [String], using dependencies: SSKDependencies = SSKDependencies() ) -> AnyPublisher { guard - !targetedMessages.isEmpty, - let recipient: String = targetedMessages.first?.message.recipient + !messages.isEmpty, + let recipient: String = messages.first?.message.recipient else { return Fail(error: SnodeAPIError.generic) .eraseToAnyPublisher() @@ -644,7 +638,7 @@ public final class SnodeAPI { let userX25519PublicKey: String = getUserHexEncodedPublicKey() let publicKey: String = recipient - var requests: [SnodeAPI.BatchRequest.Info] = targetedMessages + var requests: [SnodeAPI.BatchRequest.Info] = messages .map { message, namespace in // Check if this namespace requires authentication guard namespace.requiresWriteAuthentication else { @@ -677,13 +671,13 @@ public final class SnodeAPI { } // If we had any previous config messages then we should delete them - if !oldHashes.isEmpty { + if !allObsoleteHashes.isEmpty { requests.append( BatchRequest.Info( request: SnodeRequest( endpoint: .deleteMessages, body: DeleteMessagesRequest( - messageHashes: oldHashes, + messageHashes: allObsoleteHashes, requireSuccessfulDeletion: false, pubkey: userX25519PublicKey, ed25519PublicKey: userED25519KeyPair.publicKey, diff --git a/SessionUtilitiesKit/Database/StorageError.swift b/SessionUtilitiesKit/Database/StorageError.swift index 0112fddb1..7e48cabc5 100644 --- a/SessionUtilitiesKit/Database/StorageError.swift +++ b/SessionUtilitiesKit/Database/StorageError.swift @@ -14,6 +14,7 @@ public enum StorageError: Error { case objectNotSaved case invalidSearchPattern + case invalidData case devRemigrationRequired } diff --git a/SessionUtilitiesKit/Database/Utilities/Database+Utilities.swift b/SessionUtilitiesKit/Database/Utilities/Database+Utilities.swift index 2e09ce275..ae921fb3f 100644 --- a/SessionUtilitiesKit/Database/Utilities/Database+Utilities.swift +++ b/SessionUtilitiesKit/Database/Utilities/Database+Utilities.swift @@ -27,6 +27,29 @@ public extension Database { } } + func createIndex( + withCustomName customName: String? = nil, + on table: T.Type, + columns: [T.Columns], + options: IndexOptions = [], + condition: (any SQLExpressible)? = nil + ) throws where T: TableRecord, T: ColumnExpressible { + guard !columns.isEmpty else { throw StorageError.invalidData } + + let indexName: String = ( + customName ?? + "\(T.databaseTableName)_on_\(columns.map { $0.name }.joined(separator: "_and_"))" + ) + + try create( + index: indexName, + on: T.databaseTableName, + columns: columns.map { $0.name }, + options: options, + condition: condition + ) + } + func makeFTS5Pattern(rawPattern: String, forTable table: T.Type) throws -> FTS5Pattern where T: TableRecord, T: ColumnExpressible { return try makeFTS5Pattern(rawPattern: rawPattern, forTable: table.databaseTableName) } @@ -46,27 +69,27 @@ public extension Database { onRollback: @escaping (Database) -> Void = { _ in } ) { afterNextTransactionNestedOnce( - dedupeIdentifier: UUID().uuidString, + dedupeId: UUID().uuidString, onCommit: onCommit, onRollback: onRollback ) } func afterNextTransactionNestedOnce( - dedupeIdentifier: String, + dedupeId: String, onCommit: @escaping (Database) -> Void, onRollback: @escaping (Database) -> Void = { _ in } ) { - // Only allow a single observer per `dedupeIdentifier` per transaction, this allows us to + // Only allow a single observer per `dedupeId` per transaction, this allows us to // schedule an action to run at most once per transaction (eg. auto-scheduling a ConfigSyncJob // when receiving messages) - guard !TransactionHandler.registeredHandlers.wrappedValue.contains(dedupeIdentifier) else { + guard !TransactionHandler.registeredHandlers.wrappedValue.contains(dedupeId) else { return } add( transactionObserver: TransactionHandler( - identifier: dedupeIdentifier, + identifier: dedupeId, onCommit: onCommit, onRollback: onRollback ), diff --git a/SessionUtilitiesKit/General/Atomic.swift b/SessionUtilitiesKit/General/Atomic.swift index b5628a998..a2c537e46 100644 --- a/SessionUtilitiesKit/General/Atomic.swift +++ b/SessionUtilitiesKit/General/Atomic.swift @@ -6,23 +6,28 @@ import Foundation /// The `Atomic` wrapper is a generic wrapper providing a thread-safe way to get and set a value /// -/// A write-up on the need for this class and it's approach can be found here: +/// A write-up on the need for this class and it's approaches can be found at these links: +/// https://www.vadimbulavin.com/atomic-properties/ /// https://www.vadimbulavin.com/swift-atomic-properties-with-property-wrappers/ /// there is also another approach which can be taken but it requires separate types for collections and results in /// a somewhat inconsistent interface between different `Atomic` wrappers +/// +/// We use a Read-write lock approach because the `DispatchQueue` approach means mutating the property +/// occurs on a different thread, and GRDB requires it's changes to be executed on specific threads so using a lock +/// is more compatible (and Read-write locks allow for concurrent reads which shouldn't be a huge issue but could +/// help reduce cases of blocking) @propertyWrapper public class Atomic { - // Note: Using 'userInteractive' to ensure this can't be blockedby higher priority queues - // which could result in the main thread getting blocked - private let queue: DispatchQueue = DispatchQueue( - label: "io.oxen.\(UUID().uuidString)", - qos: .userInteractive - ) private var value: Value + private let lock: ReadWriteLock = ReadWriteLock() /// In order to change the value you **must** use the `mutate` function public var wrappedValue: Value { - return queue.sync { return value } + lock.readLock() + let result: Value = value + lock.unlock() + + return result } /// For more information see https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md#projections @@ -36,18 +41,34 @@ public class Atomic { self.value = initialValue } + public init(wrappedValue: Value) { + self.value = wrappedValue + } + // MARK: - Functions @discardableResult public func mutate(_ mutation: (inout Value) -> T) -> T { - return queue.sync { - return mutation(&value) - } + lock.writeLock() + let result: T = mutation(&value) + lock.unlock() + + return result } @discardableResult public func mutate(_ mutation: (inout Value) throws -> T) throws -> T { - return try queue.sync { - return try mutation(&value) + let result: T + + do { + lock.writeLock() + result = try mutation(&value) + lock.unlock() } + catch { + lock.unlock() + throw error + } + + return result } } @@ -56,3 +77,25 @@ extension Atomic where Value: CustomDebugStringConvertible { return value.debugDescription } } + +// MARK: - ReadWriteLock + +private class ReadWriteLock { + private var rwlock: pthread_rwlock_t = { + var rwlock = pthread_rwlock_t() + pthread_rwlock_init(&rwlock, nil) + return rwlock + }() + + func writeLock() { + pthread_rwlock_wrlock(&rwlock) + } + + func readLock() { + pthread_rwlock_rdlock(&rwlock) + } + + func unlock() { + pthread_rwlock_unlock(&rwlock) + } +} diff --git a/SessionUtilitiesKit/General/Collection+Utilities.swift b/SessionUtilitiesKit/General/Collection+Utilities.swift index f757a9a8d..0422552df 100644 --- a/SessionUtilitiesKit/General/Collection+Utilities.swift +++ b/SessionUtilitiesKit/General/Collection+Utilities.swift @@ -18,7 +18,6 @@ public extension Collection { } } - public extension Collection where Element == [CChar] { /// This creates an array of UnsafePointer types to access data of the C strings in memory. This array provides no automated /// memory management of it's children so after use you are responsible for handling the life cycle of the child elements and diff --git a/SessionUtilitiesKit/JobRunner/JobRunner.swift b/SessionUtilitiesKit/JobRunner/JobRunner.swift index c6928b245..ee18ab46a 100644 --- a/SessionUtilitiesKit/JobRunner/JobRunner.swift +++ b/SessionUtilitiesKit/JobRunner/JobRunner.swift @@ -143,7 +143,7 @@ public final class JobRunner { guard canStartJob else { return } // Start the job runner if needed - db.afterNextTransactionNestedOnce(dedupeIdentifier: "JobRunner-Start: \(updatedJob.variant)") { _ in + db.afterNextTransactionNestedOnce(dedupeId: "JobRunner-Start: \(updatedJob.variant)") { _ in queues.wrappedValue[updatedJob.variant]?.start() } } @@ -166,7 +166,7 @@ public final class JobRunner { guard canStartJob else { return } // Start the job runner if needed - db.afterNextTransactionNestedOnce(dedupeIdentifier: "JobRunner-Start: \(job.variant)") { _ in + db.afterNextTransactionNestedOnce(dedupeId: "JobRunner-Start: \(job.variant)") { _ in queues.wrappedValue[job.variant]?.start() } } From 7ee84fe0d38b8315deb8929bbdfa74feeb490c57 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 2 Mar 2023 17:52:37 +1100 Subject: [PATCH 034/135] Cleaned up a bunch of code, added pinned and hidden handling Added in logic to handle the 'hidden' state Replaced the 'Group Created' message with an empty state Cleaned up a bunch of boilerplate code --- .../ConversationVC+Interaction.swift | 16 +- Session/Conversations/ConversationVC.swift | 70 ++- .../Message Cells/InfoMessageCell.swift | 2 +- .../Settings/ThreadSettingsViewModel.swift | 3 +- Session/Home/HomeVC.swift | 21 +- Session/Home/HomeViewModel.swift | 6 +- .../New Conversation/NewConversationVC.swift | 3 +- Session/Home/New Conversation/NewDMVC.swift | 3 +- Session/Meta/SessionApp.swift | 3 +- .../Translations/de.lproj/Localizable.strings | 1 + .../Translations/en.lproj/Localizable.strings | 1 + .../Translations/es.lproj/Localizable.strings | 1 + .../Translations/fa.lproj/Localizable.strings | 1 + .../Translations/fi.lproj/Localizable.strings | 1 + .../Translations/fr.lproj/Localizable.strings | 1 + .../Translations/hi.lproj/Localizable.strings | 1 + .../Translations/hr.lproj/Localizable.strings | 1 + .../id-ID.lproj/Localizable.strings | 1 + .../Translations/it.lproj/Localizable.strings | 1 + .../Translations/ja.lproj/Localizable.strings | 1 + .../Translations/nl.lproj/Localizable.strings | 1 + .../Translations/pl.lproj/Localizable.strings | 1 + .../pt_BR.lproj/Localizable.strings | 1 + .../Translations/ru.lproj/Localizable.strings | 1 + .../Translations/si.lproj/Localizable.strings | 1 + .../Translations/sk.lproj/Localizable.strings | 1 + .../Translations/sv.lproj/Localizable.strings | 1 + .../Translations/th.lproj/Localizable.strings | 1 + .../vi-VN.lproj/Localizable.strings | 1 + .../zh-Hant.lproj/Localizable.strings | 1 + .../zh_CN.lproj/Localizable.strings | 1 + .../PushRegistrationManager.swift | 3 +- Session/Onboarding/Onboarding.swift | 3 +- Session/Settings/QRCodeVC.swift | 3 +- Session/Utilities/MockDataGenerator.swift | 30 +- .../Database/LegacyDatabase/SMKLegacy.swift | 2 - .../Migrations/_012_SharedUtilChanges.swift | 79 +-- .../Database/Models/SessionThread.swift | 62 +- .../Jobs/Types/MessageReceiveJob.swift | 31 +- .../Jobs/Types/MessageSendJob.swift | 3 - .../SessionUtil+Contacts.swift | 252 +++++--- .../SessionUtil+ConvoInfoVolatile.swift | 205 +++---- .../Config Handling/SessionUtil+Shared.swift | 238 ++++---- .../SessionUtil+UserGroups.swift | 574 +++++++++--------- .../SessionUtil+UserProfile.swift | 32 +- .../QueryInterfaceRequest+Utilities.swift | 6 +- .../libsession-util.xcframework/Info.plist | 24 +- .../ios-arm64/libsession-util.a | Bin 2080640 -> 2083072 bytes .../libsession-util.a | Bin 4583632 -> 4588408 bytes .../session/config/base.h | 4 +- .../session/config/community.h | 6 +- .../session/config/user_profile.h | 6 + .../session/config/user_profile.hpp | 11 +- SessionMessagingKit/Messages/Message.swift | 21 +- .../Open Groups/OpenGroupManager.swift | 30 +- .../MessageReceiver+Calls.swift | 23 +- .../MessageReceiver+ClosedGroups.swift | 152 +++-- ...eReceiver+DataExtractionNotification.swift | 14 +- .../MessageReceiver+ExpirationTimers.swift | 21 +- .../MessageReceiver+MessageRequests.swift | 3 +- .../MessageReceiver+TypingIndicators.swift | 16 +- .../MessageReceiver+UnsendRequests.swift | 14 +- .../MessageReceiver+VisibleMessages.swift | 31 +- .../MessageSender+ClosedGroups.swift | 201 +++--- .../Sending & Receiving/MessageReceiver.swift | 125 ++-- .../Sending & Receiving/Pollers/Poller.swift | 2 +- .../SessionThreadViewModel.swift | 2 +- .../Configs/ConfigUserGroupsSpec.swift | 6 +- .../NotificationServiceExtension.swift | 50 +- 69 files changed, 1391 insertions(+), 1043 deletions(-) diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 9bdd8c86e..b4a5b213d 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -457,7 +457,7 @@ extension ConversationVC: // Update the thread to be visible _ = try SessionThread .filter(id: threadId) - .updateAll(db, SessionThread.Columns.shouldBeVisible.set(to: true)) + .updateAllAndConfig(db, SessionThread.Columns.shouldBeVisible.set(to: true)) let authorId: String = { if let blindedId = self?.viewModel.threadData.currentUserBlindedPublicKey { @@ -588,7 +588,7 @@ extension ConversationVC: // Update the thread to be visible _ = try SessionThread .filter(id: threadId) - .updateAll(db, SessionThread.Columns.shouldBeVisible.set(to: true)) + .updateAllAndConfig(db, SessionThread.Columns.shouldBeVisible.set(to: true)) // Create the interaction let interaction: Interaction = try Interaction( @@ -1104,7 +1104,8 @@ extension ConversationVC: guard viewModel.threadData.canWrite else { return } guard SessionId.Prefix(from: sessionId) == .blinded else { Storage.shared.write { db in - try SessionThread.fetchOrCreate(db, id: sessionId, variant: .contact) + try SessionThread + .fetchOrCreate(db, id: sessionId, variant: .contact, shouldBeVisible: nil) } let conversationVC: ConversationVC = ConversationVC(threadId: sessionId, threadVariant: .contact) @@ -1130,7 +1131,12 @@ extension ConversationVC: ) return try SessionThread - .fetchOrCreate(db, id: (lookup.sessionId ?? lookup.blindedId), variant: .contact) + .fetchOrCreate( + db, + id: (lookup.sessionId ?? lookup.blindedId), + variant: .contact, + shouldBeVisible: nil + ) .id } @@ -1308,7 +1314,7 @@ extension ConversationVC: // Update the thread to be visible _ = try SessionThread .filter(id: thread.id) - .updateAll(db, SessionThread.Columns.shouldBeVisible.set(to: true)) + .updateAllAndConfig(db, SessionThread.Columns.shouldBeVisible.set(to: true)) let pendingReaction: Reaction? = { if remove { diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index 215b5674c..b7b6707a0 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -206,6 +206,35 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl return result }() + + private lazy var emptyStateLabel: UILabel = { + let text: String = String( + format: "GROUP_CONVERSATION_EMPTY_STATE".localized(), + self.viewModel.threadData.displayName + ) + + let result: UILabel = UILabel() + result.accessibilityLabel = "Empty state label" + result.translatesAutoresizingMaskIntoConstraints = false + result.font = .systemFont(ofSize: Values.verySmallFontSize) + result.attributedText = NSAttributedString(string: text) + .adding( + attributes: [.font: UIFont.boldSystemFont(ofSize: Values.verySmallFontSize)], + range: text.range(of: self.viewModel.threadData.displayName) + .map { NSRange($0, in: text) } + .defaulting(to: NSRange(location: 0, length: 0)) + ) + result.themeTextColor = .textSecondary + result.textAlignment = .center + result.lineBreakMode = .byWordWrapping + result.numberOfLines = 0 + result.isHidden = ( + self.viewModel.threadData.threadVariant != .legacyGroup && + self.viewModel.threadData.threadVariant != .group + ) + + return result + }() lazy var footerControlsStackView: UIStackView = { let result: UIStackView = UIStackView() @@ -367,8 +396,13 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl // Message requests view & scroll to bottom view.addSubview(scrollButton) + view.addSubview(emptyStateLabel) view.addSubview(messageRequestBackgroundView) view.addSubview(messageRequestStackView) + + emptyStateLabel.pin(.top, to: .top, of: view, withInset: Values.largeSpacing) + emptyStateLabel.pin(.leading, to: .leading, of: view, withInset: Values.largeSpacing) + emptyStateLabel.pin(.trailing, to: .trailing, of: view, withInset: -Values.largeSpacing) messageRequestStackView.addArrangedSubview(messageRequestBlockButton) messageRequestStackView.addArrangedSubview(messageRequestDescriptionContainerView) @@ -376,7 +410,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl messageRequestDescriptionContainerView.addSubview(messageRequestDescriptionLabel) messageRequestActionStackView.addArrangedSubview(messageRequestAcceptButton) messageRequestActionStackView.addArrangedSubview(messageRequestDeleteButton) - + scrollButton.pin(.trailing, to: .trailing, of: view, withInset: -20) messageRequestStackView.pin(.leading, to: .leading, of: view, withInset: 16) messageRequestStackView.pin(.trailing, to: .trailing, of: view, withInset: -16) @@ -618,6 +652,19 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl onlyNotifyForMentions: (updatedThreadData.threadOnlyNotifyForMentions == true), userCount: updatedThreadData.userCount ) + + // Update the empty state + let text: String = String( + format: "GROUP_CONVERSATION_EMPTY_STATE".localized(), + updatedThreadData.displayName + ) + emptyStateLabel.attributedText = NSAttributedString(string: text) + .adding( + attributes: [.font: UIFont.boldSystemFont(ofSize: Values.verySmallFontSize)], + range: text.range(of: updatedThreadData.displayName) + .map { NSRange($0, in: text) } + .defaulting(to: NSRange(location: 0, length: 0)) + ) } if @@ -718,6 +765,19 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl self.hasLoadedInitialInteractionData = true self.viewModel.updateInteractionData(updatedData) + // Update the empty state + let hasMessages: Bool = (updatedData + .filter { $0.model == .messages } + .first? + .elements + .isEmpty == false) + self.emptyStateLabel.isHidden = ( + hasMessages || ( + self.viewModel.threadData.threadVariant != .legacyGroup && + self.viewModel.threadData.threadVariant != .group + ) + ) + UIView.performWithoutAnimation { self.tableView.reloadData() self.performInitialScrollIfNeeded() @@ -726,6 +786,14 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl return } + // Update the empty state + self.emptyStateLabel.isHidden = ( + !updatedData.isEmpty || ( + self.viewModel.threadData.threadVariant != .legacyGroup && + self.viewModel.threadData.threadVariant != .group + ) + ) + // Update the ReactionListSheet (if one exists) if let messageUpdates: [MessageViewModel] = updatedData.first(where: { $0.model == .messages })?.elements { self.currentReactionListSheet?.handleInteractionUpdates(messageUpdates) diff --git a/Session/Conversations/Message Cells/InfoMessageCell.swift b/Session/Conversations/Message Cells/InfoMessageCell.swift index 7c4a90248..6f105b759 100644 --- a/Session/Conversations/Message Cells/InfoMessageCell.swift +++ b/Session/Conversations/Message Cells/InfoMessageCell.swift @@ -6,7 +6,7 @@ import SessionMessagingKit final class InfoMessageCell: MessageCell { private static let iconSize: CGFloat = 16 - private static let inset = Values.mediumSpacing + public static let inset = Values.mediumSpacing private var isHandlingLongPress: Bool = false diff --git a/Session/Conversations/Settings/ThreadSettingsViewModel.swift b/Session/Conversations/Settings/ThreadSettingsViewModel.swift index 24ccbfdf9..5f398c8fe 100644 --- a/Session/Conversations/Settings/ThreadSettingsViewModel.swift +++ b/Session/Conversations/Settings/ThreadSettingsViewModel.swift @@ -714,7 +714,8 @@ class ThreadSettingsViewModel: SessionTableViewModel Message { let result: Message = (instance ?? Message()) result.id = self.id - result.threadId = self.threadID result.sentTimestamp = self.sentTimestamp result.receivedTimestamp = self.receivedTimestamp result.recipient = self.recipient result.sender = self.sender - result.groupPublicKey = self.groupPublicKey result.openGroupServerMessageId = self.openGroupServerMessageID result.serverHash = self.serverHash diff --git a/SessionMessagingKit/Database/Migrations/_012_SharedUtilChanges.swift b/SessionMessagingKit/Database/Migrations/_012_SharedUtilChanges.swift index 612fcbc59..1c283078c 100644 --- a/SessionMessagingKit/Database/Migrations/_012_SharedUtilChanges.swift +++ b/SessionMessagingKit/Database/Migrations/_012_SharedUtilChanges.swift @@ -57,12 +57,9 @@ enum _012_SharedUtilChanges: Migration { // MARK: - Shared Data - let pinnedThreadIds: [String] = try SessionThread - .select(SessionThread.Columns.id) - .filter(SessionThread.Columns.isPinned) - .order(Column.rowID) - .asRequest(of: String.self) + let allThreads: [String: SessionThread] = try SessionThread .fetchAll(db) + .reduce(into: [:]) { result, next in result[next.id] = next } // MARK: - UserProfile Config Dump @@ -71,12 +68,12 @@ enum _012_SharedUtilChanges: Migration { secretKey: secretKey, cachedData: nil ) - let userProfileConfResult: SessionUtil.ConfResult = try SessionUtil.update( + try SessionUtil.update( profile: Profile.fetchOrCreateCurrentUser(db), in: userProfileConf ) - if userProfileConfResult.needsDump { + if config_needs_dump(userProfileConf) { try SessionUtil .createDump( conf: userProfileConf, @@ -89,6 +86,10 @@ enum _012_SharedUtilChanges: Migration { // MARK: - Contact Config Dump let contactsData: [ContactInfo] = try Contact + .filter( + Contact.Columns.isBlocked == true || + allThreads.keys.contains(Contact.Columns.id) + ) .including(optional: Contact.profile) .asRequest(of: ContactInfo.self) .fetchAll(db) @@ -98,21 +99,21 @@ enum _012_SharedUtilChanges: Migration { secretKey: secretKey, cachedData: nil ) - let contactsConfResult: SessionUtil.ConfResult = try SessionUtil.upsert( + try SessionUtil.upsert( contactData: contactsData .map { data in - ( - data.contact.id, - data.contact, - data.profile, - Int32(pinnedThreadIds.firstIndex(of: data.contact.id) ?? 0), - false + SessionUtil.SyncedContactInfo( + id: data.contact.id, + contact: data.contact, + profile: data.profile, + priority: Int32(allThreads[data.contact.id]?.pinnedPriority ?? 0), + hidden: (allThreads[data.contact.id]?.shouldBeVisible == true) ) }, in: contactsConf ) - if contactsConfResult.needsDump { + if config_needs_dump(contactsConf) { try SessionUtil .createDump( conf: contactsConf, @@ -130,15 +131,15 @@ enum _012_SharedUtilChanges: Migration { secretKey: secretKey, cachedData: nil ) - let convoInfoVolatileConfResult: SessionUtil.ConfResult = try SessionUtil.upsert( + try SessionUtil.upsert( convoInfoVolatileChanges: volatileThreadInfo, in: convoInfoVolatileConf ) - if convoInfoVolatileConfResult.needsDump { + if config_needs_dump(convoInfoVolatileConf) { try SessionUtil .createDump( - conf: contactsConf, + conf: convoInfoVolatileConf, for: .convoInfoVolatile, publicKey: userPublicKey )? @@ -155,16 +156,17 @@ enum _012_SharedUtilChanges: Migration { secretKey: secretKey, cachedData: nil ) - let userGroupConfResult1: SessionUtil.ConfResult = try SessionUtil.upsert( + try SessionUtil.upsert( legacyGroups: legacyGroupData, in: userGroupsConf ) - let userGroupConfResult2: SessionUtil.ConfResult = try SessionUtil.upsert( - communities: communityData.map { ($0, nil) }, + try SessionUtil.upsert( + communities: communityData + .map { SessionUtil.CommunityInfo(urlInfo: $0) }, in: userGroupsConf ) - if userGroupConfResult1.needsDump || userGroupConfResult2.needsDump { + if config_needs_dump(userGroupsConf) { try SessionUtil .createDump( conf: userGroupsConf, @@ -174,33 +176,18 @@ enum _012_SharedUtilChanges: Migration { .save(db) } - // MARK: - Pinned thread priorities + // MARK: - Threads - struct PinnedTeadInfo: Decodable, FetchableRecord { - let id: String - let creationDateTimestamp: TimeInterval - let maxInteractionTimestampMs: Int64? - - var targetTimestamp: Int64 { - (maxInteractionTimestampMs ?? Int64(creationDateTimestamp * 1000)) - } + try SessionUtil + .updatingThreads(db, Array(allThreads.values)) + + // MARK: - Syncing + + // Enqueue a config sync job to ensure the generated configs get synced + db.afterNextTransactionNestedOnce(dedupeId: SessionUtil.syncDedupeId(userPublicKey)) { db in + ConfigurationSyncJob.enqueue(db, publicKey: userPublicKey) } - // At the time of writing the thread sorting was 'pinned (flag), most recent interaction - // timestamp, thread creation timestamp) - let thread: TypedTableAlias = TypedTableAlias() - let interaction: TypedTableAlias = TypedTableAlias() - let pinnedThreads: [PinnedTeadInfo] = try SessionThread - .select(.id, .creationDateTimestamp) - .filter(SessionThread.Columns.isPinned == true) - .annotated(with: SessionThread.interactions.max(Interaction.Columns.timestampMs)) - .asRequest(of: PinnedTeadInfo.self) - .fetchAll(db) - .sorted { lhs, rhs in lhs.targetTimestamp > rhs.targetTimestamp } - - // Update the pinned thread priorities - try SessionUtil - .updateThreadPrioritiesIfNeeded(db, [SessionThread.Columns.pinnedPriority.set(to: 0)], []) Storage.update(progress: 1, for: self, in: target) // In case this is the last migration } diff --git a/SessionMessagingKit/Database/Models/SessionThread.swift b/SessionMessagingKit/Database/Models/SessionThread.swift index 15ff5d1ee..c78f7a426 100644 --- a/SessionMessagingKit/Database/Models/SessionThread.swift +++ b/SessionMessagingKit/Database/Models/SessionThread.swift @@ -146,43 +146,50 @@ public struct SessionThread: Codable, Identifiable, Equatable, FetchableRecord, } } -// MARK: - Mutation - -public extension SessionThread { - func with( - shouldBeVisible: Bool? = nil, - pinnedPriority: Int32? = nil - ) -> SessionThread { - return SessionThread( - id: id, - variant: variant, - creationDateTimestamp: creationDateTimestamp, - shouldBeVisible: (shouldBeVisible ?? self.shouldBeVisible), - messageDraft: messageDraft, - notificationSound: notificationSound, - mutedUntilTimestamp: mutedUntilTimestamp, - onlyNotifyForMentions: onlyNotifyForMentions, - markedAsUnread: markedAsUnread, - pinnedPriority: (pinnedPriority ?? self.pinnedPriority) - ) - } -} - // MARK: - GRDB Interactions public extension SessionThread { - /// Fetches or creates a SessionThread with the specified id and variant + /// Fetches or creates a SessionThread with the specified id, variant and visible state /// /// **Notes:** /// - The `variant` will be ignored if an existing thread is found /// - This method **will** save the newly created SessionThread to the database - static func fetchOrCreate(_ db: Database, id: ID, variant: Variant) throws -> SessionThread { + @discardableResult static func fetchOrCreate( + _ db: Database, + id: ID, + variant: Variant, + shouldBeVisible: Bool? + ) throws -> SessionThread { guard let existingThread: SessionThread = try? fetchOne(db, id: id) else { - return try SessionThread(id: id, variant: variant) - .saved(db) + return try SessionThread( + id: id, + variant: variant, + shouldBeVisible: (shouldBeVisible ?? false) + ).saved(db) } - return existingThread + // If the `shouldBeVisible` state matches then we can finish early + guard + let desiredVisibility: Bool = shouldBeVisible, + existingThread.shouldBeVisible != desiredVisibility + else { return existingThread } + + // Update the `shouldBeVisible` state + try SessionThread + .filter(id: id) + .updateAllAndConfig( + db, + SessionThread.Columns.shouldBeVisible.set(to: shouldBeVisible) + ) + + // Retrieve the updated thread and return it (we don't recursively call this method + // just in case something weird happened and the above update didn't work, as that + // would result in an infinite loop) + return (try fetchOne(db, id: id)) + .defaulting( + to: try SessionThread(id: id, variant: variant, shouldBeVisible: desiredVisibility) + .saved(db) + ) } func isMessageRequest(_ db: Database, includeNonVisible: Bool = false) -> Bool { @@ -199,6 +206,7 @@ public extension SessionThread { ) } + @available(*, unavailable, message: "should not be used until pin re-ordering is built") static func refreshPinnedPriorities(_ db: Database, adding threadId: String) throws { struct PinnedPriority: TableRecord, ColumnExpressible { public typealias Columns = CodingKeys diff --git a/SessionMessagingKit/Jobs/Types/MessageReceiveJob.swift b/SessionMessagingKit/Jobs/Types/MessageReceiveJob.swift index 664736b1b..19c865ca3 100644 --- a/SessionMessagingKit/Jobs/Types/MessageReceiveJob.swift +++ b/SessionMessagingKit/Jobs/Types/MessageReceiveJob.swift @@ -17,6 +17,7 @@ public enum MessageReceiveJob: JobExecutor { deferred: @escaping (Job) -> () ) { guard + let threadId: String = job.threadId, let detailsData: Data = job.details, let details: Details = try? JSONDecoder().decode(Details.self, from: detailsData) else { @@ -59,10 +60,11 @@ public enum MessageReceiveJob: JobExecutor { do { try MessageReceiver.handle( db, + threadId: threadId, + threadVariant: messageInfo.threadVariant, message: messageInfo.message, serverExpirationTimestamp: messageInfo.serverExpirationTimestamp, - associatedWithProto: protoContent, - openGroupId: nil + associatedWithProto: protoContent ) } catch { @@ -131,23 +133,27 @@ extension MessageReceiveJob { private enum CodingKeys: String, CodingKey { case message case variant + case threadVariant case serverExpirationTimestamp case serializedProtoData } public let message: Message public let variant: Message.Variant + public let threadVariant: SessionThread.Variant public let serverExpirationTimestamp: TimeInterval? public let serializedProtoData: Data public init( message: Message, variant: Message.Variant, + threadVariant: SessionThread.Variant, serverExpirationTimestamp: TimeInterval?, proto: SNProtoContent ) throws { self.message = message self.variant = variant + self.threadVariant = threadVariant self.serverExpirationTimestamp = serverExpirationTimestamp self.serializedProtoData = try proto.serializedData() } @@ -155,11 +161,13 @@ extension MessageReceiveJob { private init( message: Message, variant: Message.Variant, + threadVariant: SessionThread.Variant, serverExpirationTimestamp: TimeInterval?, serializedProtoData: Data ) { self.message = message self.variant = variant + self.threadVariant = threadVariant self.serverExpirationTimestamp = serverExpirationTimestamp self.serializedProtoData = serializedProtoData } @@ -177,6 +185,24 @@ extension MessageReceiveJob { self = MessageInfo( message: try variant.decode(from: container, forKey: .message), variant: variant, + threadVariant: (try? container.decode(SessionThread.Variant.self, forKey: .threadVariant)) + .defaulting(to: { + /// We used to store a 'groupPublicKey' value within the 'Message' type which was used to + /// determine the thread variant, now we just encode the variant directly but there may be + /// some legacy jobs which still have `groupPublicKey` so we have this mechanism + /// + /// **Note:** This can probably be removed a couple of releases after the user config + /// update release (ie. after June 2023) + class LegacyGroupPubkey: Codable { + let groupPublicKey: String? + } + + if (try? container.decode(LegacyGroupPubkey.self, forKey: .message))?.groupPublicKey != nil { + return .legacyGroup + } + + return .contact + }()), serverExpirationTimestamp: try? container.decode(TimeInterval.self, forKey: .serverExpirationTimestamp), serializedProtoData: try container.decode(Data.self, forKey: .serializedProtoData) ) @@ -192,6 +218,7 @@ extension MessageReceiveJob { try container.encode(message, forKey: .message) try container.encode(variant, forKey: .variant) + try container.encode(threadVariant, forKey: .threadVariant) try container.encodeIfPresent(serverExpirationTimestamp, forKey: .serverExpirationTimestamp) try container.encode(serializedProtoData, forKey: .serializedProtoData) } diff --git a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift index 0438f1314..18b990112 100644 --- a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift +++ b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift @@ -157,9 +157,6 @@ public enum MessageSendJob: JobExecutor { // Store the sentTimestamp from the message in case it fails due to a clockOutOfSync error let originalSentTimestamp: UInt64? = details.message.sentTimestamp - // Add the threadId to the message if there isn't one set - details.message.threadId = (details.message.threadId ?? job.threadId) - /// Perform the actual message sending /// /// **Note:** No need to upload attachments as part of this process as the above logic splits that out into it's own job diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift index 89e64d3c1..78178a999 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Contacts.swift @@ -6,6 +6,16 @@ import SessionUtil import SessionUtilitiesKit internal extension SessionUtil { + static let columnsRelatedToContacts: [ColumnExpression] = [ + Contact.Columns.isApproved, + Contact.Columns.isBlocked, + Contact.Columns.didApproveMe, + Profile.Columns.name, + Profile.Columns.nickname, + Profile.Columns.profilePictureUrl, + Profile.Columns.profileEncryptionKey + ] + // MARK: - Incoming Changes static func handleContactsUpdate( @@ -56,6 +66,7 @@ internal extension SessionUtil { contactData[contactId] = ( contactResult, profileResult, + contact.hidden ) contacts_iterator_advance(contactIterator) } @@ -82,9 +93,9 @@ internal extension SessionUtil { if (!data.profile.name.isEmpty && profile.name != data.profile.name) || - profile.nickname != data.profile.nickname || - profile.profilePictureUrl != data.profile.profilePictureUrl || - profile.profileEncryptionKey != data.profile.profileEncryptionKey + profile.nickname != data.profile.nickname || + profile.profilePictureUrl != data.profile.profilePictureUrl || + profile.profileEncryptionKey != data.profile.profileEncryptionKey { try profile.save(db) try Profile @@ -138,47 +149,59 @@ internal extension SessionUtil { /// If the contact's `hidden` flag doesn't match the visibility of their conversation then create/delete the /// associated contact conversation accordingly let threadExists: Bool = try SessionThread.exists(db, id: contact.id) + let threadIsVisible: Bool = try SessionThread + .filter(id: contact.id) + .select(.shouldBeVisible) + .asRequest(of: Bool.self) + .fetchOne(db) + .defaulting(to: false) - if data.isHiddenConversation && threadExists { - try SessionThread - .deleteOne(db, id: contact.id) - } - else if !data.isHiddenConversation && !threadExists { - try SessionThread(id: contact.id, variant: .contact) - .save(db) + switch (data.isHiddenConversation, threadExists, threadIsVisible) { + case (true, true, _): + try SessionThread + .filter(id: contact.id) + .deleteAll(db) + + case (false, false, _): + try SessionThread( + id: contact.id, + variant: .contact, + shouldBeVisible: true + ).save(db) + + case (false, true, false): + try SessionThread + .filter(id: contact.id) + .updateAll( // Handling a config update so don't use `updateAllAndConfig` + db, + SessionThread.Columns.shouldBeVisible.set(to: !data.isHiddenConversation) + ) + + default: break } } - } // MARK: - Outgoing Changes - typealias ContactData = ( - id: String, - contact: Contact?, - profile: Profile?, - priority: Int32?, - hidden: Bool? - ) - static func upsert( - contactData: [ContactData], + contactData: [SyncedContactInfo], in conf: UnsafeMutablePointer? - ) throws -> ConfResult { + ) throws { guard conf != nil else { throw SessionUtilError.nilConfigObject } // The current users contact data doesn't need to sync so exclude it let userPublicKey: String = getUserHexEncodedPublicKey() + let targetContacts: [SyncedContactInfo] = contactData .filter { $0.id != userPublicKey } - let targetContacts: [(id: String, contact: Contact?, profile: Profile?, priority: Int32?, hidden: Bool?)] = contactData // If we only updated the current user contact then no need to continue - guard !targetContacts.isEmpty else { return ConfResult(needsPush: false, needsDump: false) } + guard !targetContacts.isEmpty else { return } // Update the name targetContacts - .forEach { (id, maybeContact, maybeProfile, priority, hidden) in - var sessionId: [CChar] = id.cArray + .forEach { info in + var sessionId: [CChar] = info.id.cArray var contact: contacts_contact = contacts_contact() guard contacts_get_or_construct(conf, &contact, &sessionId) else { SNLog("Unable to upsert contact from Config Message") @@ -186,7 +209,7 @@ internal extension SessionUtil { } // Assign all properties to match the updated contact (if there is one) - if let updatedContact: Contact = maybeContact { + if let updatedContact: Contact = info.contact { contact.approved = updatedContact.isApproved contact.approved_me = updatedContact.didApproveMe contact.blocked = updatedContact.isBlocked @@ -197,7 +220,7 @@ internal extension SessionUtil { // Update the profile data (if there is one - users we have sent a message request to may // not have profile info in certain situations) - if let updatedProfile: Profile = maybeProfile { + if let updatedProfile: Profile = info.profile { let oldAvatarUrl: String? = String(libSessionVal: contact.profile_pic.url) let oldAvatarKey: Data? = Data( libSessionVal: contact.profile_pic.key, @@ -209,9 +232,13 @@ internal extension SessionUtil { contact.profile_pic.url = updatedProfile.profilePictureUrl.toLibSession() contact.profile_pic.key = updatedProfile.profileEncryptionKey.toLibSession() - // Download the profile picture if needed + // Download the profile picture if needed (this can be triggered within + // database reads/writes so dispatch the download to a separate queue to + // prevent blocking) if oldAvatarUrl != updatedProfile.profilePictureUrl || oldAvatarKey != updatedProfile.profileEncryptionKey { - ProfileManager.downloadAvatar(for: updatedProfile) + DispatchQueue.global(qos: .background).async { + ProfileManager.downloadAvatar(for: updatedProfile) + } } // Store the updated contact (needs to happen before variables go out of scope) @@ -219,15 +246,29 @@ internal extension SessionUtil { } // Store the updated contact (can't be sure if we made any changes above) - contact.hidden = (hidden ?? contact.hidden) - contact.priority = (priority ?? contact.priority) + contact.hidden = (info.hidden ?? contact.hidden) + contact.priority = (info.priority ?? contact.priority) contacts_set(conf, &contact) } - - return ConfResult( - needsPush: config_needs_push(conf), - needsDump: config_needs_dump(conf) - ) + } +} + +// MARK: - External Outgoing Changes + +public extension SessionUtil { + static func hide(_ db: Database, contactIds: [String]) throws { + try SessionUtil.performAndPushChange( + db, + for: .contacts, + publicKey: getUserHexEncodedPublicKey(db) + ) { conf in + // Mark the contacts as hidden + try SessionUtil.upsert( + contactData: contactIds + .map { SyncedContactInfo(id: $0, hidden: true) }, + in: conf + ) + } } } @@ -245,28 +286,43 @@ internal extension SessionUtil { guard !targetContacts.isEmpty else { return updated } do { - let atomicConf: Atomic?> = SessionUtil.config( + try SessionUtil.performAndPushChange( + db, for: .contacts, publicKey: userPublicKey - ) - - // Since we are doing direct memory manipulation we are using an `Atomic` type which has - // blocking access in it's `mutate` closure - try atomicConf.mutate { conf in - let result: ConfResult = try SessionUtil + ) { conf in + // When inserting new contacts (or contacts with invalid profile data) we want + // to add any valid profile information we have so identify if any of the updated + // contacts are new/invalid, and if so, fetch any profile data we have for them + let newContactIds: [String] = targetContacts + .compactMap { contactData -> String? in + var cContactId: [CChar] = contactData.id.cArray + var contact: contacts_contact = contacts_contact() + + guard + contacts_get(conf, &contact, &cContactId), + String(libSessionVal: contact.name, nullIfEmpty: true) != nil + else { return contactData.id } + + return nil + } + let newProfiles: [String: Profile] = try Profile + .fetchAll(db, ids: newContactIds) + .reduce(into: [:]) { result, next in result[next.id] = next } + + // Upsert the updated contact data + try SessionUtil .upsert( - contactData: targetContacts.map { ($0.id, $0, nil, nil, nil) }, + contactData: targetContacts + .map { contact in + SyncedContactInfo( + id: contact.id, + contact: contact, + profile: newProfiles[contact.id] + ) + }, in: conf ) - - // If we don't need to dump the data the we can finish early - guard result.needsDump else { return } - - try SessionUtil.createDump( - conf: conf, - for: .contacts, - publicKey: userPublicKey - )?.save(db) } } catch { @@ -304,52 +360,30 @@ internal extension SessionUtil { do { // Update the user profile first (if needed) if let updatedUserProfile: Profile = updatedProfiles.first(where: { $0.id == userPublicKey }) { - try SessionUtil - .config( - for: .userProfile, - publicKey: userPublicKey + try SessionUtil.performAndPushChange( + db, + for: .userProfile, + publicKey: userPublicKey + ) { conf in + try SessionUtil.update( + profile: updatedUserProfile, + in: conf ) - .mutate { conf in - let result: ConfResult = try SessionUtil.update( - profile: updatedUserProfile, - in: conf - ) - - // If we don't need to dump the data the we can finish early - guard result.needsDump else { return } - - try SessionUtil.createDump( - conf: conf, - for: .userProfile, - publicKey: userPublicKey - )?.save(db) - } + } } - // Since we are doing direct memory manipulation we are using an `Atomic` type which has - // blocking access in it's `mutate` closure - try SessionUtil - .config( - for: .contacts, - publicKey: userPublicKey - ) - .mutate { conf in - let result: ConfResult = try SessionUtil - .upsert( - contactData: targetProfiles - .map { ($0.id, nil, $0, nil, nil) }, - in: conf - ) - - // If we don't need to dump the data the we can finish early - guard result.needsDump else { return } - - try SessionUtil.createDump( - conf: conf, - for: .contacts, - publicKey: userPublicKey - )?.save(db) - } + try SessionUtil.performAndPushChange( + db, + for: .contacts, + publicKey: userPublicKey + ) { conf in + try SessionUtil + .upsert( + contactData: targetProfiles + .map { SyncedContactInfo(id: $0.id, profile: $0) }, + in: conf + ) + } } catch { SNLog("[libSession-util] Failed to dump updated data") @@ -358,3 +392,29 @@ internal extension SessionUtil { return updated } } + +// MARK: - SyncedContactInfo + +extension SessionUtil { + struct SyncedContactInfo { + let id: String + let contact: Contact? + let profile: Profile? + let priority: Int32? + let hidden: Bool? + + init( + id: String, + contact: Contact? = nil, + profile: Profile? = nil, + priority: Int32? = nil, + hidden: Bool? = nil + ) { + self.id = id + self.contact = contact + self.profile = profile + self.priority = priority + self.hidden = hidden + } + } +} diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift index f48abe88d..104bfc404 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+ConvoInfoVolatile.swift @@ -158,16 +158,22 @@ internal extension SessionUtil { // If there are no newer local last read timestamps then just return the mergeResult guard !newerLocalChanges.isEmpty else { return } - try upsert( - convoInfoVolatileChanges: newerLocalChanges, - in: conf - ) + try SessionUtil.performAndPushChange( + db, + for: .convoInfoVolatile, + publicKey: getUserHexEncodedPublicKey(db) + ) { conf in + try upsert( + convoInfoVolatileChanges: newerLocalChanges, + in: conf + ) + } } - @discardableResult static func upsert( + static func upsert( convoInfoVolatileChanges: [VolatileThreadInfo], in conf: UnsafeMutablePointer? - ) throws -> ConfResult { + ) throws { guard conf != nil else { throw SessionUtilError.nilConfigObject } convoInfoVolatileChanges.forEach { threadInfo in @@ -240,30 +246,23 @@ internal extension SessionUtil { } convo_info_volatile_set_community(conf, &community) - case .group: return // TODO: Need to add when the type is added to the lib. + case .group: return // TODO: Need to add when the type is added to the lib } } - - return ConfResult( - needsPush: config_needs_push(conf), - needsDump: config_needs_dump(conf) - ) } } // MARK: - Convenience internal extension SessionUtil { - @discardableResult static func updatingThreadsConvoInfoVolatile(_ db: Database, _ updated: [T]) throws -> [T] { - guard let updatedThreads: [SessionThread] = updated as? [SessionThread] else { - throw StorageError.generic - } - + static func updateMarkedAsUnreadState( + _ db: Database, + threads: [SessionThread] + ) throws { // If we have no updated threads then no need to continue - guard !updatedThreads.isEmpty else { return updated } - - let userPublicKey: String = getUserHexEncodedPublicKey(db) - let changes: [VolatileThreadInfo] = try updatedThreads.map { thread in + guard !threads.isEmpty else { return } + + let changes: [VolatileThreadInfo] = try threads.map { thread in VolatileThreadInfo( threadId: thread.id, variant: thread.variant, @@ -273,36 +272,17 @@ internal extension SessionUtil { changes: [.markedAsUnread(thread.markedAsUnread ?? false)] ) } - - do { - try SessionUtil - .config( - for: .convoInfoVolatile, - publicKey: userPublicKey - ) - .mutate { conf in - guard conf != nil else { throw SessionUtilError.nilConfigObject } - - let result: ConfResult = try upsert( - convoInfoVolatileChanges: changes, - in: conf - ) - - // If we don't need to dump the data the we can finish early - guard result.needsDump else { return } - - try SessionUtil.createDump( - conf: conf, - for: .convoInfoVolatile, - publicKey: userPublicKey - )?.save(db) - } + + try SessionUtil.performAndPushChange( + db, + for: .convoInfoVolatile, + publicKey: getUserHexEncodedPublicKey(db) + ) { conf in + try upsert( + convoInfoVolatileChanges: changes, + in: conf + ) } - catch { - SNLog("[libSession-util] Failed to dump updated data") - } - - return updated } static func syncThreadLastReadIfNeeded( @@ -311,7 +291,6 @@ internal extension SessionUtil { threadVariant: SessionThread.Variant, lastReadTimestampMs: Int64 ) throws { - let userPublicKey: String = getUserHexEncodedPublicKey(db) let change: VolatileThreadInfo = VolatileThreadInfo( threadId: threadId, variant: threadVariant, @@ -321,36 +300,15 @@ internal extension SessionUtil { changes: [.lastReadTimestampMs(lastReadTimestampMs)] ) - let needsPush: Bool = try SessionUtil - .config( - for: .convoInfoVolatile, - publicKey: userPublicKey + try SessionUtil.performAndPushChange( + db, + for: .convoInfoVolatile, + publicKey: getUserHexEncodedPublicKey(db) + ) { conf in + try upsert( + convoInfoVolatileChanges: [change], + in: conf ) - .mutate { conf in - guard conf != nil else { throw SessionUtilError.nilConfigObject } - - let result: ConfResult = try upsert( - convoInfoVolatileChanges: [change], - in: conf - ) - - // If we don't need to dump the data the we can finish early - guard result.needsDump else { return result.needsPush } - - try SessionUtil.createDump( - conf: conf, - for: .contacts, - publicKey: userPublicKey - )?.save(db) - - return result.needsPush - } - - // If we need to push then enqueue a 'ConfigurationSyncJob' - guard needsPush else { return } - - db.afterNextTransactionNestedOnce(dedupeId: SessionUtil.syncDedupeId(userPublicKey)) { db in - ConfigurationSyncJob.enqueue(db, publicKey: userPublicKey) } } @@ -361,51 +319,50 @@ internal extension SessionUtil { userPublicKey: String, openGroup: OpenGroup? ) -> Bool { - let atomicConf: Atomic?> = SessionUtil.config( - for: .convoInfoVolatile, - publicKey: userPublicKey - ) - - // If we don't have a config then just assume it's unread - guard atomicConf.wrappedValue != nil else { return false } - - // Since we are doing direct memory manipulation we are using an `Atomic` type which has - // blocking access in it's `mutate` closure - return atomicConf.mutate { conf in - switch threadVariant { - case .contact: - var cThreadId: [CChar] = threadId.cArray - var oneToOne: convo_info_volatile_1to1 = convo_info_volatile_1to1() - guard convo_info_volatile_get_1to1(conf, &oneToOne, &cThreadId) else { return false } - - return (oneToOne.last_read > timestampMs) - - case .legacyGroup: - var cThreadId: [CChar] = threadId.cArray - var legacyGroup: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() - - guard convo_info_volatile_get_legacy_group(conf, &legacyGroup, &cThreadId) else { - return false - } - - return (legacyGroup.last_read > timestampMs) - - case .community: - guard let openGroup: OpenGroup = openGroup else { return false } - - var cBaseUrl: [CChar] = openGroup.server.cArray - var cRoomToken: [CChar] = openGroup.roomToken.cArray - var convoCommunity: convo_info_volatile_community = convo_info_volatile_community() - - guard convo_info_volatile_get_community(conf, &convoCommunity, &cBaseUrl, &cRoomToken) else { - return false - } - - return (convoCommunity.last_read > timestampMs) - - case .group: return false // TODO: Need to add when the type is added to the lib + return SessionUtil + .config( + for: .convoInfoVolatile, + publicKey: userPublicKey + ) + .wrappedValue + .map { conf in + switch threadVariant { + case .contact: + var cThreadId: [CChar] = threadId.cArray + var oneToOne: convo_info_volatile_1to1 = convo_info_volatile_1to1() + guard convo_info_volatile_get_1to1(conf, &oneToOne, &cThreadId) else { + return false + } + + return (oneToOne.last_read > timestampMs) + + case .legacyGroup: + var cThreadId: [CChar] = threadId.cArray + var legacyGroup: convo_info_volatile_legacy_group = convo_info_volatile_legacy_group() + + guard convo_info_volatile_get_legacy_group(conf, &legacyGroup, &cThreadId) else { + return false + } + + return (legacyGroup.last_read > timestampMs) + + case .community: + guard let openGroup: OpenGroup = openGroup else { return false } + + var cBaseUrl: [CChar] = openGroup.server.cArray + var cRoomToken: [CChar] = openGroup.roomToken.cArray + var convoCommunity: convo_info_volatile_community = convo_info_volatile_community() + + guard convo_info_volatile_get_community(conf, &convoCommunity, &cBaseUrl, &cRoomToken) else { + return false + } + + return (convoCommunity.last_read > timestampMs) + + case .group: return false // TODO: Need to add when the type is added to the lib + } } - } + .defaulting(to: false) // If we don't have a config then just assume it's unread } } diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Shared.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Shared.swift index e60f9db4f..0590bfd69 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Shared.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+Shared.swift @@ -8,143 +8,172 @@ import SessionUtilitiesKit // MARK: - Convenience internal extension SessionUtil { + static let columnsRelatedToThreads: [ColumnExpression] = [ + SessionThread.Columns.pinnedPriority, + SessionThread.Columns.shouldBeVisible + ] + static func assignmentsRequireConfigUpdate(_ assignments: [ConfigColumnAssignment]) -> Bool { let targetColumns: Set = Set(assignments.map { ColumnKey($0.column) }) let allColumnsThatTriggerConfigUpdate: Set = [] .appending(contentsOf: columnsRelatedToUserProfile) .appending(contentsOf: columnsRelatedToContacts) .appending(contentsOf: columnsRelatedToConvoInfoVolatile) + .appending(contentsOf: columnsRelatedToUserGroups) .map { ColumnKey($0) } .asSet() return !allColumnsThatTriggerConfigUpdate.isDisjoint(with: targetColumns) } - /// This function assumes that the `pinnedPriority` values get set correctly elsewhere rather than trying to enforce - /// uniqueness in here (this means if we eventually allow for "priority grouping" this logic wouldn't change - just where the - /// priorities get updated in the HomeVC - static func updateThreadPrioritiesIfNeeded( + + static func performAndPushChange( _ db: Database, - _ assignments: [ConfigColumnAssignment], - _ updated: [T] + for variant: ConfigDump.Variant, + publicKey: String, + change: (UnsafeMutablePointer?) throws -> () ) throws { - // Note: This logic assumes that the 'pinnedPriority' values get set correctly elsewhere - // rather than trying to enforce uniqueness in here (this means if we eventually allow for - // "priority grouping" this logic wouldn't change - just where the priorities get updated - // in the HomeVC + // Since we are doing direct memory manipulation we are using an `Atomic` + // type which has blocking access in it's `mutate` closure + let needsPush: Bool = try SessionUtil + .config( + for: variant, + publicKey: publicKey + ) + .mutate { conf in + guard conf != nil else { throw SessionUtilError.nilConfigObject } + + // Peform the change + try change(conf) + + // If we don't need to dump the data the we can finish early + guard config_needs_dump(conf) else { return config_needs_push(conf) } + + try SessionUtil.createDump( + conf: conf, + for: variant, + publicKey: publicKey + )?.save(db) + + return config_needs_push(conf) + } + + // Make sure we need a push before scheduling one + guard needsPush else { return } + + db.afterNextTransactionNestedOnce(dedupeId: SessionUtil.syncDedupeId(publicKey)) { db in + ConfigurationSyncJob.enqueue(db, publicKey: publicKey) + } + } + + @discardableResult static func updatingThreads(_ db: Database, _ updated: [T]) throws -> [T] { + guard let updatedThreads: [SessionThread] = updated as? [SessionThread] else { + throw StorageError.generic + } + + // If we have no updated threads then no need to continue + guard !updatedThreads.isEmpty else { return updated } + let userPublicKey: String = getUserHexEncodedPublicKey(db) - let pinnedThreadInfo: [PriorityInfo] = try SessionThread - .select(.id, .variant, .pinnedPriority) - .asRequest(of: PriorityInfo.self) - .fetchAll(db) - let groupedPriorityInfo: [SessionThread.Variant: [PriorityInfo]] = pinnedThreadInfo + let groupedThreads: [SessionThread.Variant: [SessionThread]] = updatedThreads .grouped(by: \.variant) - let pinnedCommunities: [String: OpenGroupUrlInfo] = try OpenGroupUrlInfo - .fetchAll(db, ids: pinnedThreadInfo.map { $0.id }) + let urlInfo: [String: OpenGroupUrlInfo] = try OpenGroupUrlInfo + .fetchAll(db, ids: updatedThreads.map { $0.id }) .reduce(into: [:]) { result, next in result[next.threadId] = next } do { - try groupedPriorityInfo.forEach { variant, priorityInfo in + // Update the unread state for the threads first (just in case that's what changed) + try SessionUtil.updateMarkedAsUnreadState(db, threads: updatedThreads) + + // Then update the `hidden` and `priority` values + try groupedThreads.forEach { variant, threads in switch variant { case .contact: // If the 'Note to Self' conversation is pinned then we need to custom handle it // first as it's part of the UserProfile config - if let noteToSelfPriority: PriorityInfo = priorityInfo.first(where: { $0.id == userPublicKey }) { - let atomicConf: Atomic?> = SessionUtil.config( + if let noteToSelf: SessionThread = threads.first(where: { $0.id == userPublicKey }) { + try SessionUtil.performAndPushChange( + db, for: .userProfile, publicKey: userPublicKey - ) - - try SessionUtil.updateNoteToSelfPriority( - db, - priority: Int32(noteToSelfPriority.pinnedPriority ?? 0), - in: atomicConf - ) + ) { conf in + try SessionUtil.updateNoteToSelf( + db, + priority: noteToSelf.pinnedPriority + .map { Int32($0 == 0 ? 0 : max($0, 1)) } + .defaulting(to: 0), + hidden: noteToSelf.shouldBeVisible, + in: conf + ) + } } // Remove the 'Note to Self' convo from the list for updating contact priorities - let targetPriorities: [PriorityInfo] = priorityInfo.filter { $0.id != userPublicKey } + let remainingThreads: [SessionThread] = threads.filter { $0.id != userPublicKey } - guard !targetPriorities.isEmpty else { return } + guard !remainingThreads.isEmpty else { return } - // Since we are doing direct memory manipulation we are using an `Atomic` - // type which has blocking access in it's `mutate` closure - try SessionUtil - .config( - for: .contacts, - publicKey: userPublicKey + try SessionUtil.performAndPushChange( + db, + for: .contacts, + publicKey: userPublicKey + ) { conf in + try SessionUtil.upsert( + contactData: remainingThreads + .map { thread in + SyncedContactInfo( + id: thread.id, + priority: thread.pinnedPriority + .map { Int32($0 == 0 ? 0 : max($0, 1)) } + .defaulting(to: 0), + hidden: thread.shouldBeVisible + ) + }, + in: conf ) - .mutate { conf in - let result: ConfResult = try SessionUtil.upsert( - contactData: targetPriorities - .map { ($0.id, nil, nil, Int32($0.pinnedPriority ?? 0), nil) }, - in: conf - ) - - // If we don't need to dump the data the we can finish early - guard result.needsDump else { return } - - try SessionUtil.createDump( - conf: conf, - for: .contacts, - publicKey: userPublicKey - )?.save(db) - } + } case .community: - // Since we are doing direct memory manipulation we are using an `Atomic` - // type which has blocking access in it's `mutate` closure - try SessionUtil - .config( - for: .userGroups, - publicKey: userPublicKey + try SessionUtil.performAndPushChange( + db, + for: .userGroups, + publicKey: userPublicKey + ) { conf in + try SessionUtil.upsert( + communities: threads + .compactMap { thread -> CommunityInfo? in + urlInfo[thread.id].map { urlInfo in + CommunityInfo( + urlInfo: urlInfo, + priority: thread.pinnedPriority + .map { Int32($0 == 0 ? 0 : max($0, 1)) } + .defaulting(to: 0) + ) + } + }, + in: conf ) - .mutate { conf in - let result: ConfResult = try SessionUtil.upsert( - communities: priorityInfo - .compactMap { info in - guard let communityInfo: OpenGroupUrlInfo = pinnedCommunities[info.id] else { - return nil - } - - return (communityInfo, info.pinnedPriority) - }, - in: conf - ) - - // If we don't need to dump the data the we can finish early - guard result.needsDump else { return } - - try SessionUtil.createDump( - conf: conf, - for: .userGroups, - publicKey: userPublicKey - )?.save(db) - } + } case .legacyGroup: - // Since we are doing direct memory manipulation we are using an `Atomic` - // type which has blocking access in it's `mutate` closure - try SessionUtil - .config( - for: .userGroups, - publicKey: userPublicKey + try SessionUtil.performAndPushChange( + db, + for: .userGroups, + publicKey: userPublicKey + ) { conf in + try SessionUtil.upsert( + legacyGroups: threads + .map { thread in + LegacyGroupInfo( + id: thread.id, + hidden: thread.shouldBeVisible, + priority: thread.pinnedPriority + .map { Int32($0 == 0 ? 0 : max($0, 1)) } + .defaulting(to: 0) + ) + }, + in: conf ) - .mutate { conf in - let result: ConfResult = try SessionUtil.upsert( - legacyGroups: priorityInfo - .map { LegacyGroupInfo(id: $0.id, priority: $0.pinnedPriority) }, - in: conf - ) - - // If we don't need to dump the data the we can finish early - guard result.needsDump else { return } - - try SessionUtil.createDump( - conf: conf, - for: .userGroups, - publicKey: userPublicKey - )?.save(db) - } + } case .group: // TODO: Add this @@ -155,6 +184,8 @@ internal extension SessionUtil { catch { SNLog("[libSession-util] Failed to dump updated data") } + + return updated } } @@ -184,12 +215,13 @@ internal extension SessionUtil { } } -// MARK: - Pinned Priority +// MARK: - PriorityVisibilityInfo extension SessionUtil { - struct PriorityInfo: Codable, FetchableRecord, Identifiable { + struct PriorityVisibilityInfo: Codable, FetchableRecord, Identifiable { let id: String let variant: SessionThread.Variant let pinnedPriority: Int32? + let shouldBeVisible: Bool } } diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift index 084e2b9fe..e47c03aa4 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserGroups.swift @@ -8,8 +8,12 @@ import SessionUtilitiesKit import SessionSnodeKit // TODO: Expose 'GROUP_NAME_MAX_LENGTH', 'COMMUNITY_URL_MAX_LENGTH' & 'COMMUNITY_ROOM_MAX_LENGTH' internal extension SessionUtil { + static let columnsRelatedToUserGroups: [ColumnExpression] = [ + ClosedGroup.Columns.name + ] + // MARK: - Incoming Changes - + static func handleGroupsUpdate( _ db: Database, in conf: UnsafeMutablePointer?, @@ -20,12 +24,12 @@ internal extension SessionUtil { guard conf != nil else { throw SessionUtilError.nilConfigObject } var communities: [PrioritisedData] = [] - var legacyGroups: [PrioritisedData] = [] + var legacyGroups: [LegacyGroupInfo] = [] var groups: [PrioritisedData] = [] var community: ugroups_community_info = ugroups_community_info() var legacyGroup: ugroups_legacy_group_info = ugroups_legacy_group_info() let groupsIterator: OpaquePointer = user_groups_iterator_new(conf) - + while !user_groups_iterator_done(groupsIterator) { if user_groups_it_is_community(groupsIterator, &community) { let server: String = String(libSessionVal: community.base_url) @@ -48,36 +52,52 @@ internal extension SessionUtil { } else if user_groups_it_is_legacy_group(groupsIterator, &legacyGroup) { let groupId: String = String(libSessionVal: legacyGroup.session_id) + let membersIt: OpaquePointer = ugroups_legacy_members_begin(&legacyGroup) + var members: [String: Bool] = [:] + var maybeMemberSessionId: UnsafePointer? = nil + var memberAdmin: Bool = false + + while ugroups_legacy_members_next(membersIt, &maybeMemberSessionId, &memberAdmin) { + guard let memberSessionId: UnsafePointer = maybeMemberSessionId else { + continue + } + + members[String(cString: memberSessionId)] = memberAdmin + } legacyGroups.append( - PrioritisedData( - data: LegacyGroupInfo( - id: groupId, - name: String(libSessionVal: legacyGroup.name), - lastKeyPair: ClosedGroupKeyPair( - threadId: groupId, - publicKey: Data( - libSessionVal: legacyGroup.enc_pubkey, - count: ClosedGroup.pubkeyByteLength - ), - secretKey: Data( - libSessionVal: legacyGroup.enc_seckey, - count: ClosedGroup.secretKeyByteLength - ), - receivedTimestamp: (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) + LegacyGroupInfo( + id: groupId, + name: String(libSessionVal: legacyGroup.name), + lastKeyPair: ClosedGroupKeyPair( + threadId: groupId, + publicKey: Data( + libSessionVal: legacyGroup.enc_pubkey, + count: ClosedGroup.pubkeyByteLength ), - disappearingConfig: DisappearingMessagesConfiguration - .defaultWith(groupId) - .with( - // TODO: double check the 'isEnabled' flag - isEnabled: (legacyGroup.disappearing_timer > 0), - durationSeconds: (legacyGroup.disappearing_timer == 0 ? nil : - TimeInterval(legacyGroup.disappearing_timer) - ) - ), - groupMembers: [], //[GroupMember] // TODO: This - hidden: legacyGroup.hidden + secretKey: Data( + libSessionVal: legacyGroup.enc_seckey, + count: ClosedGroup.secretKeyByteLength + ), + receivedTimestamp: (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000) ), + disappearingConfig: DisappearingMessagesConfiguration + .defaultWith(groupId) + .with( + isEnabled: (legacyGroup.disappearing_timer > 0), + durationSeconds: (legacyGroup.disappearing_timer == 0 ? nil : + TimeInterval(legacyGroup.disappearing_timer) + ) + ), + groupMembers: members.map { memberId, admin in + GroupMember( + groupId: groupId, + profileId: memberId, + role: (admin ? .admin : .standard), + isHidden: false + ) + }, + hidden: legacyGroup.hidden, priority: legacyGroup.priority ) ) @@ -89,13 +109,13 @@ internal extension SessionUtil { user_groups_iterator_advance(groupsIterator) } user_groups_iterator_free(groupsIterator) // Need to free the iterator - + // If we don't have any conversations then no need to continue guard !communities.isEmpty || !legacyGroups.isEmpty || !groups.isEmpty else { return } // Extract all community/legacyGroup/group thread priorities - let existingThreadPriorities: [String: PriorityInfo] = (try? SessionThread - .select(.id, .variant, .pinnedPriority) + let existingThreadInfo: [String: PriorityVisibilityInfo] = (try? SessionThread + .select(.id, .variant, .pinnedPriority, .shouldBeVisible) .filter( [ SessionThread.Variant.community, @@ -103,7 +123,7 @@ internal extension SessionUtil { SessionThread.Variant.group ].contains(SessionThread.Columns.variant) ) - .asRequest(of: PriorityInfo.self) + .asRequest(of: PriorityVisibilityInfo.self) .fetchAll(db)) .defaulting(to: []) .reduce(into: [:]) { result, next in result[next.id] = next } @@ -121,10 +141,10 @@ internal extension SessionUtil { calledFromConfigHandling: true ) .sinkUntilComplete() - + // Set the priority if it's changed (new communities will have already been inserted at // this stage) - if existingThreadPriorities[community.data.threadId]?.pinnedPriority != community.priority { + if existingThreadInfo[community.data.threadId]?.pinnedPriority != community.priority { _ = try? SessionThread .filter(id: community.data.threadId) .updateAll( // Handling a config update so don't use `updateAllAndConfig` @@ -135,7 +155,7 @@ internal extension SessionUtil { } // Remove any communities which are no longer in the config - let communityIdsToRemove: Set = Set(existingThreadPriorities + let communityIdsToRemove: Set = Set(existingThreadInfo .filter { $0.value.variant == .community } .keys) .subtracting(communities.map { $0.data.threadId }) @@ -150,22 +170,31 @@ internal extension SessionUtil { // MARK: -- Handle Legacy Group Changes - let existingLegacyGroupIds: Set = Set(existingThreadPriorities + let existingLegacyGroupIds: Set = Set(existingThreadInfo .filter { $0.value.variant == .legacyGroup } .keys) + let existingLegacyGroups: [String: ClosedGroup] = (try? ClosedGroup + .fetchAll(db, ids: existingLegacyGroupIds)) + .defaulting(to: []) + .reduce(into: [:]) { result, next in result[next.id] = next } + let existingLegacyGroupMembers: [String: [GroupMember]] = (try? GroupMember + .filter(existingLegacyGroupIds.contains(GroupMember.Columns.groupId)) + .fetchAll(db)) + .defaulting(to: []) + .grouped(by: \.groupId) try legacyGroups.forEach { group in guard - let name: String = group.data.name, - let lastKeyPair: ClosedGroupKeyPair = group.data.lastKeyPair, - let members: [GroupMember] = group.data.groupMembers + let name: String = group.name, + let lastKeyPair: ClosedGroupKeyPair = group.lastKeyPair, + let members: [GroupMember] = group.groupMembers else { return } - if !existingLegacyGroupIds.contains(group.data.id) { + if !existingLegacyGroupIds.contains(group.id) { // Add a new group if it doesn't already exist try MessageReceiver.handleNewClosedGroup( db, - groupPublicKey: group.data.id, + groupPublicKey: group.id, name: name, encryptionKeyPair: Box.KeyPair( publicKey: lastKeyPair.publicKey.bytes, @@ -177,20 +206,22 @@ internal extension SessionUtil { admins: members .filter { $0.role == .admin } .map { $0.profileId }, - expirationTimer: UInt32(group.data.disappearingConfig?.durationSeconds ?? 0), + expirationTimer: UInt32(group.disappearingConfig?.durationSeconds ?? 0), messageSentTimestamp: UInt64(latestConfigUpdateSentTimestamp * 1000) ) } else { // Otherwise update the existing group - _ = try? ClosedGroup - .filter(id: group.data.id) - .updateAll( // Handling a config update so don't use `updateAllAndConfig` - db, - ClosedGroup.Columns.name.set(to: name) - ) + if existingLegacyGroups[group.id]?.name != name { + _ = try? ClosedGroup + .filter(id: group.id) + .updateAll( // Handling a config update so don't use `updateAllAndConfig` + db, + ClosedGroup.Columns.name.set(to: name) + ) + } - // Update the lastKey + // Add the lastKey if it doesn't already exist let keyPairExists: Bool = ClosedGroupKeyPair .filter( ClosedGroupKeyPair.Columns.threadId == lastKeyPair.threadId && @@ -205,12 +236,11 @@ internal extension SessionUtil { // Update the disappearing messages timer _ = try DisappearingMessagesConfiguration - .fetchOne(db, id: group.data.id) - .defaulting(to: DisappearingMessagesConfiguration.defaultWith(group.data.id)) + .fetchOne(db, id: group.id) + .defaulting(to: DisappearingMessagesConfiguration.defaultWith(group.id)) .with( - // TODO: double check the 'isEnabled' flag - isEnabled: (group.data.disappearingConfig?.isEnabled == true), - durationSeconds: group.data.disappearingConfig?.durationSeconds + isEnabled: (group.disappearingConfig?.isEnabled == true), + durationSeconds: group.disappearingConfig?.durationSeconds ) .saved(db) @@ -221,23 +251,35 @@ internal extension SessionUtil { // let admins: [String] } - // TODO: 'hidden' flag - just toggle the 'shouldBeVisible' flag? Delete messages as well??? - + // Make any thread-specific changes + var threadChanges: [ConfigColumnAssignment] = [] + // Set the visibility if it's changed + if existingThreadInfo[group.id]?.shouldBeVisible != (group.hidden == false) { + threadChanges.append( + SessionThread.Columns.shouldBeVisible.set(to: (group.hidden == false)) + ) + } // Set the priority if it's changed - if existingThreadPriorities[group.data.id]?.pinnedPriority != group.priority { + if existingThreadInfo[group.id]?.pinnedPriority != group.priority { + threadChanges.append( + SessionThread.Columns.pinnedPriority.set(to: group.priority) + ) + } + + if !threadChanges.isEmpty { _ = try? SessionThread - .filter(id: group.data.id) + .filter(id: group.id) .updateAll( // Handling a config update so don't use `updateAllAndConfig` db, - SessionThread.Columns.pinnedPriority.set(to: group.priority) + threadChanges ) } } // Remove any legacy groups which are no longer in the config let legacyGroupIdsToRemove: Set = existingLegacyGroupIds - .subtracting(legacyGroups.map { $0.data.id }) + .subtracting(legacyGroups.map { $0.id }) if !legacyGroupIdsToRemove.isEmpty { try ClosedGroup.removeKeysAndUnsubscribe( @@ -251,16 +293,16 @@ internal extension SessionUtil { // MARK: -- Handle Group Changes // TODO: Add this } - + // MARK: - Outgoing Changes static func upsert( legacyGroups: [LegacyGroupInfo], in conf: UnsafeMutablePointer? - ) throws -> ConfResult { + ) throws { guard conf != nil else { throw SessionUtilError.nilConfigObject } - guard !legacyGroups.isEmpty else { return ConfResult(needsPush: false, needsDump: false) } - + guard !legacyGroups.isEmpty else { return } + // Since we are doing direct memory manipulation we are using an `Atomic` type which has // blocking access in it's `mutate` closure legacyGroups @@ -268,13 +310,12 @@ internal extension SessionUtil { var cGroupId: [CChar] = legacyGroup.id.cArray let userGroup: UnsafeMutablePointer = user_groups_get_or_construct_legacy_group(conf, &cGroupId) - // Assign all properties to match the updated group (if there is one) if let updatedName: String = legacyGroup.name { userGroup.pointee.name = updatedName.toLibSession() // Store the updated group (needs to happen before variables go out of scope) - user_groups_set_legacy_group(conf, &userGroup) + user_groups_set_legacy_group(conf, userGroup) } if let lastKeyPair: ClosedGroupKeyPair = legacyGroup.lastKeyPair { @@ -282,7 +323,7 @@ internal extension SessionUtil { userGroup.pointee.enc_seckey = lastKeyPair.secretKey.toLibSession() // Store the updated group (needs to happen before variables go out of scope) - user_groups_set_legacy_group(conf, &userGroup) + user_groups_set_legacy_group(conf, userGroup) } // Assign all properties to match the updated disappearing messages config (if there is one) @@ -291,10 +332,21 @@ internal extension SessionUtil { userGroup.pointee.disappearing_timer = (!updatedConfig.isEnabled ? 0 : Int64(floor(updatedConfig.durationSeconds)) ) + + user_groups_set_legacy_group(conf, userGroup) + } + + // Add the group members and admins + legacyGroup.groupMembers?.forEach { member in + var cProfileId: [CChar] = member.profileId.cArray + ugroups_legacy_member_add(userGroup, &cProfileId, false) + } + + legacyGroup.groupAdmins?.forEach { member in + var cProfileId: [CChar] = member.profileId.cArray + ugroups_legacy_member_add(userGroup, &cProfileId, true) } - // TODO: Need to add members/admins - // Store the updated group (can't be sure if we made any changes above) userGroup.pointee.hidden = (legacyGroup.hidden ?? userGroup.pointee.hidden) userGroup.pointee.priority = (legacyGroup.priority ?? userGroup.pointee.priority) @@ -302,25 +354,20 @@ internal extension SessionUtil { // Note: Need to free the legacy group pointer user_groups_set_free_legacy_group(conf, userGroup) } - - return ConfResult( - needsPush: config_needs_push(conf), - needsDump: config_needs_dump(conf) - ) } - + static func upsert( - communities: [(info: OpenGroupUrlInfo, priority: Int32?)], + communities: [CommunityInfo], in conf: UnsafeMutablePointer? - ) throws -> ConfResult { + ) throws { guard conf != nil else { throw SessionUtilError.nilConfigObject } - guard !communities.isEmpty else { return ConfResult.init(needsPush: false, needsDump: false) } - + guard !communities.isEmpty else { return } + communities - .forEach { info, priority in - var cBaseUrl: [CChar] = info.server.cArray - var cRoom: [CChar] = info.roomToken.cArray - var cPubkey: [UInt8] = Data(hex: info.publicKey).cArray + .forEach { community in + var cBaseUrl: [CChar] = community.urlInfo.server.cArray + var cRoom: [CChar] = community.urlInfo.roomToken.cArray + var cPubkey: [UInt8] = Data(hex: community.urlInfo.publicKey).cArray var userCommunity: ugroups_community_info = ugroups_community_info() guard user_groups_get_or_construct_community(conf, &userCommunity, &cBaseUrl, &cRoom, &cPubkey) else { @@ -328,15 +375,15 @@ internal extension SessionUtil { return } - userCommunity.priority = (priority ?? userCommunity.priority) + userCommunity.priority = (community.priority ?? userCommunity.priority) user_groups_set_community(conf, &userCommunity) } - - return ConfResult( - needsPush: config_needs_push(conf), - needsDump: config_needs_dump(conf) - ) } +} + +// MARK: - External Outgoing Changes + +public extension SessionUtil { // MARK: -- Communities @@ -346,91 +393,38 @@ internal extension SessionUtil { rootToken: String, publicKey: String ) throws { - let userPublicKey: String = getUserHexEncodedPublicKey(db) - - // Since we are doing direct memory manipulation we are using an `Atomic` type which has - // blocking access in it's `mutate` closure - let needsPush: Bool = try SessionUtil - .config( - for: .userGroups, - publicKey: userPublicKey - ) - .mutate { conf in - guard conf != nil else { throw SessionUtilError.nilConfigObject } - - let result: ConfResult = try SessionUtil.upsert( - communities: [ - ( - OpenGroupUrlInfo( - threadId: OpenGroup.idFor(roomToken: rootToken, server: server), - server: server, - roomToken: rootToken, - publicKey: publicKey - ), - nil + try SessionUtil.performAndPushChange( + db, + for: .userGroups, + publicKey: getUserHexEncodedPublicKey(db) + ) { conf in + try SessionUtil.upsert( + communities: [ + CommunityInfo( + urlInfo: OpenGroupUrlInfo( + threadId: OpenGroup.idFor(roomToken: rootToken, server: server), + server: server, + roomToken: rootToken, + publicKey: publicKey ) - ], - in: conf - ) - - // If we don't need to dump the data the we can finish early - guard result.needsDump else { return result.needsPush } - - try SessionUtil.createDump( - conf: conf, - for: .userGroups, - publicKey: userPublicKey - )?.save(db) - - return result.needsPush - } - - // Make sure we need a push before scheduling one - guard needsPush else { return } - - db.afterNextTransactionNestedOnce(dedupeId: SessionUtil.syncDedupeId(userPublicKey)) { db in - ConfigurationSyncJob.enqueue(db, publicKey: userPublicKey) + ) + ], + in: conf + ) } } static func remove(_ db: Database, server: String, roomToken: String) throws { - let userPublicKey: String = getUserHexEncodedPublicKey(db) - - // Since we are doing direct memory manipulation we are using an `Atomic` type which has - // blocking access in it's `mutate` closure - let needsPush: Bool = try SessionUtil - .config( - for: .userGroups, - publicKey: userPublicKey - ) - .mutate { conf in - guard conf != nil else { throw SessionUtilError.nilConfigObject } - - var cBaseUrl: [CChar] = server.cArray - var cRoom: [CChar] = roomToken.cArray - - // Don't care if the community doesn't exist - user_groups_erase_community(conf, &cBaseUrl, &cRoom) - - let needsPush: Bool = config_needs_push(conf) - - // If we don't need to dump the data the we can finish early - guard config_needs_dump(conf) else { return needsPush } - - try SessionUtil.createDump( - conf: conf, - for: .userGroups, - publicKey: userPublicKey - )?.save(db) - - return needsPush - } - - // Make sure we need a push before scheduling one - guard needsPush else { return } - - db.afterNextTransactionNestedOnce(dedupeId: SessionUtil.syncDedupeId(userPublicKey)) { db in - ConfigurationSyncJob.enqueue(db, publicKey: userPublicKey) + try SessionUtil.performAndPushChange( + db, + for: .userGroups, + publicKey: getUserHexEncodedPublicKey(db) + ) { conf in + var cBaseUrl: [CChar] = server.cArray + var cRoom: [CChar] = roomToken.cArray + + // Don't care if the community doesn't exist + user_groups_erase_community(conf, &cBaseUrl, &cRoom) } } @@ -446,116 +440,121 @@ internal extension SessionUtil { members: Set, admins: Set ) throws { - let userPublicKey: String = getUserHexEncodedPublicKey(db) - - // Since we are doing direct memory manipulation we are using an `Atomic` type which has - // blocking access in it's `mutate` closure - let needsPush: Bool = try SessionUtil - .config( - for: .userGroups, - publicKey: userPublicKey - ) - .mutate { conf in - guard conf != nil else { throw SessionUtilError.nilConfigObject } - - let result: ConfResult = try SessionUtil.upsert( - legacyGroups: [ - LegacyGroupInfo( - id: groupPublicKey, - name: name, - lastKeyPair: ClosedGroupKeyPair( - threadId: groupPublicKey, - publicKey: latestKeyPairPublicKey, - secretKey: latestKeyPairSecretKey, - receivedTimestamp: latestKeyPairReceivedTimestamp - ), - groupMembers: members - .map { memberId in - GroupMember( - groupId: groupPublicKey, - profileId: memberId, - role: .standard, - isHidden: false - ) - } - .appending( - contentsOf: admins - .map { memberId in - GroupMember( - groupId: groupPublicKey, - profileId: memberId, - role: .admin, - isHidden: false - ) - } + try SessionUtil.performAndPushChange( + db, + for: .userGroups, + publicKey: getUserHexEncodedPublicKey(db) + ) { conf in + try SessionUtil.upsert( + legacyGroups: [ + LegacyGroupInfo( + id: groupPublicKey, + name: name, + lastKeyPair: ClosedGroupKeyPair( + threadId: groupPublicKey, + publicKey: latestKeyPairPublicKey, + secretKey: latestKeyPairSecretKey, + receivedTimestamp: latestKeyPairReceivedTimestamp + ), + groupMembers: members + .map { memberId in + GroupMember( + groupId: groupPublicKey, + profileId: memberId, + role: .standard, + isHidden: false ) - ) - ], - in: conf - ) - - // If we don't need to dump the data the we can finish early - guard result.needsDump else { return result.needsPush } - - try SessionUtil.createDump( - conf: conf, - for: .userGroups, - publicKey: userPublicKey - )?.save(db) - - return result.needsPush - } - - // Make sure we need a push before scheduling one - guard needsPush else { return } - - db.afterNextTransactionNestedOnce(dedupeId: SessionUtil.syncDedupeId(userPublicKey)) { db in - ConfigurationSyncJob.enqueue(db, publicKey: userPublicKey) + }, + groupAdmins: admins + .map { memberId in + GroupMember( + groupId: groupPublicKey, + profileId: memberId, + role: .admin, + isHidden: false + ) + } + ) + ], + in: conf + ) + } + } + + static func update( + _ db: Database, + groupPublicKey: String, + name: String? = nil, + latestKeyPair: ClosedGroupKeyPair? = nil, + members: Set? = nil, + admins: Set? = nil + ) throws { + try SessionUtil.performAndPushChange( + db, + for: .userGroups, + publicKey: getUserHexEncodedPublicKey(db) + ) { conf in + try SessionUtil.upsert( + legacyGroups: [ + LegacyGroupInfo( + id: groupPublicKey, + name: name, + lastKeyPair: latestKeyPair, + groupMembers: members? + .map { memberId in + GroupMember( + groupId: groupPublicKey, + profileId: memberId, + role: .standard, + isHidden: false + ) + }, + groupAdmins: admins? + .map { memberId in + GroupMember( + groupId: groupPublicKey, + profileId: memberId, + role: .admin, + isHidden: false + ) + } + ) + ], + in: conf + ) } } static func hide(_ db: Database, legacyGroupIds: [String]) throws { + try SessionUtil.performAndPushChange( + db, + for: .userGroups, + publicKey: getUserHexEncodedPublicKey(db) + ) { conf in + try SessionUtil.upsert( + legacyGroups: legacyGroupIds.map { groupId in + LegacyGroupInfo( + id: groupId, + hidden: true + ) + }, + in: conf + ) + } } static func remove(_ db: Database, legacyGroupIds: [String]) throws { - let userPublicKey: String = getUserHexEncodedPublicKey(db) - - // Since we are doing direct memory manipulation we are using an `Atomic` type which has - // blocking access in it's `mutate` closure - let needsPush: Bool = try SessionUtil - .config( - for: .userGroups, - publicKey: userPublicKey - ) - .mutate { conf in - guard conf != nil else { throw SessionUtilError.nilConfigObject } + try SessionUtil.performAndPushChange( + db, + for: .userGroups, + publicKey: getUserHexEncodedPublicKey(db) + ) { conf in + legacyGroupIds.forEach { threadId in + var cGroupId: [CChar] = threadId.cArray - legacyGroupIds.forEach { threadId in - var cGroupId: [CChar] = threadId.cArray - - // Don't care if the group doesn't exist - user_groups_erase_legacy_group(conf, &cGroupId) - } - - let needsPush: Bool = config_needs_push(conf) - - // If we don't need to dump the data the we can finish early - guard config_needs_dump(conf) else { return needsPush } - - try SessionUtil.createDump( - conf: conf, - for: .userGroups, - publicKey: userPublicKey - )?.save(db) - - return needsPush + // Don't care if the group doesn't exist + user_groups_erase_legacy_group(conf, &cGroupId) } - - // Make sure we need a push before scheduling one - guard needsPush else { return } - - db.afterNextTransactionNestedOnce(dedupeId: SessionUtil.syncDedupeId(userPublicKey)) { db in - ConfigurationSyncJob.enqueue(db, publicKey: userPublicKey) } } @@ -568,12 +567,6 @@ internal extension SessionUtil { } } - } - - return updated - } -} - // MARK: - LegacyGroupInfo extension SessionUtil { @@ -585,6 +578,7 @@ extension SessionUtil { case lastKeyPair case disappearingConfig case groupMembers + case groupAdmins case hidden case priority } @@ -596,6 +590,7 @@ extension SessionUtil { let lastKeyPair: ClosedGroupKeyPair? let disappearingConfig: DisappearingMessagesConfiguration? let groupMembers: [GroupMember]? + let groupAdmins: [GroupMember]? let hidden: Bool? let priority: Int32? @@ -605,6 +600,7 @@ extension SessionUtil { lastKeyPair: ClosedGroupKeyPair? = nil, disappearingConfig: DisappearingMessagesConfiguration? = nil, groupMembers: [GroupMember]? = nil, + groupAdmins: [GroupMember]? = nil, hidden: Bool? = nil, priority: Int32? = nil ) { @@ -613,6 +609,7 @@ extension SessionUtil { self.lastKeyPair = lastKeyPair self.disappearingConfig = disappearingConfig self.groupMembers = groupMembers + self.groupAdmins = groupAdmins self.hidden = hidden self.priority = priority } @@ -625,7 +622,17 @@ extension SessionUtil { .order(ClosedGroupKeyPair.Columns.receivedTimestamp.desc) .forKey(Columns.lastKeyPair.name) ) - .including(all: ClosedGroup.members) + .including( + all: ClosedGroup.members + .filter([GroupMember.Role.standard, GroupMember.Role.zombie] + .contains(GroupMember.Columns.role)) + .forKey(Columns.groupMembers.name) + ) + .including( + all: ClosedGroup.members + .filter(GroupMember.Columns.role == GroupMember.Role.admin) + .forKey(Columns.groupAdmins.name) + ) .joining( optional: ClosedGroup.thread .including( @@ -638,6 +645,19 @@ extension SessionUtil { } } + struct CommunityInfo { + let urlInfo: OpenGroupUrlInfo + let priority: Int32? + + init( + urlInfo: OpenGroupUrlInfo, + priority: Int32? = nil + ) { + self.urlInfo = urlInfo + self.priority = priority + } + } + fileprivate struct GroupThreadData { let communities: [PrioritisedData] let legacyGroups: [PrioritisedData] diff --git a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift index de9926f91..dfd1b6540 100644 --- a/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift +++ b/SessionMessagingKit/LibSessionUtil/Config Handling/SessionUtil+UserProfile.swift @@ -76,7 +76,7 @@ internal extension SessionUtil { static func update( profile: Profile, in conf: UnsafeMutablePointer? - ) throws -> ConfResult { + ) throws { guard conf != nil else { throw SessionUtilError.nilConfigObject } // Update the name @@ -88,35 +88,17 @@ internal extension SessionUtil { profilePic.url = profile.profilePictureUrl.toLibSession() profilePic.key = profile.profileEncryptionKey.toLibSession() user_profile_set_pic(conf, profilePic) - - return ConfResult( - needsPush: config_needs_push(conf), - needsDump: config_needs_dump(conf) - ) } - static func updateNoteToSelfPriority( + static func updateNoteToSelf( _ db: Database, priority: Int32, - in atomicConf: Atomic?> + hidden: Bool, + in conf: UnsafeMutablePointer? ) throws { - guard atomicConf.wrappedValue != nil else { throw SessionUtilError.nilConfigObject } + guard conf != nil else { throw SessionUtilError.nilConfigObject } - let userPublicKey: String = getUserHexEncodedPublicKey(db) - - // Since we are doing direct memory manipulation we are using an `Atomic` type which has - // blocking access in it's `mutate` closure - try atomicConf.mutate { conf in - user_profile_set_nts_priority(conf, priority) - - // If we don't need to dump the data the we can finish early - guard config_needs_dump(conf) else { return } - - try SessionUtil.createDump( - conf: conf, - for: .userProfile, - publicKey: userPublicKey - )?.save(db) - } + user_profile_set_nts_priority(conf, priority) + user_profile_set_nts_hidden(conf, hidden) } } diff --git a/SessionMessagingKit/LibSessionUtil/Database/QueryInterfaceRequest+Utilities.swift b/SessionMessagingKit/LibSessionUtil/Database/QueryInterfaceRequest+Utilities.swift index bded18a07..8c714cb5b 100644 --- a/SessionMessagingKit/LibSessionUtil/Database/QueryInterfaceRequest+Utilities.swift +++ b/SessionMessagingKit/LibSessionUtil/Database/QueryInterfaceRequest+Utilities.swift @@ -130,9 +130,11 @@ public extension QueryInterfaceRequest where RowDecoder: FetchableRecord & Table case is QueryInterfaceRequest: return try SessionUtil.updatingProfiles(db, updatedData) - case is QueryInterfaceRequest: + case is QueryInterfaceRequest: return updatedData - + + case is QueryInterfaceRequest: + return try SessionUtil.updatingThreads(db, updatedData) default: return updatedData } diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/Info.plist b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/Info.plist index 97310ed4d..c9a2d9b3e 100644 --- a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/Info.plist +++ b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/Info.plist @@ -4,6 +4,18 @@ AvailableLibraries + + LibraryIdentifier + ios-arm64 + LibraryPath + libsession-util.a + SupportedArchitectures + + arm64 + + SupportedPlatform + ios + LibraryIdentifier ios-arm64_x86_64-simulator @@ -19,18 +31,6 @@ SupportedPlatformVariant simulator - - LibraryIdentifier - ios-arm64 - LibraryPath - libsession-util.a - SupportedArchitectures - - arm64 - - SupportedPlatform - ios - CFBundlePackageType XFWK diff --git a/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64/libsession-util.a b/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/ios-arm64/libsession-util.a index f040566d8919b24ba06c7fd8db1e74b1bc872b80..a9138a8d4507c9803fedececbb03ad65c0f72960 100644 GIT binary patch delta 28047 zcmZwP30zd=*9Y)tMwVfjaW-L4mT?Ug3FIXt#HYUsfme(f?h2b zOf)oi%^@|z)I_7iEz!uO?4Kzmrj;hyd!BoKW9a9dPw>6xJm*>NHUqTeXgNI$K4>Ii1edty;yjaz;l*wT_K-I-_IaqL;Q0Rx&#X@rMwiJWwyz=vWH1 zxHoL1=hz7Ep{@Y!T@aUh31Mid7YS4+y$qYx;y4X%^5A%rr%<%QE#90M6(U5=7`?bP zj6DQ%;TYHv)`Ne(!u4Oljc_Q8f}ZfZpp?&3fgOOO)nj3DkVl!bZTp!E%+!(eW^o1TRIsbJuy9Q2#9bvyHZomJmRMCrd+J;{I z+JYN&uppA-9&l9z$D?6uc(ys`KZK)TTWEqmHsiV?_?}dut9=v_?VttTY|0&uz}0XX z%n9fAQSiVs9OuCuO*r;|=NohUF`Ppx@^(Ach#S_y>98kk2#+@8x^d7SZfn4KC+u6F zu(#_U|+;=jUUHf`Xg^-51H7s z+U#O8yWOJj#wja%!^S?3373a(tgFLD!hvu(xA$?dn_#RG#EJLe zBlucf&PUZ_2gAkib9fha3Ff*2coCYg1E-b=A-?n!-Evt+I;#Y&r*pgPLf_$hXD!zj-GCG!fMlb~VH7ka|=GdVwR4*Sepw%I(kKiU(q;K3s~ABcta%;tDK)Gg-t z1T0&E6<>Of8-C7VUs=kYf~|1-42JK(5tuj<7ujaykEU_|PFTpj861B&f$a>1fZ+5CiEM@`Jz?qMz{>lhstDZ41>L32N(kz!T?yk zk_WyG%i&?T9WICSVMCnCv3x`nF>E>y^g0UC=5zdJ0sYWLVSi!i9Aw0kiUXGZ;W_7;=ggk&cWuC zS7gUK7~mle^=f#IR+uu*ApShYPCvqmLiW~C)(`FPpnf>wMQ|Yc%|zT4btwnv{+AO( zV8A0gc);O1*~8Gei{rLv@4T7g78p1a?Y_G?e||e#`%Bh^p81ctjIqv=myHml&p#z*ajrds{qQTgM zWcVC>2L6uv`un(lT}+sQIOKEAhaO@xk>3ZEg9o|ccO>q8%yEAgyAUoxJ{%rIeihE? zPg}U&TEq@V+#JRsKNNn*r%(}l-4_cQ_QPjC<$=<%z*hS?KH{#&HFFtpAnG=wZX1qZ z16-t?;QO#xQ!$9YaB!EfLr7<-ZH)?K9gUpw@d*kw?B$8mc&9qxno;OGjjy8_34&+!d7 z{4&Ocf5B(3a6V1J#%g#Sw!X>*bKsw_`Wolce_-E(U&HY~a^8HMT?bqI#PJMR@js50 z?|$Zu@*C{FU)Y{E*%7zcFJUDNtmJ%0cnbajoxgH>cUTS|!KS}y?TYBb8{!�E2Gx z0MEfua31^!ehu%#@G9;%7*2!B;ePmors5+Eceq1y*b`2MdGG-I1qS`j{o>#tm0d&GKk^mV{~#6Gm?JmHP`C>PVesBvZuf@ckU#e)=hq`%c#q=*BrL;Spf+duMQ zzpv)HO}-o(9?92#n()vgZkYTpI}QUag&WavgCBQ1fQb*HJs9opKji-HU^?n5{^tA- zsGp0N-hbc$-w>?P#Ln?&$9l1ibPye1M|>Qn=()WV9`@we0WWxP{45-FpBHpap+hf^ zz-b)fYiO8)I1Go}`5X;!#81q$vvpw0Ivl?b^#L3|2j7CbVHJ!H-?tfZ9Y$I;?7=|_G_yw2& zyI=tiuqRuP-_exoE08|~*Cud2AMs4YDe&zM3Kxt(!EYEasR`$sp|Bg;3)^$P1>*UL z2f zbJ!WN1rwRE=lQe;_kB#_0$FA@i9_%YXzi~PJMAX%U;rD3cqq*7PxrqpIER95FcbEH zVQ@D({_!#ocmiHS-VgCu*cvW}DFeA*I6O6w?tgQHNn}t$>Kx4eK8XDcE`uXs6!d@_ z(0?WMuWD-&^Wb1O0q%e=!Kum+x}6eDLWM`DLB`*~2QU!rv9KT92R9F;iC;7cKiCv@ zfurGhs+V=|AXa`wq6CSuS9BtOut{XW5ikkxXk)KdC|;-Wh=e%tm2-#BmuN%!87zd$;B@#hOoA<-OD>f9pM;m-EqEW+ zLw&8$Tz_{I-T$)V|FEJmSPVac&8S1wFtbLkn1v6#L3tS; zhyPGM{dKdjjZt_bb_}}@PKEv83fLVThru`CO1K|hg7?txTiA50PL$6yi#l+OGL947 zVFa|mDiofAyWuLRz*k{s_&fDaoo5z_ z$iz)%(E(n9li+&T3kJa>a6XJ?72%vfD?Dfx?cpm>f!pCt>LBaN5!aij6UE2O;vvOS z1L9rDib>DsZ=^VisT1M$S)W@iTleT_@6Rm_;n~f_34VNmQ@5h(Doq zGRGGY?}u-}fv^n>fPc|=ISLJQQEre2;958rrom@1Q8N6E%&=R;X1EBx4o|}IQ*`1+ zkVVXaDKL*zWJ7%<;-DWqL1u(m#1@z^mE%e94)T78d&9=C0`2E0PH%1zZ%tzxL*Hq1 z|I3DK6m&+zYcL=A{qR><`wg9N#8|{Bs*_g2mdN*oGvHeIC0vR6n{UwlF9$HasS{n> zT0}azvx`L}!X0omTm-)-Gy7Y_ad?+Z9cdBcGw8KqmPI(AKU@Ll!XdD`f{j?%7=A={ z&9aEUr_t(vfUxoZG7=?T~T#EV(n2r24I85dhn)nnF zzrZAPXaarU-*jZ;LTh2dVAu+Fg@2I6pIgND(DXLPClNPC+?`ZPj#@UREY5%uP=aWDz_{%{)1ft%q`=z)Io=h6LN^qWQWp@eh-+z6fT(2LCj zi#SZN^yGYY1sn?#p&edF{e8p}7SPWHI;%*8W_aZtI<&H3J53~=315UmvpBy59)@>d zbNC1Yo`IWSgN5|G5MULta5T(>$qF_Okty}8Vm17cOboS(yYJGE+YPN^z#?`${FmBg z-XHB@a187TbKrJZ3@<<>5d+RdqBi2Duod!WvvnfAg;kuSy!1C{MLri!gnz)Ea3SiW z5f^Yw@BblTTTEAJoK3!%!fEhLw4bFoG1V#tFQu3J305&ps?f$G zYLE#d8kWFzurXXurcAYpGw>JavyAi4z@Bgt%!c}0s+(aIKgyW=f>h+j79_@^;3e1` zeox+i+bZ_J58y(08y<_fyj4qL^@6?Fg0jqXSkQh|(5!f52H5${HP9q}r} zbzmvnu#$eRD6xtiDti!q124m06>L;Pe{`%1W8pfqPe(ikW+Oiz@&9N6a^k)z1A@grrMd15v5}XbfjYjTnsnDiD)1Ep-z<4w+XXUp^Y2sIZ+H(!uWj7PeI%RhQT3d z&xD)dMfkT{TCkkZfw&bGkWVTi$|k1MhI9`+3GEx`Zg|cnZc!{f5C4VX)SlScCXzSN z6G}ImD1^f|avT947I1tLE`@^%=>C@-n<8KhRfhgw0}!p z9c&XGAJHiuV-q>>b@{kpGF|tc5o5g@O&gE%d8ZwoS|+bC%K73}1wwkyUGGLRd|jKeCBSF!*DRyTG@} z8(VB*EL=+(_R^7s-dj0VE+esn5?5U|F%ou$&EQ#B1?NNaHl1)5+r;1<)c|F%b@d-C+a_ zfVJQ)EZ{tJ!B=PjMFZ^O3j7Sd4VS~hJ@V&&D*OtG9&jS8{fSPj9A+1H_OcbQ1Qx+f z@GkX}^`j8K40GXh*bT<)rTbrY2t~pNM$p8CZ`p+twuimoG&lloAv3e>q6n72EASx< z-p6&(Fj2wA%Ww+Jg8?q?&r3BQFmq4$1X zXe3OAQ{ggAy8kyJu^)a7ufso~=>QMd5VnDX;AEH!x4|;_3%n0~4$^oEodc)bAlt%Z zI2z7{>)|Ib?=bh90mr~IXzz=-BYYNyLOrR-iLVv&KwrcCa1~q#C%{)=4|oj|wnY2~ zVozLb!EiLJi#p{qdH>S_Jdnso!G3uAb6(hW#Agw2gE{aG=y!5&2$+ z6zxkO$MFkuGP=Rb{ zf_N0U(jFkzO6mVDl6BI%uoO-_LO6?)1@XCf|y7oiRd zh=hGh=v8k|Jz;>qQeMXSi08nuno2zpkAxGpIYCdk<@H1ed`Nj2&p~_!aS_~ze1CX{ zthyU4&ciV$Io^jj6tP~aP+>bHX2E{&-dFS8~!6kdUsk^d6T#sY@HPOupagqz`)uqWnOT1xl7T)>-{ z;C;9i9)XR%;XO0JJ7oUnA>ta0gFWF1m;-0S5wQL#?*H*ObpOkN{E&z*qx<$`h`4-~ zO@&?HgEO4ZLftC32OfvzsQUu_j-BIvd*IJ#zpkLeMij=P;UV&l)7-HK@=Xy>M|&>% z-Gv>{o(l6Z;YjpffH)KF$_i}kfJfmN6!wF$bcE!QNrt1~LbwASf#vW|=#2#j!xUB# zBarBXi56m|1uz8p=I}c#@E;h2IzO~W;7ELd{4~Tic!}yVTZD?S=cP)hXr*n0iXQMc z6nG#Gg}L9-?}DpC#cYbD;fOaPUIu3%KNLPd-h{X#oG4XjpnWJPffr#B3cn|vKZc6) z@E0<*TbQt3V7HMOGs46{coz19F>oTxf~!bx+424bU9KfDE;n_DW3DODWlGc?exY{k zl#x@1O_(}mSlXBoBSuchJv=f{H80fps}p}WHg)~AKsU6ODRy+t8ntYZu8Df+iZPrE zFFrEbU8@)AHX6Km47FLF?hS0a7Ut;|ysgf8V5{dT|DA$X>YZr6Kv!a*-d-08i=V~e zniQ=c7pEm!FSwSbr>|P{<>9)P<~eQ%=T*yGn`6P4W1jToK1_*ZME~>wSWP!*3cL9&yoaTC3l`X&i1z>nyZKcr7+f z-F3seL%_k7|E2Y`nbSqkA0pI4WArV9-E9%*a=n!hT1`J8RGr>rSDj<^PHPpVU88 z88nt$k%PXRLxk8vri`YYZYqSI+@>%2=!&ln4n*9FN`S$ggrxydtb?Vh04bP~5Mflp)xNXKY zuJUch6pvbTO{ibpt`(w|H8&X4s@t{V^?hU1cXt~@eKKO`Fwvt>UAo&CsLzT~PwqB8 zr(YSP`tC7?>P4*DY!AieSaqz7ow4e>(h;%h7MahARc}gHQr%w4SH!B_rIl2_*Z5M$ z3L2WuU6M8jeXEY;ts8aOC~BmO<8iw`E=xx+x5uj2PI?9UBqK~x=bWH02ccbkHQweh zAo5UOz2N201*`jydxa4vH~ELT+YIu83e=ZS<7KZJ*=wcN$Njm+=Q@TnsO!J>dfaGN zCw=7aFof{3oV+Y2LJ#%y9)CwTBKluMp4jJL?2|o|JNu(ELK^wA5Qi>QZJX&A+O2CC z7ttsBg?Ehu(;kB)I{*Ln6tu_J=XQhI@qDnu(14>X^>9vzqiaKsX3;^fU1RJK=$q7- z+vyiELCbi2QnJx5p5gX0MuB6B(Abx1Xtc*e6<{cTx^d+ri@s{M z??rna+S8wEKZs9_qEPyd3Jq2!ly=)wWu7&9uYVAgVdHR}eMFKj)r5LY+Z} z+o$YVu1irnUZ!g&N*yFG4?HOjP!E^Wo;{AVl4vf8cK2Jlfi8>3(XO2o;n_Jw)s@i2 znu^e0jrIw3=vu0EyV=6rHQK=o^vQf&BQH~*Y=j22*Cv12Wl;QoUCe5_i?Usa}cPOB-^6d{TB;P09{=r0kGI$_d7ka)J@0oFIji^~t2HPayl;aCh)>qS6k@fe;1p0$SG;bD+EYRr53e`k zg;n?@!7dF|Y|9+@37hgi3lodzbnjY)Hpnu0&8` zJXzL&okz+&Nhj%2v zj&C5hlFGdhA-d5<6-n1kawVH^o!m_M3UV8Hj_g70Wo$wzIhEoPHsL5K$0;W1vPnM3 zChR5UIPw)wF2qclg(#5DpzAN$9|F3SlbT(l;xq;4Bnx-O#cBPF`RG3K04)LVy5X&Y+k+MSsJJe3f zL+DS^1sz#kj~99nZiVTvJt?n^>-3Y0>{mh3br@Mnex%Swq1+%>!XYr3TuONZIhed3 z#0#t<;B|KBQ8JC{50Y|+_L8!GC!4U9l=X$ALK{PKXhW_ni=+!YGJ}*0 zY7Zk|l>>W9$~_uE%6>ygx;`V5(H;&Rup*HAm6ADBH;;Ua%nYRaUk;E#iI2(gDC|qh zt3C?(2;_swWt8_P<<(yqz*m0}xtrnwatk??97GNw&160@HjyGU2DHm8o$|F(;$HO5o8JeMil>JKV+^-1Eg5%*3`h$QruoIfeKGe|w ztLZTGr?rkw=cEJbk)F+v7~~4cg;bY(fSp_n1DNw5*Lld@hh zBVi!rA+GchVl`Px%6KcxCgq_YK*~cbe{bJHHX`N1%;XZX%pgQId61;bqd+=~j!Y3^ zDa8qU$;I+V$2{67qz>sY6*eMYrM#1rht5ID3GR9G1lLJ9fmBX#6|q!KP(jiJkp<)e zay2RYXOMD%X{4F1zXG`-2NhLMAzt;#q7$I%#f7dmuMvP{Vkcpjb+ALK>;kK#Alkp*}a;BS8OwP!HL>1C)Hq z-}B^G|uT?OA5yf);o#cx&?p8LTkeo(w0h_Rrl=DkxQa%r{l;VUO zQqC{`%~3gjlBPPl4(6}c^ZZpL&HqxSM}Q*te>KG^=EM*kZUE z%J;Wqdm5B)i^%;6C*_G~#3ndNc_M<@1hY=G({_k9v;*o-${m!-18N}U4qX*OyiXRB z>q)vw`oSubR9*l4;~3GL;-b4j@O8DdHs|#?VGrN{l5F z$zfzXnMy{HFO%WqYowE;Cw!BcEFcZ!nd=mcKpr&Z1+zm<IVg z%Js1Gcvl;G2W!iC+?HD3FPPi%A6KO`^450cn_LfVq~CHq0(3iEZG+#b@HY0qiFi8>p&2XgiwV*(z$3W_G++thNxVm51o|I# z?*QTsh*u((|JJRE{#fhbjOGqxqHpx%EFbiba4TBEfyA3~LOQ4J_k1G|yWiujZKrm+2J&goV9K)Hx}rABR9SYoqsu@`<>gI03N`j8BdT)KMl(X4TyuVAon}9TV+h` zc{s!k-QMf|6N0o-q@uHvgyxZjt34ebT!;DLBAVm~<`oig|H z#%0;=Nw+Nyd7g~D=y?J05E*;ZyBs*;gKj(M?RM&rkAnMt^!B>0Scnil0{=5`Tq$34 z=`jJZ{Ao@;mLqo3n?3Ro;*LYq-e-(Wl;`Sm|3(<^iW4y%b@>=-ExjG}pEdm`oK<7cXqvHVwq>7j^b+5|%Q*VPi@A{j=f=qR09 z`Dhx(@pv1@(Y_pK1#&#c$?-vdj;m=$WdA~Mj+Y^hKztYnV5_zOy1u%(j~I^N%Q(W} zl$Vcf)KRY3{dc2%bYx}h{!`B_I=wQkK>rjRp>v3XwO9!EU#;YC5ORURwP|qm{8?i? zMZTXUA04%(Vo{9Pj?!}4XZiRaPDU(FW*q(eAgA_^;PzWIq>SBvYgtUkNXAKYRmn#s z{eU6kVB9XtX&#E{6Zy@#Ae4T+mj&*>u*mndWbFQn${u-gyfPnOu4VKLBI9EEF-|@@ zqdgPxVi|j8KEB*H%6f{A<7y7m+LcUEh{R}ZfMT%{1I&;eyoyCR;)jS0h~L$&hGJos z2a_HrD6UNhHw{-qu@A@YKM6XiMz-hCIP%evPIhgD63JFh#G!-y{ZT%4W5AVk=;dPr z+LLjLsH-+$2?lH~2b4*Arjm~jslN=}4==ipYQ~??)uEQ2HwJe){Zxy58@FcV9J+IA z#t*!ojMs&!&Av5;*8hcey=I+S|H=3rb>O$gXO*{~D$96kdie`*&0$(URezlhM9ub( zX~%2ESD)H(`JP9OcAGdv7jKP(m}XY%UZB4QzZ|62EjNZ*7d|yXWRUvq1!HK(@1M#K zd@3IG)Wp-Dil?Z*Trh^!H$K&ET+=6)(@SkyZVc0X;ObUxO!U$|UQ#>y$xolS(k~ka z)M`C#%E-yX(kD+GHD>(CVNZU9MGSkQvF2x2)H%)k8mi8h4dH4^xY1XAwz;2|E9?j3 zalP8Ur%y+9=M|&r|NTTvRXg2uSL#p3qXBC7YGVuay;1a8g0^Y;I;w55Ph<71(Z(iz z(Q#t@uohFMPHqvcMx_}e)fsy{ebw95#(HYTufC1dQx)DOHT)lAg!d7IVNi|CJ>{T>-#RLk%B+SJ+6 zetzn^g8D0_++!`fR~J8|rp}&7%<;Ad zx%6?Sw~a_>ALm)0WXg3Xdi_LQdi6F<@j(S8f?PigHofLsGx6Fe(;#<3`()3>bW@Bs zDzs1bglCwp*VYpBF$G%3f~Hv}SJSN8gTFzQ)@^-%^I0rTev-(QGsJw3KMxe6YQtov zn&%;*6*xzjlij;_x{=l4S~%G}&cH)bfx~5)X^zp3X{%V3jximeIp(SSfv6ZY+AqN2 zy0h9`D*%h2A%a}iJ?2i>U2Tr^gO3-X#i_;SgL>?WHe~z}bETtZBC+DB3da?*KY!>7 zt4MrcF17sECCJsx&(fRsT&oCjy>GTmb|>T~om@==EZ_N|i~Ky6t9}E^B0k2k+6S~; zCz@Jp?u6FmL6oJ3Ke}j%R}(CI+-E^cM89Y`!pDzJOPW5=WgcSL%10ws)haGdw$yTu zLDL4g{+?}V;69>Sm-zXXZ1=vr>Y#;PTk+JO#TzYoW~@W&lD^MU=}|M0alld#QZo^M z(cYVkoE4}JkXSyfR62COEj^m3XR-H82Odp?XVV&YWEn0=H zt<~mUhE@^X$$I38E|z5L5nsMXtD3|B|jky=`xu8gYih z7VN(GECKXHv#W`1!{dbZ*~C`1;qE=tKCF1BjqR@cSkyma4&0Y$n`(70R1UGUx9uT6 zdBv*HYXTjv&8fD*?(?G!QGdK`xce^AW?etUHj}UL7?s9!xY}ge&hljuqiS86&b2+{ zS?M`L4%%^{t+RVjnNU})w>|4B%d?$xpLy-cxOk22AU|8tB}&iEYJQ$A%2l%d$ugD7 zqFnU<5@y$|b>(cgHFA%pJyD<8XZz5-Yua<&(xWy%KF!N>=p+_>W6RTNhgXJb{xw@| z*PCUw2LJ=;hIs`lGGA_T1!teJpxl z^ViEgj#Z=91O~aTJ+ytGofdb&N^ko??T}GIUM{ZmI`-gDdA(3VJGGl*?B8iun7blB z-o96RMRh0A+uAF%XAw&5YD%wuV|&`aaKB7zgLdt2zo$KU$wB2sIB=@{fOZ{ILcZ9! zqUYM{C1F~69iz|E9=E&39Ix}R0g~F~LO0Dl?aGwh0ebse!TT{=^eMGcZ)buU&3L2qD zuZ32}g6LEAT+ye3`)DtcvGLlg==z(%?OLOVUs;?Xxw%NJr;Et7WpBv4eAuwElzky~ zJ{N7&jurIEJm+vo?r?Njno2JuEgOfPc^wIULCX!z?Sce7=Q~`dFNQW8RkOm981^u# MW<}FUVK2Y*ePp)qBpI-x&G6?|eS`z2`jVS?+W19bmBX7i;-{ z&nby`e-D?s6L1S$06W5pVN%y@m?j7Ip`sy6jCxe;|A;tm zh?qZE95+b(dVn}PQ|!=R+}uyh$Pk}?NSyqjIHj*>(fY{2SG~m@_lv93#bv$3hkJ^% zdWg|!;%D8($!_t!Zep9RVyk;$7x8ju@pY&T$HC%G(r~e(m=9Cn$`r{j=^ze)Auu3W z@`u}tZ^36^4>%-A+UweR)zor1P(LL~g8{B?EAd0{^)?cZgPq{lttI~s91HJ-X88R* zQdbUN^{V-)u}E}B)bIHfdxy!O!nTLvG44d9koX}=%7 z)>z`P&z6E|DMmn~vWLvfE;ylN6Z4HMrAMLtBl{J6LWE`d+M z9xz(N!LN@=hcDn7I1(noKzMSb)bD^V!yM>_5%AY3N~InMRGXKHuR(1d4r<^e=$I~r zk%$lI63>7`p$+!N1hMc!p49J#kHG|Zb*Z;qQ@=bfh5IF;-i8N|AB5NlFT5u8E8tSp zWg$LOD(&NtPlfZIll*|!#d2?*7N{OaVhVf>1B{3BkWWXv81e5*WS|c@#B2;?fLY@t?|DLe=V`GQ+@B-yu1Vs1&x&ov z(euv>xHM4`^U+X*ICPBUqYLrOk7_Z5} zVsyMzBystCahh*n44Ctx%cJkq`ZJ_2v-lKgNiXf(#(wTte09`FLrBy+dK`REXe z!pr+5zX$CR6%uEnt_iF}-HrF9y$=@rBFsgkc2z#R5 zfVe5*W9YwOhxE&XJ>cQ(k{=JX5FF%hlLi;uyH(=RP_2`=BX(-v7Kwk`C$>TRQN#-o zSKy2s1@3+$I))uqs&;m;dh3vAs}h&jh%ql`p;?3xghzTZOV$Ec_rUqf=mn;g91;rbeIcU!>_-TybiBimN*LWH%BDCjd2q`llW8A6{CIE=k)yZ z0zStXjX^>GOVYuGGdO!g;t${(Fdq(sona*W@w)We4NKt^*#9~`|Ga?KNEqR{Ym)yE zz6PI#!(jrfyDIgE;k&R9j)mP}B>Y|bSvua2EeVE$4od#yVR0Euz)dv?*ShvwY5xWH zKnmg?{v-KcYsKk^3lK*m)((9m1=-LK4#qXBxhU-)9TUIAM1v4_!hq+`N_!c6mG5ra z+26hvbCAzPzMaIH`t6JqEQX`u#nX}>56_;GcpSWSQsP~3I_w4&Tzo?6OmO*eiBr64 z!D2H+C4Li_FSq<$d!9YMYOSEaK53{l&D zBQ-i$$p_>jI04=d<6zKDsha@T!o%6Rn<9?TX4B`nU1AYCc z0isa28v`9c;od+Q;En%DN0V9Nu88CRko+ll4&$^7kh(Y}8vl~IJ#fn35zlbG2301Z@@#RFZvqen2Pkg1Vc&4GauAA7Vjp%ZUZ$Yg=q$Hk!OW;>sC7*%; zKDt-pCwhv*ke`NlrbF`asO#HB;`yoK>dxYlPGUhvubP^XB8ll8#21srrR~L>B(V`D z=$at$7Wg~#VBuTr(jF2e?uNHvIu`s=J8suP)o(T_d=|Rl9INDi!4}WM8IQqMFGapD zVt2IEUx%Y&Bt8neHkP;mRiSj5#NlH{E2(SrY^B3;@)(IJpKUWw_t}RBVXEGlY#;icwlrB z$-e+qQ;EmHGWZoVHk0=I;8vItFZqoyuDQeyz&s5H8(=uL`e(%HEu=#^cH|6f&{OhF z;IpW^1SjK6JGPX%&Nr3WYCXpb5PtKg>5U%*#OjK`{*Zqp0uvfaIqKxgZ9KL=;?y z8uG6q{*dC__Ga}Pd=Hw3Oa6Prhv9RSukK`4jYf#SBK{ig)Nrr_X2E-5Q`mAOO^|L@ z88i@^0>{H5xCVX-e}G2xAMlt#xdximK8jgw6%Jm2?NAU5MYp@L zGpqknVqLCTeGIGN3!@CG?s>ENVYIjt=EDb}1J*u)I`{&75Jthv7}vvUG{Hb5n!($& z0G_zZljMA}dSa{?K1OT>yTAwGqi_a%8U6?3tU~-V;$0MLJV66Y5C>~ecpV0zLkv6s z`@vE0dAJZRhvl#x#=jp9981qXFJKBKav!m%pWtD*5iWq1aR!w=!lE7^GqWvfKFonF z;Ck}fXp3s}lsF|v9E5lY;*L*g(y$YWui-Cn1ndGM;cuAW8@LB1jyI^RDHc^k6S42W zO1Kd1>tU6KgEJ_ogI47G!(2EG?axgxsM;A8)fxuE@2QUSjV6j+VHMgh!{tyLfP*Kf zFsZ>Nx&L)YKUy`a%tk zqYh~m7BvjcfE&rAN{dFT#81OT za1zy5C0W%aGRbXK=I6yPpQGoW6aPcQa@dUuR6na~0SA(`!>#noYWi9+#j3u5yWx7c z1Qx>Q;W;<~Hprs|&9SP<8XXwtS=EXeVimjuLuN{TG3xffbFjg5X>X0#0W)Dcv=4>z z;CiSX#=)=9g^7E@F|aijG7s_1+4S{%sa3sCd9R4`W=Xsl@iTB0@`F)7QPk8DB&t#H z3yea?0K~0eI`ZGb&U0kKhtVF6_*;xynor;2t+A>nU?2FdSB-8OB=*2Cc$w}E?)W7< zgZvJ}ufj>N4(*l#>6d|g5_G_8v_KyBTUMikJxC0NtKdAC17~7_Ry0A{E~^?0yTO;> zB)FdJv)igpk;dazHT?zA0h39M8-^g!doDcYr{DlVKd@nc^E{6xId!k3U33ERU!co+jOhuz^>8n8CZrZ&PQumFAv|3_9^ZL0AC zu_sySu&JlDmki1kWmAt)f}H^8z*4vwR>2eSDl}o7IM@jeghg;U^g!(k9QZFZsPcH5 z8eA-1q&&yTh{NFx)E$AFVF{cJ`@vT5RqCIXXj9+9DWay{N21xwv^8C9$_@XdJjbgj zRy}R196kXHpuL1H!9zB6>{W3iEQS-{1F!}B5B0BoNV6&9E7H*g-S9E^BHRS4;a~7K z446y{seHtyCX$vXX+iKq_y+t5j$}0|EJUKuB0A&oHgy?RLj^a(7h!vt1A`Y+|6H5u z2}i+~;Ct{OY(i?>q1zJrIe)rM#X}qXg9ySVD z8a@A9xF3o2umnzn!(lJ@t4`l=?6Il)X+U-;JPR*Fzh%;X9`$WuG&E~C*p3c=!53i- z+NaOnU3yFG zyj(njcon=%^;{Q#I1YCAs@au>L^eE)fnzb?hlq9fC!7Wc!}c(4C4Jd-+SO3%&pr=z zxEr2jH7YQ!kit|r6wZMg;8*ZB_y8t)2A0APVGWtz!me!Zh<_t)h4?{IOY3M?kI?~} z0;j_HWMvn-x<*#tXIHVS#7AKf`~Y5qtyW9jFt`wY0kWXMo%4ZL>t3za7wq0F?ec)&~7jA@|-leBuf?XB7tI0t)62svMGIy$7C9M-j z!uOEhi+C~ghc{u9_4KV)zFnom@nqc#b~T^0EU>HNaFd3Em<=>hsa^TQHDt+Kb~O_Y zhh5-zWX?*vIta&Zq>t%0*wvpDv$2TX@EtOHvt8|n=b>iaBn`>1KU{!!h)exS2PvS^;gZokY*Kh~)pnWndfjvY`oy35zp~HuWUx$O>WcVkU^u1j*-9l&j zyIrj%xBkzrUWYwl66_D>!}sAduk`#sj>JJSC&;1Fw$jtk$e|LUKXk%Oc$(_D?oGr$ zBQ8KZ5&lW$b#SO_@OxOpYSi!v67R!}@WE{~!MzT(c)O^u7;S(?ghQkmTw1d7}_jjm!V0UM3>+*hsyMb z}OFSA*huX_1*a1I-r=Yf5I{txp5jy4|{}U#(mrLCV#Gk-;ABiiN_#!5%gOQaoVH^0(9*G~t z4x}MJ3id(19SnmvK9qh(VOj;n8eOZwd_azYGvPb%19bQl@fr9xjKo&=g0r!ZSKtb` z18${zh36Yj^%@@>L1GpLSO{0Zo9K8PF2jP(qhA&B{G|gc zBx9?4p`Z^{2)MO~X~|PsB5b2O@rt?DJE&nh1}; zdGOXg>i1TJy85a36?_jC!)%xegP?ZefOM>YOW`#50E~rK_DlU9xEd~m6Jg`er0#yi zP2uNQur6vU2Z?^L75t0Nkk9NK?1}+3GHpY&8V57r->?=Qggc-PhgH!#?3dAMnO7}Z zy^ln|L1}1@I0DwfSum68axcZGQE)bV1MY;4U==(GQ{d?@=rdr=)mZJ}19Bx?0H1?? zpG&(9x?$*H$B@!e`-d zm=2pmBfRznJ^#Fb=2+n{7;?m*T)P^pe7KJ$<~RZIcz6~0G{l+kH1eC_Qh4zwJ+^xr zt9@UJTllkGPUuL?gk#|l*cmp3W_Xb%q&zi4QNL>1zl(2V>qIFqw*CE8ztow2aq z@MF}y4U6FOaG0p63s`9uI@Tecf_NcZ;~N+rgS{|d3pks0Mm3LBqu~a45O#t6ylSzk z6p5DK(9eR4V^t#<@gIp-Af68=!PC@_>#xHsH~L@b-8is2)qEpzLk6(;%LOla53_k-x`**PwTuSe~WW&Vwxv0&9L_YeO!^j z?&(-$nBuR$Vl>-4KP@zzH0lr6nnLuerG_DTyINC(C+{`G&=>TQr~DiN{QnZr^DkPW zJRwd$Ta?5r&RHWpU6TDKbPn|XzXtT9KUE|bll*DWhfTc=Df;yYzX*@<$Dk1}$gtImZISZ}CKx>h6N03N}3=(%Df1|-@d=I8GV_2lmh3wR&N zMR8VVKuVWRDf*f`(`cLVKIMHS_>0E-^cg<}bq`V1QL#WgkkmC_4B%?{m?AKb)BS$-u6s-~lUVxqCTwHEm-?m{o4zU@-beU8+Q5{J;nM$hix=W4ta&8c+5^Q(rMdHQQKXS_bZ>1WqhgqoYgbn><;mr=CbD`l1eNzSKP zDSF|W1|NBz+CsnA(d}ROyYzP3OxyHqr(b}lcAF_Pz;9VQeOOh4Sih=v`i!au-TNkC zXWInPqUgnIqb*7I2K3ipw6FXBw2wi1yxzVnEKaZ6WpXy1hFndEj8Z~-RDaAw>!>G5M8a9LO1iRlE`>(`CW97Obl{%bR`F-8&Ei|%UAMZ2Y?jGcQ|dp_Fp z(H5vDq88o>ilDaGunE~RF>YPnO{UO z|BpQQH9|s_C?Q(2{uS!3f>5IYm0|7vRXGM>m_E6k#i=)GXN=Qp6OBegn0M8|g?HKq zSYoAJE2Z38Oh@nl3bTha$O#oO8<3T+rm~#O{2s_k_Q|E)5z9JWpoqW3FH{=NEdlEnFRU& zX2ShukvvWg;=yDa>YpjP!_Zzww<6bHV>LQBLNyNnXeZ(Vb560@6fx z7&(^eZ_@*P59RAbcO}XBB9i+Titc=A&zL2;b4WhnRFW4o%SGX6I>>M-uZrX|xk7IP z{m6A>4p~T!BL~A&QlmJYtl{fFI3&LY>5JWeTTC#NCKARnT*4Y`I4i=|O`i^}N@ zjyrB8xnmj09cLjPOmfE*k~=y`?pG3nt%aMSrT=QOnA(?-3&>)U$1fyTkhx?}aw7e7 zt>|)QxlkvvXWlq{r}w9s1F z$y8T_SmOgqxC_abs6!t4CYej}8T29PzRE}!-Kl6#BIiK*bLIf?pR z5#1L_U8#xZ#gY5T$LT7zt)K($u#@BtTSfOKk~^#uN0yMhg+*jjGLPhic7tu;HK*h& zNWLe=klb$sNw-(q474YBrRU#?#3}lYfd@Q9&Lay+eme3=9w3igPEI7bZZOIBND}gG zkdGl>qa+uW#F6nN4{)2l z?HNd3gq0*Ou$<(Dt%DQc2$%sa&`4_BvC1JG%i%0I5srW^XdyGGpAp`sZRk(&74l&+ zljMaYz&e}kL@D_g<%>u@;e7g?wfBr?QG(9gok#K+PZLM>A$fsmBoCN`*oD|a#!&vY zRoc&!(G*v6tk3?;6rpjqObW|L7s}{gf_jHLMo$E{7n6Le^dXm!G02C(>V{HRL~@;z zTuM4fzLH_&G}1`&a{0R)zEf_|e?8!x;FMWzm2#3Bc9J~tYLai2Qj%|#X(TT+jpQrc zhU5uuo9G*J@(RiM8j?4#nw&(};*jXBB6{QyBa>Ojf?w#Zd{Qg!-2Rv{t%!cvg zP|EWUoxB51k|($oBomw`c>4=~1? zP^(!UAe-a?5=b7vMe^4s3(04ErGZ!l%V7!0Uz&Zw1QNuqyIFir10F5x$(x(i7@L6%QAA2G`(Jd5NL9!xGGlf3GQ`2j6#Wfa%?%LyMNY5pU=`O7Kh z`L~k2Y1~bsdmTBE;?<&i8OigrQz&1Gn5EcVLh}6df0gvkpYByZ9as-)0jwCbr>oO(HeJC-COd}s9Q%Nc^Cy|+C0y&Iyk@SXdwvfw6Bl$E* zco(8yn;)I6*W`zU1+1d9o>>&_(pM}B2=iN|^v#O`!X=&7G}s|W`CQ;y91s@6^=b5I z(w;R)CFvS8NFq7Ew1+WX>a!OII1HRzTBX0-$1fs`n>okLdS-l(gO@ipJ}6cuscjOa+i_4SLP&i(rOMR4hn(tu66?U*TB+n-XE669CZvAAQ9UwNmBPC{yvsblM&=s)$dTh zAiwH|k3AgZS23`1An(0W`&-gE(fRmZ;q-;cOdI3-g!ECZg(M<6NJ${avcvIPDfU-tYqHe;!R__pWbRLKCEEMOQ%n=)VEkX2NxTuU)(;CvO_2e2@dVW26~v9` zCgb=xI)q|@RrE;ls}l9mz5x*rL>$L4oq+G7%x`!>6yu{yO|3w}_gQ6hAQjM-`92XF zh<4xSmT&O_3@X6)vCLn5Ml|3lek$nISGGLB_c@Ipp8&P{KF_=tCm^3+(zjgKv4z#x zvZI*5_sPydJ_DN2_tDNooKYIR$?$8t?*tG(?^`fp6Be{JQR+LP-TUDt&9AaCq3;8t zb2ua4r<*0*A%K3mr*}7gHRJ_Qd>EJL0NSl+zs)h7vG23RW{7YDQ$1Zm-)Dg9tr92DiSnyC zy-{&{N(-4^jll}DF`xyp?*qRItT-R-n;Xdhz7P7gIV3Kmt>#y6EXem!UVDydLf>a< zjb+DGZaeS!)7_4OJlxe6cmaG9B0eS8Q29Qzi{$!%f1cwnxjv9Sqrwip!7=Ta?{^w6 zaU4Xyguo7(XvZ5;ef9u!cqx=VPBm02-)G3%={=m|9QsL}U-u&BZ>#t<3UOJq#6QyD zTwfNYe{$T^LL1?dcm?|Z87DDcN`B1@li2s^?>bs6_xF7`e8FGh^d?f@1^xH)S7Uk= zHb#GvUx9RmxW7y0qyG;Q{e&M!JLvn^|AAPEC(^yauYL4R$nCz5{+D|vP^y9!#;+*v z1WK)=6XVxbnwaZ-zY2L78<2n#Y=oFU;_!aB@nZ;gs8k8mz^~~v5XWKYu#0w*<7~v2 z>2h+Mg*XN~l8HE7A9~W%QcFi@!f?LdA=G-y=^3GE`4vDvr15ZDae;<3l(?vsvP@ALH#`s&U7eIHuypm8YHl<#9}o|p@KAC^Z+f%1Jgev~gjgZw+U;Lmi+ zah#7^a1`3p5O3r-F#paiypQWCo{u}!?QPfcRSpvKyaUjNVSr`c324QLBhg-k`WRdg z3!OQ?hEv>-u4oRfxQlWe`+k}8HQF<5Tur~EqC2MH2rA$o#`wiQMsf$=Z>Z?d8&}c9 z{Q3y(DqP|ein+%3if{7oxImwN+7#FG>RstebSKvDw!G=T;{$Xj)NlVNRzGpt6l;Co z_3!*ZJ@SmHo#vp^tzW+MuIc!Ouisup?+o?h=V%-1$L_n@r`&axiT|F@u1?YZ*B@X> zL;b`VQ}^U&>H5{r|47?hKOS<|#1A?32hW;fdpvVjzWH5o-Cc3$T?@QVUw_sVXFGn^ zbemi1Q8c_>ch+PvjPnH7n$jBR**$}s>u+WoY)f31LiH6+Ly(@mDWs>qwO4SeC+&i1 zmY@E2pWsJ5{Vth4^IIBXh|zy;YkJIc-@xEdKac5}=>v!U&E=35dhv;%5WPCU9IqeT z7HHRBo)S#IHQpNa;0|?e!)N%ugEH>yyg^JLywC2@Ti7gUkhb_Z(xCo|hMF(esUFvu-Ii*!5q6 z%&qi(7fhl07^8WG-aXjt(K9C)9r$hk-Z{Y$o`eu{MFUDCcj>G**=cur3|8|Pzwjqh zI*odwa~JhoQeskK`?d)Am*R~)C3bU)!8=B>&i@|3Q|mCNN7PTGwK4BE*H5^*n$rX8 zC-T$Gg{Jxm*Td%0dWk5{*Tc<|ga4^;dPY5I9vxCY@!d1#;q?*`o~#+>lps`i|4qcY z0`vKX-b4re=wkYp3+on{J?j=V9B~9y*10yPr%ATuYv1C$-7I4*$K+o?BzymD#jf#| z!uqji#Ux9H@BFr{jc|H`XIUl~>yNpp(9(gowL?;}-oA<^`R*ml)M#Ht813`qwHE(y zB=-J56^kk?y?iIki}hSP@Xs=2jDwcle(17t0gds&Aq6f?AI)+^dLHax zwfPc@7MbZEzT9ig2tyZdqPDknr(B6--J96@i1p4ba{XdZgs0?5>n7iRcq_7ISp({g z;q*)|vNrSWskfs16>G8YTxblsMOIk%`L4^N0<+W8V~cf(1?%YXSBTSd{bTFp03?o9 z)5-n5-@2-C{fZCHSwnrN@9py41?y$swBD6o{MkCuci(vvxxZTreOKT6P+9zJW8^7I z*1d_6aN9B8wBAI1wC$MhQhF1)v9>!;$n}{Pr{`!h+sr$&aN?;%+XT5kI_TCA+T?4U zZPVnsAd%n8=8$zDvGpO_{(p5T8DiTngHi?m+ny=ewq)5cZx^R$@6)zba{5RVOtXDo zFA?F%pJ6MmH;dELYOd{na&jGX`lmn3 zgz@F#gkd8sd{m(0O};vJDx6kF1#jP-#I;yQOzb}ix>ekclYG0~sc`jl?Br=V;hpw+ zFULu~(>dXNkNPpov5y}gPI%we4nFR<$!}|%@V+%3pY7PqH}#z^+ln30>E1-D_w8i< z#cGG=Vs)e2P5io*%-`&N}B{JiWysyzHvt-~E(U|jG0 zS@&!B&Mipr{WAaH+6el?M$(@s&%w3Pp}rlVG3j~R5xpuNJ-*vd{~~f*O|)lQP0S$v z0-D?OY;}dOhv9aY65^T1(_zH>T zV`udb)iSFqwVFC+M!BfME+P-au@B-uXn2m7!rJf^>YBn5u1Za=?4ma26K_#}j<0!$ zYu&^u$fvm~%~7z{T^eeKE0sRXMWw`x2TbCx9mKsb3#P$8gQUF%V(&r{=f^k^$oED5 zH0q!FXi|6t4TTCz>{eEM(Oztf{BXD&9))9yNZlrQ8M+mf{1!O3n8f>FPc2#!XXC`B zFbnx5nCUS(yu4>j5n@Jh@eC%o)Jx(e$Un_5aX37T{6kn0<7(NRr0`{!=wCvt1bf1n za5uaMO(mtiBJ2rQ!i%t6DXHrP7mAvyA0rK`p`os0JXZ}O|7BH)i&hagA^&%X#D7#0^DN?>isG#bVrEAT ziSpu)W-&WST!ewX@t3#}^3(k!_Cs8!oWzNV;!>D^{tpr)KLH!q786~oS*0P(SA2nj z=1~&c3W}RyGt?gomi+Z_(To9CBW{TmT81f=deL1KTqOF!4+|yMo?_u9JPG%~4R9%( z4l`j-*ap^sC7~65Tp%la3a^Tq8izy~_=_%uhvAp74J-|f@Vog^KL|E~`Qf2?(w+hf z!jE$~ueqyBjsGMlz2Kf{65pRH&iP7gK1D1zS-db2PJrXZx#PrHW5s4LAAFKY*FT>e(`kWi5B=Z~ zx;i+&bF?@cro*nIB>&_~aV_i!P4K`-X&(T+;D!-&{qqVUM@T`yaPj0YaS2R^KYtl{r7jh28YJ=if#OB@HJku{ zNSF5Z@cjUZ&uUoc4+G(@{!-8izU?P*U%32piL1bKeI@pUKlhP%8|)A3KpWKV_Ljok zoy34H;^$q(oNl6D53zMm@mqKY`t*|gXt)^;Pm}x^_y!i2^=hgeCNLBJx<;wIqDJ-Y zTCp-cV{zPQiRiIT+=sfSizOa`x^%=nmP&pe@^z7qM?VioyJl2>p|IUD>97ZeESGpV zya6j`OMV&jTOsi{m9bmTqW@UIFmO}qb)m$gl)AHF9Tu#@Be)eu)MhE3CR+sg$pc>dgUh*Kx5;j<}HOxjh(l ztq)544EYv^BtC|I@rNbej`mB)pE*p|KM!>0h!oU3D)xqR;f!x3-yO!n>pLaC4yMBo zJ0!mlR)AZ!OTICDwoQ}7>}}$Ut)gX@I2_)F$}ah**n*vy;7at1LVGII;1>8RjM^>Z zOwh2f8$N=`d!(Qz+zY2c?_<*58!L8$r?;RV{PP=$>%$wHB@TpISq$_ywx|Q*Q846$ zbhwN-4)J@$9lw_Lx0}S-urWNlQS$SkH>@Y)XsRey`~X|laj$fkgcWVxAaVD7Vspf` z(eKrI$zMZzSM-|?gZ4}NeAEp`ewm{`UH=*ieuuTclMc(_BiIK6bbh7OiPN5H6Ai>Z zfPN<>PJ-RwO!x#gJSBB&S&as8KP`zF@EA-vBl)TDIb45M@-9D!z2P-j=SRs;gBM}Y zImy?8nWV;Ba2yH0^HR_PUVx?xlJ5x*z!$L9MQJ|{lP*cz6;6l6E=ztK{QWXr|2%=R zSEOMEd<&~wmHc9u@0!Fd;ZA6~F8Kj4?1seM;3jwjdj2Hs6@H@YpC>Q?i5oEGrsU7U zN6ar!!oy|eI*RNE%6#yC|BZT@GLB=-I0bqa231?BkxN4K)4^q-IILMFXAS6 z3L1Zv{5n|wH;He+-uETezQw}9JV_LKC>DGqMm`q*g7bfuc+(Tn=cyPEJHp8@=$W)v zhP~h{QB#r6rQkD|4i~}b7t&tzwW#17T!zQt3RoTWo#6zy8J>nu9BQ5_;H3;)4Gw@S z;eL1%8vc;_;;=Cs1n0v&@F(bk3C68E%GQ zu;e>wKk-&||;2@c{+DqMVF{s>iUaFO!*w0(s;VGV@I&L3} z_RoDJpYc)h>ya<1B!3(FqW#R@Qa8REZGe}`QG|a*T%!m`{4NhL+cd&w+#22xm zDSyd;y-kuYfp{G1PJfX0I=<3A2yw2PCW#DeSv36FDEW_wtGY{ULC2l%Wx%WXB%h0c zm!PhehvX-t&I1!|k9Y#wOKTV)4h4xQ@bZ!YOo*%d8=G(2s3B_ek=RpE5_!-SE2xAO?2MN7y_iU?0Ey$1#osW_S;TqJ6)TE@ z)nK_WM?X!CswD$-KtnfaP$Ln4i-Go8r0xvb-&K{keZ2Unrua5kte7CW77*>QSfs?d zL(N+mY?7!0XTUqKew4J&g4bYLqU2kb6We2gg{w*2*(#O9i9N9A&SJ~&AwFDL+D~8tB@0U2zKZCB`hrxi@ky0bN*bzQ zfI8TM893IfF##9EYn*k3q+jPa@kMFzGt@uDIGeFT3%0xpHXwg-y8d|r192>KFmNyi z{=19}_#0MytE9x=!xuP5{BVwBAb%0##T1eHu5bbT0lF8}q+n=K@lQCgn8d$AADsPF z;aY5I7PicQ6?ZBt{f-qDFJOYdVvFD7YD&jAZW^xVR@m~<7_b>E2@7F`*)cM(5oX}f zCF87rM9(9Mc}*7B%g+XGBJ@0$ZvuNkpCTqHJqn1l%jBW{j(Fnrlc#yJ63!qKo9j3G5XcKflSL#fiS31&RE zrNp~hh^3ii{mFa}obY*5wjn3RHFQ=a4Hi0>jk z0yB~C03+ZZ>~oV!?IK=5d;o4kUK@ag+0YEjz=mB7>Y2e;dBaDP=lC2Pi2NM52mStMR-Wm)Fvcmz`v*i7kVO&fMwxu*Z}tGF7-`dc^Cm*;A7P1P@Ea+t6n

    xWf8MEzKBKNQ?2IwSUp!p6e1kcslA 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%0AP}&`owy|#7< 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 Y0uYb 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